Feed syncing (#1044)

Closes #1036

Also:

- Store sync info for feeds more compactly. Address #1037
This commit is contained in:
Adomas Ven 2016-06-22 10:24:22 +03:00 committed by Dan Stillman
parent 451c12513e
commit dd8fd2b1ac
7 changed files with 112 additions and 52 deletions

View File

@ -70,7 +70,6 @@ Zotero.Feed = function(params = {}) {
this._feedUnreadCount = null;
this._updating = false;
this._syncedSettings = null;
this._previousURL = null;
}
@ -301,8 +300,8 @@ Zotero.Feed.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
}
if (env.isNew || this._previousURL) {
Zotero.Feeds.register(this);
yield this.storeSyncedSettings();
}
yield this.storeSyncedSettings();
this._previousURL = null;
});
@ -335,12 +334,7 @@ Zotero.Feed.prototype.erase = Zotero.Promise.coroutine(function* (options = {})
Zotero.Feed.prototype.storeSyncedSettings = Zotero.Promise.coroutine(function* () {
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
syncedFeeds[this.url] = {
url: this.url,
name: this.name,
cleanupAfter: this.cleanupAfter,
refreshInterval: this.refreshInterval
};
syncedFeeds[this.url] = [this.name, this.cleanupAfter, this.refreshInterval];
return Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedFeeds);
});

View File

@ -30,6 +30,18 @@ Zotero.Feeds = new function() {
this.scheduleNextFeedCheck();
this._timeoutID = null;
}, 5000);
Zotero.SyncedSettings.onSyncDownload.addListener(Zotero.Libraries.userLibraryID, 'feeds',
(oldValue, newValue, conflict) => {
Zotero.Feeds.restoreFromJSON(newValue, conflict);
}
);
Zotero.Notifier.registerObserver({notify: function(event) {
if (event == 'finish') {
Zotero.Feeds.updateFeeds();
}
}}, ['sync'], 'feedsUpdate');
};
this.uninit = function () {
@ -126,37 +138,37 @@ Zotero.Feeds = new function() {
Zotero.debug(json);
if (merge) {
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
// Overwrite with remote values for names, etc.
for (let url in json) {
if (syncedFeeds[url]) {
syncedFeeds[url].name = json[url].name;
syncedFeeds[url].cleanupAfter = json[url].cleanupAfter;
syncedFeeds[url].refreshInterval = json[url].refreshInterval;
for (let guid in json[url].markedAsRead) {
syncedFeeds[url].markedAsRead[guid] = true;
}
} else {
syncedFeeds[url] = json[url];
}
syncedFeeds[url] = json[url];
}
// But keep all local feeds
json = syncedFeeds;
}
json = this._compactifyFeedJSON(json);
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', json);
let feeds = Zotero.Feeds.getAll();
for (let feed of feeds) {
if (json[feed.url]) {
Zotero.debug("Feed " + feed.url + " exists remotely and locally");
delete json[feed.url];
feed.name = json[feed.url][0];
feed.cleanupAfter = json[feed.url][1];
feed.refreshInterval = json[feed.url][2];
} else {
Zotero.debug("Feed " + feed.url + " does not exist in remote JSON. Deleting");
yield feed.eraseTx();
yield feed.erase();
}
}
// Because existing json[feed.url] got deleted, `json` now only contains new feeds
for (let url in json) {
Zotero.debug("Feed " + url + " exists remotely but not locally. Creating");
let feed = new Zotero.Feed(json[url]);
yield feed.saveTx();
yield feed.updateFeed();
let feed = new Zotero.Feed({
url,
name: json[url][0],
cleanupAfter: json[url][1],
refreshInterval: json[url[2]]
});
yield feed.save();
}
});
@ -259,4 +271,16 @@ Zotero.Feeds = new function() {
Zotero.debug("All feed updates done");
this.scheduleNextFeedCheck();
});
// Conversion from expansive to compact format sync json
// TODO: Remove after beta
this._compactifyFeedJSON = function(json) {
for (let url in json) {
if(Array.isArray(json[url])) {
continue;
}
json[url] = [json[url].name, json[url].cleanupAfter, json[url].refreshInterval];
}
return json;
};
}

View File

