Added news service for easy, community driven UI translation support.

New or modified translation (via /Translator_p.html) can be shared/distributed
via the YaCy internal news service. Remote peers can see and vote on the
translation via the new http://localhost:8090/TransNews_p.html servlet.
A positive vote will add the received translation to the local translation
list and post a voting message to the news service.
(at this no processing of received votings is implemented)

+ fixed the msg service retention time check (NewsPool.automaticProcessP)
pull/71/head
reger 9 years ago
parent 290ca9e914
commit 9462a32244

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<title>YaCy '#[clientname]#': Translation News</title>
#%env/templates/metas.template%#
</head>
<body>
#%env/templates/header.template%#
<h2 class="yacy">Translation News for Language #[currentlang]#</h2>
<form method="post" enctype="multipart/form-data">
<p>
You can share your local addition to translations and distribute it to other peers.
The remote peer can vote on your translation and add it to the own local translation.<br>
(#[transsize]# entries available)&nbsp;&nbsp;<input type="submit" class="btn btn-default" name="publishtranslation" value="Publish">
&nbsp;&nbsp;<small>You can check your outgoing messages <a href="News.html?page=3">here</a></small>
</p>
</form>
#{results}#
<!-- link begin -->
<fieldset>
<table>
<tr>
<th>File:</th><th><a href="#[url]#" target="transnewsfile">#[filename]#</a></th><th>Originator</th>
</tr>
<tr>
<td>English:</td><td>#[source]#</td><td></td>
</tr>
#(existing)#xxx::
<tr>
<td>existing</td><td class="warning">#[target]#</td><td></td>
</tr>
#(/existing)#
<tr>
<td>Translation:</td><td>#[target]#</td><td>#[peername]#</td>
</tr>
<tr><td><small>score #[score]#</small></td>
<td>&nbsp;
<a href="TransNews_p.html?voteNegative=#[refid]#" title="negative vote">
<span class="warning glyphicon glyphicon-thumbs-down"</span></a>
&nbsp;&nbsp;
<a href="TransNews_p.html?votePositive=#[refid]#&amp;filename=#[filename]#&amp;source=#[source]#&amp;target=#[target]#" title="positive vote" >
<span class="success glyphicon glyphicon-thumbs-up"></span></a>
&nbsp;&nbsp;<small>Vote on this translation. If you vote positive the translation is added to your local translation list.</small>
</td><td></td>
</tr>
</table>
</fieldset>
<!-- link end -->
#{/results}#
<p>
<br>
</p>
#%env/templates/footer.template%#
</body>
</html>

@ -0,0 +1,323 @@
// TransNews_p.java
//
// This is a part of YaCy, a peer-to-peer based web search engine
// published on http://yacy.net
//
// This file is contributed by Burkhard Buelte
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.sorting.ConcurrentScoreMap;
import net.yacy.cora.sorting.ScoreMap;
import net.yacy.cora.util.SpaceExceededException;
import net.yacy.peers.NewsDB;
import net.yacy.peers.NewsPool;
import net.yacy.search.Switchboard;
import net.yacy.server.serverObjects;
import net.yacy.server.serverSwitch;
import net.yacy.utils.crypt;
import net.yacy.utils.translation.TranslationManager;
import net.yacy.utils.translation.TranslatorXliff;
public class TransNews_p {
public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects();
String currentlang = sb.getConfig("locale.language", "default");
prop.put("currentlang", currentlang);
TranslatorXliff transx = new TranslatorXliff();
File langFile = transx.getScratchFile(new File(currentlang + ".lng"));
TranslationManager trans = new TranslationManager(langFile);
prop.put("transsize", trans.size());
// read voting
if ((post != null) && post.containsKey("publishtranslation")) {
Map<String, Map<String, String>> localTrans = trans.loadTranslationsLists(langFile);
Iterator<String> filenameit = localTrans.keySet().iterator();
while (filenameit.hasNext()) {
String file = filenameit.next();
Map<String, String> tmptrans = localTrans.get(file);
for (String sourcetxt : tmptrans.keySet()) {
String targettxt = tmptrans.get(sourcetxt);
if (targettxt != null && !targettxt.isEmpty()) {
boolean sendit = true;
// check if already published (in newsPool)
Iterator<NewsDB.Record> it = sb.peers.newsPool.recordIterator(NewsPool.INCOMING_DB);
while (it.hasNext()) {
NewsDB.Record rtmp = it.next();
if (rtmp == null) {
continue;
}
if (NewsPool.CATEGORY_TRANSLATION_ADD.equals(rtmp.category())) {
String tmplng = rtmp.attribute("language", null);
String tmpfile = rtmp.attribute("file", null);
String tmpsource = rtmp.attribute("source", null);
String tmptarget = rtmp.attribute("target", null);
if (sb.peers.mySeed().hash.equals(rtmp.originator())) {
/*
if (tmplng != null && tmplng.equals(currentlang)) {
sendit = false;
break;
}*/
if (tmpfile != null && tmpfile.equals(file)) {
sendit = false;
break;
}
if (tmpsource != null && tmpsource.equals(sourcetxt)) {
sendit = false;
break;
}
if (tmptarget != null && tmptarget.equals(targettxt)) {
sendit = false;
break;
}
}
// if news with file and source exist (maybe from other peer) - skip sending another msg (to avoid confusion)
if ((tmpfile != null && tmpfile.equals(file))
&& (tmpsource != null && tmpsource.equals(sourcetxt))) {
sendit = false;
break;
}
}
}
if (sendit) {
final HashMap<String, String> map = new HashMap<String, String>();
map.put("language", currentlang);
map.put("file", file);
map.put("source", sourcetxt);
map.put("target", targettxt);
sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_TRANSLATION_ADD, map);
}
}
}
}
}
String refid;
if ((post != null) && ((refid = post.get("voteNegative", null)) != null)) {
// make new news message with voting
if (!sb.isRobinsonMode()) {
final HashMap<String, String> map = new HashMap<String, String>();
map.put("language", currentlang);
map.put("file", crypt.simpleDecode(post.get("filename", "")));
map.put("source", crypt.simpleDecode(post.get("source", "")));
map.put("target", crypt.simpleDecode(post.get("target", "")));
map.put("vote", "negative");
map.put("refid", refid);
sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_TRANSLATION_VOTE_ADD, map);
try {
sb.peers.newsPool.moveOff(NewsPool.INCOMING_DB, refid);
} catch (IOException | SpaceExceededException ex) {
}
}
}
if ((post != null) && ((refid = post.get("votePositive", null)) != null)) {
if (!sb.verifyAuthentication(header)) {
prop.authenticationRequired();
return prop;
}
// add to local translation extension
if (trans.addTranslation(post.get("filename"), post.get("source"), post.get("target"))) {
File f = new File(currentlang + ".lng");
f = trans.getScratchFile(f);
trans.saveLng(currentlang, f);
}
// make new news message with voting
final HashMap<String, String> map = new HashMap<String, String>();
map.put("language", currentlang);
map.put("file", crypt.simpleDecode(post.get("filename", "")));
map.put("source", crypt.simpleDecode(post.get("source", "")));
map.put("target", crypt.simpleDecode(post.get("target", "")));
map.put("vote", "positive");
map.put("refid", refid);
sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_TRANSLATION_VOTE_ADD, map);
try {
sb.peers.newsPool.moveOff(NewsPool.INCOMING_DB, refid);
} catch (IOException | SpaceExceededException ex) {
}
}
// create Translation voting list
final HashMap<String, Integer> negativeHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes)
final HashMap<String, Integer> positiveHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes)
accumulateVotes(sb, negativeHashes, positiveHashes, NewsPool.INCOMING_DB);
final ScoreMap<String> ranking = new ConcurrentScoreMap<String>(); // score cluster for url hashes
final HashMap<String, NewsDB.Record> Translation = new HashMap<String, NewsDB.Record>(); // a mapping from an url hash to a kelondroRow.Entry with display properties
accumulateTranslations(sb, Translation, ranking, negativeHashes, positiveHashes, NewsPool.INCOMING_DB);
// read out translation-news array and create property entries
final Iterator<String> k = ranking.keys(false);
int i = 0;
NewsDB.Record row;
String filename;
String source;
String target;
while (k.hasNext()) {
String existingtarget = null;
refid = k.next();
if (refid == null) {
continue;
}
row = Translation.get(refid);
if (row == null) {
continue;
}
String lang = row.attribute("language", null);
filename = row.attribute("file", null);
source = row.attribute("source", null);
target = row.attribute("target", null);
if ((lang == null) || (filename == null) || (source == null) || (target == null)) {
continue;
}
existingtarget = trans.getTranslation(filename, source);
boolean altexist = existingtarget != null && !target.isEmpty() && !existingtarget.isEmpty() && !existingtarget.equals(target);
prop.put("results_" + i + "_refid", refid);
prop.put("results_" + i + "_url", filename); // url to local file
prop.put("results_" + i + "_targetlanguage", lang);
prop.put("results_" + i + "_filename", filename);
prop.putHTML("results_" + i + "_source", source);
prop.putHTML("results_" + i + "_target", target);
prop.put("results_" + i + "_existing", altexist);
prop.putHTML("results_" + i + "_existing_target", existingtarget);
prop.put("results_" + i + "_score", ranking.get(refid));
prop.put("results_" + i + "_peername", sb.peers.get(row.originator()).getName());
i++;
if (i >= 50) {
break;
}
}
prop.put("results", i);
return prop;
}
private static void accumulateVotes(final Switchboard sb, final HashMap<String, Integer> negativeHashes, final HashMap<String, Integer> positiveHashes, final int dbtype) {
final int maxCount = Math.min(1000, sb.peers.newsPool.size(dbtype));
NewsDB.Record newsrecord;
final Iterator<NewsDB.Record> recordIterator = sb.peers.newsPool.recordIterator(dbtype);
int j = 0;
while ((recordIterator.hasNext()) && (j++ < maxCount)) {
newsrecord = recordIterator.next();
if (newsrecord == null) {
continue;
}
if (newsrecord.category().equals(NewsPool.CATEGORY_TRANSLATION_VOTE_ADD)) {
final String refid = newsrecord.attribute("refid", "");
final String vote = newsrecord.attribute("vote", "");
final int factor = ((dbtype == NewsPool.OUTGOING_DB) || (dbtype == NewsPool.PUBLISHED_DB)) ? 2 : 1;
if (vote.equals("negative")) {
final Integer i = negativeHashes.get(refid);
if (i == null) {
negativeHashes.put(refid, Integer.valueOf(factor));
} else {
negativeHashes.put(refid, Integer.valueOf(i.intValue() + factor));
}
}
if (vote.equals("positive")) {
final Integer i = positiveHashes.get(refid);
if (i == null) {
positiveHashes.put(refid, Integer.valueOf(factor));
} else {
positiveHashes.put(refid, Integer.valueOf(i.intValue() + factor));
}
}
}
}
}
private static void accumulateTranslations(
final Switchboard sb,
final HashMap<String, NewsDB.Record> translationmsg, final ScoreMap<String> ranking,
final HashMap<String, Integer> negativeHashes, final HashMap<String, Integer> positiveHashes, final int dbtype) {
final int maxCount = Math.min(1000, sb.peers.newsPool.size(dbtype));
NewsDB.Record newsrecord;
final Iterator<NewsDB.Record> recordIterator = sb.peers.newsPool.recordIterator(dbtype);
int j = 0;
String refid = "";
String targetlanguage ="";
String filename="";
String source="";
String target="";
int score = 0;
Integer vote;
while ((recordIterator.hasNext()) && (j++ < maxCount)) {
newsrecord = recordIterator.next();
if (newsrecord == null) {
continue;
}
if ((newsrecord.category().equals(NewsPool.CATEGORY_TRANSLATION_ADD))
&& ((sb.peers.get(newsrecord.originator())) != null)) {
refid = newsrecord.id();
targetlanguage = newsrecord.attribute("language", "");
filename = newsrecord.attribute("file", "");
source = newsrecord.attribute("source", "");
target = newsrecord.attribute("target", "");
if (refid.isEmpty() || targetlanguage.isEmpty() || filename.isEmpty() || source.isEmpty() || target.isEmpty()) {
continue;
}
score = 0;
}
// add/subtract votes and write record
if ((vote = negativeHashes.get(refid)) != null) {
score -= vote.intValue();
}
if ((vote = positiveHashes.get(refid)) != null) {
score += vote.intValue();
}
// consider double-entries
if (translationmsg.containsKey(refid)) {
ranking.inc(refid, score);
} else {
ranking.set(refid, score);
translationmsg.put(refid, newsrecord);
}
}
}
}

