diff --git a/htroot/TransNews_p.html b/htroot/TransNews_p.html new file mode 100644 index 000000000..7a154c9dc --- /dev/null +++ b/htroot/TransNews_p.html @@ -0,0 +1,61 @@ + + + + YaCy '#[clientname]#': Translation News + #%env/templates/metas.template%# + + + #%env/templates/header.template%# + +

Translation News for Language #[currentlang]#

+ +
+

+ 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.
+ (#[transsize]# entries available)   +   You can check your outgoing messages here +

+
+ #{results}# + + +
+ + + + + + + + #(existing)#xxx:: + + + + #(/existing)# + + + + + + + +
File:#[filename]#Originator
English:#[source]#
existing#[target]#
Translation:#[target]##[peername]#
score #[score]#  + + +    + + +   Vote on this translation. If you vote positive the translation is added to your local translation list. +
+
+ + + #{/results}# +

+
+

+ + #%env/templates/footer.template%# + + diff --git a/htroot/TransNews_p.java b/htroot/TransNews_p.java new file mode 100644 index 000000000..829c0d488 --- /dev/null +++ b/htroot/TransNews_p.java @@ -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> localTrans = trans.loadTranslationsLists(langFile); + Iterator filenameit = localTrans.keySet().iterator(); + while (filenameit.hasNext()) { + String file = filenameit.next(); + Map 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 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 map = new HashMap(); + 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 map = new HashMap(); + 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 map = new HashMap(); + + 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 negativeHashes = new HashMap(); // a mapping from an url hash to Integer (count of votes) + final HashMap positiveHashes = new HashMap(); // a mapping from an url hash to Integer (count of votes) + accumulateVotes(sb, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); + final ScoreMap ranking = new ConcurrentScoreMap(); // score cluster for url hashes + final HashMap Translation = new HashMap(); // 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 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 negativeHashes, final HashMap positiveHashes, final int dbtype) { + final int maxCount = Math.min(1000, sb.peers.newsPool.size(dbtype)); + NewsDB.Record newsrecord; + final Iterator 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 translationmsg, final ScoreMap ranking, + final HashMap negativeHashes, final HashMap positiveHashes, final int dbtype) { + final int maxCount = Math.min(1000, sb.peers.newsPool.size(dbtype)); + NewsDB.Record newsrecord; + final Iterator 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); + } + + } + } +} diff --git a/htroot/Translator_p.html b/htroot/Translator_p.html index f37527bea..1025e6bc0 100644 --- a/htroot/Translator_p.html +++ b/htroot/Translator_p.html @@ -46,7 +46,7 @@ - +

Check for remote translation proposals and/or share your own added translations Translation News

#%env/templates/footer.template%# diff --git a/source/net/yacy/peers/NewsPool.java b/source/net/yacy/peers/NewsPool.java index 7d946359a..1c40ee086 100644 --- a/source/net/yacy/peers/NewsPool.java +++ b/source/net/yacy/peers/NewsPool.java @@ -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 categories = new HashSet(); 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; }