home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / komunikace / kmeleon / K-Meleon1.1.3en-US.exe / chrome / newsfox.jar / content / newsfox / rss.js < prev    next >
Text File  |  2007-10-23  |  18KB  |  562 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is NewsFox.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Andy Frank <andy@andyfrank.com>.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005-2007
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Andrey Gromyko <andrey@gromyko.name>
  23.  *   Ron Pruitt <wa84it@gmail.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the LGPL or the GPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. ////////////////////////////////////////////////////////////////
  40. // Global
  41. ////////////////////////////////////////////////////////////////
  42.  
  43. const KM_ALT_TITLE = "NEWSFOX";
  44. const POLL_INTERVAL = 1000;
  45. const MATHML_ENTITY = " <!ENTITY % mDTD SYSTEM \"http://m/mathml.dtd\" > %mDTD; ";
  46.  
  47. var cancelCheck = false;
  48. var httpRequestTimeout;
  49. var httpPollingTimeout;
  50. var xmlhttp = null;
  51.  
  52. ////////////////////////////////////////////////////////////////
  53. // Check feeds
  54. ////////////////////////////////////////////////////////////////
  55.  
  56. function doCancelCheckFeeds()
  57. {
  58.   if (gCheckInProgress) cancelCheck = true;
  59. }
  60.  
  61. function precheckFeed(gFeedsToCheck)
  62. {
  63.   var url = gFeedsToCheck.shift();
  64.  
  65.   if (url == null)
  66.   {
  67.     if (gCheckInProgress) postRefresh();
  68.     return;
  69.   }
  70.  
  71.   var i=gFmodel.size();
  72.   while(gFmodel.get(--i).url != url) if (i==0) precheckFeed(gFeedsToCheck);
  73.   var index = i;
  74.  
  75. // TODO using this causes article pane to reset, checking feed with categories
  76. //     open is okay?, but may not be displaying all categories afterward
  77. //     I didn't like the jumping around of the feedtree with this in either
  78. // closes feed before checking as # of categories may change
  79. //  var feedtree = document.getElementById("newsfox.feedTree");
  80. //  var row = feedtree.currentIndex;
  81. //  if (row != -1)
  82. //  {
  83. //    var curGrp = gIdx.fdgp[row];
  84. //    var nFeed = gIdx.feed[row];
  85. //    i= gIdx.feed.length;
  86. //    while (--i >= 0)
  87. //      if (gIdx.feed[i] == index && gIdx.open[i] == true)
  88. //        feedtree.view.toggleOpenState(i);
  89. //    refreshModelSelect(getFeedRow(curGrp,nFeed));
  90. //  }
  91.  
  92.   var elem = document.getElementById("busyTextNumbers");
  93.   elem.value = (gNumToCheck-gFeedsToCheck.length) + " / " + gNumToCheck;
  94.  
  95.   var feed = gFmodel.get(index);
  96.   feed.error = ERROR_OK;
  97.  
  98.     var httpicon = document.getElementById("newsfox-icon");
  99.     httpicon.src = feed.icon.src;
  100.     httpicon.width = 16;
  101.     httpicon.height = 16;
  102.  
  103.   xmlhttp = new XMLHttpRequest();
  104.   url = feed.url;
  105.   // trick of adding the current time to bypass the cache; suggested by Konstantin Svist
  106.     // Commented out since 0.6.4 because of problems the trick caused with different feeds
  107.     // url += (url.match(/\?/) == null ? '?' : '&') + (new Date()).getTime();
  108.     // Instead, Ron Pruitt proposed to put it as experienced user option to replace:
  109.     url = url.replace(/%CURRENT_DATETIME%/, (new Date()).getTime());
  110.  
  111.   xmlhttp.open("get", url);
  112.   xmlhttp.setRequestHeader("User-Agent", "Mozilla/5.0 NewsFox/" + VERSION);
  113.   xmlhttp.overrideMimeType("application/xml");
  114.   // TODO: do error handling
  115.   // xmlhttp.onerror = foo;
  116.   xmlhttp.onload = function() { checkFeed(xmlhttp,gFeedsToCheck,url); }
  117.  
  118.         httpRequestTimeout = setTimeout(this.abortHttpRequest, gOptions.refreshTimeout);
  119.         httpPollingTimeout = setTimeout(this.pollCancelCheck, POLL_INTERVAL);
  120.   // the next line doesn't have any effect :( Any other ideas how to bypass the cache?
  121.   // xmlhttp.channel.loadFlags = Components.interfaces.nsICachingChannel.LOAD_BYPASS_CACHE;
  122.     // local file not found gives error, this bypasses waiting the timeout
  123.   try
  124.     {
  125.         xmlhttp.send(null);
  126.     }
  127.     catch(err)
  128.     {
  129.         gFmodel.get(index).error = err.toString();
  130.         stopHttpRequest(true);
  131.     }
  132. }
  133.  
  134. function checkFeed(xmlhttp, gFeedsToCheck, url)
  135. {
  136.     if(null != httpRequestTimeout)
  137.         clearTimeout(httpRequestTimeout);
  138.     if(null != httpPollingTimeout)
  139.         clearTimeout(httpPollingTimeout);
  140.   try
  141.   {
  142.         var index = gFmodel.getIndexByURL(url);
  143.     var feed = gFmodel.getFeedByURL(url);
  144.         loadFeed(feed);
  145.  
  146.         var xml = xmlhttp.responseXML;
  147.         if (xml.documentElement.localName.toLowerCase() == 'parsererror')
  148.             xml = repairIt(xmlhttp);
  149.         var parser = new Parser2(xml,url);
  150.  
  151.         var refreshingDispFeed = false;
  152.         if ((gCollect.type == 1 || gCollect.type == 2) && feed == gCollect.getFeed(0))
  153.             refreshingDispFeed = true;
  154.     if (parser.title != null) 
  155.       feed.defaultName = parser.title;
  156.         if (refreshingDispFeed)
  157.             document.getElementById("feedTitle").value = feed.getDisplayName();
  158.  
  159.         if (feed.homepage == null || feed.homepage == "")
  160.             feed.homepage = encodeUrl(parser.link);
  161.         downloadIcon(feed);
  162.  
  163.         var now = new Date();
  164.         for (var i=0; i<feed.size(); i++)
  165.         {
  166.             var art = feed.get(i);
  167.             var artAge = now - art.date;
  168.             if (!feed.deleteOld || feed.isFlagged(i) || (!feed.isRead(i) && feed.dontDeleteUnread) || artAge < feed.daysToKeep*24*60*60*1000)
  169.                 art.toRemove = false;
  170.             else
  171.                 art.toRemove = true;
  172.         }
  173.         for (i=0; i<feed.deletedsize(); i++)
  174.             feed.deletedget(i).toRemove = true;
  175.  
  176.         var idArray = new Array();
  177.         for (i=0; i<parser.items.length; i++)
  178.         {
  179.             var uniq = true;
  180.             for (var j=0; j<idArray.length; j++)
  181.                 if (parser.items[i].id == idArray[j]) uniq = false;
  182.             if (uniq) idArray.push(parser.items[i].id);
  183.             else parser.items[i].id = null;
  184.         }
  185.  
  186.     for (i=0; i<parser.items.length; i++)
  187.     {
  188.       var item = parser.items[i];
  189.       if (doesArticleExist(feed, item)) continue;
  190.             gNewItemsCount++;
  191.       if (!item.title || item.title == "") item.title = (item.body) ? entityDecode(item.body).substr(0, 70) + "..." : "...";
  192.       feed.add(item,0);  // unread, unflagged
  193.     }
  194.  
  195.     // Need to turn off article pane here if it is from this feed since
  196.     // collection and feed won't agree once we start deleting and sorting
  197.     // adding articles above is okay since disagreement is at the end
  198.             var refreshingDispColl = false;
  199.             if ((gCollect.type == 1 || gCollect.type == 2) && feed == gCollect.getFeed(0))
  200.                 refreshingDispColl = true;
  201.             else if (gCollect.type == 0 || gCollect.type == 3)
  202.             {
  203.                 var curGrp = gFdGroup[gCollect.grpindex];
  204.                 for (i=0; i<curGrp.list.length; i++)
  205.                     if (index == curGrp.list[i]) refreshingDispColl = true;
  206.             }
  207.         if (refreshingDispColl)
  208.         {
  209.         var arttree = document.getElementById("newsfox.articleTree");
  210.             var artIndex = arttree.currentIndex;
  211.             var artId = null;
  212.             if (artIndex > -1) artId = gCollect.get(arttree.currentIndex).id;
  213.             arttree.view = null;  // will be replaced with new one, no need to save
  214.         }
  215.  
  216.         for (i=feed.size()-1; i>=0; i--)
  217.             if (feed.get(i).toRemove) feed.remove(i);
  218.         for (i=feed.deletedsize()-1; i>=0; i--)
  219.             if (feed.deletedget(i).toRemove) feed.deletedremove(i);
  220.  
  221.         var sortcollect = new NormalCollection(index,0,false);  // index of feed
  222.         sortcollect.artSort("date","descending");
  223.         feed = deleteDuplicates(feed);
  224.     feed.sortCategories();
  225.     saveFeed(feed);
  226.         saveFeedModel();   // keep flags synchronized on disk
  227.   }
  228.   catch (err) { feed.error = err.toString(); }
  229.  
  230.   // Update Title, feedTree, and articleTree if current feed
  231.     setTitle();
  232.     var feedtree = document.getElementById("newsfox.feedTree");
  233.     feedtree.treeBoxObject.invalidate();
  234.     if (refreshingDispColl)
  235.     {
  236.     feedSelected();  // replaces arttree.view with new one
  237.         var index = -1;
  238.         if (artId != null)
  239.             for (i=0; i<gCollect.size(); i++)
  240.                 if (gCollect.get(i).id == artId) index = i;
  241.         gDisplayArticle = false;
  242.         arttree.view.selection.select(index);
  243.         gDisplayArticle = true;
  244.         arttree.treeBoxObject.ensureRowIsVisible(index);
  245.     }
  246.  
  247.   if (!cancelCheck)  // Check next feed
  248.     precheckFeed(gFeedsToCheck);
  249.   else              // We're done!
  250.     postRefresh();
  251. }
  252.  
  253. function abortHttpRequest()
  254. {
  255.     stopHttpRequest(true);
  256. }
  257.  
  258. function pollCancelCheck()
  259. {
  260.     stopHttpRequest(false);
  261.     httpPollingTimeout = setTimeout(pollCancelCheck,POLL_INTERVAL);
  262. }
  263.  
  264. function stopHttpRequest(fromAbort)
  265. {
  266.     if (!fromAbort && !cancelCheck) return;
  267.     xmlhttp.abort();
  268.     if (!cancelCheck)
  269.         precheckFeed(gFeedsToCheck);
  270.     else
  271.         postRefresh();
  272. }
  273.  
  274. function postRefresh()
  275. {
  276.     var httpicon = document.getElementById("newsfox-icon");
  277.     httpicon.src = "chrome://newsfox/skin/newsfox-16.png";
  278.   var elem = document.getElementById("busyTextNumbers");
  279.   elem.value = "";
  280.   var elem = document.getElementById("notBusyText");
  281.     elem.value = NEWSFOX + " " + VERSION;
  282.   elem.removeAttribute("hidden");
  283.   var elem = document.getElementById("busyText");
  284.   elem.hidden = "true";
  285.   gCheckInProgress = false;
  286.   cancelCheck = false;
  287.   gFeedsToCheck.length = 0;
  288.     if( gOptions.autoRefresh )
  289.         gAutoRefreshTimer = setTimeout(this.checkFeeds, gOptions.autoRefreshInterval * 60 * 1000);
  290.     if( gOptions.notifyUponNew )
  291.         reportRefreshResults();
  292. }
  293.  
  294. function reportRefreshResults()
  295. {
  296.     if (gKMeleon)
  297.     {
  298.         const DONETIME = 500;
  299.         const TITLETIME = 500;
  300.         const BLINKS = 5;
  301.         for (var i=0; i<BLINKS; i++)
  302.         {
  303.             var offset = i*(DONETIME+TITLETIME);
  304.             setTimeout(doneTitle,1+offset);
  305.             setTimeout(setTitle,1+DONETIME+offset);
  306.         }
  307.     }
  308.     else
  309.     {
  310.         var unreadTotalCount = gFdGroup[0].getUnread();
  311.         if( gNewItemsCount > 0 )
  312.         {
  313.             const NF_SB = document.getElementById("newsfox-string-bundle");
  314.             var strNew = NF_SB.getString('alert.new');
  315.             var strUnread = NF_SB.getString('alert.unread');
  316.             var message = gNewItemsCount + " " + strNew + ", " + unreadTotalCount + " " + strUnread;
  317.             var alerts = Components.classes["@mozilla.org/alerts-service;1"]
  318.                               .getService(Components.interfaces.nsIAlertsService);
  319.             alerts.showAlertNotification("chrome://newsfox/skin/newsfox-32.png", "NewsFox", message, false, "", null);
  320.         }
  321.     }
  322. }
  323.  
  324. function doneTitle()
  325. {
  326.     document.title = KM_ALT_TITLE;
  327. }
  328.  
  329. ////////////////////////////////////////////////////////////////
  330. // Util
  331. ////////////////////////////////////////////////////////////////
  332.  
  333. /**
  334.  * Encode url problem charaters.
  335.  */
  336. function encodeUrl(s)
  337. {
  338.     if (!s) return "";  // so we know it's not a new feed any more
  339.   s = s.replace(new RegExp('&','gi'), '&');
  340.   return s;
  341. }
  342.  
  343. /**
  344.  * Return true if this article already exists.
  345.  */
  346. function doesArticleExist(feed, item)
  347. {
  348.     var id = item.id;
  349.     if (id == null) return false;
  350.  
  351.   for (var i=0; i<feed.size(); i++)
  352.         if (feed.get(i).id == id)
  353.         {
  354.             var art = feed.get(i);
  355.             if (!item.title || item.title == "") item.title = (item.body) ? entityDecode(item.body).substr(0, 70) + "..." : "...";
  356.             if (art.title != item.title || art.body != item.body)
  357.             {
  358.                 feed.set(i,item);
  359.                 if (gOptions.changedUnread && 
  360.                 (entityDecode(art.body) != entityDecode(item.body)))
  361.                     feed.setRead(i,false);
  362.             }
  363.             feed.get(i).toRemove = false;
  364.             return true;
  365.         }
  366.   for (var i=0; i<feed.deletedsize(); i++)
  367.     if (feed.deletedget(i).id == id)
  368.         {
  369.             feed.deletedget(i).toRemove = false;
  370.       return true;
  371.         }
  372.   return false;
  373. }
  374.  
  375. function downloadIcon(feed)
  376. {
  377.   if (!gOptions.favicons)
  378.     { 
  379.         for (var i=0; i<gFmodel.size(); i++)
  380.             gFmodel.get(i).icon.src = ICON_OK;
  381.     }
  382.   else
  383.   {
  384.     if (feed.icon.src == null || feed.icon.src == "" || feed.icon.src == ICON_OK)
  385.     {
  386.       feed.icon.src = ICON_OK;
  387.             // don't guessHomepage before feed refreshed, if feed.homepage is null
  388.             if (gOptions.guessHomepage && feed.homepage == "")
  389.                 feed.homepage = guessHomepage(feed);
  390.       if (feed.homepage != null && feed.homepage != "")
  391.       {
  392.         var favicon = feed.homepage.replace("index.html","");
  393.         if (favicon.charAt(favicon.length-1) != "/") favicon += "/";
  394.         favicon += "favicon.ico";
  395.             var file = getProfileDir();
  396.             file.append(feed.uid + ".ico");
  397.                 getFavIcon(favicon,file);
  398.       }
  399.     }
  400.   }
  401. }
  402.  
  403. function getFavIcon(favicon,file)
  404. {
  405.     try
  406.     {
  407.       var IOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
  408.       var IOchannel = IOService.newChannel(favicon,null,null);
  409.       var nfListener = Components.classes["@mozilla.org/network/downloader;1"].createInstance(Components.interfaces.nsIDownloader);
  410.       nfListener.init(nfObserver,file);
  411.       IOchannel.asyncOpen(nfListener,null);
  412.     }
  413.     catch(e) {}
  414. }
  415.  
  416. function isImg(file)
  417. {
  418. // TODO doesn't work with K-M
  419. //  if (!file.exists() || file.fileSize == 0) return false;
  420.   if (file.fileSize == 0) return false;
  421.   var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance( Components.interfaces.nsIFileInputStream );
  422.   inputStream.init( file,0x01,00004,null);
  423.   var scInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance( Components.interfaces.nsIScriptableInputStream );
  424.   scInputStream.init(inputStream);
  425.   var output = scInputStream.read(-1);
  426.   scInputStream.close();
  427.   inputStream.close();
  428.   if (output.toLowerCase().indexOf('html') == -1) return true;
  429.   return false;
  430. }
  431.  
  432. var nfObserver =
  433. {
  434.   onDownloadComplete: function(adownloader, arequest, actxt , astatus, aresult)
  435.   {
  436.     var aleafName = aresult.leafName;
  437.     var auid = aleafName.substr(0,aleafName.length-4);
  438.     var i=gFmodel.size();
  439.         if (i==0) return;
  440.     while(gFmodel.get(--i).uid != auid) if (i==0) return;
  441.     if (isImg(aresult))
  442.     {
  443.       gFmodel.get(i).icon.src = "file:///" + aresult.path;
  444.       var feedtree = document.getElementById("newsfox.feedTree");
  445.       var index = feedtree.currentIndex;
  446.       refreshModelSelect(index);
  447.     }
  448. // doesn't allow removal, shouldn't need
  449. //        else
  450. //            if (aresult.exists()) aresult.remove(false);
  451.   }
  452. }
  453.  
  454. function guessHomepage(feed)
  455. {
  456.     var hmpg = feed.url;
  457.     var feedburner = hmpg.indexOf("feeds.feedburner.com");
  458.     hmpg = hmpg.replace("feeds.feedburner.com/","www.");
  459.     var start = hmpg.indexOf("file://");
  460.     if (start != -1) return "";
  461.     start = hmpg.indexOf("://");
  462.     if (start == -1) return "";
  463.     var end = hmpg.indexOf("/",start+3);
  464.     if (end > -1) hmpg = hmpg.substring(0,end);
  465.     if (feedburner > -1) hmpg += ".com";
  466.     hmpg = hmpg.replace("/rss.","/www.");
  467.     hmpg += "/";
  468.     return hmpg;
  469. }
  470.  
  471. function deleteDuplicates(feed)
  472. {
  473.     for (var i=feed.size()-1; i>=0; i--)
  474.     {
  475.         var art = feed.get(i);
  476.         if (art.id == art.link)  // never delete ones with real ids
  477.             for (var j=i+1; j<feed.size(); j++)
  478.             {
  479.                 var art2 = feed.get(j);
  480.                 if (Math.abs(art.date-art2.date) > 1000) break;
  481.                 if (art2.link == art.link && art2.title == art.title && encStr(art2.body) == encStr(art.body))
  482.                     feed.remove(i);
  483.             }
  484.     }
  485.     return feed;
  486. }
  487.  
  488. function encStr(s) 
  489. {
  490.     if( !s ) return "";
  491.     var i=0;
  492.     var code, replace, hex, j, from;
  493.     while (i < s.length)
  494.     {
  495.         code = s.charCodeAt(i);
  496.         if (code < 32 || code > 126)
  497.         {
  498.             replace = "&#" + code + ";";
  499.             hex = code.toString(16);
  500.             for (j=hex.length; j<4; j++) hex = "0" + hex;
  501.             from = "\\u" + hex;
  502.             s = s.replace(new RegExp(from,"g"),replace);
  503.             i += replace.length;
  504.         }
  505.         else
  506.             i++;
  507.     }
  508.   return s;
  509. }
  510.  
  511. // temporary function until Firefox handled external DTDs in xml parsing
  512. function repairIt(xmlhttp)
  513. {
  514.     var xml2 = xmlhttp.responseXML;
  515.     var domParser = new DOMParser();
  516.     // kludge: add mathml.dtd to doctype
  517.     var httpText = xmlhttp.responseText;
  518.     var endHeader = httpText.indexOf("?>");
  519.     if (endHeader > -1)
  520.     {
  521.         var docIndex = httpText.indexOf("<!DOCTYPE");
  522.         if (docIndex > -1)
  523.         {
  524.             var strtDtd = httpText.indexOf("[",docIndex);
  525.             var nestLevel = 1;
  526.             var index = docIndex+1;
  527.             var nxtLt = httpText.indexOf("<",index);
  528.             var nxtGt = httpText.indexOf(">",index);
  529.             while (nestLevel > 0)
  530.             {
  531.                 if (nxtLt < nxtGt)
  532.                 {
  533.                     nestLevel++;
  534.                     index = nxtLt+1;
  535.                     nxtLt = httpText.indexOf("<",index);
  536.                 }
  537.                 else
  538.                 {
  539.                     nestLevel--;
  540.                     index = nxtGt+1;
  541.                     nxtGt = httpText.indexOf(">",index);
  542.                 }
  543.             }
  544.             var endDoctype = httpText.lastIndexOf(">",nxtGt-1);
  545.             if (strtDtd > -1 && strtDtd < endDoctype)
  546.                 var newText = httpText.substring(0,strtDtd+1) + MATHML_ENTITY + httpText.substring(strtDtd+1);
  547.             else
  548.                 var newText = httpText.substring(0,endDoctype) +" [" + MATHML_ENTITY + "] " + httpText.substring(endDoctype);
  549.         }
  550.         else
  551.             var newText = httpText.substring(0,endHeader+2) + "\n<!DOCTYPE mathml [" + MATHML_ENTITY + "]>\n" + httpText.substring(endHeader+2);
  552.         xml2 = domParser.parseFromString(newText, "application/xml");
  553.     }
  554.     if (xml2.documentElement.localName.toLowerCase() == 'parsererror')
  555.     {
  556.         // from Nils Maier, Sage bug#15473, just replace & with &
  557.         httpText = httpText.replace(/&(?!amp;|quot;|lt;|gt;)/gm, '&');
  558.         xml2 = domParser.parseFromString(httpText, "application/xml");
  559.     }
  560.     return xml2;
  561. }
  562.