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:
parent
4c8431ca7d
commit
bb93f019dc
27 changed files with 2895 additions and 2163 deletions
|
@ -59,7 +59,7 @@
|
|||
padding-top: 1px;
|
||||
}
|
||||
|
||||
#zotero-tb-sync-warning[error=true]
|
||||
#zotero-tb-sync-error[error=true]
|
||||
{
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
#zotero-tb-sync-warning {
|
||||
#zotero-tb-sync-error {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
|
|
182
chrome/content/zotero/bindings/filesyncstatus.xml
Normal file
182
chrome/content/zotero/bindings/filesyncstatus.xml
Normal 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>
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
}
|
||||
else {
|
||||
sql += "libraryID";
|
||||
if (libraryID) {
|
||||
if (libraryID && libraryID !== '0') {
|
||||
sql += "=? ";
|
||||
params.push(libraryID);
|
||||
}
|
||||
|
|
|
@ -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 = {};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
94
chrome/content/zotero/xpcom/storage/eventLog.js
Normal file
94
chrome/content/zotero/xpcom/storage/eventLog.js
Normal 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] = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -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 = [];
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
BIN
chrome/skin/default/zotero/pie.png
Normal file
BIN
chrome/skin/default/zotero/pie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -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;
|
||||
|
|
|
@ -95,7 +95,7 @@ const xpcomFilesLocal = [
|
|||
'sync',
|
||||
'storage',
|
||||
'storage/streamListener',
|
||||
'storage/eventManager',
|
||||
'storage/eventLog',
|
||||
'storage/queueManager',
|
||||
'storage/queue',
|
||||
'storage/request',
|
||||
|
|
Loading…
Reference in a new issue