@ -30,12 +30,19 @@ Zotero.Notifier = new function(){
var _types = [
'collection', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'publications',
'bucket', 'relation', 'feed', 'feedItem'
'bucket', 'relation', 'feed', 'feedItem', 'sync'
];
var _inTransaction;
var _queue = {};
/**
* @param ref {Object} - signature {notify: function(event, type, ids, extraData) {}}
* @param types {Array} - a list of types of events observer should be triggered on
* @param id {String} - an id of the observer used in debug output
* @param priority {Integer} - lower numbers correspond to higher priority of observer execution
* @returns {string}
*/
this.registerObserver = function (ref, types, id, priority) {
if (types){
types = Zotero.flattenArguments(types);

View File

@ -149,7 +149,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
firstInSession: _firstInSession
};
let librariesToSync = options.libraries = yield this.checkLibraries(
var librariesToSync = options.libraries = yield this.checkLibraries(
client,
options,
keyInfo,
@ -223,6 +223,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
Zotero.debug("Done syncing");
Zotero.Notifier.trigger('finish', 'sync', librariesToSync);
}
});

View File

@ -35,6 +35,40 @@ Zotero.SyncedSettings = (function () {
var module = {
idColumn: "setting",
table: "syncedSettings",
/**
* An event which allows to tap into the sync transaction and update
* parts of the client which rely on synced settings.
*/
onSyncDownload: {
listeners: {},
addListener: function(libraryID, setting, fn, bindTarget=null) {
if (!this.listeners[libraryID]) {
this.listeners[libraryID] = {};
}
if (!this.listeners[libraryID][setting]) {
this.listeners[libraryID][setting] = [];
}
this.listeners[libraryID][setting].push([fn, bindTarget]);
},
/**
* @param {Integer} libraryID
* @param {String} setting - name of the setting
* @param {Object} oldValue
* @param {Object} newValue
* @param {Boolean} conflict - true if both client and remote values had changed before sync
*/
trigger: Zotero.Promise.coroutine(function* (libraryID, setting, oldValue, newValue, conflict) {
var libListeners = this.listeners[libraryID] || {};
var settingListeners = libListeners[setting] || [];
Array.prototype.splice.call(arguments, 0, 2);
if (settingListeners) {
for (let listener of settingListeners) {
yield Zotero.Promise.resolve(listener[0].apply(listener[1], arguments));
}
}
})
},
loadAll: Zotero.Promise.coroutine(function* (libraryID) {
Zotero.debug("Loading synced settings for library " + libraryID);
@ -175,11 +209,13 @@ Zotero.SyncedSettings = (function () {
version = parseInt(version);
if (hasCurrentValue) {
var sql = "UPDATE syncedSettings SET value=?, version=?, synced=? "
+ "WHERE setting=? AND libraryID=?";
yield Zotero.DB.queryAsync(
sql, [JSON.stringify(value), version, synced, setting, libraryID]
);
var sql = "UPDATE syncedSettings SET " + (version > 0 ? "version=?, " : "") +
"value=?, synced=? WHERE setting=? AND libraryID=?";
var args = [JSON.stringify(value), synced, setting, libraryID];
if (version > 0) {
args.unshift(version)
}
yield Zotero.DB.queryAsync(sql, args);
}
else {
var sql = "INSERT INTO syncedSettings "
@ -188,13 +224,19 @@ Zotero.SyncedSettings = (function () {
sql, [setting, libraryID, JSON.stringify(value), version, synced]
);
}
var metadata = this.getMetadata(libraryID, setting);
_cache[libraryID][setting] = {
value,
synced: !!synced,
version
}
version: version > 0 ? version : metadata.version
};
var conflict = metadata && !metadata.synced && metadata.version < version;
if (version > 0) {
yield this.onSyncDownload.trigger(libraryID, setting, currentValue, value, conflict);
}
yield Zotero.Notifier.trigger(event, 'setting', [id], extraData);
return true;
}),

View File

@ -200,17 +200,18 @@ describe("Zotero.Feed", function() {
});
describe("#storeSyncedSettings", function() {
it("should store updated settings for the feed", function* () {
let settings = {
name: Zotero.Utilities.randomString(),
url: 'http://' + Zotero.Utilities.randomString().toLowerCase() + '.com/feed.rss',
refreshInterval: 1,
cleanupAfter: 1
};
let feed = yield createFeed(settings);
it("should store settings for feed in compact format", function* () {
let url = 'http://' + Zotero.Utilities.randomString().toLowerCase() + '.com/feed.rss';
let settings = [Zotero.Utilities.randomString(), 1, 1];
let feed = yield createFeed({
url,
name: settings[0],
cleanupAfter: settings[1],
refreshInterval: settings[2]
});
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
assert.deepEqual(syncedFeeds[feed.url], settings);
assert.deepEqual(syncedFeeds[url], settings);
});
});

View File

@ -47,14 +47,6 @@ describe("Zotero.Feeds", function () {
var json = {};
var expiredFeedURL, existingFeedURL;
before(function() {
sinon.stub(Zotero.Feed.prototype, 'updateFeed').resolves();
});
after(function() {
Zotero.Feed.prototype.updateFeed.restore();
});
beforeEach(function* () {
yield clearFeeds();
@ -65,7 +57,6 @@ describe("Zotero.Feeds", function () {
name: Zotero.Utilities.randomString(),
refreshInterval: 5,
cleanupAfter: 3,
markedAsRead: []
};
if (i == 0) {
existingFeedURL = url;