Fixing a metric-ton of bugs: Google Reader import on badly parsed xml, refreshing toplevel feeds loses their manage menu, retrying exception feeds now de-dupes and correctly updates on the page. Plus a bunch of other bugs.

This commit is contained in:
Samuel Clay 2011-02-06 15:04:21 -05:00
parent 5afdaf6836
commit 4fd6a7cfe0
12 changed files with 256 additions and 91 deletions

View file

@ -4,7 +4,8 @@ from django.contrib.auth.models import User
from apps.rss_feeds.models import Feed, DuplicateFeed
from apps.reader.models import UserSubscription, UserSubscriptionFolders
import datetime
import lxml.etree
from StringIO import StringIO
from lxml import etree
from utils import json_functions as json, urlnorm
import utils.opml as opml
from utils import log as logging
@ -114,7 +115,9 @@ class GoogleReaderImporter(Importer):
def parse(self):
self.feeds = lxml.etree.fromstring(self.feeds_xml).xpath('/object/list/object')
parser = etree.XMLParser(recover=True)
tree = etree.parse(StringIO(self.feeds_xml), parser)
self.feeds = tree.xpath('/object/list/object')
def process_item(self, item, folders):
feed_title = item.xpath('./string[@name="title"]') and \

15
apps/reader/managers.py Normal file
View file

@ -0,0 +1,15 @@
from django.db import models
from apps.rss_feeds.models import DuplicateFeed
from utils import log as logging
class UserSubscriptionManager(models.Manager):
def get(self, *args, **kwargs):
try:
return super(UserSubscriptionManager, self).get(*args, **kwargs)
except:
dupe_feed = DuplicateFeed.objects.filter(duplicate_feed_id=kwargs['feed'].pk)
if dupe_feed:
feed = dupe_feed[0].feed
kwargs['feed'] = feed
logging.debug(" ---> [%s] ~BRFound dupe UserSubscription: ~SB%s" % (kwargs['user'].username, kwargs['feed']))
return super(UserSubscriptionManager, self).get(*args, **kwargs)

View file

