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 >
Wrap
Text File
|
2007-10-23
|
18KB
|
562 lines
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is NewsFox.
*
* The Initial Developer of the Original Code is
* Andy Frank <andy@andyfrank.com>.
* Portions created by the Initial Developer are Copyright (C) 2005-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Andrey Gromyko <andrey@gromyko.name>
* Ron Pruitt <wa84it@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the LGPL or the GPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
////////////////////////////////////////////////////////////////
// Global
////////////////////////////////////////////////////////////////
const KM_ALT_TITLE = "NEWSFOX";
const POLL_INTERVAL = 1000;
const MATHML_ENTITY = " <!ENTITY % mDTD SYSTEM \"http://m/mathml.dtd\" > %mDTD; ";
var cancelCheck = false;
var httpRequestTimeout;
var httpPollingTimeout;
var xmlhttp = null;
////////////////////////////////////////////////////////////////
// Check feeds
////////////////////////////////////////////////////////////////
function doCancelCheckFeeds()
{
if (gCheckInProgress) cancelCheck = true;
}
function precheckFeed(gFeedsToCheck)
{
var url = gFeedsToCheck.shift();
if (url == null)
{
if (gCheckInProgress) postRefresh();
return;
}
var i=gFmodel.size();
while(gFmodel.get(--i).url != url) if (i==0) precheckFeed(gFeedsToCheck);
var index = i;
// TODO using this causes article pane to reset, checking feed with categories
// open is okay?, but may not be displaying all categories afterward
// I didn't like the jumping around of the feedtree with this in either
// closes feed before checking as # of categories may change
// var feedtree = document.getElementById("newsfox.feedTree");
// var row = feedtree.currentIndex;
// if (row != -1)
// {
// var curGrp = gIdx.fdgp[row];
// var nFeed = gIdx.feed[row];
// i= gIdx.feed.length;
// while (--i >= 0)
// if (gIdx.feed[i] == index && gIdx.open[i] == true)
// feedtree.view.toggleOpenState(i);
// refreshModelSelect(getFeedRow(curGrp,nFeed));
// }
var elem = document.getElementById("busyTextNumbers");
elem.value = (gNumToCheck-gFeedsToCheck.length) + " / " + gNumToCheck;
var feed = gFmodel.get(index);
feed.error = ERROR_OK;
var httpicon = document.getElementById("newsfox-icon");
httpicon.src = feed.icon.src;
httpicon.width = 16;
httpicon.height = 16;
xmlhttp = new XMLHttpRequest();
url = feed.url;
// trick of adding the current time to bypass the cache; suggested by Konstantin Svist
// Commented out since 0.6.4 because of problems the trick caused with different feeds
// url += (url.match(/\?/) == null ? '?' : '&') + (new Date()).getTime();
// Instead, Ron Pruitt proposed to put it as experienced user option to replace:
url = url.replace(/%CURRENT_DATETIME%/, (new Date()).getTime());
xmlhttp.open("get", url);
xmlhttp.setRequestHeader("User-Agent", "Mozilla/5.0 NewsFox/" + VERSION);
xmlhttp.overrideMimeType("application/xml");
// TODO: do error handling
// xmlhttp.onerror = foo;
xmlhttp.onload = function() { checkFeed(xmlhttp,gFeedsToCheck,url); }
httpRequestTimeout = setTimeout(this.abortHttpRequest, gOptions.refreshTimeout);
httpPollingTimeout = setTimeout(this.pollCancelCheck, POLL_INTERVAL);
// the next line doesn't have any effect :( Any other ideas how to bypass the cache?
// xmlhttp.channel.loadFlags = Components.interfaces.nsICachingChannel.LOAD_BYPASS_CACHE;
// local file not found gives error, this bypasses waiting the timeout
try
{
xmlhttp.send(null);
}
catch(err)
{
gFmodel.get(index).error = err.toString();
stopHttpRequest(true);
}
}
function checkFeed(xmlhttp, gFeedsToCheck, url)
{
if(null != httpRequestTimeout)
clearTimeout(httpRequestTimeout);
if(null != httpPollingTimeout)
clearTimeout(httpPollingTimeout);
try
{
var index = gFmodel.getIndexByURL(url);
var feed = gFmodel.getFeedByURL(url);
loadFeed(feed);
var xml = xmlhttp.responseXML;
if (xml.documentElement.localName.toLowerCase() == 'parsererror')
xml = repairIt(xmlhttp);
var parser = new Parser2(xml,url);
var refreshingDispFeed = false;
if ((gCollect.type == 1 || gCollect.type == 2) && feed == gCollect.getFeed(0))
refreshingDispFeed = true;
if (parser.title != null)
feed.defaultName = parser.title;
if (refreshingDispFeed)
document.getElementById("feedTitle").value = feed.getDisplayName();
if (feed.homepage == null || feed.homepage == "")
feed.homepage = encodeUrl(parser.link);
downloadIcon(feed);
var now = new Date();
for (var i=0; i<feed.size(); i++)
{
var art = feed.get(i);
var artAge = now - art.date;
if (!feed.deleteOld || feed.isFlagged(i) || (!feed.isRead(i) && feed.dontDeleteUnread) || artAge < feed.daysToKeep*24*60*60*1000)
art.toRemove = false;
else
art.toRemove = true;
}
for (i=0; i<feed.deletedsize(); i++)
feed.deletedget(i).toRemove = true;
var idArray = new Array();
for (i=0; i<parser.items.length; i++)
{
var uniq = true;
for (var j=0; j<idArray.length; j++)
if (parser.items[i].id == idArray[j]) uniq = false;
if (uniq) idArray.push(parser.items[i].id);
else parser.items[i].id = null;
}
for (i=0; i<parser.items.length; i++)
{
var item = parser.items[i];
if (doesArticleExist(feed, item)) continue;
gNewItemsCount++;
if (!item.title || item.title == "") item.title = (item.body) ? entityDecode(item.body).substr(0, 70) + "..." : "...";
feed.add(item,0); // unread, unflagged
}
// Need to turn off article pane here if it is from this feed since
// collection and feed won't agree once we start deleting and sorting
// adding articles above is okay since disagreement is at the end
var refreshingDispColl = false;
if ((gCollect.type == 1 || gCollect.type == 2) && feed == gCollect.getFeed(0))
refreshingDispColl = true;
else if (gCollect.type == 0 || gCollect.type == 3)
{
var curGrp = gFdGroup[gCollect.grpindex];
for (i=0; i<curGrp.list.length; i++)
if (index == curGrp.list[i]) refreshingDispColl = true;
}
if (refreshingDispColl)
{
var arttree = document.getElementById("newsfox.articleTree");
var artIndex = arttree.currentIndex;
var artId = null;
if (artIndex > -1) artId = gCollect.get(arttree.currentIndex).id;
arttree.view = null; // will be replaced with new one, no need to save
}
for (i=feed.size()-1; i>=0; i--)
if (feed.get(i).toRemove) feed.remove(i);
for (i=feed.deletedsize()-1; i>=0; i--)
if (feed.deletedget(i).toRemove) feed.deletedremove(i);
var sortcollect = new NormalCollection(index,0,false); // index of feed
sortcollect.artSort("date","descending");
feed = deleteDuplicates(feed);
feed.sortCategories();
saveFeed(feed);
saveFeedModel(); // keep flags synchronized on disk
}
catch (err) { feed.error = err.toString(); }
// Update Title, feedTree, and articleTree if current feed
setTitle();
var feedtree = document.getElementById("newsfox.feedTree");
feedtree.treeBoxObject.invalidate();
if (refreshingDispColl)
{
feedSelected(); // replaces arttree.view with new one
var index = -1;
if (artId != null)
for (i=0; i<gCollect.size(); i++)
if (gCollect.get(i).id == artId) index = i;
gDisplayArticle = false;
arttree.view.selection.select(index);
gDisplayArticle = true;
arttree.treeBoxObject.ensureRowIsVisible(index);
}
if (!cancelCheck) // Check next feed
precheckFeed(gFeedsToCheck);
else // We're done!
postRefresh();
}
function abortHttpRequest()
{
stopHttpRequest(true);
}
function pollCancelCheck()
{
stopHttpRequest(false);
httpPollingTimeout = setTimeout(pollCancelCheck,POLL_INTERVAL);
}
function stopHttpRequest(fromAbort)
{
if (!fromAbort && !cancelCheck) return;
xmlhttp.abort();
if (!cancelCheck)
precheckFeed(gFeedsToCheck);
else
postRefresh();
}
function postRefresh()
{
var httpicon = document.getElementById("newsfox-icon");
httpicon.src = "chrome://newsfox/skin/newsfox-16.png";
var elem = document.getElementById("busyTextNumbers");
elem.value = "";
var elem = document.getElementById("notBusyText");
elem.value = NEWSFOX + " " + VERSION;
elem.removeAttribute("hidden");
var elem = document.getElementById("busyText");
elem.hidden = "true";
gCheckInProgress = false;
cancelCheck = false;
gFeedsToCheck.length = 0;
if( gOptions.autoRefresh )
gAutoRefreshTimer = setTimeout(this.checkFeeds, gOptions.autoRefreshInterval * 60 * 1000);
if( gOptions.notifyUponNew )
reportRefreshResults();
}
function reportRefreshResults()
{
if (gKMeleon)
{
const DONETIME = 500;
const TITLETIME = 500;
const BLINKS = 5;
for (var i=0; i<BLINKS; i++)
{
var offset = i*(DONETIME+TITLETIME);
setTimeout(doneTitle,1+offset);
setTimeout(setTitle,1+DONETIME+offset);
}
}
else
{
var unreadTotalCount = gFdGroup[0].getUnread();
if( gNewItemsCount > 0 )
{
const NF_SB = document.getElementById("newsfox-string-bundle");
var strNew = NF_SB.getString('alert.new');
var strUnread = NF_SB.getString('alert.unread');
var message = gNewItemsCount + " " + strNew + ", " + unreadTotalCount + " " + strUnread;
var alerts = Components.classes["@mozilla.org/alerts-service;1"]
.getService(Components.interfaces.nsIAlertsService);
alerts.showAlertNotification("chrome://newsfox/skin/newsfox-32.png", "NewsFox", message, false, "", null);
}
}
}
function doneTitle()
{
document.title = KM_ALT_TITLE;
}
////////////////////////////////////////////////////////////////
// Util
////////////////////////////////////////////////////////////////
/**
* Encode url problem charaters.
*/
function encodeUrl(s)
{
if (!s) return ""; // so we know it's not a new feed any more
s = s.replace(new RegExp('&','gi'), '&');
return s;
}
/**
* Return true if this article already exists.
*/
function doesArticleExist(feed, item)
{
var id = item.id;
if (id == null) return false;
for (var i=0; i<feed.size(); i++)
if (feed.get(i).id == id)
{
var art = feed.get(i);
if (!item.title || item.title == "") item.title = (item.body) ? entityDecode(item.body).substr(0, 70) + "..." : "...";
if (art.title != item.title || art.body != item.body)
{
feed.set(i,item);
if (gOptions.changedUnread &&
(entityDecode(art.body) != entityDecode(item.body)))
feed.setRead(i,false);
}
feed.get(i).toRemove = false;
return true;
}
for (var i=0; i<feed.deletedsize(); i++)
if (feed.deletedget(i).id == id)
{
feed.deletedget(i).toRemove = false;
return true;
}
return false;
}
function downloadIcon(feed)
{
if (!gOptions.favicons)
{
for (var i=0; i<gFmodel.size(); i++)
gFmodel.get(i).icon.src = ICON_OK;
}
else
{
if (feed.icon.src == null || feed.icon.src == "" || feed.icon.src == ICON_OK)
{
feed.icon.src = ICON_OK;
// don't guessHomepage before feed refreshed, if feed.homepage is null
if (gOptions.guessHomepage && feed.homepage == "")
feed.homepage = guessHomepage(feed);
if (feed.homepage != null && feed.homepage != "")
{
var favicon = feed.homepage.replace("index.html","");
if (favicon.charAt(favicon.length-1) != "/") favicon += "/";
favicon += "favicon.ico";
var file = getProfileDir();
file.append(feed.uid + ".ico");
getFavIcon(favicon,file);
}
}
}
}
function getFavIcon(favicon,file)
{
try
{
var IOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
var IOchannel = IOService.newChannel(favicon,null,null);
var nfListener = Components.classes["@mozilla.org/network/downloader;1"].createInstance(Components.interfaces.nsIDownloader);
nfListener.init(nfObserver,file);
IOchannel.asyncOpen(nfListener,null);
}
catch(e) {}
}
function isImg(file)
{
// TODO doesn't work with K-M
// if (!file.exists() || file.fileSize == 0) return false;
if (file.fileSize == 0) return false;
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance( Components.interfaces.nsIFileInputStream );
inputStream.init( file,0x01,00004,null);
var scInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance( Components.interfaces.nsIScriptableInputStream );
scInputStream.init(inputStream);
var output = scInputStream.read(-1);
scInputStream.close();
inputStream.close();
if (output.toLowerCase().indexOf('html') == -1) return true;
return false;
}
var nfObserver =
{
onDownloadComplete: function(adownloader, arequest, actxt , astatus, aresult)
{
var aleafName = aresult.leafName;
var auid = aleafName.substr(0,aleafName.length-4);
var i=gFmodel.size();
if (i==0) return;
while(gFmodel.get(--i).uid != auid) if (i==0) return;
if (isImg(aresult))
{
gFmodel.get(i).icon.src = "file:///" + aresult.path;
var feedtree = document.getElementById("newsfox.feedTree");
var index = feedtree.currentIndex;
refreshModelSelect(index);
}
// doesn't allow removal, shouldn't need
// else
// if (aresult.exists()) aresult.remove(false);
}
}
function guessHomepage(feed)
{
var hmpg = feed.url;
var feedburner = hmpg.indexOf("feeds.feedburner.com");
hmpg = hmpg.replace("feeds.feedburner.com/","www.");
var start = hmpg.indexOf("file://");
if (start != -1) return "";
start = hmpg.indexOf("://");
if (start == -1) return "";
var end = hmpg.indexOf("/",start+3);
if (end > -1) hmpg = hmpg.substring(0,end);
if (feedburner > -1) hmpg += ".com";
hmpg = hmpg.replace("/rss.","/www.");
hmpg += "/";
return hmpg;
}
function deleteDuplicates(feed)
{
for (var i=feed.size()-1; i>=0; i--)
{
var art = feed.get(i);
if (art.id == art.link) // never delete ones with real ids
for (var j=i+1; j<feed.size(); j++)
{
var art2 = feed.get(j);
if (Math.abs(art.date-art2.date) > 1000) break;
if (art2.link == art.link && art2.title == art.title && encStr(art2.body) == encStr(art.body))
feed.remove(i);
}
}
return feed;
}
function encStr(s)
{
if( !s ) return "";
var i=0;
var code, replace, hex, j, from;
while (i < s.length)
{
code = s.charCodeAt(i);
if (code < 32 || code > 126)
{
replace = "" + code + ";";
hex = code.toString(16);
for (j=hex.length; j<4; j++) hex = "0" + hex;
from = "\\u" + hex;
s = s.replace(new RegExp(from,"g"),replace);
i += replace.length;
}
else
i++;
}
return s;
}
// temporary function until Firefox handled external DTDs in xml parsing
function repairIt(xmlhttp)
{
var xml2 = xmlhttp.responseXML;
var domParser = new DOMParser();
// kludge: add mathml.dtd to doctype
var httpText = xmlhttp.responseText;
var endHeader = httpText.indexOf("?>");
if (endHeader > -1)
{
var docIndex = httpText.indexOf("<!DOCTYPE");
if (docIndex > -1)
{
var strtDtd = httpText.indexOf("[",docIndex);
var nestLevel = 1;
var index = docIndex+1;
var nxtLt = httpText.indexOf("<",index);
var nxtGt = httpText.indexOf(">",index);
while (nestLevel > 0)
{
if (nxtLt < nxtGt)
{
nestLevel++;
index = nxtLt+1;
nxtLt = httpText.indexOf("<",index);
}
else
{
nestLevel--;
index = nxtGt+1;
nxtGt = httpText.indexOf(">",index);
}
}
var endDoctype = httpText.lastIndexOf(">",nxtGt-1);
if (strtDtd > -1 && strtDtd < endDoctype)
var newText = httpText.substring(0,strtDtd+1) + MATHML_ENTITY + httpText.substring(strtDtd+1);
else
var newText = httpText.substring(0,endDoctype) +" [" + MATHML_ENTITY + "] " + httpText.substring(endDoctype);
}
else
var newText = httpText.substring(0,endHeader+2) + "\n<!DOCTYPE mathml [" + MATHML_ENTITY + "]>\n" + httpText.substring(endHeader+2);
xml2 = domParser.parseFromString(newText, "application/xml");
}
if (xml2.documentElement.localName.toLowerCase() == 'parsererror')
{
// from Nils Maier, Sage bug#15473, just replace & with &
httpText = httpText.replace(/&(?!amp;|quot;|lt;|gt;)/gm, '&');
xml2 = domParser.parseFromString(httpText, "application/xml");
}
return xml2;
}