@ -46,7 +46,7 @@
</fieldset>
</form>
<p>Check for remote translation proposals and/or share your own added translations <a href="TransNews_p.html">Translation News</a></p>
#%env/templates/footer.template%#
</body>
</html>

@ -206,6 +206,18 @@ public class NewsPool {
*/
private static final String CATEGORY_BLOG_DEL = "blog_del";
/* ------------------------------------------------------------------------
* TRANSLATION related CATEGORIES
* ------------------------------------------------------------------------ */
/**
* a translation was added
*/
public static final String CATEGORY_TRANSLATION_ADD = "transadd";
/**
* a vote on a translation
*/
public static final String CATEGORY_TRANSLATION_VOTE_ADD = "transavt";
/* ========================================================================
* ARRAY of valid CATEGORIES
* ======================================================================== */
@ -250,7 +262,11 @@ public class NewsPool {
// BLOG related CATEGORIES
CATEGORY_BLOG_ADD,
CATEGORY_BLOG_DEL
CATEGORY_BLOG_DEL,
// TRANSLATION related CATEGORIES
CATEGORY_TRANSLATION_ADD,
CATEGORY_TRANSLATION_VOTE_ADD
};
private static final Set<String> categories = new HashSet<String>();
static {
@ -398,28 +414,35 @@ public class NewsPool {
return pc;
}
/**
* Check max keep duration depending on news category and return true if duration
* is exceeded
*
* @param seedDB
* @param record
* @return true if news should be removed
*/
private static boolean automaticProcessP(final SeedDB seedDB, final NewsDB.Record record) {
if (record == null) return false;
if (record.category() == null) return true;
final long created = record.created().getTime();
if ((System.currentTimeMillis() - created) > (6L * MILLISECONDS_PER_HOUR)) {
// remove everything after 1 day
return true;
}
final long duration = System.currentTimeMillis() - created;
if ((record.category().equals(CATEGORY_WIKI_UPDATE)) &&
((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) {
(duration > (3L * MILLISECONDS_PER_DAY))) {
return true;
}
if ((record.category().equals(CATEGORY_BLOG_ADD)) &&
((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) {
(duration > (3L * MILLISECONDS_PER_DAY))) {
return true;
}
if ((record.category().equals(CATEGORY_PROFILE_UPDATE)) &&
((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) {
(duration > (3L * MILLISECONDS_PER_DAY))) {
return true;
}
if ((record.category().equals(CATEGORY_CRAWL_START)) &&
((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) {
(duration > (3L * MILLISECONDS_PER_DAY))) {
final Seed seed = seedDB.get(record.originator());
if (seed == null) return true;
try {
@ -428,6 +451,14 @@ public class NewsPool {
return true;
}
}
if ((record.category().equals(CATEGORY_TRANSLATION_ADD) || record.category().equals(CATEGORY_TRANSLATION_VOTE_ADD))
&& (duration > (7L * MILLISECONDS_PER_DAY))) {
return true;
}
if (duration > MILLISECONDS_PER_DAY) {
// remove everything else after 1 day
return true;
}
return false;
}

Loading…
Cancel
Save