diff --git a/apps/reader/views.py b/apps/reader/views.py index ebe7f6206..eb6c54f6a 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -362,6 +362,7 @@ def mark_story_as_read(request): @json.json_view def mark_feed_as_read(request): feed_ids = request.REQUEST.getlist('feed_id') + code = 0 for feed_id in feed_ids: feed = Feed.objects.get(id=feed_id) code = 0 diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index b7609e1d4..c0ea2e881 100644 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -75,16 +75,14 @@ class Feed(models.Model): try: - if lock: - lock.acquire() - try: - super(Feed, self).save(*args, **kwargs) - finally: - lock.release() - else: - super(Feed, self).save(*args, **kwargs) - except IntegrityError: + super(Feed, self).save(*args, **kwargs) + except IntegrityError, e: + duplicate_feed = Feed.objects.filter(feed_address=self.feed_address) + logging.debug("%s: %s" % (self.feed_address, duplicate_feed)) + if duplicate_feed: + merge_feeds(self.pk, duplicate_feed[0].pk) # Feed has been deleted. Just ignore it. + logging.debug(' ***> [%-30s] Feed deleted. Could not save: %s' % (self, e)) pass def update_all_statistics(self, lock=None): @@ -896,6 +894,8 @@ class DuplicateFeed(models.Model): def merge_feeds(original_feed_id, duplicate_feed_id): from apps.reader.models import UserSubscription, UserSubscriptionFolders, MUserStory from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag + if original_feed_id > duplicate_feed_id: + original_feed_id, duplicate_feed_id = duplicate_feed_id, original_feed_id try: original_feed = Feed.objects.get(pk=original_feed_id) duplicate_feed = Feed.objects.get(pk=duplicate_feed_id) diff --git a/media/js/jquery.ajaxmanager.3.js b/media/js/jquery.ajaxmanager.3.js index 08de8707e..ffc6f44e3 100644 --- a/media/js/jquery.ajaxmanager.3.js +++ b/media/js/jquery.ajaxmanager.3.js @@ -2,28 +2,12 @@ * project-site: http://plugins.jquery.com/project/AjaxManager * repository: http://github.com/aFarkas/Ajaxmanager * @author Alexander Farkas - * @version 3.0 + * @version 3.06 * Copyright 2010, Alexander Farkas * Dual licensed under the MIT or GPL Version 2 licenses. */ (function($){ - - //this can be deleted if jQuery 1.4.2 is out - $.support.ajax = !!(window.XMLHttpRequest); - if(window.ActiveXObject){ - try{ - new ActiveXObject("Microsoft.XMLHTTP"); - $.support.ajax = true; - } catch(e){ - if(window.XMLHttpRequest){ - $.ajaxSetup({xhr: function(){ - return new XMLHttpRequest(); - }}); - } - } - } - var managed = {}, cache = {} ; @@ -33,8 +17,17 @@ return managed[name]; } + function destroy(name){ + if(managed[name]){ + managed[name].clear(true); + delete managed[name]; + } + } + + var publicFns = { - create: create + create: create, + destroy: destroy }; return publicFns; @@ -47,7 +40,7 @@ this.qName = name; this.opts = $.extend({}, $.ajaxSettings, $.manageAjax.defaults, opts); - if(opts.queue && opts.queue !== true && typeof opts.queue === 'string' && opts.queue !== 'clear'){ + if(opts && opts.queue && opts.queue !== true && typeof opts.queue === 'string' && opts.queue !== 'clear'){ this.qName = opts.queue; } }; @@ -56,24 +49,23 @@ add: function(o){ o = $.extend({}, this.opts, o); - var origCom = o.complete, - origSuc = o.success, - beforeSend = o.beforeSend, - origError = o.error, + var origCom = o.complete || $.noop, + origSuc = o.success || $.noop, + beforeSend = o.beforeSend || $.noop, + origError = o.error || $.noop, strData = (typeof o.data == 'string') ? o.data : $.param(o.data || {}), xhrID = o.type + o.url + strData, that = this, ajaxFn = this._createAjax(xhrID, o, origSuc, origCom) ; + if(this.requests[xhrID] && o.preventDoubbleRequests){ return; } ajaxFn.xhrID = xhrID; o.xhrID = xhrID; - // NEWSBLUR.log(['add', o, o.queue, this.opts, this.requests[xhrID], this.qName]); o.beforeSend = function(xhr, opts){ - // NEWSBLUR.log(['o.beforeSend', xhr, opts]); var ret = beforeSend.call(this, xhr, opts); if(ret === false){ that._removeXHR(xhrID); @@ -82,29 +74,29 @@ return ret; }; o.complete = function(xhr, status){ - // NEWSBLUR.log(['o.complete', xhr, status, o]); that._complete.call(that, this, origCom, xhr, status, xhrID, o); xhr = null; }; o.success = function(data, status, xhr){ - // NEWSBLUR.log(['o.success', data, status]); that._success.call(that, this, origSuc, data, status, xhr, o); xhr = null; }; //always add some error callback o.error = function(ahr, status, errorStr){ - // NEWSBLUR.log(['o.error', errorStr, status]); - ahr = (ahr || {}); - var httpStatus = ahr.status, - content = ahr.responseXML || ahr.responseText + var httpStatus = '', + content = '' ; + if(status !== 'timeout' && ahr){ + httpStatus = ahr.status; + content = ahr.responseXML || ahr.responseText; + } if(origError) { origError.call(this, ahr, status, errorStr, o); } else { setTimeout(function(){ - throw status + ':: status: ' + httpStatus + ' | URL: ' + o.url + ' | data: '+ strData + ' | thrown: '+ errorStr + ' | response: '+ content; + throw status + '| status: ' + httpStatus + ' | URL: ' + o.url + ' | data: '+ strData + ' | thrown: '+ errorStr + ' | response: '+ content; }, 0); } ahr = null; @@ -115,7 +107,6 @@ } if(o.queue){ - // NEWSBLUR.log(['Queueing', o.queue, this.qName]); $.queue(document, this.qName, ajaxFn); if(this.inProgress < o.maxRequests){ $.dequeue(document, this.qName); @@ -129,18 +120,21 @@ return function(){ if(o.beforeCreate.call(o.context || that, id, o) === false){return;} that.inProgress++; + if(that.inProgress === 1){ + $.event.trigger(that.name +'AjaxStart'); + } if(o.cacheResponse && cache[id]){ that.requests[id] = {}; setTimeout(function(){ - that._complete.call(that, o.context || o, origCom, {}, 'success', id, o); - that._success.call(that, o.context || o, origSuc, cache[id], 'success', {}, o); + that._complete.call(that, o.context || o, origCom, cache[id], 'success', id, o); + that._success.call(that, o.context || o, origSuc, cache[id]._successData, 'success', cache[id], o); }, 0); } else { - // NEWSBLUR.log(['create_ajax', o, o.complete, o.error, o.success]); - that.requests[id] = $.ajax(o); - } - if(that.inProgress === 1){ - $.event.trigger(that.name +'AjaxStart'); + if (o.async) { + that.requests[id] = $.ajax(o); + } else { + $.ajax(o); + } } return id; }; @@ -159,16 +153,19 @@ return ret; }, _complete: function(context, origFn, xhr, status, xhrID, o){ - // NEWSBLUR.log(['complete', o]); if(this._isAbort(xhr, o)){ status = 'abort'; o.abort.call(context, xhr, status, o); } origFn.call(context, xhr, status, o); + $.event.trigger(this.name +'AjaxComplete', [xhr, status, o]); if(o.domCompleteTrigger){ - $(o.domCompleteTrigger).trigger(this.name +'DOMComplete', [xhr, status, o]); + $(o.domCompleteTrigger) + .trigger(this.name +'DOMComplete', [xhr, status, o]) + .trigger('DOMComplete', [xhr, status, o]) + ; } this._removeXHR(xhrID); @@ -178,7 +175,6 @@ xhr = null; }, _success: function(context, origFn, data, status, xhr, o){ - // NEWSBLUR.log(['_success', data, status]); var that = this; if(this._isAbort(xhr, o)){ xhr = null; @@ -193,17 +189,39 @@ }); } if(o.cacheResponse && !cache[o.xhrID]){ - cache[o.xhrID] = data; + cache[o.xhrID] = { + status: xhr.status, + statusText: xhr.statusText, + responseText: xhr.responseText, + responseXML: xhr.responseXML, + _successData: data + }; + if(xhr.getAllResponseHeaders){ + var responseHeaders = xhr.getAllResponseHeaders(); + $.extend(cache[o.xhrID], { + getAllResponseHeaders: function() {return responseHeaders;}, + getResponseHeader: (function(){ + var parsedHeaders = {}; + $.each(responseHeaders.split("\n"), function(i, headerLine){ + var delimiter = headerLine.indexOf(":"); + parsedHeaders[headerLine.substr(0, delimiter)] = headerLine.substr(delimiter + 2); + }); + return function(name) {return parsedHeaders[name];}; + }()) + }); + } } origFn.call(context, data, status, xhr, o); $.event.trigger(this.name +'AjaxSuccess', [xhr, o, data]); if(o.domSuccessTrigger){ - $(o.domSuccessTrigger).trigger(this.name +'DOMSuccess', [data, o]); + $(o.domSuccessTrigger) + .trigger(this.name +'DOMSuccess', [data, o]) + .trigger('DOMSuccess', [data, o]) + ; } xhr = null; }, getData: function(id){ - // NEWSBLUR.log(['getData', id]); if( id ){ var ret = this.requests[id]; if(!ret && this.opts.queue) { @@ -220,7 +238,6 @@ }; }, abort: function(id){ - // NEWSBLUR.log(['abort', id]); var xhr; if(id){ xhr = this.getData(id); @@ -251,7 +268,6 @@ }); }, clear: function(shouldAbort){ - // NEWSBLUR.log(['clear', shouldAbort]); $(document).clearQueue(this.qName); if(shouldAbort){ this.abort(); @@ -260,9 +276,6 @@ }; $.manageAjax._manager.prototype.getXHR = $.manageAjax._manager.prototype.getData; $.manageAjax.defaults = { - complete: $.noop, - success: $.noop, - beforeSend: $.noop, beforeCreate: $.noop, abort: $.noop, abortIsNoSuccess: true, @@ -289,4 +302,4 @@ }; }); -})(jQuery); +})(jQuery); \ No newline at end of file diff --git a/media/js/jquery.simplemodal-1.3.js b/media/js/jquery.simplemodal-1.3.js index 74a276afc..c1faffe73 100644 --- a/media/js/jquery.simplemodal-1.3.js +++ b/media/js/jquery.simplemodal-1.3.js @@ -531,7 +531,6 @@ this.d.data = data; } w = this.getDimensions(); - NEWSBLUR.log(['resize', w]); this.setContainerDimensions(); }, setContainerDimensions: function () { diff --git a/media/js/newsblur/assetmodel.js b/media/js/newsblur/assetmodel.js index 21fc00597..f21c6f96a 100644 --- a/media/js/newsblur/assetmodel.js +++ b/media/js/newsblur/assetmodel.js @@ -29,11 +29,12 @@ NEWSBLUR.AssetModel.Reader.prototype = { init: function() { this.ajax = {}; - this.ajax['queue'] = $.manageAjax.create('queue', {queue: false, domSuccessTrigger: true, traditional: true}); - this.ajax['queue_clear'] = $.manageAjax.create('queue_clear', {queue: 'clear', domSuccessTrigger: true, traditional: true}); - this.ajax['feed'] = $.manageAjax.create('feed', {queue: 'clear', abortOld: true, domSuccessTrigger: true, traditional: true}); - this.ajax['feed_page'] = $.manageAjax.create('feed_page', {queue: false, abortOld: true, abortIsNoSuccess: false, domSuccessTrigger: true, domCompleteTrigger: true, traditional: true}); - this.ajax['statistics'] = $.manageAjax.create('feed', {queue: 'clear', abortOld: true, domSuccessTrigger: true, traditional: true}); + this.ajax['queue'] = $.manageAjax.create('queue', {queue: false}); + this.ajax['queue_clear'] = $.manageAjax.create('queue_clear', {queue: 'clear'}); + this.ajax['feed'] = $.manageAjax.create('feed', {queue: 'clear', abortOld: true, domCompleteTrigger: true}); + this.ajax['feed_page'] = $.manageAjax.create('feed_page', {queue: false, abortOld: true, abortIsNoSuccess: false, domCompleteTrigger: true}); + this.ajax['statistics'] = $.manageAjax.create('feed', {queue: 'clear', abortOld: true}); + $.ajaxSettings.traditional = true; return; }, @@ -42,6 +43,7 @@ NEWSBLUR.AssetModel.Reader.prototype = { var options = $.extend({ 'ajax_group': 'queue', 'traditional': true, + 'domSuccessTrigger': true, 'preventDoubbleRequests': false }, options); var request_type = 'POST'; @@ -166,7 +168,7 @@ NEWSBLUR.AssetModel.Reader.prototype = { load_feed_precallback: function(data, feed_id, callback, first_load) { // NEWSBLUR.log(['pre_callback', data]); - if (feed_id != this.feed_id) { + if (feed_id != this.feed_id && data) { this.stories = data.stories; this.feed_tags = data.feed_tags; this.feed_authors = data.feed_authors; @@ -176,7 +178,7 @@ NEWSBLUR.AssetModel.Reader.prototype = { for (var s in data.stories) { this.story_keys.push(data.stories[s].id); } - } else { + } else if (data) { $.merge(this.stories, data.stories); // Assemble key cache for later, removing dupes diff --git a/media/js/newsblur/reader.js b/media/js/newsblur/reader.js index 907447674..51ef6a1ea 100644 --- a/media/js/newsblur/reader.js +++ b/media/js/newsblur/reader.js @@ -42,6 +42,7 @@ 'feed_view_story_positions_keys': [], 'mouse_position_y': parseInt(this.model.preference('lock_mouse_indicator'), 10) }; + this.FEED_REFRESH_INTERVAL = (1000 * 60) / 2; // 1/2 minutes // ================== // = Event Handlers = @@ -647,12 +648,6 @@ if (self.flags['has_chosen_feeds']) { $folder.fadeIn(500); } - $('.feed', $folder).rightClick(function() { - self.show_manage_menu('feed', $(this)); - }); - $('.folder_title', $folder).rightClick(function() { - self.show_manage_menu('folder', $(this).parents('li.folder').eq(0)); - }); self.hover_over_feed_titles($folder); }, 50); })($feeds, $folder, is_collapsed, collapsed_parent); @@ -982,17 +977,32 @@ }, hover_over_feed_titles: function($folder) { - var $feed_list = $folder || this.$s.$feed_list; - var $feeds = $('.feed, .folder_title', $feed_list); - var $manage_menu_container = $('.NB-menu-manage-container'); + var self = this; + var $feeds; + $folder = $folder || this.$s.$feed_list; - $feeds.each(function() { - $(this).unbind('mouseenter').unbind('mouseleave'); + if ($folder.is('.feed')) { + $feeds = $folder; + } else { + $feeds = $('.feed, .folder_title', $folder); + } + + $feeds.rightClick(function() { + var $this = $(this); + if ($this.is('.feed')) { + self.show_manage_menu('feed', $this); + } else if ($this.is('.folder_title')) { + self.show_manage_menu('folder', $this.parents('li.folder').eq(0)); + } }); + // NEWSBLUR.log(['hover_over_feed_titles', $folder, $feeds]); + + $feeds.unbind('mouseenter').unbind('mouseleave'); + $feeds.hover(function() { var $this = $(this); - $('.NB-hover', $feed_list).removeClass('NB-hover'); + $('.NB-hover', $folder).removeClass('NB-hover'); $this.addClass("NB-hover"); // NEWSBLUR.log(['scroll', $this.scrollTop(), $this.offset(), $this.position()]); if ($this.offset().top > $(window).height() - 181) { @@ -1000,9 +1010,9 @@ } }, function() { var $this = $(this); - $('.NB-hover', $feed_list).removeClass('NB-hover').removeClass('NB-hover-inverse'); $this.removeClass("NB-hover"); $this.removeClass('NB-hover-inverse'); + $('.NB-hover', $folder).removeClass('NB-hover').removeClass('NB-hover-inverse'); }); }, @@ -1125,6 +1135,9 @@ }, post_open_feed: function(e, data, first_load) { + if (!data) { + return this.open_feed(this.active_feed, null, true); + } var stories = data.stories; var tags = data.tags; var feed_id = this.active_feed; @@ -2736,7 +2749,6 @@ setup_feed_refresh: function() { var self = this; - var FEED_REFRESH_INTERVAL = (1000 * 60) / 2; // 1/2 minutes clearInterval(this.flags.feed_refresh); @@ -2746,7 +2758,7 @@ self.post_feed_refresh(updated_feeds); }, self), self.flags['has_unfetched_feeds']); } - }, FEED_REFRESH_INTERVAL); + }, this.FEED_REFRESH_INTERVAL); }, force_feed_refresh: function(callback, update_all) { @@ -2790,6 +2802,7 @@ } $feed_on_page.replaceWith($feed); } + this.hover_over_feed_titles($feed); } this.check_feed_fetch_progress(); diff --git a/utils/feed_fetcher.py b/utils/feed_fetcher.py index bc938cba9..aea977a10 100644 --- a/utils/feed_fetcher.py +++ b/utils/feed_fetcher.py @@ -46,7 +46,8 @@ class FetchFeed: @timelimit(20) def fetch(self): - """ Downloads and parses a feed. + """ + Uses feedparser to download the feed. Will be parsed later. """ socket.setdefaulttimeout(30) identity = self.get_identity() @@ -130,11 +131,12 @@ class ProcessFeed: if self.fpf.status in (302, 301): self.feed.feed_address = self.fpf.href - self.feed.save() if first_run: self.feed.schedule_feed_fetch_immediately() - self.feed.save_feed_history(self.fpf.status, "HTTP Error") - return FEED_ERRHTTP, ret_values + if not self.fpf.entries: + self.feed.save() + self.feed.save_feed_history(self.fpf.status, "HTTP Redirect") + return FEED_ERRHTTP, ret_values if self.fpf.status >= 400: self.feed.save() @@ -143,7 +145,7 @@ class ProcessFeed: if self.fpf.bozo and isinstance(self.fpf.bozo_exception, feedparser.NonXMLContentType): if not self.fpf.entries: - logging.debug(" ---> [%-30s] Feed is Non-XML. Checking address..." % unicode(self.feed)[:30]) + logging.debug(" ---> [%-30s] Feed is Non-XML. %s entries. Checking address..." % (unicode(self.feed)[:30]), len(self.fpf.entries)) fixed_feed = self.feed.check_feed_address_for_feed_link() if not fixed_feed: self.feed.save_feed_history(502, 'Non-xml feed', self.fpf.bozo_exception) @@ -152,7 +154,7 @@ class ProcessFeed: self.feed.save() return FEED_ERRPARSE, ret_values elif self.fpf.bozo and isinstance(self.fpf.bozo_exception, xml.sax._exceptions.SAXException): - logging.debug(" ---> [%-30s] Feed is Bad XML (SAX). Checking address..." % unicode(self.feed)[:30]) + logging.debug(" ---> [%-30s] Feed is Bad XML (SAX). %s entries. Checking address..." % (unicode(self.feed)[:30]), len(self.fpf.entries)) if not self.fpf.entries: fixed_feed = self.feed.check_feed_address_for_feed_link() if not fixed_feed: @@ -255,6 +257,9 @@ class Dispatcher: self.time_start = datetime.datetime.now() self.workers = [] + def refresh_feed(self, feed_id): + return Feed.objects.get(pk=feed_id) # Update feed, since it may have changed + def process_feed_wrapper(self, feed_queue): """ wrapper for ProcessFeed """ @@ -283,6 +288,8 @@ class Dispatcher: } start_time = datetime.datetime.now() + feed = self.refresh_feed(feed_id) + try: ffeed = FetchFeed(feed_id, self.options) ret_feed, fetched_feed = ffeed.fetch() @@ -291,7 +298,7 @@ class Dispatcher: pfeed = ProcessFeed(feed_id, fetched_feed, db, self.options) ret_feed, ret_entries = pfeed.process() - feed = Feed.objects.get(pk=feed_id) # Update feed, since it may have changed + feed = self.refresh_feed(feed_id) if ret_entries.get(ENTRY_NEW) or self.options['force'] or not feed.fetched_once: if not feed.fetched_once: @@ -323,7 +330,7 @@ class Dispatcher: feed.save_feed_history(500, "Error", tb) fetched_feed = None - feed = Feed.objects.get(pk=feed_id) + feed = self.refresh_feed(feed_id) if ((self.options['force']) or (fetched_feed and feed.feed_link and @@ -334,6 +341,7 @@ class Dispatcher: page_importer = PageImporter(feed.feed_link, feed) page_importer.fetch_page() + feed = self.refresh_feed(feed_id) delta = datetime.datetime.now() - start_time feed.last_load_time = max(1, delta.seconds)