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') {
this._treebox.invalidate();
// 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);
}
return this._verified;
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('username', function () {
try {
return this._username;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._username;
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('password', function () {
try {
return this._password;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._password;
});
Zotero.Sync.Storage.Mode.prototype.__defineSetter__('password', function (val) {
try {
this._password = val;
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
this._password = val;
});
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);
}
return this._init();
}
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);
}
return this._checkServer(callback);
}
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);
}
return this._checkServerCallback(uri, status, window, skipSuccessMessage);
}
Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) {
try {
return this._cacheCredentials(callback);
}
catch (e) {
Zotero.Sync.Storage.EventManager.error(e);
}
return this._cacheCredentials(callback);
}
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) {
queues.push(queue);
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;
if (this.channel) {
try {
Zotero.debug("Stopping request '" + this.name + "'");
this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
}
catch (e) {
Zotero.debug(e);
}
}
catch (e) {}
if (!this._running || !this.channel || finishNow) {
this.finish();
return;
else {
this._finish();
}
Zotero.debug("Stopping request '" + this.name + "'");
this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
}
/**
* 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?
this.queue.finishedRequests++;
this.queue.updateProgress();
if (this._onStop) {
for each(var f in this._onStop) {
f();
}
// TEMP: mechanism for failures?
try {
this.queue.finishedRequests++;
this.queue.updateProgress();
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
this._deferred.reject(e);
throw e;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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);
},
// ZFS skip
onSkip: function () {
setTimeout(function () {
if (skipSyncNeeded) {
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);
},
Zotero.Sync.Storage.sync()
.then(function (results) {
Zotero.debug("File sync is finished");
// WebDAV skip
onSkip: function () {
zfsSync();
},
if (results.errors.length) {
Zotero.Sync.Runner.setErrors(results.errors);
return;
}
// WebDAV cancel
onStop: Zotero.Sync.Runner.stop,
// WebDAV failure
onError: Zotero.Sync.Runner.error
if (results.changesMade) {
Zotero.debug("Changes made during file sync "
+ "-- performing additional data sync");
Zotero.Sync.Server.sync(finalCallbacks);
}
else {
Zotero.Sync.Runner.stop();
}
})
.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;
// 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;
}
default:
throw ("Invalid sync icon status '" + status
+ "' in Zotero.Sync.Runner.setSyncIcon()");
if (!_errorsByLibrary[e.libraryID]) {
_errorsByLibrary[e.libraryID] = [];
}
_errorsByLibrary[e.libraryID].push(e);
}
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;
}
// 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 (!message) {
if (e.message) {
message = e.message;
}
else {
message = e;
}
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);
var upgradeRequired = false;
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) {
var libraryType = Zotero.Libraries.getType(libraryID);
}
else {
libraryType = 'user';
}
libraryID = libraryID ? parseInt(libraryID) : 0;
var libraryType = Zotero.Libraries.getType(libraryID);
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");
}
},
onProgress: function (progress, progressMax) {
},
onStop: function () {
if (!downloadedItem.getFile()) {
ZoteroPane_Local.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
return;
}
// check if unchanged?
// maybe not necessary, since we'll get an error if there's an error
ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
},
});
if (started) {
continue;
}
if (!item.isImportedAttachment() || !Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
return;
}
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
let downloadedItem = item;
Q.fcall(function () {
return Zotero.Sync.Storage.downloadFile(
downloadedItem,
{
onProgress: function (progress, progressMax) {}
});
})
.then(function () {
if (!downloadedItem.getFile()) {
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);
})
.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>
<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>
<hbox id="zotero-tb-sync-progress-tooltip-progress">
<label value="&zotero.sync.storage.progress;"/>
<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',