File sync overhaul

- New promise-based architecture
- Library-specific file sync queues, allowing other libraries to
  continue if there's an error in one library
- Library-specific sync errors, with error icons next to each library
- Changed file uploading in on-demand download mode, which had been missing
- On-demand download progress indicator in middle pane
- More accurate progress indicator
- Various tweaks and bug fixes
- Various future tweaks and bug fixes
This commit is contained in:
Dan Stillman 2012-12-11 15:16:40 -05:00
parent 4c8431ca7d
commit bb93f019dc
27 changed files with 2895 additions and 2163 deletions

View file

@ -59,7 +59,7 @@
padding-top: 1px;
}
#zotero-tb-sync-warning[error=true]
#zotero-tb-sync-error[error=true]
{
margin-bottom: 2px;
}

View file

@ -21,7 +21,7 @@
visibility: hidden;
}
#zotero-tb-sync-warning {
#zotero-tb-sync-error {
margin-right: 2px;
}

View file

@ -0,0 +1,182 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2012 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
-->
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
<bindings xmlns="http://www.mozilla.org/xbl">
<binding id="file-sync-status">
<implementation>
<property name="data"
onget="return this._data;"
onset="this._data = val; this.refresh();">
</property>
<field name="_libraries">[]</field>
<method name="refresh">
<body>
<![CDATA[
var rows = this._id('rows');
// Get libraries with active downloads or uploads
var newLibraries = [];
for (var libraryID in this._data) {
if ((this._data[libraryID].download
&& !this._data[libraryID].download.finished)
||
(this._data[libraryID].upload
&& !this._data[libraryID].upload.finished)) {
newLibraries.push(parseInt(libraryID));
}
}
// If set of libraries is different, clear and recreate
var toRemove = Zotero.Utilities.arrayDiff(this._libraries, newLibraries);
var toAdd = Zotero.Utilities.arrayDiff(newLibraries, this._libraries);
if (toRemove.length || toAdd.length) {
while (rows.hasChildNodes()) {
rows.removeChild(rows.firstChild);
}
}
this._libraries = newLibraries;
// Update
if (rows.hasChildNodes()) {
for (var libraryID in this._data) {
var libraryStatus = this._data[libraryID];
// Library is finished
if (newLibraries.indexOf(parseInt(libraryID)) == -1) {
continue;
}
var libraryNameRow = this._id('library-name-row-' + libraryID);
var downloadsRow = this._id('downloads-row-' + libraryID);
var uploadsRow = this._id('uploads-row-' + libraryID);
downloadsRow.lastChild.setAttribute('value',
libraryStatus.download
? libraryStatus.download.statusString
: Zotero.getString('sync.storage.none'));
uploadsRow.lastChild.setAttribute('value',
libraryStatus.upload
? libraryStatus.upload.statusString
: Zotero.getString('sync.storage.none'));
}
}
// Build from scratch
else {
// Get ordered list of library names
var libraryNames = [];
for each(var libraryID in newLibraries) {
libraryNames.push({
libraryID: libraryID,
name: Zotero.Libraries.getName(libraryID)
});
}
var collation = Zotero.getLocaleCollation();
libraryNames.sort(function (a, b) {
if (a.libraryID == 0) {
return -1;
}
if (b.libraryID == 0) {
return 1;
}
return collation.compareString(1, a.name, b.name);
});
for (var i in libraryNames) {
var libraryID = libraryNames[i].libraryID;
var libraryStatus = this._data[libraryID];
var label = document.createElement('label');
label.id = 'library-name-row-' + libraryID;
label.setAttribute('value', libraryNames[i].name);
rows.appendChild(label);
var row = this._createRow('download',
libraryStatus.download
? libraryStatus.download.statusString
: false);
row.id = 'downloads-row-' + libraryID;
rows.appendChild(row);
var row = this._createRow('upload',
libraryStatus.upload
? libraryStatus.upload.statusString
: false);
row.id = 'uploads-row-' + libraryID;
rows.appendChild(row);
}
}
]]>
</body>
</method>
<method name="_createRow">
<parameter name="type"/>
<parameter name="value"/>
<body>
<![CDATA[
var row = document.createElement('row');
var label = document.createElement('label');
label.setAttribute('value', Zotero.getString('sync.storage.' + type + 's'));
row.appendChild(label);
label = document.createElement('label');
label.setAttribute('value',
value ? value : Zotero.getString('sync.storage.none'));
row.appendChild(label);
return row;
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]>
</body>
</method>
</implementation>
<content>
<grid xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1">
<columns>
<column/>
<column/>
</columns>
<rows id="rows"/>
</grid>
</content>
</binding>
</bindings>

View file

@ -240,7 +240,7 @@ Zotero.CollectionTreeView.prototype.reload = function()
*/
Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
{
if ((!ids || ids.length == 0) && action != 'refresh') {
if ((!ids || ids.length == 0) && action != 'refresh' && action != 'redraw') {
return;
}
@ -254,6 +254,11 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
return;
}
if (action == 'redraw') {
this._treebox.invalidate();
return;
}
this.selection.selectEventsSuppressed = true;
var savedSelection = this.saveSelection();
@ -420,8 +425,12 @@ Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
{
var obj = this._getItemAtRow(row);
if(column.id == "zotero-collections-name-column")
if (column.id == 'zotero-collections-name-column') {
return obj.getName();
}
else if (column.id == 'zotero-collections-sync-status-column') {
return "";
}
else
return "";
}
@ -430,7 +439,41 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
{
var itemGroup = this._getItemAtRow(row);
var collectionType = itemGroup.type;
if (collectionType == 'group') {
collectionType = 'library';
}
// Show sync icons only in library rows
if (collectionType != 'library' && col.index != 0) {
return '';
}
switch (collectionType) {
case 'library':
if (col.id == 'zotero-collections-sync-status-column') {
if (itemGroup.isLibrary(true)) {
var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
var errors = Zotero.Sync.Runner.getErrors(libraryID);
if (errors) {
var e = Zotero.Sync.Runner.getPrimaryError(errors);
switch (e.status) {
case 'warning':
var image = 'error';
break;
default:
var image = 'exclamation';
break;
}
return 'chrome://zotero/skin/' + image + '.png';
}
}
return '';
}
break;
case 'trash':
if (this._trashNotEmpty[itemGroup.ref.libraryID ? itemGroup.ref.libraryID : 0]) {
collectionType += '-full';
@ -446,7 +489,7 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
}
break;
case 'group':
collectionType = 'library';
break;

View file

@ -112,7 +112,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
else {
sql += "libraryID";
if (libraryID) {
if (libraryID && libraryID !== '0') {
sql += "=? ";
params.push(libraryID);
}

View file

@ -674,7 +674,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
this._disabledCheck();
//Zotero.debug("Setting field '" + field + "' to '" + value + "' (loadIn: " + (loadIn ? 'true' : 'false') + ")");
//Zotero.debug("Setting field '" + field + "' to '" + value + "' (loadIn: " + (loadIn ? 'true' : 'false') + ") for item " + this.id + " ");
if (!field) {
throw ("Field not specified in Item.setField()");
@ -1609,6 +1609,7 @@ Zotero.Item.prototype.save = function() {
'libraryID',
'key'
];
for each(var field in updateFields) {
if (this._changedPrimaryData && this._changedPrimaryData[field]) {
sql += field + '=?, ';
@ -3000,7 +3001,6 @@ Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
if (val === this.attachmentLinkMode) {
return;
}
if (!this._changedAttachmentData) {
this._changedAttachmentData = {};
}

View file

@ -45,6 +45,10 @@ Zotero.Libraries = new function () {
this.getName = function (libraryID) {
if (!libraryID) {
return Zotero.getString('pane.collections.library');
}
var type = this.getType(libraryID);
switch (type) {
case 'group':
@ -59,6 +63,9 @@ Zotero.Libraries = new function () {
this.getType = function (libraryID) {
if (libraryID === 0) {
return 'user';
}
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
var libraryType = Zotero.DB.valueQuery(sql, libraryID);
if (!libraryType) {

View file

@ -402,7 +402,8 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams)
}
else {
if (checkParams && numParams > 0) {
throw ("No parameters provided for query containing placeholders");
throw ("No parameters provided for query containing placeholders "
+ "[QUERY: " + sql + "]");
}
}
return statement;

View file

@ -344,9 +344,29 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
var savedSelection = this.saveSelection();
var previousRow = false;
// Redraw the tree (for tag color changes)
// Redraw the tree (for tag color and progress changes)
if (action == 'redraw') {
// Redraw specific rows
if (type == 'item' && ids.length) {
// Redraw specific cells
if (extraData && extraData.column) {
var col = this._treebox.columns.getNamedColumn(
'zotero-items-column-' + extraData.column
);
for each(var id in ids) {
this._treebox.invalidateCell(this._itemRowMap[id], col);
}
}
else {
for each(var id in ids) {
this._treebox.invalidateRow(this._itemRowMap[id]);
}
}
}
// Redraw the whole tree
else {
this._treebox.invalidate();
}
return;
}
@ -849,6 +869,12 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
if (this._itemGroup.isTrash()) return false;
var treerow = this._getItemAtRow(row);
if ((!this.isContainer(row) || !this.isContainerOpen(row))
&& Zotero.Sync.Storage.getItemDownloadImageNumber(treerow.ref)) {
return '';
}
if (treerow.level === 0) {
if (treerow.ref.isRegularItem()) {
switch (treerow.ref.getBestAttachmentState()) {
@ -2746,7 +2772,8 @@ Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) {
}
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
var itemID = this._getItemAtRow(row).ref.id;
var treeRow = this._getItemAtRow(row);
var itemID = treeRow.ref.id;
// Set tag colors
//
@ -2767,6 +2794,30 @@ Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
getService(Components.interfaces.nsIAtomService);
prop.AppendElement(aServ.getAtom("contextRow"));
}
// Mark hasAttachment column, which needs special image handling
if (col.id == 'zotero-items-column-hasAttachment') {
var aServ = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
prop.AppendElement(aServ.getAtom("hasAttachment"));
// Don't show pie for open parent items, since we show it for the
// child item
if (this.isContainer(row) && this.isContainerOpen(row)) {
return;
}
var num = Zotero.Sync.Storage.getItemDownloadImageNumber(treeRow.ref);
//var num = Math.round(new Date().getTime() % 10000 / 10000 * 64);
if (num !== false) {
if (!aServ) {
var aServ = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
}
prop.AppendElement(aServ.getAtom("pie"));
prop.AppendElement(aServ.getAtom("pie" + num));
}
}
}
Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,94 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2012 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Sync.Storage.EventLog = (function () {
// Non-library-specific
var _general = { warnings: [], errors: [] };
// Library-specific
var _warnings = {};
var _errors = {};
function call(type, data, libraryID) {
if (libraryID) {
switch (type) {
case 'warning':
var target = _general.warnings;
break;
case 'error':
var target = _general.errors;
break;
}
}
else {
switch (type) {
case 'warning':
var target = _warnings;
break;
case 'error':
var target = _errors;
break;
}
}
if (!target[libraryID]) {
target[libraryID] = [];
}
target[libraryID].push(data);
Zotero.debug(data, type == 'error' ? 1 : 2);
Components.utils.reportError(new Error(data));
}
return {
error: function (e, libraryID) call('error', e, libraryID),
warning: function (e, libraryID) call('warning', e, libraryID),
clear: function (libraryID) {
var queues = Zotero.Sync.Storage.QueueManager.getAll();
for each(var queue in queues) {
if (queue.isRunning()) {
Zotero.debug(queue.name[0].toUpperCase() + queue.name.substr(1)
+ " queue not empty -- not clearing storage sync event observers");
return;
}
}
if (typeof libraryID == 'undefined') {
Zotero.debug("Clearing file sync event log");
_general = { warnings: [], errors: [] };
_warnings = {};
_errors = {};
}
else {
Zotero.debug("Clearing file sync event log for library " + libraryID);
_warnings[libraryID] = [];
_errors[libraryID] = [];
}
}
};
}());

View file

@ -1,143 +0,0 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Sync.Storage.EventManager = (function () {
var _observers = [];
function call(handler, data, clear) {
Zotero.debug("Calling storage sync " + handler + " handlers");
var observers = _observers;
var cont = true;
var handled = false;
if (clear) {
Zotero.Sync.Storage.EventManager.clear();
}
// Process most recently assigned observers first
for (var i = observers.length - 1; i >= 0; i--) {
let observer = observers[i].observer;
let j = i;
if (observer[handler]) {
handled = true;
if (observers[i].async) {
setTimeout(function () {
Zotero.debug("Calling " + handler + " handler " + j);
var cont = observer[handler](data);
if (cont === false) {
throw new Error("Cannot cancel events from async observer");
}
}, 0);
}
else {
Zotero.debug("Calling " + handler + " handler " + j);
var cont = observer[handler](data);
// If handler returns explicit false, cancel further events
if (cont === false) {
break;
}
}
}
}
if (!handled && data) {
var msg = "Unhandled storage sync event: " + data;
Zotero.debug(msg, 1);
if (handler == 'onError') {
throw new Error(msg);
}
else {
Components.utils.reportError(msg);
}
}
// Throw errors to stop execution
if (handler == 'onError') {
if (!data) {
throw new Error("Data not provided for error");
}
if (cont !== false) {
throw (data);
}
}
}
return {
registerObserver: function (observer, async, id) {
var pos = -1;
if (id) {
for (var i = 0, len = _observers.length; i < len; i++) {
var o = _observers[i];
if (o.id === id && o.async == async) {
pos = o;
break;
}
}
}
if (pos == -1) {
Zotero.debug("Registering storage sync event observer '" + id + "'");
_observers.push({
observer: observer,
async: !!async,
id: id
});
}
else {
Zotero.debug("Replacing storage sync event observer '" + id + "'");
_observers[pos] = {
observer: observer,
async: !!async,
id: id
};
}
},
success: function () call('onSuccess', false, true),
skip: function (clear) call('onSkip', false, true),
stop: function () call('onStop', false, true),
error: function (e) call('onError', e, true),
warning: function (e) call('onWarning', e),
changesMade: function () call('onChangesMade'),
clear: function () {
var queues = Zotero.Sync.Storage.QueueManager.getAll();
for each(var queue in queues) {
if (queue.isRunning()) {
Zotero.debug(queue.name[0].toUpperCase() + queue.name.substr(1)
+ " queue not empty -- not clearing storage sync event observers");
return;
}
}
Zotero.debug("Clearing storage sync event observers");
_observers = [];
}
};
}());

View file

@ -26,159 +26,62 @@
Zotero.Sync.Storage.Mode = function () {};
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('enabled', function () {
try {
return this._enabled;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('verified', function () {
try {
return this._verified;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('active', function () {
try {
return this._enabled && this._verified && this._initFromPrefs();
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('username', function () {
try {
return this._username;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('password', function () {
try {
return this._password;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
Zotero.Sync.Storage.Mode.prototype.__defineSetter__('password', function (val) {
try {
this._password = val;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
Zotero.Sync.Storage.Mode.prototype.init = function () {
try {
return this._init();
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
}
Zotero.Sync.Storage.Mode.prototype.initFromPrefs = function () {
try {
return this._initFromPrefs();
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
}
Zotero.Sync.Storage.Mode.prototype.sync = function (observer) {
Zotero.Sync.Storage.sync(this.name, observer);
return Zotero.Sync.Storage.sync(this.name, observer);
}
Zotero.Sync.Storage.Mode.prototype.downloadFile = function (request) {
try {
this._downloadFile(request);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._downloadFile(request);
}
Zotero.Sync.Storage.Mode.prototype.uploadFile = function (request) {
try {
this._uploadFile(request);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._uploadFile(request);
}
Zotero.Sync.Storage.Mode.prototype.getLastSyncTime = function (callback) {
try {
this._getLastSyncTime(callback);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
Zotero.Sync.Storage.Mode.prototype.getLastSyncTime = function (libraryID) {
return this._getLastSyncTime(libraryID);
}
Zotero.Sync.Storage.Mode.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
try {
this._setLastSyncTime(callback, useLastSyncTime);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._setLastSyncTime(callback, useLastSyncTime);
}
Zotero.Sync.Storage.Mode.prototype.checkServer = function (callback) {
try {
return this._checkServer(callback);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
}
Zotero.Sync.Storage.Mode.prototype.checkServerCallback = function (uri, status, window, skipSuccessMessage) {
try {
return this._checkServerCallback(uri, status, window, skipSuccessMessage);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
}
Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) {
try {
return this._cacheCredentials(callback);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
}
Zotero.Sync.Storage.Mode.prototype.purgeDeletedStorageFiles = function (callback) {
try {
this._purgeDeletedStorageFiles(callback);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._purgeDeletedStorageFiles(callback);
}
Zotero.Sync.Storage.Mode.prototype.purgeOrphanedStorageFiles = function (callback) {
try {
this._purgeOrphanedStorageFiles(callback);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._purgeOrphanedStorageFiles(callback);
}

View file

@ -26,13 +26,14 @@
/**
* Queue for storage sync transfer requests
*
* @param {String} name Queue name (e.g., 'download' or 'upload')
* @param {String} type Queue type (e.g., 'download' or 'upload')
*/
Zotero.Sync.Storage.Queue = function (name) {
Zotero.debug("Initializing " + name + " queue");
Zotero.Sync.Storage.Queue = function (type, libraryID) {
Zotero.debug("Initializing " + type + " queue for library " + libraryID);
// Public properties
this.name = name;
this.type = type;
this.libraryID = libraryID;
this.maxConcurrentRequests = 1;
this.activeRequests = 0;
this.totalRequests = 0;
@ -42,16 +43,25 @@ Zotero.Sync.Storage.Queue = function (name) {
this._highPriority = [];
this._running = false;
this._stopping = false;
this._finished = false;
this._error = false;
this._finishedReqs = 0;
this._lastTotalRequests = 0;
this._localChanges = false;
this._remoteChanges = false;
this._conflicts = [];
}
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('Name', function () {
return this.name[0].toUpperCase() + this.name.substr(1);
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('name', function () {
return this.type + "/" + this.libraryID;
});
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('Type', function () {
return this.type[0].toUpperCase() + this.type.substr(1);
});
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('running', function () this._running);
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('stopping', function () this._stopping);
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('finished', function () this._finished);
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('unfinishedRequests', function () {
return this.totalRequests - this.finishedRequests;
@ -73,22 +83,51 @@ Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', functio
// Last request
if (val == this.totalRequests) {
Zotero.debug(this.Name + " queue is done");
Zotero.debug(this.Type + " queue is done for library " + this.libraryID);
// DEBUG info
Zotero.debug("Active requests: " + this.activeRequests);
if (this.activeRequests) {
throw new Error(this.Name + " queue can't be done if there are active requests");
throw new Error(this.Type + " queue for library " + this.libraryID
+ " can't be done if there are active requests");
}
this._running = false;
this._stopping = false;
this._finished = true;
this._requests = {};
this._highPriority = [];
this._finishedReqs = 0;
this._lastTotalRequests = this.totalRequests;
this.totalRequests = 0;
var localChanges = this._localChanges;
var remoteChanges = this._remoteChanges;
var conflicts = this._conflicts.concat();
this._localChanges = false;
this._remoteChanges = false;
this._conflicts = [];
if (!this._error) {
Zotero.debug("Resolving promise for queue " + this.name);
Zotero.debug(this._localChanges);
Zotero.debug(this._remoteChanges);
Zotero.debug(this._conflicts);
this._deferred.resolve({
libraryID: this.libraryID,
type: this.type,
localChanges: localChanges,
remoteChanges: remoteChanges,
conflicts: conflicts
});
}
else {
Zotero.debug("Rejecting promise for queue " + this.name);
var e = this._error;
this._error = false;
e.libraryID = this.libraryID;
e.type = this.type;
this._deferred.reject(e);
}
return;
}
@ -99,10 +138,6 @@ Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', functio
this.advance();
});
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('lastTotalRequests', function () {
return this._lastTotalRequests;
});
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('queuedRequests', function () {
return this.unfinishedRequests - this.activeRequests;
});
@ -119,6 +154,9 @@ Zotero.Sync.Storage.Queue.prototype.__defineGetter__('percentage', function () {
if (this.totalRequests == 0) {
return 0;
}
if (this._finished) {
return 100;
}
var completedRequests = 0;
for each(var request in this._requests) {
@ -144,9 +182,13 @@ Zotero.Sync.Storage.Queue.prototype.isStopping = function () {
* @param {Boolean} highPriority Add or move request to high priority queue
*/
Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority) {
if (this._finished) {
this.reset();
}
request.queue = this;
var name = request.name;
Zotero.debug("Queuing " + this.name + " request '" + name + "'");
Zotero.debug("Queuing " + this.type + " request '" + name + "' for library " + this.libraryID);
if (this._requests[name]) {
if (highPriority) {
@ -166,56 +208,133 @@ Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority
if (highPriority) {
this._highPriority.push(name);
}
this.advance();
}
Zotero.Sync.Storage.Queue.prototype.start = function () {
if (!this._deferred || this._deferred.promise.isResolved()) {
Zotero.debug("Creating deferred for queue " + this.name);
this._deferred = Q.defer();
}
// The queue manager needs to know what queues were running in the
// current session
Zotero.Sync.Storage.QueueManager.addCurrentQueue(this);
this.advance();
return this._deferred.promise;
}
/**
* Start another request in this queue if there's an available slot
*/
Zotero.Sync.Storage.Queue.prototype.advance = function () {
this._running = true;
this._finished = false;
if (this._stopping) {
Zotero.debug(this.Name + " queue is being stopped in "
+ "Zotero.Sync.Storage.Queue.advance()", 2);
Zotero.debug(this.Type + " queue for library " + this.libraryID
+ "is being stopped in Zotero.Sync.Storage.Queue.advance()", 2);
return;
}
if (!this.queuedRequests) {
Zotero.debug("No remaining requests in " + this.name + " queue ("
Zotero.debug("No remaining requests in " + this.type
+ " queue for library " + this.libraryID + " ("
+ this.activeRequests + " active, "
+ this.finishedRequests + " finished)");
return;
}
if (this.activeRequests >= this.maxConcurrentRequests) {
Zotero.debug(this.Name + " queue is busy ("
+ this.activeRequests + "/" + this.maxConcurrentRequests + ")");
Zotero.debug(this.Type + " queue for library " + this.libraryID
+ " is busy (" + this.activeRequests + "/"
+ this.maxConcurrentRequests + ")");
return;
}
// Start the first unprocessed request
// Try the high-priority queue first
var name, request;
var self = this;
var request, name;
while (name = this._highPriority.shift()) {
request = this._requests[name];
if (!request.isRunning() && !request.isFinished()) {
request.start();
this.advance();
return;
if (request.isRunning() || request.isFinished()) {
continue;
}
let requestName = name;
Q.fcall(function () {
var promise = request.start();
self.advance();
return promise;
})
.then(function (result) {
if (result.localChanges) {
self._localChanges = true;
}
if (result.remoteChanges) {
self._remoteChanges = true;
}
if (result.conflict) {
self.addConflict(
requestName,
result.conflict.local,
result.conflict.remote
);
}
})
.fail(function (e) {
self.error(e);
});
return;
}
// And then others
for each(request in this._requests) {
if (!request.isRunning() && !request.isFinished()) {
request.start();
this.advance();
return;
for each(var request in this._requests) {
if (request.isRunning() || request.isFinished()) {
continue;
}
let requestName = request.name;
// This isn't in an fcall() because the request needs to get marked
// as running immediately so that it doesn't get run again by a
// subsequent advance() call.
try {
var promise = request.start();
self.advance();
}
catch (e) {
self.error(e);
}
Q.when(promise)
.then(function (result) {
if (result.localChanges) {
self._localChanges = true;
}
if (result.remoteChanges) {
self._remoteChanges = true;
}
if (result.conflict) {
self.addConflict(
requestName,
result.conflict.local,
result.conflict.remote
);
}
})
.fail(function (e) {
self.error(e);
});
return;
}
}
@ -225,8 +344,26 @@ Zotero.Sync.Storage.Queue.prototype.updateProgress = function () {
}
Zotero.Sync.Storage.Queue.prototype.addConflict = function (requestName, localData, remoteData) {
Zotero.debug('===========');
Zotero.debug(localData);
Zotero.debug(remoteData);
this._conflicts.push({
name: requestName,
localData: localData,
remoteData: remoteData
});
}
Zotero.Sync.Storage.Queue.prototype.error = function (e) {
Zotero.Sync.Storage.EventManager.error(e);
if (!this._error) {
this._error = e;
}
Zotero.debug(e, 1);
Components.utils.reportError(e.message ? e.message : e);
this.stop();
}
@ -235,14 +372,18 @@ Zotero.Sync.Storage.Queue.prototype.error = function (e) {
*/
Zotero.Sync.Storage.Queue.prototype.stop = function () {
if (!this._running) {
Zotero.debug(this.Name + " queue is not running");
Zotero.debug(this.Type + " queue for library " + this.libraryID
+ " is not running");
return;
}
if (this._stopping) {
Zotero.debug("Already stopping " + this.name + " queue");
Zotero.debug("Already stopping " + this.type + " queue for library "
+ this.libraryID);
return;
}
Zotero.debug("Stopping " + this.type + " queue for library " + this.libraryID);
// If no requests, finish manually
/*if (this.activeRequests == 0) {
this._finishedRequests = this._finishedRequests;
@ -255,4 +396,13 @@ Zotero.Sync.Storage.Queue.prototype.stop = function () {
request.stop();
}
}
Zotero.debug("Queue is stopped");
}
Zotero.Sync.Storage.Queue.prototype.reset = function () {
this._finished = false;
this._finishedReqs = 0;
this.totalRequests = 0;
}

View file

@ -26,8 +26,63 @@
Zotero.Sync.Storage.QueueManager = new function () {
var _queues = {};
var _conflicts = [];
var _cancelled = false;
var _currentQueues = [];
this.start = function (libraryID) {
if (libraryID === 0 || libraryID) {
var queues = this.getAll(libraryID);
}
else {
var queues = this.getAll();
}
Zotero.debug("Starting file sync queues");
var promises = [];
for each(var queue in queues) {
if (!queue.unfinishedRequests) {
continue;
}
Zotero.debug("Starting queue " + queue.name);
promises.push(queue.start());
}
if (!promises.length) {
Zotero.debug("No files to sync");
}
return Q.allResolved(promises)
.then(function (promises) {
Zotero.debug("All storage queues are finished");
promises.forEach(function (promise) {
if (promise.isFulfilled()) {
var result = promise.valueOf();
if (result.conflicts.length) {
Zotero.debug("Reconciling conflicts for library " + result.libraryID);
Zotero.debug(result.conflicts);
var data = _reconcileConflicts(result.conflicts);
if (data) {
_processMergeData(data);
}
}
}
});
return promises;
});
};
this.stop = function (libraryID) {
if (libraryID === 0 || libraryID) {
var queues = this.getAll(libraryID);
}
else {
var queues = this.getAll();
}
for (var queue in queues) {
queue.stop();
}
};
/**
@ -35,13 +90,19 @@ Zotero.Sync.Storage.QueueManager = new function () {
*
* @param {String} queueName
*/
this.get = function (queueName, noInit) {
this.get = function (queueName, libraryID, noInit) {
if (typeof libraryID == 'undefined') {
throw new Error("libraryID not specified");
}
var hash = queueName + "/" + libraryID;
// Initialize the queue if it doesn't exist yet
if (!_queues[queueName]) {
if (!_queues[hash]) {
if (noInit) {
return false;
}
var queue = new Zotero.Sync.Storage.Queue(queueName);
var queue = new Zotero.Sync.Storage.Queue(queueName, libraryID);
switch (queueName) {
case 'download':
queue.maxConcurrentRequests =
@ -56,22 +117,36 @@ Zotero.Sync.Storage.QueueManager = new function () {
default:
throw ("Invalid queue '" + queueName + "' in Zotero.Sync.Storage.QueueManager.get()");
}
_queues[queueName] = queue;
_queues[hash] = queue;
}
return _queues[queueName];
}
return _queues[hash];
};
this.getAll = function () {
this.getAll = function (libraryID) {
var queues = [];
for each(var queue in _queues) {
if (typeof libraryID == 'undefined' || queue.libraryID === libraryID) {
queues.push(queue);
}
}
return queues;
};
this.addCurrentQueue = function (queue) {
if (!this.hasCurrentQueue(queue)) {
_currentQueues.push(queue.name);
}
}
this.hasCurrentQueue = function (queue) {
return _currentQueues.indexOf(queue.name) != -1;
}
/**
* Stop all queues
*
@ -81,7 +156,6 @@ Zotero.Sync.Storage.QueueManager = new function () {
*/
this.cancel = function (skipStorageFinish) {
Zotero.debug("Stopping all storage queues");
_cancelled = true;
for each(var queue in _queues) {
if (queue.isRunning() && !queue.isStopping()) {
queue.stop();
@ -92,26 +166,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
this.finish = function () {
Zotero.debug("All storage queues are finished");
if (!_cancelled && _conflicts.length) {
var data = _reconcileConflicts();
if (data) {
_processMergeData(data);
}
}
try {
if (_cancelled) {
Zotero.Sync.Storage.EventManager.stop();
}
else {
Zotero.Sync.Storage.EventManager.success();
}
}
finally {
_cancelled = false;
_conflicts = [];
}
_currentQueues = [];
}
@ -132,86 +187,32 @@ Zotero.Sync.Storage.QueueManager = new function () {
activeRequests += queue.activeRequests;
}
if (activeRequests == 0) {
this.updateProgressMeters(0);
_updateProgressMeters(0);
if (allFinished) {
this.finish();
}
return;
}
// Percentage
var percentageSum = 0;
var numQueues = 0;
var status = {};
for each(var queue in _queues) {
percentageSum += queue.percentage;
numQueues++;
}
var percentage = Math.round(percentageSum / numQueues);
//Zotero.debug("Total percentage is " + percentage);
// Remaining KB
var downloadStatus = _queues.download ?
_getQueueStatus(_queues.download) : 0;
var uploadStatus = _queues.upload ?
_getQueueStatus(_queues.upload) : 0;
this.updateProgressMeters(
activeRequests, percentage, downloadStatus, uploadStatus
);
}
/**
* Cycle through windows, updating progress meters with new values
*/
this.updateProgressMeters = function (activeRequests, percentage, downloadStatus, uploadStatus) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var enumerator = wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if (!win.ZoteroPane) continue;
var doc = win.ZoteroPane.document;
//
// TODO: Move to overlay.js?
//
var box = doc.getElementById("zotero-tb-sync-progress-box");
var meter = doc.getElementById("zotero-tb-sync-progress");
if (activeRequests == 0) {
box.hidden = true;
if (!this.hasCurrentQueue(queue)) {
continue;
}
meter.setAttribute("value", percentage);
box.hidden = false;
var tooltip = doc.
getElementById("zotero-tb-sync-progress-tooltip-progress");
tooltip.setAttribute("value", percentage + "%");
var tooltip = doc.
getElementById("zotero-tb-sync-progress-tooltip-downloads");
tooltip.setAttribute("value", downloadStatus);
var tooltip = doc.
getElementById("zotero-tb-sync-progress-tooltip-uploads");
tooltip.setAttribute("value", uploadStatus);
if (!status[queue.libraryID]) {
status[queue.libraryID] = {};
}
if (!status[queue.libraryID][queue.type]) {
status[queue.libraryID][queue.type] = {};
}
status[queue.libraryID][queue.type].statusString = _getQueueStatus(queue);
status[queue.libraryID][queue.type].percentage = queue.percentage;
status[queue.libraryID][queue.type].totalRequests = queue.totalRequests;
status[queue.libraryID][queue.type].finished = queue.finished;
}
this.addConflict = function (requestName, localData, remoteData) {
Zotero.debug('===========');
Zotero.debug(localData);
Zotero.debug(remoteData);
_conflicts.push({
name: requestName,
localData: localData,
remoteData: remoteData
});
_updateProgressMeters(activeRequests, status);
}
@ -226,26 +227,76 @@ Zotero.Sync.Storage.QueueManager = new function () {
var unfinishedRequests = queue.unfinishedRequests;
if (!unfinishedRequests) {
return Zotero.getString('sync.storage.none')
return Zotero.getString('sync.storage.none');
}
var kbRemaining = Zotero.getString(
'sync.storage.kbRemaining',
Zotero.Utilities.numberFormat(remaining / 1024, 0)
if (remaining > 1000) {
var bytesRemaining = Zotero.getString(
'sync.storage.mbRemaining',
Zotero.Utilities.numberFormat(remaining / 1000 / 1000, 1)
);
}
else {
var bytesRemaining = Zotero.getString(
'sync.storage.kbRemaining',
Zotero.Utilities.numberFormat(remaining / 1000, 0)
);
}
var totalRequests = queue.totalRequests;
var filesRemaining = Zotero.getString(
'sync.storage.filesRemaining',
[totalRequests - unfinishedRequests, totalRequests]
);
var status = Zotero.localeJoin([kbRemaining, '(' + filesRemaining + ')']);
return status;
return bytesRemaining + ' (' + filesRemaining + ')';
}
/**
* Cycle through windows, updating progress meters with new values
*/
function _updateProgressMeters(activeRequests, status) {
// Get overall percentage across queues
var sum = 0, num = 0, percentage, total;
for each(var libraryStatus in status) {
for each(var queueStatus in libraryStatus) {
percentage = queueStatus.percentage;
total = queueStatus.totalRequests;
sum += total * percentage;
num += total;
}
}
var percentage = Math.round(sum / num);
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var enumerator = wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if (!win.ZoteroPane) continue;
var doc = win.ZoteroPane.document;
var box = doc.getElementById("zotero-tb-sync-progress-box");
var meter = doc.getElementById("zotero-tb-sync-progress");
if (activeRequests == 0) {
box.hidden = true;
continue;
}
meter.setAttribute("value", percentage);
box.hidden = false;
var percentageLabel = doc.getElementById('zotero-tb-sync-progress-tooltip-progress');
percentageLabel.lastChild.setAttribute('value', percentage + "%");
var statusBox = doc.getElementById('zotero-tb-sync-progress-status');
statusBox.data = status;
}
}
function _reconcileConflicts() {
function _reconcileConflicts(conflicts) {
var objectPairs = [];
for each(var conflict in _conflicts) {
for each(var conflict in conflicts) {
var item = Zotero.Sync.Storage.getItemFromRequestName(conflict.name);
var item1 = item.clone(false, false, true);
item1.setField('dateModified',
@ -279,8 +330,8 @@ Zotero.Sync.Storage.QueueManager = new function () {
// Since we're only putting cloned items into the merge window,
// we have to manually set the ids
for (var i=0; i<_conflicts.length; i++) {
io.dataOut[i].id = Zotero.Sync.Storage.getItemFromRequestName(_conflicts[i].name).id;
for (var i=0; i<conflicts.length; i++) {
io.dataOut[i].id = Zotero.Sync.Storage.getItemFromRequestName(conflicts[i].name).id;
}
return io.dataOut;
@ -292,8 +343,6 @@ Zotero.Sync.Storage.QueueManager = new function () {
return false;
}
Zotero.Sync.Storage.resyncOnFinish = true;
for each(var mergeItem in data) {
var itemID = mergeItem.id;
var dateModified = mergeItem.ref.getField('dateModified');

View file

@ -29,12 +29,11 @@
*
* @param {String} name Identifier for request (e.g., "[libraryID]/[key]")
* @param {Function} onStart Callback to run when request starts
* @param {Function} onStop Callback to run when request stops
*/
Zotero.Sync.Storage.Request = function (name, callbacks) {
Zotero.debug("Initializing request '" + name + "'");
//Zotero.debug("Initializing request '" + name + "'");
this.callbacks = ['onStart', 'onProgress', 'onStop'];
this.callbacks = ['onStart', 'onProgress'];
this.name = name;
this.channel = null;
@ -42,10 +41,13 @@ Zotero.Sync.Storage.Request = function (name, callbacks) {
this.progress = 0;
this.progressMax = 0;
this._deferred = Q.defer();
this._running = false;
this._percentage = 0;
this._remaining = null;
this._maxSize = null;
this._finished = false;
this._changesMade = false;
for (var func in callbacks) {
if (this.callbacks.indexOf(func) !== -1) {
@ -59,6 +61,11 @@ Zotero.Sync.Storage.Request = function (name, callbacks) {
}
Zotero.Sync.Storage.Request.prototype.setMaxSize = function (size) {
this._maxSize = size;
};
/**
* Add callbacks from another request to this request
*/
@ -90,7 +97,16 @@ Zotero.Sync.Storage.Request.prototype.importCallbacks = function (request) {
}
Zotero.Sync.Storage.Request.prototype.__defineGetter__('promise', function () {
return this._deferred.promise;
});
Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function () {
if (this._finished) {
return 100;
}
if (this.progressMax == 0) {
return 0;
}
@ -98,14 +114,14 @@ Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function ()
var percentage = Math.round((this.progress / this.progressMax) * 100);
if (percentage < this._percentage) {
Zotero.debug(percentage + " is less than last percentage of "
+ this._percentage + " for request '" + this.name + "'", 2);
+ this._percentage + " for request " + this.name, 2);
Zotero.debug(this.progress);
Zotero.debug(this.progressMax);
percentage = this._percentage;
}
else if (percentage > 100) {
Zotero.debug(percentage + " is greater than 100 for "
+ this.name + " request", 2);
+ "request " + this.name, 2);
Zotero.debug(this.progress);
Zotero.debug(this.progressMax);
percentage = 100;
@ -119,7 +135,15 @@ Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function ()
Zotero.Sync.Storage.Request.prototype.__defineGetter__('remaining', function () {
if (this._finished) {
return 0;
}
if (!this.progressMax) {
if (this.queue.type == 'upload' && this._maxSize) {
return Math.round(Zotero.Sync.Storage.compressionTracker.ratio * this._maxSize);
}
//Zotero.debug("Remaining not yet available for request '" + this.name + "'");
return 0;
}
@ -151,22 +175,70 @@ Zotero.Sync.Storage.Request.prototype.setChannel = function (channel) {
Zotero.Sync.Storage.Request.prototype.start = function () {
if (!this.queue) {
throw ("Request '" + this.name + "' must be added to a queue before starting");
throw ("Request " + this.name + " must be added to a queue before starting");
}
Zotero.debug("Starting " + this.queue.name + " request " + this.name);
if (this._running) {
throw ("Request '" + this.name + "' already running in "
+ "Zotero.Sync.Storage.Request.start()");
throw new Error("Request " + this.name + " already running");
}
Zotero.debug("Starting " + this.queue.name + " request '" + this.name + "'");
this._running = true;
this.queue.activeRequests++;
if (this._onStart) {
for each(var f in this._onStart) {
f(this);
if (this.queue.type == 'download') {
Zotero.Sync.Storage.setItemDownloadPercentage(this.name, 0);
}
var self = this;
// this._onStart is an array of promises returning changesMade.
//
// The main sync logic is triggered here.
Q.all([f(this) for each(f in this._onStart)])
.then(function (results) {
return {
localChanges: results.some(function (val) val && val.localChanges == true),
remoteChanges: results.some(function (val) val && val.remoteChanges == true),
conflict: results.reduce(function (prev, cur) {
return prev.conflict ? prev : cur;
}).conflict
};
})
.then(function (results) {
Zotero.debug('!!!!');
Zotero.debug(results);
if (results.localChanges) {
Zotero.debug("Changes were made by " + self.queue.name
+ " request " + self.name);
}
else {
Zotero.debug("No changes were made by " + self.queue.name
+ " request " + self.name);
}
// This promise updates localChanges/remoteChanges on the queue
self._deferred.resolve(results);
})
.fail(function (e) {
Zotero.debug(self.queue.Type + " request " + self.name + " failed");
Zotero.debug(self._deferred);
Zotero.debug(self._deferred.promise.isFulfilled());
self._deferred.reject(e);
Zotero.debug(self._deferred.promise.isFulfilled());
Zotero.debug(self._deferred.promise.isRejected());
})
// Finish the request (and in turn the queue, if this is the last request)
.fin(function () {
if (!self._finished) {
self._finish();
}
});
return this._deferred.promise;
}
@ -191,6 +263,8 @@ Zotero.Sync.Storage.Request.prototype.isFinished = function () {
* (usually total bytes)
*/
Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress, progressMax) {
Zotero.debug(progress + "/" + progressMax + " for request " + this.name);
if (!this._running) {
Zotero.debug("Trying to update finished request " + this.name + " in "
+ "Zotero.Sync.Storage.Request.onProgress() "
@ -219,6 +293,10 @@ Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress,
this.progressMax = progressMax;
this.queue.updateProgress();
if (this.queue.type == 'download') {
Zotero.Sync.Storage.setItemDownloadPercentage(this.name, this.percentage);
}
if (this.onProgress) {
for each(var f in this._onProgress) {
f(progress, progressMax);
@ -227,62 +305,48 @@ Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress,
}
Zotero.Sync.Storage.Request.prototype.error = function (e) {
this.queue.error(e);
}
/**
* Stop the request's underlying network request, if there is one
*/
Zotero.Sync.Storage.Request.prototype.stop = function () {
var finishNow = false;
try {
// If upload already finished, finish() will never be called otherwise
if (this.channel) {
this.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
// Throws error if request not finished
this.channel.requestSucceeded;
Zotero.debug("Channel is no longer running for request " + this.name);
Zotero.debug(this.channel.requestSucceeded);
finishNow = true;
}
}
catch (e) {}
if (!this._running || !this.channel || finishNow) {
this.finish();
return;
}
try {
Zotero.debug("Stopping request '" + this.name + "'");
this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
}
catch (e) {
Zotero.debug(e);
}
}
else {
this._finish();
}
}
/**
* Mark request as finished and notify queue that it's done
*/
Zotero.Sync.Storage.Request.prototype.finish = function () {
if (this._finished) {
throw ("Request '" + this.name + "' is already finished");
}
Zotero.Sync.Storage.Request.prototype._finish = function () {
Zotero.debug("Finishing " + this.queue.name + " request '" + this.name + "'");
this._finished = true;
var active = this._running;
this._running = false;
Zotero.Sync.Storage.setItemDownloadPercentage(this.name, false);
if (active) {
this.queue.activeRequests--;
}
// mechanism for failures?
// TEMP: mechanism for failures?
try {
this.queue.finishedRequests++;
this.queue.updateProgress();
if (this._onStop) {
for each(var f in this._onStop) {
f();
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
this._deferred.reject(e);
throw e;
}
}

View file

@ -30,6 +30,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
var _defaultError = "A WebDAV file sync error occurred. Please try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
var _defaultErrorRestart = "A WebDAV file sync error occurred. Please restart " + Zotero.appName + " and try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
var _initialized = false;
var _parentURI;
var _rootURI;
var _cachedCredentials = false;
@ -46,32 +47,25 @@ Zotero.Sync.Storage.WebDAV = (function () {
* @param {Zotero.Item} item
* @param {Function} callback Callback f(item, mdate)
*/
function getStorageModificationTime(item, callback) {
function getStorageModificationTime(item) {
var uri = getItemPropertyURI(item);
Zotero.HTTP.doGet(uri, function (req) {
return Zotero.HTTP.promise(
"GET", uri, { debug: true, successCodes: [200, 300, 404] }
)
.then(function (req) {
checkResponse(req);
var funcName = "Zotero.Sync.Storage.WebDAV.getStorageModificationTime()";
// mod_speling can return 300s for 404s with base name matches
if (req.status == 404 || req.status == 300) {
callback(item, false);
return;
return false;
}
else if (req.status != 200) {
Zotero.debug(req.responseText);
Zotero.Sync.Storage.EventManager.error(
"Unexpected status code " + req.status + " in " + funcName
);
}
Zotero.debug(req.responseText);
// No modification time set
if (!req.responseText) {
callback(item, false);
return;
return false;
}
try {
@ -119,11 +113,17 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
deleteStorageFiles([item.key + ".prop"]);
Zotero.Sync.Storage.EventManager.error(_defaultError);
throw _defaultError;
}
var mdate = new Date(parseInt(mtime));
callback(item, mdate);
return new Date(parseInt(mtime));
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
Zotero.debug(req.responseText);
throw new Error("Unexpected status code " + e.status + " in " + funcName);
}
throw e;
});
}
@ -132,9 +132,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
* Set mod time of file on storage server
*
* @param {Zotero.Item} item
* @param {Function} callback Callback f(item, props)
*/
function setStorageModificationTime(item, callback) {
function setStorageModificationTime(item) {
var uri = getItemPropertyURI(item);
var mtime = item.attachmentModificationTime;
@ -145,19 +144,16 @@ Zotero.Sync.Storage.WebDAV = (function () {
<hash>{hash}</hash>
</properties>;
Zotero.HTTP.WebDAV.doPut(uri, prop.toXMLString(), function (req) {
switch (req.status) {
case 200:
case 201:
case 204:
break;
default:
Zotero.debug(req.responseText);
throw new Error("Unexpected status code " + req.status);
}
callback(item, { mtime: mtime, hash: hash });
return Zotero.HTTP.promise("PUT", uri, prop.toXMLString(),
{ debug: true, successCodes: [200, 201, 204] })
.then(function (req) {
return { mtime: mtime, hash: hash };
})
.fail(function (e) {
throw new Error("Unexpected status code " + e.xmlhttp.status);
});
};
/**
@ -175,12 +171,12 @@ Zotero.Sync.Storage.WebDAV = (function () {
var request = data.request;
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
getStorageModificationTime(item, function (item, mdate) {
try {
return getStorageModificationTime(item)
.then(function (mdate) {
if (!request.isRunning()) {
Zotero.debug("Upload request '" + request.name
+ "' is no longer running after getting mod time");
return;
return false;
}
// Check for conflict
@ -222,9 +218,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime, true);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
Zotero.DB.commitTransaction();
onChangesMade();
request.finish();
return;
return true;
}
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
@ -237,8 +231,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug("Conflict -- last synced file mod time "
+ "does not match time on storage server"
+ " (" + smtime + " != " + mtime + ")");
request.finish();
return;
return false;
}
}
else {
@ -271,13 +264,22 @@ Zotero.Sync.Storage.WebDAV = (function () {
channel.setRequestHeader('Keep-Alive', '', false);
channel.setRequestHeader('Connection', '', false);
var deferred = Q.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
onProgress: function (a, b, c) {
request.onProgress(a, b, c);
},
onStop: function (httpRequest, status, response, data) { onUploadComplete(httpRequest, status, response,data); },
onCancel: function (httpRequest, status, data) { onUploadCancel(httpRequest, status, data); },
onStop: function (httpRequest, status, response, data) {
deferred.resolve(
onUploadComplete(httpRequest, status, response, data)
);
},
onCancel: function (httpRequest, status, data) {
onUploadCancel(httpRequest, status, data);
deferred.resolve(false);
},
request: request,
item: item,
streams: [fis, bis]
@ -292,10 +294,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug("HTTP PUT of " + file.leafName + " to " + dispURI.spec);
channel.asyncOpen(listener, null);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return deferred.promise;
});
}
@ -317,30 +317,25 @@ Zotero.Sync.Storage.WebDAV = (function () {
case 403:
case 500:
Zotero.debug(response);
Zotero.Sync.Storage.EventManager.error(
Zotero.getString('sync.storage.error.fileUploadFailed')
+ " " + Zotero.getString('sync.storage.error.checkFileSyncSettings')
);
throw (Zotero.getString('sync.storage.error.fileUploadFailed') +
" " + Zotero.getString('sync.storage.error.checkFileSyncSettings'));
case 507:
Zotero.debug(response);
Zotero.Sync.Storage.EventManager.error(
Zotero.getString('sync.storage.error.webdav.insufficientSpace')
);
throw Zotero.getString('sync.storage.error.webdav.insufficientSpace');
default:
Zotero.debug(response);
Zotero.Sync.Storage.EventManager.error(
"Unexpected file upload status " + status
+ " in Zotero.Sync.Storage.WebDAV.onUploadComplete()"
);
throw ("Unexpected file upload status " + status +
" in Zotero.Sync.Storage.WebDAV.onUploadComplete()");
}
setStorageModificationTime(item, function (item, props) {
return setStorageModificationTime(item)
.then(function (props) {
if (!request.isRunning()) {
Zotero.debug("Upload request '" + request.name
+ "' is no longer running after getting mod time");
return;
return false;
}
Zotero.DB.beginTransaction();
@ -360,8 +355,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
Components.utils.reportError(e);
}
onChangesMade();
request.finish();
return {
localChanges: true,
remoteChanges: true
};
});
}
@ -380,9 +377,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
catch (e) {
Components.utils.reportError(e);
}
request.finish();
}
}
@ -649,8 +643,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
}
}
);
Zotero.Sync.Storage.EventManager.error(e);
throw e;
}
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
var msg = Zotero.getString('sync.storage.error.webdav.sslConnectionError', host) +
@ -667,7 +660,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
}
}
);
Zotero.Sync.Storage.EventManager.error(e);
throw e;
}
}
}
@ -686,10 +679,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
});
obj.includeGroupItems = false;
Object.defineProperty(obj, "_enabled", {
get: function () this.includeUserFiles
});
Object.defineProperty(obj, "_verified", {
get: function () Zotero.Prefs.get("sync.storage.verified")
});
@ -755,7 +744,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Object.defineProperty(obj, "rootURI", {
get: function () {
if (!_rootURI) {
throw new Error("Root URI not initialized");
this._init();
}
return _rootURI.clone();
}
@ -764,62 +753,16 @@ Zotero.Sync.Storage.WebDAV = (function () {
Object.defineProperty(obj, "parentURI", {
get: function () {
if (!_parentURI) {
throw new Error("Parent URI not initialized");
this._init();
}
return _parentURI.clone();
}
});
obj._init = function (url, dir, username, password) {
if (!url) {
var msg = "WebDAV URL not provided";
Zotero.debug(msg);
throw ({
message: msg,
name: "Z_ERROR_NO_URL",
filename: "webdav.js",
toString: function () { return this.message; }
});
}
obj._init = function () {
_rootURI = false;
_parentURI = false;
if (username && !password) {
var msg = "WebDAV password not provided";
Zotero.debug(msg);
throw ({
message: msg,
name: "Z_ERROR_NO_PASSWORD",
filename: "webdav.js",
toString: function () { return this.message; }
});
}
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
try {
var uri = ios.newURI(url, null, null);
if (username) {
uri.username = username;
uri.password = password;
}
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
return false;
}
if (!uri.spec.match(/\/$/)) {
uri.spec += "/";
}
_parentURI = uri;
var uri = uri.clone();
uri.spec += "zotero/";
_rootURI = uri;
return true;
};
obj._initFromPrefs = function () {
var scheme = Zotero.Prefs.get('sync.storage.scheme');
switch (scheme) {
case 'http':
@ -832,7 +775,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
var url = Zotero.Prefs.get('sync.storage.url');
if (!url) {
return false;
var msg = "WebDAV URL not provided";
Zotero.debug(msg);
throw ({
message: msg,
name: "Z_ERROR_NO_URL",
filename: "webdav.js",
toString: function () { return this.message; }
});
}
url = scheme + '://' + url;
@ -840,7 +790,41 @@ Zotero.Sync.Storage.WebDAV = (function () {
var username = this._username;
var password = this._password;
return this._init(url, dir, username, password);
if (!username) {
var msg = "WebDAV username not provided";
Zotero.debug(msg);
throw ({
message: msg,
name: "Z_ERROR_NO_USERNAME",
filename: "webdav.js",
toString: function () { return this.message; }
});
}
if (!password) {
var msg = "WebDAV password not provided";
Zotero.debug(msg);
throw ({
message: msg,
name: "Z_ERROR_NO_PASSWORD",
filename: "webdav.js",
toString: function () { return this.message; }
});
}
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
var uri = ios.newURI(url, null, null);
uri.username = username;
uri.password = password;
if (!uri.spec.match(/\/$/)) {
uri.spec += "/";
}
_parentURI = uri;
var uri = uri.clone();
uri.spec += "zotero/";
_rootURI = uri;
};
@ -856,20 +840,20 @@ Zotero.Sync.Storage.WebDAV = (function () {
}
// Retrieve modification time from server to store locally afterwards
getStorageModificationTime(item, function (item, mdate) {
return getStorageModificationTime(item)
.then(function (mdate) {
if (!request.isRunning()) {
Zotero.debug("Download request '" + request.name
+ "' is no longer running after getting mod time");
return;
return false;
}
if (!mdate) {
Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item));
request.finish();
return;
return false;
}
try {
var syncModTime = mdate.getTime();
// Skip download if local file exists and matches mod time
@ -883,9 +867,9 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
Zotero.DB.commitTransaction();
onChangesMade();
request.finish();
return;
return {
localChanges: true
};
}
var uri = getItemURI(item);
@ -895,6 +879,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
destFile.remove(false);
}
var deferred = Q.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
onStart: function (request, data) {
@ -902,7 +888,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug("Download request " + data.request.name
+ " stopped before download started -- closing channel");
request.cancel(0x804b0002); // NS_BINDING_ABORTED
return;
deferred.resolve(false);
}
},
onProgress: function (a, b, c) {
@ -917,7 +903,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
// Delete the orphaned prop file
deleteStorageFiles([item.key + ".prop"]);
data.request.finish();
deferred.resolve(false);
return;
}
else if (status != 200) {
@ -926,26 +912,31 @@ Zotero.Sync.Storage.WebDAV = (function () {
+ " in Zotero.Sync.Storage.WebDAV.downloadFile()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(_defaultError);
deferred.reject(_defaultError);
return;
}
// Don't try to process if the request has been cancelled
if (data.request.isFinished()) {
Zotero.debug("Download request " + data.request.name
+ " is no longer running after file download");
deferred.resolve(false);
return;
}
Zotero.debug("Finished download of " + destFile.path);
try {
Zotero.Sync.Storage.processDownload(data);
data.request.finish();
deferred.resolve(Zotero.Sync.Storage.processDownload(data));
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
deferred.reject(e);
}
},
onCancel: function (request, status, data) {
Zotero.debug("Request cancelled");
deferred.resolve(false);
},
request: request,
item: item,
compressed: true,
@ -972,146 +963,134 @@ Zotero.Sync.Storage.WebDAV = (function () {
// XXX Always use when we no longer support Firefox < 18
wbp.saveURI(uri, null, null, null, null, destFile, null);
}
}
catch (e) {
request.error(e);
}
return deferred.promise;
});
};
obj._uploadFile = function (request) {
Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); });
var deferred = Q.defer();
Zotero.Sync.Storage.createUploadFile(
request,
function (data) {
deferred.resolve(processUploadFile(data));
}
);
return deferred.promise;
};
obj._getLastSyncTime = function (callback) {
obj._getLastSyncTime = function () {
// Cache the credentials at the root URI
var self = this;
this._cacheCredentials(function () {
try {
var uri = this.rootURI;
var successFileURI = uri.clone();
successFileURI.spec += "lastsync";
Zotero.HTTP.doGet(successFileURI, function (req) {
var ts = undefined;
try {
if (req.responseText) {
Zotero.debug(req.responseText);
return Q.fcall(function () {
return self._cacheCredentials();
})
.then(function () {
var lastSyncURI = this.rootURI;
lastSyncURI.spec += "lastsync";
return Zotero.HTTP.promise("GET", lastSyncURI,
{ debug: true, successCodes: [200, 404] });
})
.then(function (req) {
if (req.status == 404) {
Zotero.debug("No last WebDAV sync time");
return null;
}
Zotero.debug(req.status);
if (req.status == 403) {
var lastModified = req.getResponseHeader("Last-Modified");
var date = new Date(lastModified);
Zotero.debug("Last successful WebDAV sync was " + date);
return Zotero.Date.toUnixTimestamp(date);
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 403) {
Zotero.debug("Clearing WebDAV authentication credentials", 2);
_cachedCredentials = false;
}
if (req.status != 200 && req.status != 404) {
var msg = "Unexpected status code " + req.status + " for HEAD request "
+ "in Zotero.Sync.Storage.WebDAV.getLastSyncTime()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(_defaultError);
}
if (req.status == 200) {
var lastModified = req.getResponseHeader("Last-Modified");
var date = new Date(lastModified);
Zotero.debug("Last successful storage sync was " + date);
ts = Zotero.Date.toUnixTimestamp(date);
}
else {
ts = null;
throw("Unexpected status code " + e.status + " getting "
+ "WebDAV last sync time");
}
callback(ts);
return Q.reject(e);
}
catch(e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
Zotero.Sync.Storage.EventManager.error(_defaultError);
}
});
return;
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
Zotero.Sync.Storage.EventManager.error(_defaultError);
// TODO: handle browser offline exception
else {
throw (e);
}
});
};
obj._setLastSyncTime = function (callback) {
try {
obj._setLastSyncTime = function (libraryID, localLastSyncTime) {
if (libraryID) {
throw new Error("libraryID must be 0");
}
// DEBUG: is this necessary for WebDAV?
if (localLastSyncTime) {
var sql = "REPLACE INTO version VALUES (?, ?)";
Zotero.DB.query(
sql, ['storage_webdav_' + libraryID, { int: localLastSyncTime }]
);
return;
}
var uri = this.rootURI;
var successFileURI = uri.clone();
successFileURI.spec += "lastsync";
Zotero.HTTP.WebDAV.doPut(successFileURI, " ", function (req) {
Zotero.debug(req.responseText);
Zotero.debug(req.status);
var self = this;
switch (req.status) {
case 200:
case 201:
case 204:
getLastSyncTime(function (ts) {
return Zotero.HTTP.promise("PUT", successFileURI, " ",
{ debug: true, successCodes: [200, 201, 204] })
.then(function () {
return self._getLastSyncTime()
.then(function (ts) {
if (ts) {
var sql = "REPLACE INTO version VALUES ('storage_webdav', ?)";
Zotero.DB.query(sql, { int: ts });
}
if (callback) {
callback();
var sql = "REPLACE INTO version VALUES (?, ?)";
Zotero.DB.query(
sql, ['storage_webdav_' + libraryID, { int: ts }]
);
}
});
return;
}
})
.fail(function (e) {
var msg = "Unexpected error code " + req.status + " uploading storage success file";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
if (callback) {
callback();
}
throw _defaultError;
});
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
if (callback) {
callback();
}
return;
}
};
obj._cacheCredentials = function (callback) {
obj._cacheCredentials = function () {
if (_cachedCredentials) {
Zotero.debug("Credentials are already cached");
setTimeout(function () {
callback();
}, 0);
return false;
return;
}
Zotero.HTTP.doOptions(this.rootURI, function (req) {
return Zotero.HTTP.promise("OPTIONS", this.rootURI)
.then(function (req) {
// TODO: promisify
checkResponse(req);
if (req.status != 200) {
var msg = "Unexpected status code " + req.status + " for OPTIONS request "
+ "in Zotero.Sync.Storage.WebDAV.getLastSyncTime()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(_defaultErrorRestart);
}
Zotero.debug("Credentials are cached");
_cachedCredentials = true;
callback();
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
var msg = "Unexpected status code " + e.status + " "
+ "for OPTIONS request caching WebDAV credentials";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
throw new Error(_defaultErrorRestart);
}
throw e;
});
return true;
};
@ -1120,9 +1099,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
* @param {Object} errorCallbacks
*/
obj._checkServer = function (callback) {
this._initFromPrefs();
try {
// Clear URIs
this.init();
var parentURI = this.parentURI;
var uri = this.rootURI;
}
@ -1132,6 +1112,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
callback(null, Zotero.Sync.Storage.ERROR_NO_URL);
return;
case 'Z_ERROR_NO_USERNAME':
callback(null, Zotero.Sync.Storage.ERROR_NO_USERNAME);
return;
case 'Z_ERROR_NO_PASSWORD':
callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD);
return;
@ -1399,6 +1383,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
var errorMessage = Zotero.getString('sync.storage.error.webdav.enterURL');
break;
case Zotero.Sync.Storage.ERROR_NO_USERNAME:
var errorMessage = Zotero.getString('sync.error.usernameNotSet');
break;
case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
var errorMessage = Zotero.getString('sync.error.enterPassword');
break;
@ -1525,7 +1513,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
*/
obj._purgeDeletedStorageFiles = function (callback) {
if (!this._active) {
return;
return false;
}
Zotero.debug("Purging deleted storage files");
@ -1535,10 +1523,11 @@ Zotero.Sync.Storage.WebDAV = (function () {
if (callback) {
callback();
}
Zotero.Sync.Storage.EventManager.skip();
return;
return false;
}
// TODO: promisify
// Add .zip extension
var files = files.map(function (file) file + ".zip");
@ -1582,16 +1571,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
const daysBeforeSyncTime = 1;
if (!this._active) {
Zotero.Sync.Storage.EventManager.skip();
return;
return false;
}
// If recently purged, skip
var lastpurge = Zotero.Prefs.get('lastWebDAVOrphanPurge');
var days = 10;
if (lastpurge && new Date(lastpurge * 1000) > (new Date() - (1000 * 60 * 60 * 24 * days))) {
Zotero.Sync.Storage.EventManager.skip();
return;
return false;
}
Zotero.debug("Purging orphaned storage files");

View file

@ -28,7 +28,6 @@ Zotero.Sync.Storage.ZFS = (function () {
var _rootURI;
var _userURI;
var _cachedCredentials = false;
var _lastSyncTime = null;
/**
* Get file metadata on storage server
@ -36,23 +35,13 @@ Zotero.Sync.Storage.ZFS = (function () {
* @param {Zotero.Item} item
* @param {Function} callback Callback f(item, etag)
*/
function getStorageFileInfo(item, callback) {
var uri = getItemInfoURI(item);
Zotero.HTTP.doGet(uri, function (req) {
function getStorageFileInfo(item) {
var funcName = "Zotero.Sync.Storage.ZFS.getStorageFileInfo()";
return Zotero.HTTP.promise("GET", getItemInfoURI(item), { successCodes: [200, 404] })
.then(function (req) {
if (req.status == 404) {
callback(item, false);
return;
}
else if (req.status != 200) {
var msg = "Unexpected status code " + req.status + " in " + funcName
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
Zotero.debug(msg, 1);
Zotero.debug(req.responseText);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
return false;
}
var info = {};
@ -73,7 +62,7 @@ Zotero.Sync.Storage.ZFS = (function () {
var msg = "A file sync error occurred. Please restart " + Zotero.appName + " and/or your computer and try syncing again.\n\n"
+ "If the error persists, there may be a problem with either your computer or your network: security software, proxy server, VPN, etc. "
+ "Try disabling any security/firewall software you're using or, if this is a laptop, try from a different network.";
Zotero.Sync.Storage.EventManager.error(msg);
throw msg;
}
info.filename = req.getResponseHeader('X-Zotero-Filename');
var mtime = req.getResponseHeader('X-Zotero-Modification-Time');
@ -81,7 +70,19 @@ Zotero.Sync.Storage.ZFS = (function () {
info.compressed = req.getResponseHeader('X-Zotero-Compressed') == 'Yes';
Zotero.debug(info);
callback(item, info);
return info;
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
var msg = "Unexpected status code " + e.xmlhttp.status
+ " getting storage file info";
Zotero.debug(msg, 1);
Zotero.debug(e.xmlhttp.responseText);
Components.utils.reportError(msg);
throw new Error(Zotero.Sync.Storage.defaultError);
}
throw e;
});
}
@ -101,14 +102,14 @@ Zotero.Sync.Storage.ZFS = (function () {
var request = data.request;
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
getStorageFileInfo(item, function (item, info) {
return getStorageFileInfo(item)
.then(function (info) {
if (request.isFinished()) {
Zotero.debug("Upload request '" + request.name
+ "' is no longer running after getting file info");
return;
return false;
}
try {
// Check for conflict
if (Zotero.Sync.Storage.getSyncState(item.id)
!= Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
@ -156,23 +157,25 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
Zotero.DB.commitTransaction();
Zotero.Sync.Storage.EventManager.changesMade();
request.finish();
return;
return {
localChanges: true,
remoteChanges: false
};
}
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
if (!useLocal && smtime != mtime) {
var localData = { modTime: fmtime };
var remoteData = { modTime: mtime };
Zotero.Sync.Storage.QueueManager.addConflict(
request.name, localData, remoteData
);
Zotero.debug("Conflict -- last synced file mod time "
+ "does not match time on storage server"
+ " (" + smtime + " != " + mtime + ")");
request.finish();
return;
return {
localChanges: false,
remoteChanges: false,
conflict: {
local: { modTime: fmtime },
remote: { modTime: mtime }
}
};
}
}
else {
@ -180,25 +183,19 @@ Zotero.Sync.Storage.ZFS = (function () {
}
}
getFileUploadParameters(
return getFileUploadParameters(
item,
function (item, target, uploadKey, params) {
try {
postFile(request, item, target, uploadKey, params);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return postFile(request, item, target, uploadKey, params);
},
function () {
updateItemFileInfo(item);
request.finish();
return {
localChanges: true,
remoteChanges: false
};
}
);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
});
}
@ -212,6 +209,8 @@ Zotero.Sync.Storage.ZFS = (function () {
* on server and uploading isn't necessary
*/
function getFileUploadParameters(item, uploadCallback, existsCallback) {
var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()";
var uri = getItemURI(item);
if (Zotero.Attachments.getNumFiles(item) > 1) {
@ -236,11 +235,39 @@ Zotero.Sync.Storage.ZFS = (function () {
body += "&zip=1";
}
Zotero.HTTP.doPost(uri, body, function (req) {
var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()";
return Zotero.HTTP.promise("POST", uri, { body: body, debug: true })
.then(function (req) {
try {
// Strip XML declaration and convert to E4X
var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, '')));
}
catch (e) {
throw new Error("Invalid response retrieving file upload parameters");
}
if (req.status == 413) {
var retry = req.getResponseHeader('Retry-After');
if (xml.name() != 'upload' && xml.name() != 'exists') {
throw new Error("Invalid response retrieving file upload parameters");
}
// File was already available, so uploading isn't required
if (xml.name() == 'exists') {
existsCallback();
return false;
}
var url = xml.url.toString();
var uploadKey = xml.key.toString();
var params = {}, p = '';
for each(var param in xml.params.children()) {
params[param.name()] = param.toString();
}
Zotero.debug(params);
return uploadCallback(item, url, uploadKey, params);
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 413) {
var retry = e.xmlhttp.getResponseHeader('Retry-After');
if (retry) {
var minutes = Math.round(retry / 60);
// TODO: localize
@ -249,7 +276,7 @@ Zotero.Sync.Storage.ZFS = (function () {
+ "Please try again in " + minutes + " minutes.",
"ZFS_UPLOAD_QUEUE_LIMIT"
);
Zotero.Sync.Storage.EventManager.error(e);
throw e;
}
var text, buttonText = null, buttonCallback;
@ -279,8 +306,6 @@ Zotero.Sync.Storage.ZFS = (function () {
// TODO: localize
text += "\n\n" + filename + " (" + Math.round(file.fileSize / 1024) + "KB)";
Zotero.debug(req.responseText);
var e = new Zotero.Error(
"The file '" + filename + "' would exceed your Zotero File Storage quota",
"ZFS_OVER_QUOTA",
@ -292,15 +317,12 @@ Zotero.Sync.Storage.ZFS = (function () {
);
Zotero.debug(e, 2);
Components.utils.reportError(e);
// Stop uploads, log warning, and continue
Zotero.Sync.Storage.QueueManager.get('upload').stop();
Zotero.Sync.Storage.EventManager.warning(e);
Zotero.Sync.Storage.EventManager.success();
return;
// Stop uploads from this library, log warning, and continue
Zotero.Sync.Storage.QueueManager.get('upload', item.libraryID).stop();
Zotero.Sync.Storage.EventLog.warning(e, item.libraryID);
return false;
}
else if (req.status == 403) {
Zotero.debug(req.responseText);
else if (e.status == 403) {
var groupID = Zotero.Groups.getGroupIDFromLibraryID(item.libraryID);
var e = new Zotero.Error(
"File editing denied for group",
@ -309,9 +331,9 @@ Zotero.Sync.Storage.ZFS = (function () {
groupID: groupID
}
);
Zotero.Sync.Storage.EventManager.error(e);
throw e;
}
else if (req.status == 404) {
else if (e.status == 404) {
Components.utils.reportError("Unexpected status code 404 in " + funcName
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")");
if (Zotero.Prefs.get('sync.debugNoAutoResetClient')) {
@ -324,49 +346,18 @@ Zotero.Sync.Storage.ZFS = (function () {
}
Zotero.Sync.Server.resetClient();
Zotero.Sync.Server.canAutoResetClient = false;
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
throw new Error(Zotero.Sync.Storage.defaultError);
}
else if (req.status != 200) {
var msg = "Unexpected status code " + req.status + " in " + funcName
var msg = "Unexpected status code " + e.status + " in " + funcName
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
Zotero.debug(msg, 1);
Zotero.debug(req.responseText);
Zotero.debug(req.getAllResponseHeaders());
Zotero.debug(e.xmlhttp.getAllResponseHeaders());
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
throw new Error(Zotero.Sync.Storage.defaultError);
}
Zotero.debug(req.responseText);
try {
// Strip XML declaration and convert to E4X
var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, '')));
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(
"Invalid response retrieving file upload parameters"
);
}
if (xml.name() != 'upload' && xml.name() != 'exists') {
Zotero.Sync.Storage.EventManager.error(
"Invalid response retrieving file upload parameters"
);
}
// File was already available, so uploading isn't required
if (xml.name() == 'exists') {
existsCallback();
return;
}
var url = xml.url.toString();
var uploadKey = xml.key.toString();
var params = {}, p = '';
for each(var param in xml.params.children()) {
params[param.name()] = param.toString();
}
Zotero.debug(params);
uploadCallback(item, url, uploadKey, params);
throw e;
});
}
@ -374,7 +365,7 @@ Zotero.Sync.Storage.ZFS = (function () {
function postFile(request, item, url, uploadKey, params) {
if (request.isFinished()) {
Zotero.debug("Upload request " + request.name + " is no longer running after getting upload parameters");
return;
return false;
}
var file = getUploadFile(item);
@ -458,13 +449,22 @@ Zotero.Sync.Storage.ZFS = (function () {
request.setChannel(channel);
var deferred = Q.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
onProgress: function (a, b, c) {
request.onProgress(a, b, c);
},
onStop: function (httpRequest, status, response, data) { onUploadComplete(httpRequest, status, response, data); },
onCancel: function (httpRequest, status, data) { onUploadCancel(httpRequest, status, data); },
onStop: function (httpRequest, status, response, data) {
deferred.resolve(
onUploadComplete(httpRequest, status, response, data)
);
},
onCancel: function (httpRequest, status, data) {
onUploadCancel(httpRequest, status, data)
deferred.resolve(false);
},
request: request,
item: item,
uploadKey: uploadKey,
@ -480,6 +480,8 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.debug("HTTP POST of " + file.leafName + " to " + dispURI.spec);
channel.asyncOpen(listener, null);
return deferred.promise;
}
@ -498,9 +500,7 @@ Zotero.Sync.Storage.ZFS = (function () {
break;
case 500:
Zotero.Sync.Storage.EventManager.error(
"File upload failed. Please try again."
);
throw new Error("File upload failed. Please try again.");
default:
var msg = "Unexpected file upload status " + status
@ -509,29 +509,30 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Components.utils.reportError(response);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
throw new Error(Zotero.Sync.Storage.defaultError);
}
var uri = getItemURI(item);
var body = "update=" + uploadKey + "&mtime=" + item.attachmentModificationTime;
// Register upload on server
Zotero.HTTP.doPost(uri, body, function (req) {
if (req.status != 204) {
var msg = "Unexpected file registration status " + req.status
+ " in Zotero.Sync.Storage.ZFS.onUploadComplete()"
return Zotero.HTTP.promise("POST", uri, { body: body, successCodes: [204] })
.then(function (req) {
updateItemFileInfo(item);
return {
localChanges: true,
remoteChanges: true
};
})
.fail(function (e) {
var msg = "Unexpected file registration status " + e.status
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
Zotero.debug(msg, 1);
Zotero.debug(req.responseText);
Zotero.debug(req.getAllResponseHeaders());
Zotero.debug(e.xmlhttp.responseText);
Zotero.debug(e.xmlhttp.getAllResponseHeaders());
Components.utils.reportError(msg);
Components.utils.reportError(req.responseText);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
}
updateItemFileInfo(item);
request.finish();
Components.utils.reportError(e.xmlhttp.responseText);
throw new Error(Zotero.Sync.Storage.defaultError);
});
}
@ -563,8 +564,6 @@ Zotero.Sync.Storage.ZFS = (function () {
catch (e) {
Components.utils.reportError(e);
}
Zotero.Sync.Storage.EventManager.changesMade();
}
@ -584,8 +583,6 @@ Zotero.Sync.Storage.ZFS = (function () {
catch (e) {
Components.utils.reportError(e);
}
request.finish();
}
@ -649,16 +646,12 @@ Zotero.Sync.Storage.ZFS = (function () {
}
});
Object.defineProperty(obj, "_enabled", {
get: function () this.includeUserFiles || this.includeGroupFiles
});
obj._verified = true;
Object.defineProperty(obj, "rootURI", {
get: function () {
if (!_rootURI) {
throw ("Root URI not initialized in Zotero.Sync.Storage.ZFS.rootURI");
this._init();
}
return _rootURI.clone();
}
@ -667,7 +660,7 @@ Zotero.Sync.Storage.ZFS = (function () {
Object.defineProperty(obj, "userURI", {
get: function () {
if (!_userURI) {
throw ("User URI not initialized in Zotero.Sync.Storage.ZFS.userURI");
this._init();
}
return _userURI.clone();
}
@ -675,35 +668,23 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._init = function (url, username, password) {
_rootURI = false;
_userURI = false;
var url = ZOTERO_CONFIG.API_URL;
var username = Zotero.Sync.Server.username;
var password = Zotero.Sync.Server.password;
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
try {
var uri = ios.newURI(url, null, null);
if (username) {
uri.username = username;
uri.password = password;
}
}
catch (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
return false;
}
_rootURI = uri;
uri = uri.clone();
uri.spec += 'users/' + Zotero.userID + '/';
_userURI = uri;
return true;
};
obj._initFromPrefs = function () {
var url = ZOTERO_CONFIG.API_URL;
var username = Zotero.Sync.Server.username;
var password = Zotero.Sync.Server.password;
return this._init(url, username, password);
};
@ -719,20 +700,19 @@ Zotero.Sync.Storage.ZFS = (function () {
}
// Retrieve file info from server to store locally afterwards
getStorageFileInfo(item, function (item, info) {
return getStorageFileInfo(item)
.then(function (info) {
if (!request.isRunning()) {
Zotero.debug("Download request '" + request.name
+ "' is no longer running after getting remote file info");
return;
return false;
}
if (!info) {
Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key);
request.finish();
return;
return false;
}
try {
var syncModTime = info.mtime;
var syncHash = info.hash;
@ -749,9 +729,10 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
Zotero.DB.commitTransaction();
Zotero.Sync.Storage.EventManager.changesMade();
request.finish();
return;
return {
localChanges: true,
remoteChanges: false
};
}
// If not compressed, check hash, in case only timestamp changed
else if (!info.compressed && item.attachmentHash == syncHash) {
@ -767,9 +748,10 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
Zotero.DB.commitTransaction();
Zotero.Sync.Storage.EventManager.changesMade();
request.finish();
return;
return {
localChanges: true,
remoteChanges: false
};
}
}
@ -799,6 +781,8 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.File.checkFileAccessError(e, destFile, 'create');
}
var deferred = Q.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
onStart: function (request, data) {
@ -806,7 +790,7 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.debug("Download request " + data.request.name
+ " stopped before download started -- closing channel");
request.cancel(0x804b0002); // NS_BINDING_ABORTED
return;
deferred.resolve(false);
}
},
onProgress: function (a, b, c) {
@ -819,26 +803,31 @@ Zotero.Sync.Storage.ZFS = (function () {
+ " in Zotero.Sync.Storage.ZFS.downloadFile()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
deferred.reject(Zotero.Sync.Storage.defaultError);
return;
}
// Don't try to process if the request has been cancelled
if (data.request.isFinished()) {
Zotero.debug("Download request " + data.request.name
+ " is no longer running after file download", 2);
deferred.resolve(false);
return;
}
Zotero.debug("Finished download of " + destFile.path);
try {
Zotero.Sync.Storage.processDownload(data);
data.request.finish();
deferred.resolve(Zotero.Sync.Storage.processDownload(data));
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
deferred.reject(e);
}
},
onCancel: function (request, status, data) {
Zotero.debug("Request cancelled");
deferred.resolve(false);
},
request: request,
item: item,
compressed: info.compressed,
@ -868,10 +857,8 @@ Zotero.Sync.Storage.ZFS = (function () {
// XXX Always use when we no longer support Firefox < 18
wbp.saveURI(uri, null, null, null, null, destFile, null);
}
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return deferred.promise;
});
};
@ -879,140 +866,151 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._uploadFile = function (request) {
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
if (Zotero.Attachments.getNumFiles(item) > 1) {
Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); });
var deferred = Q.defer();
Zotero.Sync.Storage.createUploadFile(
request,
function (data) {
deferred.resolve(processUploadFile(data));
}
);
return deferred.promise;
}
else {
processUploadFile({ request: request });
return processUploadFile({ request: request });
}
};
obj._getLastSyncTime = function (callback) {
var uri = this.userURI;
var successFileURI = uri.clone();
successFileURI.spec += "laststoragesync?auth=1";
/**
* @return {Promise} A promise for the last sync time
*/
obj._getLastSyncTime = function (libraryID) {
var lastSyncURI = this._getLastSyncURI(libraryID);
// Cache the credentials at the root
var self = this;
this._cacheCredentials(function () {
Zotero.HTTP.doGet(successFileURI, function (req) {
if (req.responseText) {
Zotero.debug(req.responseText);
}
Zotero.debug(req.status);
if (req.status == 401 || req.status == 403) {
Zotero.debug("Clearing ZFS authentication credentials", 2);
_cachedCredentials = false;
return Q.fcall(function () {
// Cache the credentials at the root
return self._cacheCredentials();
})
.then(function () {
return Zotero.HTTP.promise("GET", lastSyncURI,
{ debug: true, successCodes: [200, 404] });
})
.then(function (req) {
// Not yet synced
if (req.status == 404) {
Zotero.debug("No last sync time for library " + libraryID);
return null;
}
if (req.status != 200 && req.status != 404) {
Zotero.Sync.Storage.EventManager.error(
"Unexpected status code " + req.status + " getting "
+ "last file sync time"
);
}
if (req.status == 200) {
var ts = req.responseText;
var date = new Date(ts * 1000);
Zotero.debug("Last successful storage sync was " + date);
_lastSyncTime = ts;
Zotero.debug("Last successful ZFS sync for library "
+ libraryID + " was " + date);
return ts;
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 401 || e.status == 403) {
Zotero.debug("Clearing ZFS authentication credentials", 2);
_cachedCredentials = false;
}
return Q.reject(e);
}
// TODO: handle browser offline exception
else {
var ts = null;
_lastSyncTime = null;
throw e;
}
callback(ts);
});
});
};
obj._setLastSyncTime = function (callback, useLastSyncTime) {
if (useLastSyncTime) {
if (!_lastSyncTime) {
if (callback) {
callback();
}
obj._setLastSyncTime = function (libraryID, localLastSyncTime) {
if (localLastSyncTime) {
var sql = "REPLACE INTO version VALUES (?, ?)";
Zotero.DB.query(
sql, ['storage_zfs_' + libraryID, { int: localLastSyncTime }]
);
return;
}
var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)";
Zotero.DB.query(sql, { int: _lastSyncTime });
Zotero.debug("Clearing ZFS authentication credentials", 2);
_lastSyncTime = null;
_cachedCredentials = false;
if (callback) {
callback();
}
return;
}
_lastSyncTime = null;
var uri = this.userURI;
var successFileURI = uri.clone();
successFileURI.spec += "laststoragesync?auth=1";
Zotero.HTTP.doPost(successFileURI, "", function (req) {
Zotero.debug(req.responseText);
Zotero.debug(req.status);
if (req.status != 200) {
var msg = "Unexpected status code " + req.status + " setting last file sync time";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
}
var lastSyncURI = this._getLastSyncURI(libraryID);
return Zotero.HTTP.promise("POST", lastSyncURI, { debug: true })
.then(function (req) {
var ts = req.responseText;
var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)";
Zotero.DB.query(sql, { int: ts });
Zotero.debug("Clearing ZFS authentication credentials", 2);
_cachedCredentials = false;
if (callback) {
callback();
}
var sql = "REPLACE INTO version VALUES (?, ?)";
Zotero.DB.query(
sql, ['storage_zfs_' + libraryID, { int: ts }]
);
})
.fail(function (e) {
var msg = "Unexpected status code " + e.xmlhttp.status
+ " setting last file sync time";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
throw new Error(Zotero.Sync.Storage.defaultError);
});
};
obj._cacheCredentials = function (callback) {
obj._getLastSyncURI = function (libraryID) {
if (libraryID === 0) {
var lastSyncURI = this.userURI;
}
else if (libraryID) {
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
var uri = ios.newURI(Zotero.URI.getLibraryURI(libraryID), null, null);
var path = uri.path;
// We don't want the user URI, but it already has the right domain
// and credentials, so just start with that and replace the path
var lastSyncURI = this.userURI;
lastSyncURI.path = path + "/";
}
else {
throw new Error("libraryID not specified");
}
lastSyncURI.spec += "laststoragesync";
return lastSyncURI;
}
obj._cacheCredentials = function () {
if (_cachedCredentials) {
Zotero.debug("Credentials are already cached");
setTimeout(function () {
callback();
}, 0);
return false;
return;
}
var uri = this.rootURI;
// TODO: move to root uri
uri.spec += "?auth=1";
Zotero.HTTP.doGet(uri, function (req) {
if (req.status == 401) {
// TODO: localize
var msg = "File sync login failed\n\nCheck your username and password in the Sync pane of the Zotero preferences.";
Zotero.Sync.Storage.EventManager.error(msg);
}
else if (req.status != 200) {
var msg = "Unexpected status code " + req.status + " caching "
+ "authentication credentials in Zotero.Sync.Storage.ZFS.cacheCredentials()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultErrorRestart);
}
return Zotero.HTTP.promise("GET", uri).
then(function (req) {
Zotero.debug("Credentials are cached");
_cachedCredentials = true;
callback();
})
.fail(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 401) {
var msg = "File sync login failed\n\n"
+ "Check your username and password in the Sync "
+ "pane of the Zotero preferences.";
throw (msg);
}
var msg = "Unexpected status code " + e.status + " "
+ "caching ZFS credentials";
Zotero.debug(msg, 1);
throw (msg);
}
else {
throw (e);
}
});
return true;
};
@ -1022,17 +1020,17 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._purgeDeletedStorageFiles = function (callback) {
// If we don't have a user id we've never synced and don't need to bother
if (!Zotero.userID) {
Zotero.Sync.Storage.EventManager.skip();
return;
return false;
}
var sql = "SELECT value FROM settings WHERE setting=? AND key=?";
var values = Zotero.DB.columnQuery(sql, ['storage', 'zfsPurge']);
if (!values) {
Zotero.Sync.Storage.EventManager.skip();
return;
return false;
}
// TODO: promisify
Zotero.debug("Unlinking synced files on ZFS");
var uri = this.userURI;
@ -1049,9 +1047,8 @@ Zotero.Sync.Storage.ZFS = (function () {
break;
default:
Zotero.Sync.Storage.EventManager.error(
"Invalid zfsPurge value '" + value + "' in ZFS purgeDeletedStorageFiles()"
);
throw "Invalid zfsPurge value '" + value
+ "' in ZFS purgeDeletedStorageFiles()";
}
}
uri.spec = uri.spec.substr(0, uri.spec.length - 1);
@ -1061,9 +1058,7 @@ Zotero.Sync.Storage.ZFS = (function () {
if (callback) {
callback(false);
}
Zotero.Sync.Storage.EventManager.error(
"Unexpected status code " + xmlhttp.status + " purging ZFS files"
);
throw "Unexpected status code " + xmlhttp.status + " purging ZFS files";
}
var sql = "DELETE FROM settings WHERE setting=? AND key=?";
@ -1072,8 +1067,6 @@ Zotero.Sync.Storage.ZFS = (function () {
if (callback) {
callback(true);
}
Zotero.Sync.Storage.EventManager.success();
});
};

View file

@ -505,12 +505,12 @@ Zotero.Sync.Runner = new function () {
var _autoSyncTimer;
var _queue;
var _running;
var _background;
var _lastSyncStatus;
var _currentSyncStatusLabel;
var _currentLastSyncLabel;
var _errorsByLibrary = {};
var _warning = null;
@ -526,16 +526,9 @@ Zotero.Sync.Runner = new function () {
this.clearSyncTimeout(); // DEBUG: necessary?
var msg = "Zotero cannot sync while " + Zotero.appName + " is in offline mode.";
var e = new Zotero.Error(msg, 0, { dialogButtonText: null })
this.setSyncIcon('error', e);
return false;
}
if (_running) {
// TODO: show status in all windows
var msg = "A sync process is already running. To view progress, check "
+ "the window in which the sync began or restart " + Zotero.appName + ".";
var e = new Zotero.Error(msg, 0, { dialogButtonText: null, frontWindowOnly: true })
this.setSyncIcon('error', e);
Components.utils.reportError(e);
Zotero.debug(e, 1);
this.setSyncIcon(e);
return false;
}
@ -543,7 +536,6 @@ Zotero.Sync.Runner = new function () {
Zotero.purgeDataObjects(true);
_background = !!background;
_running = true;
this.setSyncIcon('animate');
var finalCallbacks = {
@ -554,61 +546,30 @@ Zotero.Sync.Runner = new function () {
};
var storageSync = function () {
var syncNeeded = false;
Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
var zfsSync = function (skipSyncNeeded) {
Zotero.Sync.Storage.ZFS.sync({
// ZFS success
onSuccess: function () {
setTimeout(function () {
Zotero.Sync.Server.sync(finalCallbacks);
}, 0);
},
Zotero.Sync.Storage.sync()
.then(function (results) {
Zotero.debug("File sync is finished");
// ZFS skip
onSkip: function () {
setTimeout(function () {
if (skipSyncNeeded) {
if (results.errors.length) {
Zotero.Sync.Runner.setErrors(results.errors);
return;
}
if (results.changesMade) {
Zotero.debug("Changes made during file sync "
+ "-- performing additional data sync");
Zotero.Sync.Server.sync(finalCallbacks);
}
else {
Zotero.Sync.Runner.stop();
}
}, 0);
},
// ZFS cancel
onStop: function () {
setTimeout(function () {
Zotero.Sync.Runner.stop();
}, 0);
},
// ZFS failure
onError: Zotero.Sync.Runner.error,
onWarning: Zotero.Sync.Runner.warning
})
};
Zotero.Sync.Storage.WebDAV.sync({
// WebDAV success
onSuccess: function () {
zfsSync(true);
},
// WebDAV skip
onSkip: function () {
zfsSync();
},
// WebDAV cancel
onStop: Zotero.Sync.Runner.stop,
// WebDAV failure
onError: Zotero.Sync.Runner.error
.fail(function (e) {
Zotero.debug("File sync failed", 1);
Zotero.Sync.Runner.error(e);
});
};
@ -620,23 +581,26 @@ Zotero.Sync.Runner = new function () {
onSkip: storageSync,
// Sync 1 stop
onStop: Zotero.Sync.Runner.stop,
onStop: function () {
Zotero.Sync.Runner.stop();
},
// Sync 1 error
onError: Zotero.Sync.Runner.error
onError: function (e) {
Zotero.Sync.Runner.error(e);
}
});
}
this.stop = function () {
if (_warning) {
Zotero.Sync.Runner.setSyncIcon('warning', _warning);
Zotero.Sync.Runner.setSyncIcon(_warning);
_warning = null;
}
else {
Zotero.Sync.Runner.setSyncIcon();
}
_running = false;
}
@ -644,14 +608,17 @@ Zotero.Sync.Runner = new function () {
* Log a warning, but don't throw an error
*/
this.warning = function (e) {
Zotero.debug(e, 2);
Components.utils.reportError(e);
e.status = 'warning';
_warning = e;
}
this.error = function (e) {
Zotero.Sync.Runner.setSyncIcon('error', e);
_running = false;
Components.utils.reportError(e);
Zotero.debug(e, 1);
Zotero.Sync.Runner.setSyncIcon(e);
throw (e);
}
@ -740,60 +707,85 @@ Zotero.Sync.Runner = new function () {
}
this.setSyncIcon = function (status, e) {
var message;
var buttonText;
var buttonCallback;
var frontWindowOnly = false;
/**
* Trigger updating of the main sync icon, the sync error icon, and
* library-specific sync error icons across all windows
*/
this.setErrors = function (errors) {
Zotero.debug(errors);
errors = [this.parseSyncError(e) for each(e in errors)];
Zotero.debug(errors);
_errorsByLibrary = {};
status = status ? status : '';
var primaryError = this.getPrimaryError(errors);
Zotero.debug(primaryError);
this.setSyncIcon(primaryError);
switch (status) {
case '':
case 'animate':
case 'warning':
case 'error':
break;
default:
throw ("Invalid sync icon status '" + status
+ "' in Zotero.Sync.Runner.setSyncIcon()");
// Store other errors by libraryID to be shown in the source list
for each(var e in errors) {
// Skip non-library-specific errors
if (typeof e.libraryID == 'undefined') {
continue;
}
if (e) {
if (e.data) {
if (e.data.dialogText) {
message = e.data.dialogText;
}
if (typeof e.data.dialogButtonText != 'undefined') {
buttonText = e.data.dialogButtonText;
buttonCallback = e.data.dialogButtonCallback;
}
if (e.data.frontWindowOnly) {
frontWindowOnly = e.data.frontWindowOnly;
}
}
if (!message) {
if (e.message) {
message = e.message;
}
else {
message = e;
}
if (!_errorsByLibrary[e.libraryID]) {
_errorsByLibrary[e.libraryID] = [];
}
_errorsByLibrary[e.libraryID].push(e);
}
var upgradeRequired = false;
// Refresh source list
Zotero.Notifier.trigger('redraw', 'collection', []);
}
this.getErrors = function (libraryID) {
if (!_errorsByLibrary[libraryID]) {
return false;
}
return _errorsByLibrary[libraryID];
}
this.getPrimaryError = function (errors) {
errors = [this.parseSyncError(e) for each(e in errors)];
// Set highest priority error as the primary (sync error icon)
var statusPriorities = {
info: 1,
warning: 2,
error: 3,
upgrade: 4,
// Skip these
animate: -1
};
var primaryError = false;
for each(var error in errors) {
if (!error.status || statusPriorities[error.status] == -1) {
continue;
}
if (!primaryError || statusPriorities[error.status]
> statusPriorities[primaryError.status]) {
primaryError = error;
}
}
return primaryError;
}
/**
* Set the main sync error icon across all windows
*/
this.setSyncIcon = function (e) {
e = this.parseSyncError(e);
if (Zotero.Sync.Server.upgradeRequired) {
upgradeRequired = true;
e.status = 'upgrade';
Zotero.Sync.Server.upgradeRequired = false;
}
if (status == 'error') {
var errorsLogged = Zotero.getErrors().length > 0;
}
if (frontWindowOnly) {
if (e.frontWindowOnly) {
// Fake an nsISimpleEnumerator with just the topmost window
var enumerator = {
_returned: false,
@ -820,100 +812,17 @@ Zotero.Sync.Runner = new function () {
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if(!win.ZoteroPane) continue;
var warning = win.ZoteroPane.document.getElementById('zotero-tb-sync-warning');
var icon = win.ZoteroPane.document.getElementById('zotero-tb-sync');
if (!win.ZoteroPane) continue;
var doc = win.ZoteroPane.document;
if (status == 'warning' || status == 'error') {
icon.setAttribute('status', '');
warning.hidden = false;
if (upgradeRequired) {
warning.setAttribute('mode', 'upgrade');
buttonText = null;
}
else {
warning.setAttribute('mode', status);
}
warning.tooltipText = message;
warning.onclick = function () {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
// Warning
if (status == 'warning') {
var title = Zotero.getString('general.warning');
// If secondary button not specified, just use an alert
if (!buttonText) {
ps.alert(null, title, message);
return;
}
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
var index = ps.confirmEx(
null,
title,
message,
buttonFlags,
"",
buttonText,
"", null, {}
);
if (index == 1) {
setTimeout(function () { buttonCallback(); }, 1);
}
}
// Error
else if (status == 'error') {
// Probably not necessary, but let's be sure
if (!errorsLogged) {
Components.utils.reportError(message);
}
if (typeof buttonText == 'undefined') {
buttonText = Zotero.getString('errorReport.reportError');
buttonCallback = function () {
win.ZoteroPane.reportErrors();
}
}
// If secondary button is explicitly null, just use an alert
else if (buttonText === null) {
ps.alert(null, title, message);
return;
}
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
var index = ps.confirmEx(
null,
Zotero.getString('general.error'),
message,
buttonFlags,
"",
buttonText,
"", null, {}
);
if (index == 1) {
setTimeout(function () { buttonCallback(); }, 1);
}
}
}
}
else {
icon.setAttribute('status', status);
warning.hidden = true;
warning.onclick = null;
}
var button = doc.getElementById('zotero-tb-sync-error');
this.setErrorIcon(button, [e]);
var syncIcon = doc.getElementById('zotero-tb-sync');
// Update sync icon state
syncIcon.setAttribute('status', e.status ? e.status : "");
// Disable button while spinning
icon.disabled = status == 'animate';
syncIcon.disabled = e.status == 'animate';
}
// Clear status
@ -921,6 +830,9 @@ Zotero.Sync.Runner = new function () {
}
/**
* Set the sync icon tooltip message
*/
this.setSyncStatus = function (msg) {
_lastSyncStatus = msg;
@ -931,6 +843,132 @@ Zotero.Sync.Runner = new function () {
}
this.parseSyncError = function (e) {
if (!e) {
return { parsed: true };
}
var parsed = {
parsed: true
};
// In addition to actual errors, string states (e.g., 'animate')
// can be passed
if (typeof e == 'string') {
parsed.status = e;
return parsed;
}
// Already parsed
if (e.parsed) {
return e;
}
if (typeof e.libraryID != 'undefined') {
parsed.libraryID = e.libraryID;
}
parsed.status = e.status ? e.status : 'error';
if (e.data) {
if (e.data.dialogText) {
parsed.message = e.data.dialogText;
}
if (typeof e.data.dialogButtonText != 'undefined') {
parsed.buttonText = e.data.dialogButtonText;
parsed.buttonCallback = e.data.dialogButtonCallback;
}
}
if (!parsed.message) {
parsed.message = e.message ? e.message : e;
}
parsed.frontWindowOnly = !!(e && e.data && e.data.frontWindowOnly);
return parsed;
}
/**
* Set the state of the sync error icon and add an onclick to populate
* the error panel
*/
this.setErrorIcon = function (icon, errors) {
if (!errors || !errors.length) {
icon.hidden = true;
icon.onclick = null;
return;
}
// TEMP: for now, use the first error
var e = this.getPrimaryError(errors);
if (!e.status) {
icon.hidden = true;
icon.onclick = null;
return;
}
icon.hidden = false;
icon.setAttribute('mode', e.status);
icon.onclick = function () {
var doc = this.ownerDocument;
var panel = Zotero.Sync.Runner.updateErrorPanel(doc, errors);
panel.openPopup(this, "after_end", 4, 0, false, false);
}
}
this.updateErrorPanel = function (doc, errors) {
var panel = doc.getElementById('zotero-sync-error-panel');
var panelContent = doc.getElementById('zotero-sync-error-panel-content');
var panelButtons = doc.getElementById('zotero-sync-error-panel-buttons');
// Clear existing panel content
while (panelContent.hasChildNodes()) {
panelContent.removeChild(panelContent.firstChild);
}
while (panelButtons.hasChildNodes()) {
panelButtons.removeChild(panelButtons.firstChild);
}
// TEMP: for now, we only show one error
var e = errors.concat().shift();
e = this.parseSyncError(e);
var desc = doc.createElement('description');
desc.textContent = e.message;
panelContent.appendChild(desc);
// If not an error and there's no explicit button text, don't show
// button to report errors
if (e.status != 'error' && typeof e.buttonText == 'undefined') {
e.buttonText = null;
}
if (e.buttonText !== null) {
if (typeof e.buttonText == 'undefined') {
var buttonText = Zotero.getString('errorReport.reportError');
var buttonCallback = function () {
doc.defaultView.ZoteroPane.reportErrors();
};
}
else {
var buttonText = e.buttonText;
var buttonCallback = e.buttonCallback;
}
var button = doc.createElement('button');
button.setAttribute('label', buttonText);
button.onclick = buttonCallback;
panelButtons.appendChild(button);
}
return panel;
}
/**
* Register label in sync icon tooltip to receive updates
*
@ -1440,7 +1478,7 @@ Zotero.Sync.Server = new function () {
Zotero.suppressUIUpdates = true;
_updatesInProgress = true;
var errorHandler = function (e) {
var errorHandler = function (e, rethrow) {
Zotero.DB.rollbackTransaction();
Zotero.UnresponsiveScriptIndicator.enable();
@ -1451,6 +1489,9 @@ Zotero.Sync.Server = new function () {
Zotero.suppressUIUpdates = false;
_updatesInProgress = false;
if (rethrow) {
throw (e);
}
_error(e);
}
@ -1662,7 +1703,7 @@ Zotero.Sync.Server = new function () {
Zotero.pumpGenerator(gen, false, errorHandler);
}
catch (e) {
errorHandler(e);
errorHandler(e, true);
}
}
catch (e) {
@ -2987,17 +3028,16 @@ Zotero.Sync.Server.Data = new function() {
obj.attachmentSyncState =
Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
}
// Set existing attachments mtime update check
// Set existing attachments for mtime update check
else {
var mtime = objectNode.getAttribute('storageModTime');
if (mtime) {
var lk = Zotero.Items.getLibraryKeyHash(obj)
// Convert previously used Unix timestamps to ms-based timestamps
if (mtime < 10000000000) {
Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
mtime = mtime * 1000;
}
itemStorageModTimes[lk] = parseInt(mtime);
itemStorageModTimes[obj.id] = parseInt(mtime);
}
}
}
@ -3313,18 +3353,8 @@ Zotero.Sync.Server.Data = new function() {
// Check mod times and hashes of updated items against stored values to see
// if they've been updated elsewhere and mark for download if so
if (type == 'item') {
var ids = [];
var modTimes = {};
for (var libraryKeyHash in itemStorageModTimes) {
var lk = Zotero.Items.parseLibraryKeyHash(libraryKeyHash);
var item = Zotero.Items.getByLibraryAndKey(lk.libraryID, lk.key);
ids.push(item.id);
modTimes[item.id] = itemStorageModTimes[libraryKeyHash];
}
if (ids.length > 0) {
Zotero.Sync.Storage.checkForUpdatedFiles(ids, modTimes);
}
if (type == 'item' && Object.keys(itemStorageModTimes).length) {
Zotero.Sync.Storage.checkForUpdatedFiles(itemStorageModTimes);
}
}

View file

@ -83,12 +83,9 @@ Zotero.URI = new function () {
* Get path portion of library URI (e.g., users/6 or groups/1)
*/
this.getLibraryPath = function (libraryID) {
if (libraryID) {
libraryID = libraryID ? parseInt(libraryID) : 0;
var libraryType = Zotero.Libraries.getType(libraryID);
}
else {
libraryType = 'user';
}
switch (libraryType) {
case 'user':
var id = Zotero.userID;

View file

@ -153,6 +153,7 @@ var ZoteroPane = new function()
var collectionsTree = document.getElementById('zotero-collections-tree');
collectionsTree.view = ZoteroPane_Local.collectionsView;
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
collectionsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true);
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
var itemsTree = document.getElementById('zotero-items-tree');
@ -2509,11 +2510,32 @@ var ZoteroPane = new function()
var t = event.originalTarget;
var tree = t.parentNode;
var itemGroup = ZoteroPane_Local.getItemGroup();
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
if (row.value == -1) {
return;
}
var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
// Prevent the tree's select event from being called for a click
// on a library sync error icon
if (tree.id == 'zotero-collections-tree') {
if (itemGroup.isLibrary(true)) {
if (col.value.id == 'zotero-collections-sync-status-column') {
var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
var errors = Zotero.Sync.Runner.getErrors(libraryID);
if (errors) {
event.stopPropagation();
return;
}
}
}
}
// Automatically select all equivalent items when clicking on an item
// in duplicates view
if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
else if (tree.id == 'zotero-items-tree' && itemGroup.isDuplicates()) {
// Trigger only on primary-button single clicks with modifiers
// (so that items can still be selected and deselected manually)
if (!event || event.detail != 1 || event.button != 0 || event.metaKey || event.shiftKey) {
@ -2558,22 +2580,52 @@ var ZoteroPane = new function()
var tree = t.parentNode;
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
// We care only about primary-button double and triple clicks
if (!event || (event.detail != 2 && event.detail != 3) || event.button != 0) {
if (row.value == -1) {
return;
}
var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
// Show the error panel when clicking a library-specific
// sync error icon
if (itemGroup.isLibrary(true)) {
if (col.value.id == 'zotero-collections-sync-status-column') {
var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
var errors = Zotero.Sync.Runner.getErrors(libraryID);
if (!errors) {
return;
}
var panel = Zotero.Sync.Runner.updateErrorPanel(window.document, errors);
var anchor = document.getElementById('zotero-collections-tree-shim');
var x = {}, y = {}, width = {}, height = {};
tree.treeBoxObject.getCoordsForCellItem(row.value, col.value, 'image', x, y, width, height);
x = x.value + Math.round(width.value / 2);
y = y.value + height.value + 3;
panel.openPopup(anchor, "after_start", x, y, false, false);
}
return;
}
// The Mozilla tree binding fires select() in mousedown(),
// but if when it gets to click() the selection differs from
// what it expects (say, because multiple items had been
// selected during mousedown()), it fires select() again.
// We prevent that here.
var itemGroup = ZoteroPane_Local.getItemGroup();
if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
// selected during mousedown(), as is the case in duplicates mode),
// it fires select() again. We prevent that here.
else if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
if (event.metaKey || event.shiftKey) {
return;
}
// Allow twisty click to work in duplicates mode
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
if (obj.value == 'twisty') {
return;
}
@ -2597,9 +2649,6 @@ var ZoteroPane = new function()
}
}
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
// obj.value == 'cell'/'text'/'image'
if (!obj.value) {
return;
@ -3424,6 +3473,8 @@ var ZoteroPane = new function()
function viewAttachment(itemIDs, event, noLocateOnMissing, forceExternalViewer) {
Components.utils.import("resource://zotero/q.js");
// If view isn't editable, don't show Locate button, since the updated
// path couldn't be sent back up
if (!this.collectionsView.editable) {
@ -3478,38 +3529,39 @@ var ZoteroPane = new function()
}
}
else {
if (item.isImportedAttachment() && Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
let downloadedItem = item;
var started = Zotero.Sync.Storage.downloadFile(item, {
onStart: function (request) {
if (!(request instanceof Zotero.Sync.Storage.Request)) {
throw new Error("Invalid request object");
if (!item.isImportedAttachment() || !Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
return;
}
},
onProgress: function (progress, progressMax) {
},
onStop: function () {
let downloadedItem = item;
Q.fcall(function () {
return Zotero.Sync.Storage.downloadFile(
downloadedItem,
{
onProgress: function (progress, progressMax) {}
});
})
.then(function () {
if (!downloadedItem.getFile()) {
ZoteroPane_Local.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
ZoteroPane_Local.showAttachmentNotFoundDialog(downloadedItem.id, noLocateOnMissing);
return;
}
// check if unchanged?
// maybe not necessary, since we'll get an error if there's an error
Zotero.Notifier.trigger('redraw', 'item', []);
ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
},
});
if (started) {
continue;
}
}
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
})
.fail(function (e) {
// TODO: show error somewhere else
Zotero.debug(e, 1);
ZoteroPane_Local.syncAlert(e);
})
.end();
}
}
}
@ -3744,6 +3796,83 @@ var ZoteroPane = new function()
}
this.syncAlert = function (e) {
e = Zotero.Sync.Runner.parseSyncError(e);
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
// Warning
if (e.status == 'warning') {
var title = Zotero.getString('general.warning');
// If secondary button not specified, just use an alert
if (e.buttonText) {
var buttonText = e.buttonText;
}
else {
ps.alert(null, title, e.message);
return;
}
var index = ps.confirmEx(
null,
title,
e.message,
buttonFlags,
"",
buttonText,
"", null, {}
);
if (index == 1) {
setTimeout(function () { buttonCallback(); }, 1);
}
}
// Error
else if (e.status == 'error') {
var title = Zotero.getString('general.error');
// If secondary button is explicitly null, just use an alert
if (buttonText === null) {
ps.alert(null, title, e.message);
return;
}
if (typeof buttonText == 'undefined') {
var buttonText = Zotero.getString('errorReport.reportError');
var buttonCallback = function () {
ZoteroPane.reportErrors();
};
}
else {
var buttonText = e.buttonText;
var buttonCallback = e.buttonCallback;
}
var index = ps.confirmEx(
null,
title,
e.message,
buttonFlags,
"",
buttonText,
"", null, {}
);
if (index == 1) {
setTimeout(function () { buttonCallback(); }, 1);
}
}
// Upgrade
else if (e.status == 'upgrade') {
ps.alert(null, "", e.message);
}
};
this.createParentItemsFromSelected = function () {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();

View file

@ -192,32 +192,22 @@
value="0" tooltip="zotero-tb-sync-progress-tooltip">
</progressmeter>
<tooltip id="zotero-tb-sync-progress-tooltip" noautohide="true">
<grid>
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<hbox id="zotero-tb-sync-progress-tooltip-progress">
<label value="&zotero.sync.storage.progress;"/>
<label id="zotero-tb-sync-progress-tooltip-progress"/>
</row>
<row>
<label value="&zotero.sync.storage.downloads;"/>
<label
id="zotero-tb-sync-progress-tooltip-downloads"/>
</row>
<row>
<label value="&zotero.sync.storage.uploads;"/>
<label
id="zotero-tb-sync-progress-tooltip-uploads"/>
</row>
</rows>
</grid>
<label/>
</hbox>
<zoterofilesyncstatus id="zotero-tb-sync-progress-status"/>
</tooltip>
</hbox>
</hbox>
<toolbarbutton id="zotero-tb-sync-warning" hidden="true"/>
<toolbarbutton id="zotero-tb-sync-error" hidden="true"/>
<!-- We put this here, but it's used for all sync errors -->
<panel id="zotero-sync-error-panel" type="arrow">
<vbox>
<hbox id="zotero-sync-error-panel-content"/>
<hbox id="zotero-sync-error-panel-buttons"/>
</vbox>
</panel>
<toolbarbutton id="zotero-tb-sync" class="zotero-tb-button" tooltip="_child"
oncommand="Zotero.Sync.Server.canAutoResetClient = true; Zotero.Sync.Server.manualSyncRequired = false; Zotero.Sync.Runner.sync()">
<tooltip
@ -292,6 +282,10 @@
<hbox id="zotero-trees" flex="1">
<vbox id="zotero-collections-pane" zotero-persist="width">
<!-- This is used for positioning the sync error icon panel
under specific tree cells, which don't exist as
elements on their own -->
<box id="zotero-collections-tree-shim"/>
<!-- This extra vbox prevents the toolbar from getting compressed when resizing
the tag selector to max height -->
<tree id="zotero-collections-tree" hidecolumnpicker="true" context="zotero-collectionmenu"
@ -309,6 +303,9 @@
flex="1"
primary="true"
hideheader="true"/>
<treecol
id="zotero-collections-sync-status-column"
hideheader="true"/>
</treecols>
<treechildren/>
</tree>

View file

@ -683,9 +683,12 @@ sync.status.uploadingData = Uploading data to sync server
sync.status.uploadAccepted = Upload accepted \u2014 waiting for sync server
sync.status.syncingFiles = Syncing files
sync.storage.mbRemaining = %SMB remaining
sync.storage.kbRemaining = %SKB remaining
sync.storage.filesRemaining = %1$S/%2$S files
sync.storage.none = None
sync.storage.downloads = Downloads:
sync.storage.uploads = Uploads:
sync.storage.localFile = Local File
sync.storage.remoteFile = Remote File
sync.storage.savedFile = Saved File

View file

@ -20,11 +20,19 @@
min-height: 5.2em;
}
#zotero-collections-tree treechildren::-moz-tree-image
#zotero-collections-tree treechildren::-moz-tree-image(primary)
{
margin-right: 5px;
}
#zotero-collections-tree #zotero-collections-sync-status-column {
width: 35px;
}
#zotero-collections-tree[hidevscroll] #zotero-collections-sync-status-column {
width: 21px;
}
/* Set by setHighlightedRows() and getRowProperties() in collectionTreeView.js) */
#zotero-collections-tree treechildren::-moz-tree-row(highlighted)
{
@ -54,6 +62,145 @@
margin-right: 5px;
}
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie)
{
margin: 1px 0 0;
list-style-image: url(chrome://zotero/skin/pie.png);
height: 16px;
-moz-image-region: rect(0px, 32px, 32px, 0px);
}
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie1) { -moz-image-region: rect(0px, 32px, 32px, 0x); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie2) { -moz-image-region: rect(0px, 64px, 32px, 32px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie3) { -moz-image-region: rect(0px, 96px, 32px, 64px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie4) { -moz-image-region: rect(0px, 128px, 32px, 96px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie5) { -moz-image-region: rect(0px, 160px, 32px, 128px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie6) { -moz-image-region: rect(0px, 192px, 32px, 160px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie7) { -moz-image-region: rect(0px, 224px, 32px, 192px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie8) { -moz-image-region: rect(0px, 256px, 32px, 224px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie9) { -moz-image-region: rect(0px, 288px, 32px, 256px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie10) { -moz-image-region: rect(0px, 320px, 32px, 288px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie11) { -moz-image-region: rect(0px, 352px, 32px, 320px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie12) { -moz-image-region: rect(0px, 384px, 32px, 352px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie13) { -moz-image-region: rect(0px, 416px, 32px, 384px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie14) { -moz-image-region: rect(0px, 448px, 32px, 416px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie15) { -moz-image-region: rect(0px, 480px, 32px, 448px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie16) { -moz-image-region: rect(0px, 512px, 32px, 480px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie17) { -moz-image-region: rect(0px, 544px, 32px, 512px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie18) { -moz-image-region: rect(0px, 576px, 32px, 544px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie19) { -moz-image-region: rect(0px, 608px, 32px, 576px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie20) { -moz-image-region: rect(0px, 640px, 32px, 608px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie21) { -moz-image-region: rect(0px, 672px, 32px, 640px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie22) { -moz-image-region: rect(0px, 704px, 32px, 672px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie23) { -moz-image-region: rect(0px, 736px, 32px, 704px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie24) { -moz-image-region: rect(0px, 768px, 32px, 736px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie25) { -moz-image-region: rect(0px, 800px, 32px, 768px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie26) { -moz-image-region: rect(0px, 832px, 32px, 800px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie27) { -moz-image-region: rect(0px, 864px, 32px, 832px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie28) { -moz-image-region: rect(0px, 896px, 32px, 864px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie29) { -moz-image-region: rect(0px, 928px, 32px, 896px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie30) { -moz-image-region: rect(0px, 960px, 32px, 928px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie31) { -moz-image-region: rect(0px, 992px, 32px, 960px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie32) { -moz-image-region: rect(0px, 1024px, 32px, 992px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie33) { -moz-image-region: rect(0px, 1056px, 32px, 1024px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie34) { -moz-image-region: rect(0px, 1088px, 32px, 1056px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie35) { -moz-image-region: rect(0px, 1120px, 32px, 1088px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie36) { -moz-image-region: rect(0px, 1152px, 32px, 1120px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie37) { -moz-image-region: rect(0px, 1184px, 32px, 1152px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie38) { -moz-image-region: rect(0px, 1216px, 32px, 1184px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie39) { -moz-image-region: rect(0px, 1248px, 32px, 1216px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie40) { -moz-image-region: rect(0px, 1280px, 32px, 1248px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie41) { -moz-image-region: rect(0px, 1312px, 32px, 1280px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie42) { -moz-image-region: rect(0px, 1344px, 32px, 1312px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie43) { -moz-image-region: rect(0px, 1376px, 32px, 1344px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie44) { -moz-image-region: rect(0px, 1408px, 32px, 1376px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie45) { -moz-image-region: rect(0px, 1440px, 32px, 1408px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie46) { -moz-image-region: rect(0px, 1472px, 32px, 1440px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie47) { -moz-image-region: rect(0px, 1504px, 32px, 1472px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie48) { -moz-image-region: rect(0px, 1536px, 32px, 1504px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie49) { -moz-image-region: rect(0px, 1568px, 32px, 1536px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie50) { -moz-image-region: rect(0px, 1600px, 32px, 1568px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie51) { -moz-image-region: rect(0px, 1632px, 32px, 1600px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie52) { -moz-image-region: rect(0px, 1664px, 32px, 1632px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie53) { -moz-image-region: rect(0px, 1696px, 32px, 1664px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie54) { -moz-image-region: rect(0px, 1728px, 32px, 1696px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie55) { -moz-image-region: rect(0px, 1760px, 32px, 1728px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie56) { -moz-image-region: rect(0px, 1792px, 32px, 1760px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie57) { -moz-image-region: rect(0px, 1824px, 32px, 1792px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie58) { -moz-image-region: rect(0px, 1856px, 32px, 1824px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie59) { -moz-image-region: rect(0px, 1888px, 32px, 1856px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie60) { -moz-image-region: rect(0px, 1920px, 32px, 1888px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie61) { -moz-image-region: rect(0px, 1952px, 32px, 1920px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie62) { -moz-image-region: rect(0px, 1984px, 32px, 1952px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie63) { -moz-image-region: rect(0px, 2016px, 32px, 1984px); }
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie64) { -moz-image-region: rect(0px, 2048px, 32px, 2016px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie1) { -moz-image-region: rect(32px, 32px, 64px, 0px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie2) { -moz-image-region: rect(32px, 64px, 64px, 32px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie3) { -moz-image-region: rect(32px, 96px, 64px, 64px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie4) { -moz-image-region: rect(32px, 128px, 64px, 96px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie5) { -moz-image-region: rect(32px, 160px, 64px, 128px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie6) { -moz-image-region: rect(32px, 192px, 64px, 160px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie7) { -moz-image-region: rect(32px, 224px, 64px, 192px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie8) { -moz-image-region: rect(32px, 256px, 64px, 224px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie9) { -moz-image-region: rect(32px, 288px, 64px, 256px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie10) { -moz-image-region: rect(32px, 320px, 64px, 288px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie11) { -moz-image-region: rect(32px, 352px, 64px, 320px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie12) { -moz-image-region: rect(32px, 384px, 64px, 352px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie13) { -moz-image-region: rect(32px, 416px, 64px, 384px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie14) { -moz-image-region: rect(32px, 448px, 64px, 416px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie15) { -moz-image-region: rect(32px, 480px, 64px, 448px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie16) { -moz-image-region: rect(32px, 512px, 64px, 480px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie17) { -moz-image-region: rect(32px, 544px, 64px, 512px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie18) { -moz-image-region: rect(32px, 576px, 64px, 544px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie19) { -moz-image-region: rect(32px, 608px, 64px, 576px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie20) { -moz-image-region: rect(32px, 640px, 64px, 608px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie21) { -moz-image-region: rect(32px, 672px, 64px, 640px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie22) { -moz-image-region: rect(32px, 704px, 64px, 672px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie23) { -moz-image-region: rect(32px, 736px, 64px, 704px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie24) { -moz-image-region: rect(32px, 768px, 64px, 736px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie25) { -moz-image-region: rect(32px, 800px, 64px, 768px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie26) { -moz-image-region: rect(32px, 832px, 64px, 800px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie27) { -moz-image-region: rect(32px, 864px, 64px, 832px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie28) { -moz-image-region: rect(32px, 896px, 64px, 864px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie29) { -moz-image-region: rect(32px, 928px, 64px, 896px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie30) { -moz-image-region: rect(32px, 960px, 64px, 928px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie31) { -moz-image-region: rect(32px, 992px, 64px, 960px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie32) { -moz-image-region: rect(32px, 1024px, 64px, 992px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie33) { -moz-image-region: rect(32px, 1056px, 64px, 1024px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie34) { -moz-image-region: rect(32px, 1088px, 64px, 1056px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie35) { -moz-image-region: rect(32px, 1120px, 64px, 1088px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie36) { -moz-image-region: rect(32px, 1152px, 64px, 1120px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie37) { -moz-image-region: rect(32px, 1184px, 64px, 1152px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie38) { -moz-image-region: rect(32px, 1216px, 64px, 1184px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie39) { -moz-image-region: rect(32px, 1248px, 64px, 1216px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie40) { -moz-image-region: rect(32px, 1280px, 64px, 1248px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie41) { -moz-image-region: rect(32px, 1312px, 64px, 1280px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie42) { -moz-image-region: rect(32px, 1344px, 64px, 1312px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie43) { -moz-image-region: rect(32px, 1376px, 64px, 1344px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie44) { -moz-image-region: rect(32px, 1408px, 64px, 1376px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie45) { -moz-image-region: rect(32px, 1440px, 64px, 1408px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie46) { -moz-image-region: rect(32px, 1472px, 64px, 1440px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie47) { -moz-image-region: rect(32px, 1504px, 64px, 1472px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie48) { -moz-image-region: rect(32px, 1536px, 64px, 1504px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie49) { -moz-image-region: rect(32px, 1568px, 64px, 1536px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie50) { -moz-image-region: rect(32px, 1600px, 64px, 1568px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie51) { -moz-image-region: rect(32px, 1632px, 64px, 1600px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie52) { -moz-image-region: rect(32px, 1664px, 64px, 1632px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie53) { -moz-image-region: rect(32px, 1696px, 64px, 1664px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie54) { -moz-image-region: rect(32px, 1728px, 64px, 1696px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie55) { -moz-image-region: rect(32px, 1760px, 64px, 1728px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie56) { -moz-image-region: rect(32px, 1792px, 64px, 1760px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie57) { -moz-image-region: rect(32px, 1824px, 64px, 1792px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie58) { -moz-image-region: rect(32px, 1856px, 64px, 1824px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie59) { -moz-image-region: rect(32px, 1888px, 64px, 1856px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie60) { -moz-image-region: rect(32px, 1920px, 64px, 1888px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie61) { -moz-image-region: rect(32px, 1952px, 64px, 1920px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie62) { -moz-image-region: rect(32px, 1984px, 64px, 1952px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie63) { -moz-image-region: rect(32px, 2016px, 64px, 1984px); }
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie64) { -moz-image-region: rect(32px, 2048px, 64px, 2016px); }
/* Set tag colors */
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFFF) { color:#FFFFFF }
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCCC) { color:#FFCCCC }
@ -380,28 +527,58 @@
margin-left: 0;
}
#zotero-tb-sync-progress-tooltip row label:first-child
{
text-align: right;
font-weight: bold;
#zotero-tb-sync-progress-tooltip-progress {
margin-bottom: 5px;
}
#zotero-tb-sync-warning, #zotero-tb-sync-warning[mode=warning]
/* Library names */
#zotero-tb-sync-progress-tooltip rows > label
{
font-weight: bold;
margin-top: 8px;
}
/* Queue names */
#zotero-tb-sync-progress-tooltip row:not(.library-name) label:first-child
{
text-align: right;
}
/* Sync error icon */
#zotero-tb-sync-error, #zotero-tb-sync-error[mode=warning]
{
list-style-image: url(chrome://zotero/skin/error.png);
margin-right: -5px;
}
#zotero-tb-sync-warning[mode=error]
#zotero-tb-sync-error[mode=error]
{
list-style-image: url(chrome://zotero/skin/exclamation.png);
}
#zotero-tb-sync-warning[mode=upgrade]
#zotero-tb-sync-error[mode=upgrade]
{
list-style-image: url(chrome://zotero/skin/bell_error.png);
}
#zotero-tb-sync-error {
/*border: 1px orange dashed;*/
}
/* Sync error panel */
#zotero-sync-error-panel {
margin-right: 0px;
}
#zotero-sync-error-panel description {
width: 350px;
white-space: pre-wrap;
}
#zotero-sync-error-panel-buttons {
-moz-box-pack: end;
}
#zotero-tb-sync {
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
margin-left: -6px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -156,6 +156,11 @@ zoteroguidancepanel
-moz-binding: url('chrome://zotero/content/bindings/columnpicker.xml#extended-columnpicker');
}
zoterofilesyncstatus {
-moz-binding: url('chrome://zotero/content/bindings/filesyncstatus.xml#file-sync-status');
}
label.zotero-text-link {
-moz-binding: url('chrome://zotero/content/bindings/text-link.xml#text-link');
-moz-user-focus: normal;

View file

@ -95,7 +95,7 @@ const xpcomFilesLocal = [
'sync',
'storage',
'storage/streamListener',
'storage/eventManager',
'storage/eventLog',
'storage/queueManager',
'storage/queue',
'storage/request',