@ -6,12 +6,14 @@ from django.db import models, IntegrityError
from django.conf import settings
from django.contrib.auth.models import User
from django.core.cache import cache
from apps.reader.managers import UserSubscriptionManager
from apps.rss_feeds.models import Feed, MStory, DuplicateFeed
from apps.analyzer.models import MClassifierFeed, MClassifierAuthor, MClassifierTag, MClassifierTitle
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
from utils import urlnorm
from utils.feed_functions import fetch_address_from_page
from utils.feed_functions import add_object_to_folder
from utils.feed_functions import relative_timesince
class UserSubscription(models.Model):
"""
@ -37,10 +39,42 @@ class UserSubscription(models.Model):
needs_unread_recalc = models.BooleanField(default=False)
feed_opens = models.IntegerField(default=0)
is_trained = models.BooleanField(default=False)
objects = UserSubscriptionManager()
def __unicode__(self):
return '[' + self.feed.feed_title + '] '
def canonical(self, full=False):
feed = {
'id': self.feed.pk,
'feed_title': self.user_title or self.feed.feed_title,
'feed_address': self.feed.feed_address,
'feed_link': self.feed.feed_link,
'ps': self.unread_count_positive,
'nt': self.unread_count_neutral,
'ng': self.unread_count_negative,
'updated': relative_timesince(self.feed.last_update),
'subs': self.feed.num_subscribers,
'active': self.active,
'favicon': self.feed.icon.data,
'favicon_color': self.feed.icon.color,
'favicon_fetching': bool(not (self.feed.icon.not_found or self.feed.icon.data))
}
if not self.feed.fetched_once:
feed['not_yet_fetched'] = True
if self.feed.has_page_exception or self.feed.has_feed_exception:
feed['has_exception'] = True
feed['exception_type'] = 'feed' if self.feed.has_feed_exception else 'page'
feed['exception_code'] = self.feed.exception_code
elif full:
feed['has_exception'] = False
feed['exception_type'] = None
feed['exception_code'] = self.feed.exception_code
return feed
def save(self, *args, **kwargs):
try:
super(UserSubscription, self).save(*args, **kwargs)

View file

@ -140,30 +140,9 @@ def load_feeds(request):
user_subs = UserSubscription.objects.select_related('feed', 'feed__feed_icon').filter(user=user)
for sub in user_subs:
feeds[sub.feed.pk] = {
'id': sub.feed.pk,
'feed_title': sub.user_title or sub.feed.feed_title,
'feed_address': sub.feed.feed_address,
'feed_link': sub.feed.feed_link,
'ps': sub.unread_count_positive,
'nt': sub.unread_count_neutral,
'ng': sub.unread_count_negative,
'updated': relative_timesince(sub.feed.last_update),
'subs': sub.feed.num_subscribers,
'active': sub.active,
'favicon': sub.feed.icon.data,
'favicon_color': sub.feed.icon.color,
'favicon_fetching': bool(not (sub.feed.icon.not_found or sub.feed.icon.data))
}
if not sub.feed.fetched_once:
feeds[sub.feed.pk] = sub.canonical()
if feeds[sub.feed.pk].get('not_yet_fetched'):
not_yet_fetched = True
feeds[sub.feed.pk]['not_yet_fetched'] = True
if sub.feed.has_page_exception or sub.feed.has_feed_exception:
feeds[sub.feed.pk]['has_exception'] = True
feeds[sub.feed.pk]['exception_type'] = 'feed' if sub.feed.has_feed_exception else 'page'
feeds[sub.feed.pk]['feed_address'] = sub.feed.feed_address
feeds[sub.feed.pk]['exception_code'] = sub.feed.exception_code
if not sub.feed.active and not sub.feed.has_feed_exception and not sub.feed.has_page_exception:
sub.feed.count_subscribers()
sub.feed.schedule_feed_fetch_immediately()

View file

@ -0,0 +1,95 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Removing unique constraint on 'DuplicateFeed', fields ['duplicate_address']
db.delete_unique('rss_feeds_duplicatefeed', ['duplicate_address'])
# Changing field 'FeedData.feed_tagline'
db.alter_column('rss_feeds_feeddata', 'feed_tagline', self.gf('django.db.models.fields.CharField')(max_length=1000, null=True))
def backwards(self, orm):
# Adding unique constraint on 'DuplicateFeed', fields ['duplicate_address']
db.create_unique('rss_feeds_duplicatefeed', ['duplicate_address'])
# Changing field 'FeedData.feed_tagline'
db.alter_column('rss_feeds_feeddata', 'feed_tagline', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
models = {
'rss_feeds.duplicatefeed': {
'Meta': {'object_name': 'DuplicateFeed'},
'duplicate_address': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'duplicate_feed_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'duplicate_addresses'", 'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'rss_feeds.feed': {
'Meta': {'ordering': "['feed_title']", 'object_name': 'Feed', 'db_table': "'feeds'"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
'active_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1', 'db_index': 'True'}),
'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}),
'etag': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'exception_code': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'feed_address': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}),
'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}),
'feed_title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'fetched_once': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_feed_exception': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
'has_page_exception': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_load_time': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
'premium_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
'queued_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'rss_feeds.feeddata': {
'Meta': {'object_name': 'FeedData'},
'feed': ('utils.fields.AutoOneToOneField', [], {'related_name': "'data'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
'feed_tagline': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'popular_authors': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
'popular_tags': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'story_count_history': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedicon': {
'Meta': {'object_name': 'FeedIcon'},
'color': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}),
'data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'feed': ('utils.fields.AutoOneToOneField', [], {'related_name': "'icon'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['rss_feeds.Feed']"}),
'icon_url': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
'not_found': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'rss_feeds.feedloadtime': {
'Meta': {'object_name': 'FeedLoadtime'},
'date_accessed': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'loadtime': ('django.db.models.fields.FloatField', [], {})
},
'rss_feeds.feedupdatehistory': {
'Meta': {'object_name': 'FeedUpdateHistory'},
'average_per_feed': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '1'}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'number_of_feeds': ('django.db.models.fields.IntegerField', [], {}),
'seconds_taken': ('django.db.models.fields.IntegerField', [], {})
}
}
complete_apps = ['rss_feeds']

View file

@ -338,6 +338,7 @@ class Feed(models.Model):
disp = feed_fetcher.Dispatcher(options, 1)
disp.add_jobs([[self.pk]])
disp.run_jobs()
return self
def add_update_stories(self, stories, existing_stories):
ret_values = {
@ -910,7 +911,7 @@ class FeedLoadtime(models.Model):
return "%s: %s sec" % (self.feed, self.loadtime)
class DuplicateFeed(models.Model):
duplicate_address = models.CharField(max_length=255, unique=True)
duplicate_address = models.CharField(max_length=255)
duplicate_feed_id = models.CharField(max_length=255, null=True)
feed = models.ForeignKey(Feed, related_name='duplicate_addresses')

View file

@ -88,11 +88,12 @@ def exception_retry(request):
feed.fetched_once = True
feed.save()
feed.update(force=True, compute_scores=False)
feed = feed.update(force=True, compute_scores=False)
usersub = UserSubscription.objects.get(user=request.user, feed=feed)
usersub.calculate_feed_scores(silent=False)
return {'code': 1}
feeds = {feed.pk: usersub.canonical(full=True)}
return {'code': 1, 'feeds': feeds}
@ajax_login_required
@ -125,7 +126,11 @@ def exception_change_feed_address(request):
logging.info(" ---> [%s] ~FRFixing feed exception by address: ~SB%s" % (request.user, retry_feed.feed_address))
retry_feed.update()
return {'code': 1}
usersub = UserSubscription.objects.get(user=request.user, feed=retry_feed)
usersub.calculate_feed_scores(silent=False)
feeds = {feed.pk: usersub.canonical(full=True)}
return {'code': 1, 'feeds': feeds}
@ajax_login_required
@json.json_view
@ -162,6 +167,10 @@ def exception_change_feed_link(request):
logging.info(" ---> [%s] ~FRFixing feed exception by link: ~SB%s" % (request.user, retry_feed.feed_link))
retry_feed.update()
return {'code': code}
usersub = UserSubscription.objects.get(user=request.user, feed=retry_feed)
usersub.calculate_feed_scores(silent=False)
feeds = {feed.pk: usersub.canonical(full=True)}
return {'code': code, 'feeds': feeds}

View file

@ -331,37 +331,7 @@ NEWSBLUR.AssetModel.Reader.prototype = {
var self = this;
var pre_callback = function(data) {
var updated_feeds = [];
for (var f in data.feeds) {
if (!self.feeds[f]) continue;
var updated = false;
f = parseInt(f, 10);
var feed = data.feeds[f];
for (var k in feed) {
if (self.feeds[f][k] != feed[k]) {
// NEWSBLUR.log(['New Feed', self.feeds[f][k], feed[k], f, k]);
self.feeds[f][k] = feed[k];
NEWSBLUR.log(['Different', k, self.feeds[f], feed]);
updated = true;
}
}
if ((feed['has_exception'] && !self.feeds[f]['has_exception']) ||
(self.feeds[f]['has_exception'] && !feed['has_exception'])) {
updated = true;
self.feeds[f]['has_exception'] = !!feed['has_exception'];
}
if (feed['favicon']) {
self.feeds[f]['favicon'] = feed['favicon'];
self.feeds[f]['favicon_color'] = feed['favicon_color'];
self.feeds[f]['favicon_fetching'] = false;
updated = true;
}
if (updated && !(f in updated_feeds)) {
updated_feeds.push(f);
}
}
callback(updated_feeds);
self.post_refresh_feeds(data, callback);
};
var data = {};
@ -382,6 +352,44 @@ NEWSBLUR.AssetModel.Reader.prototype = {
}
},
post_refresh_feeds: function(data, callback) {
var updated_feeds = [];
for (var f in data.feeds) {
if (!this.feeds[f]) continue;
var updated = false;
f = parseInt(f, 10);
var feed = data.feeds[f];
if (feed.id && f != feed.id) {
NEWSBLUR.log(['Dupe feed being refreshed', f, feed.id, this.feeds[f]]);
this.feeds[feed.id] = this.feeds[f];
}
for (var k in feed) {
if (this.feeds[f][k] != feed[k]) {
// NEWSBLUR.log(['New Feed', this.feeds[f][k], feed[k], f, k]);
this.feeds[f][k] = feed[k];
NEWSBLUR.log(['Different', k, this.feeds[f], feed]);
updated = true;
}
}
if ((feed['has_exception'] && !this.feeds[f]['has_exception']) ||
(this.feeds[f]['has_exception'] && !feed['has_exception'])) {
updated = true;
this.feeds[f]['has_exception'] = !!feed['has_exception'];
}
if (feed['favicon']) {
this.feeds[f]['favicon'] = feed['favicon'];
this.feeds[f]['favicon_color'] = feed['favicon_color'];
this.feeds[f]['favicon_fetching'] = false;
updated = true;
}
if (updated && !(f in updated_feeds)) {
updated_feeds.push(f);
}
}
callback(updated_feeds);
},
refresh_feed: function(feed_id, callback, limit) {
var self = this;
@ -637,11 +645,17 @@ NEWSBLUR.AssetModel.Reader.prototype = {
save_exception_retry: function(feed_id, callback) {
var self = this;
var pre_callback = function(data) {
// NEWSBLUR.log(['refresh_feed pre_callback', data]);
self.post_refresh_feeds(data, callback);
};
if (NEWSBLUR.Globals.is_authenticated) {
this.make_request('/rss_feeds/exception_retry', {
'feed_id': feed_id,
'reset_fetch': !!(this.feeds[feed_id].has_feed_exception || this.feeds[feed_id].has_page_exception)
}, callback);
}, pre_callback);
} else {
if ($.isFunction(callback)) callback();
}
@ -649,11 +663,18 @@ NEWSBLUR.AssetModel.Reader.prototype = {
save_exception_change_feed_link: function(feed_id, feed_link, callback) {
var self = this;
var pre_callback = function(data) {
NEWSBLUR.log(['save_exception_change_feed_link pre_callback', feed_id, feed_link, data]);
self.post_refresh_feeds(data, callback);
NEWSBLUR.reader.force_feed_refresh(feed_id);
};
if (NEWSBLUR.Globals.is_authenticated) {
this.make_request('/rss_feeds/exception_change_feed_link', {
'feed_id': feed_id,
'feed_link': feed_link
}, callback);
}, pre_callback);
} else {
if ($.isFunction(callback)) callback();
}
@ -661,11 +682,18 @@ NEWSBLUR.AssetModel.Reader.prototype = {
save_exception_change_feed_address: function(feed_id, feed_address, callback) {
var self = this;
var pre_callback = function(data) {
NEWSBLUR.log(['save_exception_change_feed_address pre_callback', feed_id, feed_address, data]);
self.post_refresh_feeds(data, callback);
NEWSBLUR.reader.force_feed_refresh(feed_id);
};
if (NEWSBLUR.Globals.is_authenticated) {
this.make_request('/rss_feeds/exception_change_feed_address', {
'feed_id': feed_id,
'feed_address': feed_address
}, callback);
}, pre_callback);
} else {
if ($.isFunction(callback)) callback();
}

View file

@ -704,22 +704,23 @@
make_feeds: function() {
var self = this;
var $feed_list = this.$s.$feed_list.empty();
var $feed_list = this.$s.$feed_list;
var folders = this.model.folders;
var feeds = this.model.feeds;
// NEWSBLUR.log(['Making feeds', {'folders': folders, 'feeds': feeds}]);
$feed_list.empty();
this.$s.$story_taskbar.css({'display': 'block'});
this.flags['has_chosen_feeds'] = this.detect_all_inactive_feeds();
this.make_feeds_folder($feed_list, folders, 0);
this.$s.$feed_list.css({
$feed_list.css({
'display': 'block',
'opacity': 0
}).animate({'opacity': 1}, {'duration': 500});
this.hover_over_feed_titles();
this.$s.$feed_list.prepend($.make('li', { className: 'feed NB-empty' }));
$feed_list.prepend($.make('li', { className: 'feed NB-empty' }));
this.$s.$feed_link_loader.fadeOut(250);
if (folders.length) {
@ -739,8 +740,6 @@
if (folders.length) {
this.load_sortable_feeds();
$('.feed', $feed_list).tsort('.feed_title');
$('.folder', $feed_list).tsort('.folder_title_text');
this.update_header_counts();
_.delay(_.bind(this.update_starred_count, this), 250);
}
@ -785,9 +784,7 @@
$.make('div', { className: 'NB-feedlist-manage-icon' }),
$.make('span', { className: 'folder_title_text' }, o)
]),
$.make('ul', { className: 'folder' }, [
$.make('li', { className: 'feed NB-empty' })
])
$.make('ul', { className: 'folder' }, [])
]);
var is_collapsed = _.contains(NEWSBLUR.Preferences.collapsed_folders, o);
@ -821,6 +818,9 @@
// 'opacity': 0
// }).animate({'opacity': 1}, {'duration': 500});
// }
// $('.feed', $feeds).tsort('.feed_title');
// $('.folder', $feeds).tsort('.folder_title_text');
self.hover_over_feed_titles($folder);
};
if (!self.flags['has_chosen_feeds']) {
@ -833,9 +833,7 @@
}
}
}
$('.feed', $feeds).tsort('.feed_title');
$('.folder', $feeds).tsort('.folder_title_text');
$feeds.append($.make('li', { className: 'feed NB-empty' }));
},
make_feed_title_line: function(feed, list_item, type) {
@ -1111,7 +1109,6 @@
} else {
$feeds = $('.feed, .folder_title', $folder);
}
// $feeds.rightClick(function() {
// var $this = $(this);
// if ($this.is('.feed')) {
@ -4043,6 +4040,7 @@
this.force_feeds_refresh(function(feeds) {
var $new_feed = self.make_feed_title_line(feeds[feed_id], true, 'feed');
if ($feed.hasClass('NB-toplevel')) $new_feed.addClass('NB-toplevel');
$feed.replaceWith($new_feed);
self.hover_over_feed_titles($new_feed);
if (self.active_feed == feed_id) {
@ -4056,7 +4054,7 @@
var refresh_interval = this.FEED_REFRESH_INTERVAL;
if (new_feeds) {
refresh_interval = (1000 * 60) * 1/4;
refresh_interval = (1000 * 60) * 1/6;
}
clearInterval(this.flags.feed_refresh);
@ -4067,7 +4065,7 @@
self.post_feed_refresh(updated_feeds);
}, self), self.flags['has_unfetched_feeds']);
}
}, this.FEED_REFRESH_INTERVAL);
}, refresh_interval);
},
force_feeds_refresh: function(callback, update_all) {
@ -4109,6 +4107,7 @@
if (!this.flags['has_unfetched_feeds']) {
NEWSBLUR.log(['UPDATING', feed.feed_title, $feed, $feed_on_page]);
}
if ($feed_on_page.hasClass('NB-toplevel')) $feed.addClass('NB-toplevel');
$feed_on_page.replaceWith($feed);
}
this.hover_over_feed_titles($feed);
@ -4481,7 +4480,7 @@
setTimeout(function() {
if (self.flags['count_unreads_after_import_working']) {
this.animate_progress_bar($bar, feeds_count / 10);
self.animate_progress_bar($bar, feeds_count / 10);
self.show_progress_bar();
}
}, 500);

View file

@ -170,8 +170,8 @@ NEWSBLUR.ReaderFeedException.prototype = {
$('.NB-modal-submit-retry', this.$modal).addClass('NB-disabled').attr('value', 'Fetching...');
this.model.save_exception_retry(this.feed_id, function() {
NEWSBLUR.reader.flags['has_unfetched_feeds'] = true;
NEWSBLUR.reader.force_instafetch_stories(self.feed_id);
// NEWSBLUR.reader.flags['has_unfetched_feeds'] = true;
// NEWSBLUR.reader.force_instafetch_stories(self.feed_id);
$.modal.close();
});
},
@ -201,8 +201,8 @@ NEWSBLUR.ReaderFeedException.prototype = {
if (feed_address.length) {
this.model.save_exception_change_feed_address(feed_id, feed_address, function(code) {
NEWSBLUR.reader.flags['has_unfetched_feeds'] = true;
NEWSBLUR.reader.load_feeds();
// NEWSBLUR.reader.flags['has_unfetched_feeds'] = true;
// NEWSBLUR.reader.load_feeds();
$.modal.close();
});
}
@ -219,8 +219,8 @@ NEWSBLUR.ReaderFeedException.prototype = {
if (feed_link.length) {
this.model.save_exception_change_feed_link(feed_id, feed_link, function(code) {
NEWSBLUR.reader.flags['has_unfetched_feeds'] = true;
NEWSBLUR.reader.load_feeds();
// NEWSBLUR.reader.flags['has_unfetched_feeds'] = true;
// NEWSBLUR.reader.load_feeds();
$.modal.close();
});
}

View file

@ -11,7 +11,7 @@ from apps.rss_feeds.icon_importer import IconImporter
from utils import feedparser
from utils.story_functions import pre_process_story
from utils import log as logging
from utils.feed_functions import timelimit, TimeoutError, mail_error_to_admin
from utils.feed_functions import timelimit, TimeoutError, mail_error_to_admin, utf8encode
import time
import datetime
import traceback
@ -186,7 +186,7 @@ class ProcessFeed:
self.feed.feed_title = self.fpf.feed.get('title', self.feed.feed_title)
tagline = self.fpf.feed.get('tagline', self.feed.data.feed_tagline)
if tagline:
self.feed.data.feed_tagline = tagline.encode('utf-8')
self.feed.data.feed_tagline = utf8encode(tagline)
self.feed.data.save()
self.feed.feed_link = self.fpf.feed.get('link') or self.fpf.feed.get('id') or self.feed.feed_link
@ -281,7 +281,7 @@ class Dispatcher:
ENTRY_ERR: 0
}
start_time = datetime.datetime.utcnow()
ret_feed = FEED_ERREXC
try:
feed = self.refresh_feed(feed_id)
@ -347,7 +347,6 @@ class Dispatcher:
tb = traceback.format_exc()
logging.error(tb)
logging.debug('[%d] ! -------------------------' % (feed_id,))
ret_feed = FEED_ERREXC
feed.save_page_history(550, "Page Error", tb)
fetched_feed = None
mail_error_to_admin(feed, e)

View file

@ -40,7 +40,7 @@ def timelimit(timeout):
return _2
return _1
def encode(tstr):
def utf8encode(tstr):
""" Encodes a unicode string in utf-8
"""
if not tstr:
@ -50,7 +50,10 @@ def encode(tstr):
return tstr.encode('utf-8', "xmlcharrefreplace")
except UnicodeDecodeError:
# it's already UTF8.. sigh
return tstr.decode('utf-8').encode('utf-8')
try:
return tstr.decode('utf-8').encode('utf-8')
except UnicodeDecodeError:
return ''
# From: http://www.poromenos.org/node/87
def levenshtein_distance(first, second):