Saner feed database management (#1131)
* Mark feedItems read in a single batch SQL update * Automatically remove old feed items * User-facing preference globally and per-feed for feed item expiration
This commit is contained in:
parent
7fc352b9b7
commit
d6d4e4b33e
14 changed files with 157 additions and 93 deletions
|
@ -72,9 +72,13 @@ var Zotero_Feed_Settings = new function() {
|
||||||
}
|
}
|
||||||
document.getElementById('feed-ttl').value = ttl;
|
document.getElementById('feed-ttl').value = ttl;
|
||||||
|
|
||||||
let cleanupAfter = data.cleanupAfter;
|
let cleanupReadAfter = data.cleanupReadAfter;
|
||||||
if (cleanupAfter === undefined) cleanupAfter = Zotero.Prefs.get('feeds.defaultCleanupAfter');
|
if (cleanupReadAfter === undefined) cleanupReadAfter = Zotero.Prefs.get('feeds.defaultCleanupReadAfter');
|
||||||
document.getElementById('feed-cleanupAfter').value = cleanupAfter;
|
document.getElementById('feed-cleanupReadAfter').value = cleanupReadAfter;
|
||||||
|
|
||||||
|
let cleanupUnreadAfter = data.cleanupUnreadAfter;
|
||||||
|
if (cleanupUnreadAfter === undefined) cleanupUnreadAfter = Zotero.Prefs.get('feeds.defaultCleanupUnreadAfter');
|
||||||
|
document.getElementById('feed-cleanupUnreadAfter').value = cleanupUnreadAfter;
|
||||||
|
|
||||||
if (data.url && !data.urlIsValid) {
|
if (data.url && !data.urlIsValid) {
|
||||||
yield this.validateURL();
|
yield this.validateURL();
|
||||||
|
@ -93,7 +97,8 @@ var Zotero_Feed_Settings = new function() {
|
||||||
urlIsValid = false;
|
urlIsValid = false;
|
||||||
document.getElementById('feed-title').disabled = true;
|
document.getElementById('feed-title').disabled = true;
|
||||||
document.getElementById('feed-ttl').disabled = true;
|
document.getElementById('feed-ttl').disabled = true;
|
||||||
document.getElementById('feed-cleanupAfter').disabled = true;
|
document.getElementById('feed-cleanupReadAfter').disabled = true;
|
||||||
|
document.getElementById('feed-cleanupUnreadAfter').disabled = true;
|
||||||
document.documentElement.getButton('accept').disabled = true;
|
document.documentElement.getButton('accept').disabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -130,7 +135,8 @@ var Zotero_Feed_Settings = new function() {
|
||||||
urlIsValid = true;
|
urlIsValid = true;
|
||||||
title.disabled = false;
|
title.disabled = false;
|
||||||
ttl.disabled = false;
|
ttl.disabled = false;
|
||||||
document.getElementById('feed-cleanupAfter').disabled = false;
|
document.getElementById('feed-cleanupReadAfter').disabled = false;
|
||||||
|
document.getElementById('feed-cleanupUnreadAfter').disabled = false;
|
||||||
document.documentElement.getButton('accept').disabled = false;
|
document.documentElement.getButton('accept').disabled = false;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
@ -145,7 +151,8 @@ var Zotero_Feed_Settings = new function() {
|
||||||
data.url = document.getElementById('feed-url').value;
|
data.url = document.getElementById('feed-url').value;
|
||||||
data.title = document.getElementById('feed-title').value;
|
data.title = document.getElementById('feed-title').value;
|
||||||
data.ttl = document.getElementById('feed-ttl').value * 60;
|
data.ttl = document.getElementById('feed-ttl').value * 60;
|
||||||
data.cleanupAfter = document.getElementById('feed-cleanupAfter').value * 1;
|
data.cleanupReadAfter = document.getElementById('feed-cleanupReadAfter').value * 1;
|
||||||
|
data.cleanupUnreadAfter = document.getElementById('feed-cleanupUnreadAfter').value * 1;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,14 @@
|
||||||
<label value="&zotero.feedSettings.refresh.label2;" control="feed-ttl"/>
|
<label value="&zotero.feedSettings.refresh.label2;" control="feed-ttl"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox align="center">
|
<hbox align="center">
|
||||||
<label value="&zotero.feedSettings.cleanupAfter.label1;" control="feed-cleanupAfter"/>
|
<label value="&zotero.feedSettings.cleanupReadAfter.label1;" control="feed-cleanupReadAfter"/>
|
||||||
<textbox id="feed-cleanupAfter" type="number" min="0" increment="1" size="2"/>
|
<textbox id="feed-cleanupReadAfter" type="number" min="0" increment="1" size="2"/>
|
||||||
<label value="&zotero.feedSettings.cleanupAfter.label2;" control="feed-cleanupAfter"/>
|
<label value="&zotero.feedSettings.cleanupReadAfter.label2;" control="feed-cleanupReadAfter"/>
|
||||||
|
</hbox>
|
||||||
|
<hbox align="center">
|
||||||
|
<label value="&zotero.feedSettings.cleanupUnreadAfter.label1;" control="feed-cleanupUnreadAfter"/>
|
||||||
|
<textbox id="feed-cleanupUnreadAfter" type="number" min="0" increment="1" size="2"/>
|
||||||
|
<label value="&zotero.feedSettings.cleanupUnreadAfter.label2;" control="feed-cleanupUnreadAfter"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
|
@ -59,7 +59,8 @@
|
||||||
|
|
||||||
<preference id="pref-feeds-sortAscending" name="extensions.zotero.feeds.sortAscending" type="bool"/>
|
<preference id="pref-feeds-sortAscending" name="extensions.zotero.feeds.sortAscending" type="bool"/>
|
||||||
<preference id="pref-feeds-defaultTTL" name="extensions.zotero.feeds.defaultTTL" type="int"/>
|
<preference id="pref-feeds-defaultTTL" name="extensions.zotero.feeds.defaultTTL" type="int"/>
|
||||||
<preference id="pref-feeds-defaultCleanupAfter" name="extensions.zotero.feeds.defaultCleanupAfter" type="int"/>
|
<preference id="pref-feeds-defaultCleanupUnreadAfter" name="extensions.zotero.feeds.defaultCleanupUnreadAfter" type="int"/>
|
||||||
|
<preference id="pref-feeds-defaultCleanupReadAfter" name="extensions.zotero.feeds.defaultCleanupReadAfter" type="int"/>
|
||||||
</preferences>
|
</preferences>
|
||||||
|
|
||||||
<tabbox id="zotero-prefpane-advanced-tabs">
|
<tabbox id="zotero-prefpane-advanced-tabs">
|
||||||
|
@ -319,9 +320,16 @@
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<hbox align="center">
|
<hbox align="center">
|
||||||
<label value="&zotero.feedSettings.cleanupAfter.label1;"/>
|
<label value="&zotero.feedSettings.cleanupReadAfter.label1;"/>
|
||||||
<textbox type="number" min="0" increment="1" size="2" preference="pref-feeds-defaultCleanupAfter"/>
|
<textbox type="number" min="0" increment="1" size="2" preference="pref-feeds-defaultCleanupReadAfter"/>
|
||||||
<label value="&zotero.feedSettings.cleanupAfter.label2;"/>
|
<label value="&zotero.feedSettings.cleanupReadAfter.label2;"/>
|
||||||
|
</hbox>
|
||||||
|
</hbox>
|
||||||
|
<hbox>
|
||||||
|
<hbox align="center">
|
||||||
|
<label value="&zotero.feedSettings.cleanupUnreadAfter.label1;"/>
|
||||||
|
<textbox type="number" min="0" increment="1" size="2" preference="pref-feeds-defaultCleanupUnreadAfter"/>
|
||||||
|
<label value="&zotero.feedSettings.cleanupUnreadAfter.label2;"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
</hbox>
|
</hbox>
|
||||||
</groupbox>
|
</groupbox>
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
* Custom parameters:
|
* Custom parameters:
|
||||||
* - name - name of the feed displayed in the collection tree
|
* - name - name of the feed displayed in the collection tree
|
||||||
* - url
|
* - url
|
||||||
* - cleanupAfter - number of days after which read items should be removed
|
* - cleanupReadAfter - number of days after which read items should be removed
|
||||||
|
* - cleanupUnreadAfter - number of days after which unread items should be removed
|
||||||
* - refreshInterval - in terms of hours
|
* - refreshInterval - in terms of hours
|
||||||
*
|
*
|
||||||
* @param params
|
* @param params
|
||||||
|
@ -40,7 +41,8 @@ Zotero.Feed = function(params = {}) {
|
||||||
params.libraryType = 'feed';
|
params.libraryType = 'feed';
|
||||||
Zotero.Feed._super.call(this, params);
|
Zotero.Feed._super.call(this, params);
|
||||||
|
|
||||||
this._feedCleanupAfter = null;
|
this._feedCleanupReadAfter = null;
|
||||||
|
this._feedCleanupUnreadAfter = null;
|
||||||
this._feedRefreshInterval = null;
|
this._feedRefreshInterval = null;
|
||||||
|
|
||||||
// Feeds are not editable by the user. Remove the setter
|
// Feeds are not editable by the user. Remove the setter
|
||||||
|
@ -56,7 +58,7 @@ Zotero.Feed = function(params = {}) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Utilities.assignProps(this, params,
|
Zotero.Utilities.assignProps(this, params,
|
||||||
['name', 'url', 'refreshInterval', 'cleanupAfter']);
|
['name', 'url', 'refreshInterval', 'cleanupReadAfter', 'cleanupUnreadAfter']);
|
||||||
|
|
||||||
// Return a proxy so that we can disable the object once it's deleted
|
// Return a proxy so that we can disable the object once it's deleted
|
||||||
return new Proxy(this, {
|
return new Proxy(this, {
|
||||||
|
@ -86,7 +88,7 @@ Zotero.defineProperty(Zotero.Feed, '_unreadCountSQL', {
|
||||||
|
|
||||||
Zotero.defineProperty(Zotero.Feed, '_dbColumns', {
|
Zotero.defineProperty(Zotero.Feed, '_dbColumns', {
|
||||||
value: Object.freeze(['name', 'url', 'lastUpdate', 'lastCheck',
|
value: Object.freeze(['name', 'url', 'lastUpdate', 'lastCheck',
|
||||||
'lastCheckError', 'cleanupAfter', 'refreshInterval'])
|
'lastCheckError', 'cleanupUnreadAfter', 'cleanupReadAfter', 'refreshInterval'])
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.defineProperty(Zotero.Feed, '_primaryDataSQLParts');
|
Zotero.defineProperty(Zotero.Feed, '_primaryDataSQLParts');
|
||||||
|
@ -122,7 +124,7 @@ Zotero.defineProperty(Zotero.Feed.prototype, 'updating', {
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
// Create accessors
|
// Create accessors
|
||||||
let accessors = ['name', 'url', 'refreshInterval', 'cleanupAfter'];
|
let accessors = ['name', 'url', 'refreshInterval', 'cleanupUnreadAfter', 'cleanupReadAfter'];
|
||||||
for (let i=0; i<accessors.length; i++) {
|
for (let i=0; i<accessors.length; i++) {
|
||||||
let name = accessors[i];
|
let name = accessors[i];
|
||||||
let prop = Zotero.Feed._colToProp(name);
|
let prop = Zotero.Feed._colToProp(name);
|
||||||
|
@ -183,12 +185,13 @@ Zotero.Feed.prototype._set = function (prop, val) {
|
||||||
this._previousURL = this.url;
|
this._previousURL = this.url;
|
||||||
break;
|
break;
|
||||||
case '_feedRefreshInterval':
|
case '_feedRefreshInterval':
|
||||||
case '_feedCleanupAfter':
|
case '_feedCleanupReadAfter':
|
||||||
|
case '_feedCleanupUnreadAfter':
|
||||||
if (val === null) break;
|
if (val === null) break;
|
||||||
|
|
||||||
let newVal = Number.parseInt(val, 10);
|
let newVal = Number.parseInt(val, 10);
|
||||||
if (newVal != val || !newVal || newVal <= 0) {
|
if (newVal != val || !newVal || newVal <= 0) {
|
||||||
throw new Error(prop + " must be null or a positive integer");
|
throw new Error(`${prop} must be null or a positive integer, but is ${val}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '_feedLastCheckError':
|
case '_feedLastCheckError':
|
||||||
|
@ -198,7 +201,7 @@ Zotero.Feed.prototype._set = function (prop, val) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof val !== 'string') {
|
if (typeof val !== 'string') {
|
||||||
throw new Error(prop + " must be null or a string");
|
throw new Error(`${prop} must be null or a string, but is ${val}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -214,7 +217,8 @@ Zotero.Feed.prototype._loadDataFromRow = function(row) {
|
||||||
this._feedLastCheckError = row._feedLastCheckError || null;
|
this._feedLastCheckError = row._feedLastCheckError || null;
|
||||||
this._feedLastCheck = row._feedLastCheck || null;
|
this._feedLastCheck = row._feedLastCheck || null;
|
||||||
this._feedLastUpdate = row._feedLastUpdate || null;
|
this._feedLastUpdate = row._feedLastUpdate || null;
|
||||||
this._feedCleanupAfter = parseInt(row._feedCleanupAfter) || null;
|
this._feedCleanupReadAfter = parseInt(row._feedCleanupReadAfter) || null;
|
||||||
|
this._feedCleanupUnreadAfter = parseInt(row._feedCleanupUnreadAfter) || null;
|
||||||
this._feedRefreshInterval = parseInt(row._feedRefreshInterval) || null;
|
this._feedRefreshInterval = parseInt(row._feedRefreshInterval) || null;
|
||||||
this._feedUnreadCount = parseInt(row._feedUnreadCount);
|
this._feedUnreadCount = parseInt(row._feedUnreadCount);
|
||||||
}
|
}
|
||||||
|
@ -236,7 +240,8 @@ Zotero.Feed.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
|
||||||
if (!this._feedName) throw new Error("Feed name not set");
|
if (!this._feedName) throw new Error("Feed name not set");
|
||||||
if (!this._feedUrl) throw new Error("Feed URL not set");
|
if (!this._feedUrl) throw new Error("Feed URL not set");
|
||||||
if (!this.refreshInterval) this.refreshInterval = Zotero.Prefs.get('feeds.defaultTTL') * 60;
|
if (!this.refreshInterval) this.refreshInterval = Zotero.Prefs.get('feeds.defaultTTL') * 60;
|
||||||
if (!this.cleanupAfter) this.cleanupAfter = Zotero.Prefs.get('feeds.defaultCleanupAfter');
|
if (!this.cleanupReadAfter) this.cleanupReadAfter = Zotero.Prefs.get('feeds.defaultCleanupReadAfter');
|
||||||
|
if (!this.cleanupUnreadAfter) this.cleanupUnreadAfter = Zotero.Prefs.get('feeds.defaultCleanupUnreadAfter');
|
||||||
|
|
||||||
if (env.isNew) {
|
if (env.isNew) {
|
||||||
// Make sure URL is unique
|
// Make sure URL is unique
|
||||||
|
@ -291,7 +296,7 @@ Zotero.Feed.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
||||||
|
|
||||||
Zotero.Feed.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
|
Zotero.Feed.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
|
||||||
let syncedDataChanged =
|
let syncedDataChanged =
|
||||||
['_feedName', '_feedCleanupAfter', '_feedRefreshInterval'].some((val) => this._changed[val]);
|
['_feedName', '_feedCleanupReadAfter', '_feedCleanupUnreadAfter', '_feedRefreshInterval'].some((val) => this._changed[val]);
|
||||||
|
|
||||||
yield Zotero.Feed._super.prototype._finalizeSave.apply(this, arguments);
|
yield Zotero.Feed._super.prototype._finalizeSave.apply(this, arguments);
|
||||||
|
|
||||||
|
@ -341,7 +346,7 @@ Zotero.Feed.prototype.erase = Zotero.Promise.coroutine(function* (options = {})
|
||||||
|
|
||||||
Zotero.Feed.prototype.storeSyncedSettings = Zotero.Promise.coroutine(function* () {
|
Zotero.Feed.prototype.storeSyncedSettings = Zotero.Promise.coroutine(function* () {
|
||||||
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
|
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
|
||||||
syncedFeeds[this.url] = [this.name, this.cleanupAfter, this.refreshInterval];
|
syncedFeeds[this.url] = [this.name, this.cleanupReadAfter, this.cleanupUnreadAfter, this.refreshInterval];
|
||||||
return Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedFeeds);
|
return Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedFeeds);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -349,15 +354,17 @@ Zotero.Feed.prototype.getExpiredFeedItemIDs = Zotero.Promise.coroutine(function*
|
||||||
let sql = "SELECT itemID AS id FROM feedItems "
|
let sql = "SELECT itemID AS id FROM feedItems "
|
||||||
+ "LEFT JOIN items I USING (itemID) "
|
+ "LEFT JOIN items I USING (itemID) "
|
||||||
+ "WHERE I.libraryID=? "
|
+ "WHERE I.libraryID=? "
|
||||||
+ "AND readTime IS NOT NULL "
|
+ "AND ("
|
||||||
+ "AND julianday('now', 'utc') - (julianday(readTime, 'utc') + ?) > 0";
|
+ "(readTime IS NOT NULL AND julianday('now', 'utc') - (julianday(readTime, 'utc') + ?) > 0) "
|
||||||
return Zotero.DB.columnQueryAsync(sql, [this.id, {int: this.cleanupAfter}]);
|
+ "OR (readTime IS NULL AND julianday('now', 'utc') - (julianday(dateModified, 'utc') + ?) > 0)"
|
||||||
|
+ ")";
|
||||||
|
return Zotero.DB.columnQueryAsync(sql, [this.id, {int: this.cleanupReadAfter}, {int: this.cleanupUnreadAfter}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clearing conditions for an item:
|
* Clearing conditions for an item:
|
||||||
* - Has been read at least feed.cleanupAfter earlier AND
|
* - Has been read at least feed.cleanupReadAfter earlier OR is unread and older than feed.cleanupUnreadAfter
|
||||||
* - Does not exist in the RSS feed anymore
|
* - AND Does not exist in the RSS feed anymore
|
||||||
*
|
*
|
||||||
* If we clear items once they've been read, we may potentially end up
|
* If we clear items once they've been read, we may potentially end up
|
||||||
* with empty feeds for those that do not update very frequently.
|
* with empty feeds for those that do not update very frequently.
|
||||||
|
@ -366,7 +373,6 @@ Zotero.Feed.prototype.clearExpiredItems = Zotero.Promise.coroutine(function* (it
|
||||||
itemsInFeedIDs = itemsInFeedIDs || new Set();
|
itemsInFeedIDs = itemsInFeedIDs || new Set();
|
||||||
try {
|
try {
|
||||||
// Clear expired items
|
// Clear expired items
|
||||||
if (this.cleanupAfter) {
|
|
||||||
let expiredItems = yield this.getExpiredFeedItemIDs();
|
let expiredItems = yield this.getExpiredFeedItemIDs();
|
||||||
let toClear = expiredItems;
|
let toClear = expiredItems;
|
||||||
if (itemsInFeedIDs.size) {
|
if (itemsInFeedIDs.size) {
|
||||||
|
@ -384,7 +390,6 @@ Zotero.Feed.prototype.clearExpiredItems = Zotero.Promise.coroutine(function* (it
|
||||||
} else {
|
} else {
|
||||||
Zotero.debug("No expired feed items");
|
Zotero.debug("No expired feed items");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Zotero.debug("Error clearing expired feed items");
|
Zotero.debug("Error clearing expired feed items");
|
||||||
Zotero.debug(e);
|
Zotero.debug(e);
|
||||||
|
|
|
@ -128,7 +128,6 @@ Zotero.FeedItems = new Proxy(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.toggleReadByID = Zotero.Promise.coroutine(function* (ids, state) {
|
this.toggleReadByID = Zotero.Promise.coroutine(function* (ids, state) {
|
||||||
var feedsToUpdate = new Set();
|
|
||||||
if (!Array.isArray(ids)) {
|
if (!Array.isArray(ids)) {
|
||||||
if (typeof ids != 'string') throw new Error('ids must be a string or array in Zotero.FeedItems.toggleReadByID');
|
if (typeof ids != 'string') throw new Error('ids must be a string or array in Zotero.FeedItems.toggleReadByID');
|
||||||
|
|
||||||
|
@ -147,19 +146,20 @@ Zotero.FeedItems = new Proxy(function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let feedsToUpdate = new Set();
|
||||||
yield Zotero.DB.executeTransaction(function() {
|
let readTime = state ? Zotero.Date.dateToSQL(new Date(), true) : null;
|
||||||
for (let i=0; i<items.length; i++) {
|
for (let i=0; i<items.length; i++) {
|
||||||
items[i].isRead = state;
|
items[i]._feedItemReadTime = readTime;
|
||||||
|
|
||||||
yield items[i].save();
|
|
||||||
let feed = Zotero.Feeds.get(items[i].libraryID);
|
let feed = Zotero.Feeds.get(items[i].libraryID);
|
||||||
feedsToUpdate.add(feed);
|
feedsToUpdate.add(feed);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
yield Zotero.DB.queryAsync(`UPDATE feedItems SET readTime=? WHERE itemID IN (${ids.join(', ')})`, readTime);
|
||||||
|
yield Zotero.Notifier.trigger('modify', 'item', ids, {});
|
||||||
|
|
||||||
for (let feed of feedsToUpdate) {
|
for (let feed of feedsToUpdate) {
|
||||||
yield Zotero.Promise.all([feed.updateUnreadCount(), feed.storeSyncedSettings()]);
|
yield feed.updateUnreadCount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -164,8 +164,12 @@ Zotero.Feeds = new function() {
|
||||||
if (json[feed.url]) {
|
if (json[feed.url]) {
|
||||||
Zotero.debug("Feed " + feed.url + " exists remotely and locally");
|
Zotero.debug("Feed " + feed.url + " exists remotely and locally");
|
||||||
feed.name = json[feed.url][0];
|
feed.name = json[feed.url][0];
|
||||||
feed.cleanupAfter = json[feed.url][1];
|
feed.cleanupReadAfter = json[feed.url][1];
|
||||||
feed.refreshInterval = json[feed.url][2];
|
// TEMP after adding cleanupUnreadAfter for unread items
|
||||||
|
if (json[feed.url].length == 4) {
|
||||||
|
feed.cleanupUnreadAfter = json[feed.url][2];
|
||||||
|
}
|
||||||
|
feed.refreshInterval = json[feed.url][json[feed.url].length-1];
|
||||||
delete json[feed.url];
|
delete json[feed.url];
|
||||||
} else {
|
} else {
|
||||||
Zotero.debug("Feed " + feed.url + " does not exist in remote JSON. Deleting");
|
Zotero.debug("Feed " + feed.url + " does not exist in remote JSON. Deleting");
|
||||||
|
@ -175,12 +179,17 @@ Zotero.Feeds = new function() {
|
||||||
// Because existing json[feed.url] got deleted, `json` now only contains new feeds
|
// Because existing json[feed.url] got deleted, `json` now only contains new feeds
|
||||||
for (let url in json) {
|
for (let url in json) {
|
||||||
Zotero.debug("Feed " + url + " exists remotely but not locally. Creating");
|
Zotero.debug("Feed " + url + " exists remotely but not locally. Creating");
|
||||||
let feed = new Zotero.Feed({
|
let obj = {
|
||||||
url,
|
url,
|
||||||
name: json[url][0],
|
name: json[url][0],
|
||||||
cleanupAfter: json[url][1],
|
cleanupReadAfter: json[url][1],
|
||||||
refreshInterval: json[url[2]]
|
refreshInterval: json[url][json[url].length-1]
|
||||||
});
|
};
|
||||||
|
// TEMP after adding cleanupUnreadAfter for unread items
|
||||||
|
if (json[url].length == 4) {
|
||||||
|
obj.cleanupUnreadAfter = json[url][2];
|
||||||
|
}
|
||||||
|
let feed = new Zotero.Feed(obj);
|
||||||
yield feed.save();
|
yield feed.save();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -292,7 +301,7 @@ Zotero.Feeds = new function() {
|
||||||
if(Array.isArray(json[url])) {
|
if(Array.isArray(json[url])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
json[url] = [json[url].name, json[url].cleanupAfter, json[url].refreshInterval];
|
json[url] = [json[url].name, json[url].cleanupReadAfter, json[url].cleanupUnreadAfter, json[url].refreshInterval];
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,7 +34,7 @@ Zotero.Schema = new function(){
|
||||||
var _dbVersions = [];
|
var _dbVersions = [];
|
||||||
var _schemaVersions = [];
|
var _schemaVersions = [];
|
||||||
// Update when adding _updateCompatibility() line to schema update step
|
// Update when adding _updateCompatibility() line to schema update step
|
||||||
var _maxCompatibility = 3;
|
var _maxCompatibility = 4;
|
||||||
var _repositoryTimer;
|
var _repositoryTimer;
|
||||||
var _remoteUpdateInProgress = false, _localUpdateInProgress = false;
|
var _remoteUpdateInProgress = false, _localUpdateInProgress = false;
|
||||||
|
|
||||||
|
@ -2356,6 +2356,14 @@ Zotero.Schema = new function(){
|
||||||
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemRelations VALUES (?, ?, ?)", [newSubjectID, predicateID, newObjectURI]);
|
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemRelations VALUES (?, ?, ?)", [newSubjectID, predicateID, newObjectURI]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (i == 90) {
|
||||||
|
yield _updateCompatibility(4);
|
||||||
|
yield Zotero.DB.queryAsync("ALTER TABLE feeds RENAME TO feedsOld");
|
||||||
|
yield Zotero.DB.queryAsync("CREATE TABLE feeds (\n libraryID INTEGER PRIMARY KEY,\n name TEXT NOT NULL,\n url TEXT NOT NULL UNIQUE,\n lastUpdate TIMESTAMP,\n lastCheck TIMESTAMP,\n lastCheckError TEXT,\n cleanupReadAfter INT,\n cleanupUnreadAfter INT,\n refreshInterval INT,\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
|
||||||
|
yield Zotero.DB.queryAsync("INSERT INTO feeds SELECT libraryID, name, url, lastUpdate, lastCheck, lastCheckError, 30, cleanupAfter, refreshInterval FROM feedsOld");
|
||||||
|
yield Zotero.DB.queryAsync("DROP TABLE feedsOld");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield _updateDBVersion('userdata', toVersion);
|
yield _updateDBVersion('userdata', toVersion);
|
||||||
|
|
|
@ -915,7 +915,8 @@ var ZoteroPane = new function()
|
||||||
feed.url = data.url;
|
feed.url = data.url;
|
||||||
feed.name = data.title;
|
feed.name = data.title;
|
||||||
feed.refreshInterval = data.ttl;
|
feed.refreshInterval = data.ttl;
|
||||||
feed.cleanupAfter = data.cleanupAfter;
|
feed.cleanupReadAfter = data.cleanupReadAfter;
|
||||||
|
feed.cleanupUnreadAfter = data.cleanupUnreadAfter;
|
||||||
yield feed.saveTx();
|
yield feed.saveTx();
|
||||||
yield feed.updateFeed();
|
yield feed.updateFeed();
|
||||||
}
|
}
|
||||||
|
@ -930,7 +931,8 @@ var ZoteroPane = new function()
|
||||||
feed.url = data.url;
|
feed.url = data.url;
|
||||||
feed.name = data.title;
|
feed.name = data.title;
|
||||||
feed.refreshInterval = data.ttl;
|
feed.refreshInterval = data.ttl;
|
||||||
feed.cleanupAfter = data.cleanupAfter;
|
feed.cleanupReadAfter = data.cleanupReadAfter;
|
||||||
|
feed.cleanupUnreadAfter = data.cleanupUnreadAfter;
|
||||||
yield feed.saveTx();
|
yield feed.saveTx();
|
||||||
yield feed.updateFeed();
|
yield feed.updateFeed();
|
||||||
}
|
}
|
||||||
|
@ -2066,7 +2068,8 @@ var ZoteroPane = new function()
|
||||||
url: feed.url,
|
url: feed.url,
|
||||||
title: feed.name,
|
title: feed.name,
|
||||||
ttl: feed.refreshInterval,
|
ttl: feed.refreshInterval,
|
||||||
cleanupAfter: feed.cleanupAfter
|
cleanupReadAfter: feed.cleanupReadAfter,
|
||||||
|
cleanupUnreadAfter: feed.cleanupUnreadAfter
|
||||||
};
|
};
|
||||||
|
|
||||||
window.openDialog('chrome://zotero/content/feedSettings.xul',
|
window.openDialog('chrome://zotero/content/feedSettings.xul',
|
||||||
|
@ -2075,7 +2078,8 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
feed.name = data.title;
|
feed.name = data.title;
|
||||||
feed.refreshInterval = data.ttl;
|
feed.refreshInterval = data.ttl;
|
||||||
feed.cleanupAfter = data.cleanupAfter;
|
feed.cleanupReadAfter = data.cleanupReadAfter;
|
||||||
|
feed.cleanupUnreadAfter = data.cleanupUnreadAfter;
|
||||||
yield feed.saveTx();
|
yield feed.saveTx();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -272,8 +272,10 @@
|
||||||
<!ENTITY zotero.feedSettings.title.label "Title:">
|
<!ENTITY zotero.feedSettings.title.label "Title:">
|
||||||
<!ENTITY zotero.feedSettings.refresh.label1 "Update feed every">
|
<!ENTITY zotero.feedSettings.refresh.label1 "Update feed every">
|
||||||
<!ENTITY zotero.feedSettings.refresh.label2 "hour(s)">
|
<!ENTITY zotero.feedSettings.refresh.label2 "hour(s)">
|
||||||
<!ENTITY zotero.feedSettings.cleanupAfter.label1 "Remove read articles after ">
|
<!ENTITY zotero.feedSettings.cleanupUnreadAfter.label1 "Remove unread feed items after ">
|
||||||
<!ENTITY zotero.feedSettings.cleanupAfter.label2 "day(s)">
|
<!ENTITY zotero.feedSettings.cleanupUnreadAfter.label2 "day(s)">
|
||||||
|
<!ENTITY zotero.feedSettings.cleanupReadAfter.label1 "Remove read feed items after ">
|
||||||
|
<!ENTITY zotero.feedSettings.cleanupReadAfter.label2 "day(s)">
|
||||||
|
|
||||||
|
|
||||||
<!ENTITY zotero.recognizePDF.recognizing.label "Retrieving Metadata…">
|
<!ENTITY zotero.recognizePDF.recognizing.label "Retrieving Metadata…">
|
||||||
|
|
|
@ -54,7 +54,8 @@ pref("extensions.zotero.groups.copyTags", true);
|
||||||
|
|
||||||
pref("extensions.zotero.feeds.sortAscending", false);
|
pref("extensions.zotero.feeds.sortAscending", false);
|
||||||
pref("extensions.zotero.feeds.defaultTTL", 1);
|
pref("extensions.zotero.feeds.defaultTTL", 1);
|
||||||
pref("extensions.zotero.feeds.defaultCleanupAfter", 2);
|
pref("extensions.zotero.feeds.defaultCleanupReadAfter", 3);
|
||||||
|
pref("extensions.zotero.feeds.defaultCleanupUnreadAfter", 30);
|
||||||
|
|
||||||
pref("extensions.zotero.backup.numBackups", 2);
|
pref("extensions.zotero.backup.numBackups", 2);
|
||||||
pref("extensions.zotero.backup.interval", 1440);
|
pref("extensions.zotero.backup.interval", 1440);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- 89
|
-- 90
|
||||||
|
|
||||||
-- Copyright (c) 2009 Center for History and New Media
|
-- Copyright (c) 2009 Center for History and New Media
|
||||||
-- George Mason University, Fairfax, Virginia, USA
|
-- George Mason University, Fairfax, Virginia, USA
|
||||||
|
@ -203,7 +203,8 @@ CREATE TABLE feeds (
|
||||||
lastUpdate TIMESTAMP,
|
lastUpdate TIMESTAMP,
|
||||||
lastCheck TIMESTAMP,
|
lastCheck TIMESTAMP,
|
||||||
lastCheckError TEXT,
|
lastCheckError TEXT,
|
||||||
cleanupAfter INT,
|
cleanupReadAfter INT,
|
||||||
|
cleanupUnreadAfter INT,
|
||||||
refreshInterval INT,
|
refreshInterval INT,
|
||||||
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
|
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
|
@ -321,7 +321,8 @@ var createFeed = Zotero.Promise.coroutine(function* (props = {}) {
|
||||||
feed.description = props.description || "";
|
feed.description = props.description || "";
|
||||||
feed.url = props.url || 'http://www.' + Zotero.Utilities.randomString() + '.com/feed.rss';
|
feed.url = props.url || 'http://www.' + Zotero.Utilities.randomString() + '.com/feed.rss';
|
||||||
feed.refreshInterval = props.refreshInterval || 12;
|
feed.refreshInterval = props.refreshInterval || 12;
|
||||||
feed.cleanupAfter = props.cleanupAfter || 2;
|
feed.cleanupReadAfter = props.cleanupReadAfter || 2;
|
||||||
|
feed.cleanupUnreadAfter = props.cleanupUnreadAfter || 30;
|
||||||
yield feed.saveTx();
|
yield feed.saveTx();
|
||||||
return feed;
|
return feed;
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,7 +75,8 @@ describe("Zotero.Feed", function() {
|
||||||
name: 'Test ' + Zotero.randomString(),
|
name: 'Test ' + Zotero.randomString(),
|
||||||
url: 'http://' + Zotero.randomString() + '.com/',
|
url: 'http://' + Zotero.randomString() + '.com/',
|
||||||
refreshInterval: 30,
|
refreshInterval: 30,
|
||||||
cleanupAfter: 1
|
cleanupReadAfter: 1,
|
||||||
|
cleanupUnreadAfter: 30
|
||||||
};
|
};
|
||||||
|
|
||||||
let feed = yield createFeed(props);
|
let feed = yield createFeed(props);
|
||||||
|
@ -83,7 +84,8 @@ describe("Zotero.Feed", function() {
|
||||||
assert.equal(feed.name, props.name, "name is correct");
|
assert.equal(feed.name, props.name, "name is correct");
|
||||||
assert.equal(feed.url.toLowerCase(), props.url.toLowerCase(), "url is correct");
|
assert.equal(feed.url.toLowerCase(), props.url.toLowerCase(), "url is correct");
|
||||||
assert.equal(feed.refreshInterval, props.refreshInterval, "refreshInterval is correct");
|
assert.equal(feed.refreshInterval, props.refreshInterval, "refreshInterval is correct");
|
||||||
assert.equal(feed.cleanupAfter, props.cleanupAfter, "cleanupAfter is correct");
|
assert.equal(feed.cleanupReadAfter, props.cleanupReadAfter, "cleanupReadAfter is correct");
|
||||||
|
assert.equal(feed.cleanupUnreadAfter, props.cleanupUnreadAfter, "cleanupUnreadAfter is correct");
|
||||||
|
|
||||||
assert.isNull(feed.lastCheck, "lastCheck is null");
|
assert.isNull(feed.lastCheck, "lastCheck is null");
|
||||||
assert.isNull(feed.lastUpdate, "lastUpdate is null");
|
assert.isNull(feed.lastUpdate, "lastUpdate is null");
|
||||||
|
@ -159,7 +161,7 @@ describe("Zotero.Feed", function() {
|
||||||
assert.notOk(syncedFeeds[oldUrl]);
|
assert.notOk(syncedFeeds[oldUrl]);
|
||||||
assert.ok(syncedFeeds[feed.url]);
|
assert.ok(syncedFeeds[feed.url]);
|
||||||
});
|
});
|
||||||
it('should update syncedSettings if `name`, `url`, `refreshInterval` or `cleanupAfter` was modified', function* () {
|
it('should update syncedSettings if `name`, `url`, `refreshInterval` or `cleanupUnreadAfter` was modified', function* () {
|
||||||
let feed = yield createFeed();
|
let feed = yield createFeed();
|
||||||
let syncedSetting = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
|
let syncedSetting = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
|
||||||
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedSetting, 0, true);
|
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedSetting, 0, true);
|
||||||
|
@ -168,7 +170,7 @@ describe("Zotero.Feed", function() {
|
||||||
yield feed.saveTx();
|
yield feed.saveTx();
|
||||||
assert.isFalse(Zotero.SyncedSettings.getMetadata(Zotero.Libraries.userLibraryID, 'feeds').synced)
|
assert.isFalse(Zotero.SyncedSettings.getMetadata(Zotero.Libraries.userLibraryID, 'feeds').synced)
|
||||||
});
|
});
|
||||||
it('should not update syncedSettings if `name`, `url`, `refreshInterval` or `cleanupAfter` were not modified', function* () {
|
it('should not update syncedSettings if `name`, `url`, `refreshInterval` or `cleanupUnreadAfter` were not modified', function* () {
|
||||||
let feed = yield createFeed();
|
let feed = yield createFeed();
|
||||||
let syncedSetting = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
|
let syncedSetting = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
|
||||||
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedSetting, 0, true);
|
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedSetting, 0, true);
|
||||||
|
@ -220,12 +222,13 @@ describe("Zotero.Feed", function() {
|
||||||
describe("#storeSyncedSettings", function() {
|
describe("#storeSyncedSettings", function() {
|
||||||
it("should store settings for feed in compact format", function* () {
|
it("should store settings for feed in compact format", function* () {
|
||||||
let url = 'http://' + Zotero.Utilities.randomString().toLowerCase() + '.com/feed.rss';
|
let url = 'http://' + Zotero.Utilities.randomString().toLowerCase() + '.com/feed.rss';
|
||||||
let settings = [Zotero.Utilities.randomString(), 1, 1];
|
let settings = [Zotero.Utilities.randomString(), 1, 30, 1];
|
||||||
let feed = yield createFeed({
|
let feed = yield createFeed({
|
||||||
url,
|
url,
|
||||||
name: settings[0],
|
name: settings[0],
|
||||||
cleanupAfter: settings[1],
|
cleanupReadAfter: settings[1],
|
||||||
refreshInterval: settings[2]
|
cleanupUnreadAfter: settings[2],
|
||||||
|
refreshInterval: settings[3]
|
||||||
});
|
});
|
||||||
|
|
||||||
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
|
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
|
||||||
|
@ -234,17 +237,25 @@ describe("Zotero.Feed", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#clearExpiredItems()", function() {
|
describe("#clearExpiredItems()", function() {
|
||||||
var feed, expiredFeedItem, readFeedItem, feedItem, readStillInFeed, feedItemIDs;
|
var feed, readExpiredFI, unreadExpiredFI, readFeedItem, feedItem, readStillInFeed, feedItemIDs;
|
||||||
|
|
||||||
before(function* (){
|
before(function* (){
|
||||||
feed = yield createFeed({cleanupAfter: 1});
|
feed = yield createFeed({cleanupReadAfter: 1, cleanupUnreadAfter: 3});
|
||||||
|
|
||||||
expiredFeedItem = yield createDataObject('feedItem', { libraryID: feed.libraryID });
|
readExpiredFI = yield createDataObject('feedItem', { libraryID: feed.libraryID });
|
||||||
// Read 2 days ago
|
// Read 2 days ago
|
||||||
expiredFeedItem.isRead = true;
|
readExpiredFI.isRead = true;
|
||||||
expiredFeedItem._feedItemReadTime = Zotero.Date.dateToSQL(
|
readExpiredFI._feedItemReadTime = Zotero.Date.dateToSQL(
|
||||||
new Date(Date.now() - 2 * 24*60*60*1000), true);
|
new Date(Date.now() - 2 * 24*60*60*1000), true);
|
||||||
yield expiredFeedItem.saveTx();
|
yield readExpiredFI.saveTx();
|
||||||
|
|
||||||
|
// Added 5 days ago
|
||||||
|
unreadExpiredFI = yield createDataObject('feedItem', {
|
||||||
|
libraryID: feed.libraryID,
|
||||||
|
dateAdded: Zotero.Date.dateToSQL(new Date(Date.now() - 5 * 24*60*60*1000), true),
|
||||||
|
dateModified: Zotero.Date.dateToSQL(new Date(Date.now() - 5 * 24*60*60*1000), true)
|
||||||
|
});
|
||||||
|
yield unreadExpiredFI.saveTx();
|
||||||
|
|
||||||
readStillInFeed = yield createDataObject('feedItem', { libraryID: feed.libraryID });
|
readStillInFeed = yield createDataObject('feedItem', { libraryID: feed.libraryID });
|
||||||
// Read 2 days ago
|
// Read 2 days ago
|
||||||
|
@ -263,7 +274,7 @@ describe("Zotero.Feed", function() {
|
||||||
|
|
||||||
assert.include(feedItemIDs, feedItem.id, "feed contains unread feed item");
|
assert.include(feedItemIDs, feedItem.id, "feed contains unread feed item");
|
||||||
assert.include(feedItemIDs, readFeedItem.id, "feed contains read feed item");
|
assert.include(feedItemIDs, readFeedItem.id, "feed contains read feed item");
|
||||||
assert.include(feedItemIDs, expiredFeedItem.id, "feed contains expired feed item");
|
assert.include(feedItemIDs, readExpiredFI.id, "feed contains expired feed item");
|
||||||
assert.include(feedItemIDs, readStillInFeed.id, "feed contains expired but still in rss feed item");
|
assert.include(feedItemIDs, readStillInFeed.id, "feed contains expired but still in rss feed item");
|
||||||
|
|
||||||
yield feed.clearExpiredItems(new Set([readStillInFeed.id]));
|
yield feed.clearExpiredItems(new Set([readStillInFeed.id]));
|
||||||
|
@ -272,7 +283,8 @@ describe("Zotero.Feed", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear expired items', function() {
|
it('should clear expired items', function() {
|
||||||
assert.notInclude(feedItemIDs, expiredFeedItem.id, "feed no longer contain expired feed item");
|
assert.notInclude(feedItemIDs, readExpiredFI.id, "feed no longer contains expired read feed item");
|
||||||
|
assert.notInclude(feedItemIDs, unreadExpiredFI.id, "feed no longer contains expired feed item");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not clear read items that have not expired yet', function() {
|
it('should not clear read items that have not expired yet', function() {
|
||||||
|
|
|
@ -56,7 +56,8 @@ describe("Zotero.Feeds", function () {
|
||||||
url,
|
url,
|
||||||
name: Zotero.Utilities.randomString(),
|
name: Zotero.Utilities.randomString(),
|
||||||
refreshInterval: 5,
|
refreshInterval: 5,
|
||||||
cleanupAfter: 3,
|
cleanupReadAfter: 3,
|
||||||
|
cleanupUnreadAfter: 30,
|
||||||
};
|
};
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
existingFeedURL = url;
|
existingFeedURL = url;
|
||||||
|
|
Loading…
Reference in a new issue