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
|
@ -59,7 +59,7 @@
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#zotero-tb-sync-warning[error=true]
|
#zotero-tb-sync-error[error=true]
|
||||||
{
|
{
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#zotero-tb-sync-warning {
|
#zotero-tb-sync-error {
|
||||||
margin-right: 2px;
|
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)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +254,11 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action == 'redraw') {
|
||||||
|
this._treebox.invalidate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.selection.selectEventsSuppressed = true;
|
this.selection.selectEventsSuppressed = true;
|
||||||
var savedSelection = this.saveSelection();
|
var savedSelection = this.saveSelection();
|
||||||
|
|
||||||
|
@ -420,8 +425,12 @@ Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
|
||||||
{
|
{
|
||||||
var obj = this._getItemAtRow(row);
|
var obj = this._getItemAtRow(row);
|
||||||
|
|
||||||
if(column.id == "zotero-collections-name-column")
|
if (column.id == 'zotero-collections-name-column') {
|
||||||
return obj.getName();
|
return obj.getName();
|
||||||
|
}
|
||||||
|
else if (column.id == 'zotero-collections-sync-status-column') {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -430,7 +439,41 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
|
||||||
{
|
{
|
||||||
var itemGroup = this._getItemAtRow(row);
|
var itemGroup = this._getItemAtRow(row);
|
||||||
var collectionType = itemGroup.type;
|
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) {
|
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':
|
case 'trash':
|
||||||
if (this._trashNotEmpty[itemGroup.ref.libraryID ? itemGroup.ref.libraryID : 0]) {
|
if (this._trashNotEmpty[itemGroup.ref.libraryID ? itemGroup.ref.libraryID : 0]) {
|
||||||
collectionType += '-full';
|
collectionType += '-full';
|
||||||
|
@ -446,7 +489,7 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'group':
|
|
||||||
collectionType = 'library';
|
collectionType = 'library';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sql += "libraryID";
|
sql += "libraryID";
|
||||||
if (libraryID) {
|
if (libraryID && libraryID !== '0') {
|
||||||
sql += "=? ";
|
sql += "=? ";
|
||||||
params.push(libraryID);
|
params.push(libraryID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -674,7 +674,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
|
||||||
|
|
||||||
this._disabledCheck();
|
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) {
|
if (!field) {
|
||||||
throw ("Field not specified in Item.setField()");
|
throw ("Field not specified in Item.setField()");
|
||||||
|
@ -1609,6 +1609,7 @@ Zotero.Item.prototype.save = function() {
|
||||||
'libraryID',
|
'libraryID',
|
||||||
'key'
|
'key'
|
||||||
];
|
];
|
||||||
|
|
||||||
for each(var field in updateFields) {
|
for each(var field in updateFields) {
|
||||||
if (this._changedPrimaryData && this._changedPrimaryData[field]) {
|
if (this._changedPrimaryData && this._changedPrimaryData[field]) {
|
||||||
sql += field + '=?, ';
|
sql += field + '=?, ';
|
||||||
|
@ -3000,7 +3001,6 @@ Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
|
||||||
if (val === this.attachmentLinkMode) {
|
if (val === this.attachmentLinkMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._changedAttachmentData) {
|
if (!this._changedAttachmentData) {
|
||||||
this._changedAttachmentData = {};
|
this._changedAttachmentData = {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ Zotero.Libraries = new function () {
|
||||||
|
|
||||||
|
|
||||||
this.getName = function (libraryID) {
|
this.getName = function (libraryID) {
|
||||||
|
if (!libraryID) {
|
||||||
|
return Zotero.getString('pane.collections.library');
|
||||||
|
}
|
||||||
|
|
||||||
var type = this.getType(libraryID);
|
var type = this.getType(libraryID);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'group':
|
case 'group':
|
||||||
|
@ -59,6 +63,9 @@ Zotero.Libraries = new function () {
|
||||||
|
|
||||||
|
|
||||||
this.getType = function (libraryID) {
|
this.getType = function (libraryID) {
|
||||||
|
if (libraryID === 0) {
|
||||||
|
return 'user';
|
||||||
|
}
|
||||||
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
|
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
|
||||||
var libraryType = Zotero.DB.valueQuery(sql, libraryID);
|
var libraryType = Zotero.DB.valueQuery(sql, libraryID);
|
||||||
if (!libraryType) {
|
if (!libraryType) {
|
||||||
|
|
|
@ -402,7 +402,8 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (checkParams && numParams > 0) {
|
if (checkParams && numParams > 0) {
|
||||||
throw ("No parameters provided for query containing placeholders");
|
throw ("No parameters provided for query containing placeholders "
|
||||||
|
+ "[QUERY: " + sql + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return statement;
|
return statement;
|
||||||
|
|
|
@ -344,9 +344,29 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
||||||
var savedSelection = this.saveSelection();
|
var savedSelection = this.saveSelection();
|
||||||
var previousRow = false;
|
var previousRow = false;
|
||||||
|
|
||||||
// Redraw the tree (for tag color changes)
|
// Redraw the tree (for tag color and progress changes)
|
||||||
if (action == 'redraw') {
|
if (action == 'redraw') {
|
||||||
|
// Redraw specific rows
|
||||||
|
if (type == 'item' && ids.length) {
|
||||||
|
// Redraw specific cells
|
||||||
|
if (extraData && extraData.column) {
|
||||||
|
var col = this._treebox.columns.getNamedColumn(
|
||||||
|
'zotero-items-column-' + extraData.column
|
||||||
|
);
|
||||||
|
for each(var id in ids) {
|
||||||
|
this._treebox.invalidateCell(this._itemRowMap[id], col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for each(var id in ids) {
|
||||||
|
this._treebox.invalidateRow(this._itemRowMap[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Redraw the whole tree
|
||||||
|
else {
|
||||||
this._treebox.invalidate();
|
this._treebox.invalidate();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -849,6 +869,12 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
|
||||||
if (this._itemGroup.isTrash()) return false;
|
if (this._itemGroup.isTrash()) return false;
|
||||||
|
|
||||||
var treerow = this._getItemAtRow(row);
|
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.level === 0) {
|
||||||
if (treerow.ref.isRegularItem()) {
|
if (treerow.ref.isRegularItem()) {
|
||||||
switch (treerow.ref.getBestAttachmentState()) {
|
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.getColumnProperties = function(col, prop) { }
|
||||||
Zotero.ItemTreeView.prototype.getCellProperties = function(row, 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
|
// Set tag colors
|
||||||
//
|
//
|
||||||
|
@ -2767,6 +2794,30 @@ Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
|
||||||
getService(Components.interfaces.nsIAtomService);
|
getService(Components.interfaces.nsIAtomService);
|
||||||
prop.AppendElement(aServ.getAtom("contextRow"));
|
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)
|
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 = 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 () {
|
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('verified', function () {
|
||||||
try {
|
|
||||||
return this._verified;
|
return this._verified;
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('active', function () {
|
|
||||||
try {
|
|
||||||
return this._enabled && this._verified && this._initFromPrefs();
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('username', function () {
|
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('username', function () {
|
||||||
try {
|
|
||||||
return this._username;
|
return this._username;
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('password', function () {
|
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('password', function () {
|
||||||
try {
|
|
||||||
return this._password;
|
return this._password;
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.__defineSetter__('password', function (val) {
|
Zotero.Sync.Storage.Mode.prototype.__defineSetter__('password', function (val) {
|
||||||
try {
|
|
||||||
this._password = val;
|
this._password = val;
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.init = function () {
|
Zotero.Sync.Storage.Mode.prototype.init = function () {
|
||||||
try {
|
|
||||||
return this._init();
|
return this._init();
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.initFromPrefs = function () {
|
|
||||||
try {
|
|
||||||
return this._initFromPrefs();
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.sync = function (observer) {
|
Zotero.Sync.Storage.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) {
|
Zotero.Sync.Storage.Mode.prototype.downloadFile = function (request) {
|
||||||
try {
|
return this._downloadFile(request);
|
||||||
this._downloadFile(request);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.uploadFile = function (request) {
|
Zotero.Sync.Storage.Mode.prototype.uploadFile = function (request) {
|
||||||
try {
|
return this._uploadFile(request);
|
||||||
this._uploadFile(request);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.getLastSyncTime = function (callback) {
|
Zotero.Sync.Storage.Mode.prototype.getLastSyncTime = function (libraryID) {
|
||||||
try {
|
return this._getLastSyncTime(libraryID);
|
||||||
this._getLastSyncTime(callback);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
|
Zotero.Sync.Storage.Mode.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
|
||||||
try {
|
return this._setLastSyncTime(callback, useLastSyncTime);
|
||||||
this._setLastSyncTime(callback, useLastSyncTime);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.checkServer = function (callback) {
|
Zotero.Sync.Storage.Mode.prototype.checkServer = function (callback) {
|
||||||
try {
|
|
||||||
return this._checkServer(callback);
|
return this._checkServer(callback);
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.checkServerCallback = function (uri, status, window, skipSuccessMessage) {
|
Zotero.Sync.Storage.Mode.prototype.checkServerCallback = function (uri, status, window, skipSuccessMessage) {
|
||||||
try {
|
|
||||||
return this._checkServerCallback(uri, status, window, skipSuccessMessage);
|
return this._checkServerCallback(uri, status, window, skipSuccessMessage);
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) {
|
Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) {
|
||||||
try {
|
|
||||||
return this._cacheCredentials(callback);
|
return this._cacheCredentials(callback);
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.purgeDeletedStorageFiles = function (callback) {
|
Zotero.Sync.Storage.Mode.prototype.purgeDeletedStorageFiles = function (callback) {
|
||||||
try {
|
return this._purgeDeletedStorageFiles(callback);
|
||||||
this._purgeDeletedStorageFiles(callback);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.Mode.prototype.purgeOrphanedStorageFiles = function (callback) {
|
Zotero.Sync.Storage.Mode.prototype.purgeOrphanedStorageFiles = function (callback) {
|
||||||
try {
|
return this._purgeOrphanedStorageFiles(callback);
|
||||||
this._purgeOrphanedStorageFiles(callback);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,14 @@
|
||||||
/**
|
/**
|
||||||
* Queue for storage sync transfer requests
|
* 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.Sync.Storage.Queue = function (type, libraryID) {
|
||||||
Zotero.debug("Initializing " + name + " queue");
|
Zotero.debug("Initializing " + type + " queue for library " + libraryID);
|
||||||
|
|
||||||
// Public properties
|
// Public properties
|
||||||
this.name = name;
|
this.type = type;
|
||||||
|
this.libraryID = libraryID;
|
||||||
this.maxConcurrentRequests = 1;
|
this.maxConcurrentRequests = 1;
|
||||||
this.activeRequests = 0;
|
this.activeRequests = 0;
|
||||||
this.totalRequests = 0;
|
this.totalRequests = 0;
|
||||||
|
@ -42,16 +43,25 @@ Zotero.Sync.Storage.Queue = function (name) {
|
||||||
this._highPriority = [];
|
this._highPriority = [];
|
||||||
this._running = false;
|
this._running = false;
|
||||||
this._stopping = false;
|
this._stopping = false;
|
||||||
|
this._finished = false;
|
||||||
|
this._error = false;
|
||||||
this._finishedReqs = 0;
|
this._finishedReqs = 0;
|
||||||
this._lastTotalRequests = 0;
|
this._localChanges = false;
|
||||||
|
this._remoteChanges = false;
|
||||||
|
this._conflicts = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('Name', function () {
|
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('name', function () {
|
||||||
return this.name[0].toUpperCase() + this.name.substr(1);
|
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__('running', function () this._running);
|
||||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('stopping', function () this._stopping);
|
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 () {
|
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('unfinishedRequests', function () {
|
||||||
return this.totalRequests - this.finishedRequests;
|
return this.totalRequests - this.finishedRequests;
|
||||||
|
@ -73,22 +83,51 @@ Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', functio
|
||||||
|
|
||||||
// Last request
|
// Last request
|
||||||
if (val == this.totalRequests) {
|
if (val == this.totalRequests) {
|
||||||
Zotero.debug(this.Name + " queue is done");
|
Zotero.debug(this.Type + " queue is done for library " + this.libraryID);
|
||||||
|
|
||||||
// DEBUG info
|
// DEBUG info
|
||||||
Zotero.debug("Active requests: " + this.activeRequests);
|
Zotero.debug("Active requests: " + this.activeRequests);
|
||||||
|
|
||||||
if (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._running = false;
|
||||||
this._stopping = false;
|
this._stopping = false;
|
||||||
|
this._finished = true;
|
||||||
this._requests = {};
|
this._requests = {};
|
||||||
this._highPriority = [];
|
this._highPriority = [];
|
||||||
this._finishedReqs = 0;
|
|
||||||
this._lastTotalRequests = this.totalRequests;
|
var localChanges = this._localChanges;
|
||||||
this.totalRequests = 0;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -99,10 +138,6 @@ Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', functio
|
||||||
this.advance();
|
this.advance();
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('lastTotalRequests', function () {
|
|
||||||
return this._lastTotalRequests;
|
|
||||||
});
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('queuedRequests', function () {
|
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('queuedRequests', function () {
|
||||||
return this.unfinishedRequests - this.activeRequests;
|
return this.unfinishedRequests - this.activeRequests;
|
||||||
});
|
});
|
||||||
|
@ -119,6 +154,9 @@ Zotero.Sync.Storage.Queue.prototype.__defineGetter__('percentage', function () {
|
||||||
if (this.totalRequests == 0) {
|
if (this.totalRequests == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (this._finished) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
var completedRequests = 0;
|
var completedRequests = 0;
|
||||||
for each(var request in this._requests) {
|
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
|
* @param {Boolean} highPriority Add or move request to high priority queue
|
||||||
*/
|
*/
|
||||||
Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority) {
|
Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority) {
|
||||||
|
if (this._finished) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
request.queue = this;
|
request.queue = this;
|
||||||
var name = request.name;
|
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 (this._requests[name]) {
|
||||||
if (highPriority) {
|
if (highPriority) {
|
||||||
|
@ -166,56 +208,133 @@ Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority
|
||||||
if (highPriority) {
|
if (highPriority) {
|
||||||
this._highPriority.push(name);
|
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
|
* Start another request in this queue if there's an available slot
|
||||||
*/
|
*/
|
||||||
Zotero.Sync.Storage.Queue.prototype.advance = function () {
|
Zotero.Sync.Storage.Queue.prototype.advance = function () {
|
||||||
this._running = true;
|
this._running = true;
|
||||||
|
this._finished = false;
|
||||||
|
|
||||||
if (this._stopping) {
|
if (this._stopping) {
|
||||||
Zotero.debug(this.Name + " queue is being stopped in "
|
Zotero.debug(this.Type + " queue for library " + this.libraryID
|
||||||
+ "Zotero.Sync.Storage.Queue.advance()", 2);
|
+ "is being stopped in Zotero.Sync.Storage.Queue.advance()", 2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.queuedRequests) {
|
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.activeRequests + " active, "
|
||||||
+ this.finishedRequests + " finished)");
|
+ this.finishedRequests + " finished)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.activeRequests >= this.maxConcurrentRequests) {
|
if (this.activeRequests >= this.maxConcurrentRequests) {
|
||||||
Zotero.debug(this.Name + " queue is busy ("
|
Zotero.debug(this.Type + " queue for library " + this.libraryID
|
||||||
+ this.activeRequests + "/" + this.maxConcurrentRequests + ")");
|
+ " is busy (" + this.activeRequests + "/"
|
||||||
|
+ this.maxConcurrentRequests + ")");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Start the first unprocessed request
|
// Start the first unprocessed request
|
||||||
|
|
||||||
// Try the high-priority queue first
|
// Try the high-priority queue first
|
||||||
var name, request;
|
var self = this;
|
||||||
|
var request, name;
|
||||||
while (name = this._highPriority.shift()) {
|
while (name = this._highPriority.shift()) {
|
||||||
request = this._requests[name];
|
request = this._requests[name];
|
||||||
if (!request.isRunning() && !request.isFinished()) {
|
if (request.isRunning() || request.isFinished()) {
|
||||||
request.start();
|
continue;
|
||||||
this.advance();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// And then others
|
||||||
for each(request in this._requests) {
|
for each(var request in this._requests) {
|
||||||
if (!request.isRunning() && !request.isFinished()) {
|
if (request.isRunning() || request.isFinished()) {
|
||||||
request.start();
|
continue;
|
||||||
this.advance();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.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 () {
|
Zotero.Sync.Storage.Queue.prototype.stop = function () {
|
||||||
if (!this._running) {
|
if (!this._running) {
|
||||||
Zotero.debug(this.Name + " queue is not running");
|
Zotero.debug(this.Type + " queue for library " + this.libraryID
|
||||||
|
+ " is not running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._stopping) {
|
if (this._stopping) {
|
||||||
Zotero.debug("Already stopping " + this.name + " queue");
|
Zotero.debug("Already stopping " + this.type + " queue for library "
|
||||||
|
+ this.libraryID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Zotero.debug("Stopping " + this.type + " queue for library " + this.libraryID);
|
||||||
|
|
||||||
// If no requests, finish manually
|
// If no requests, finish manually
|
||||||
/*if (this.activeRequests == 0) {
|
/*if (this.activeRequests == 0) {
|
||||||
this._finishedRequests = this._finishedRequests;
|
this._finishedRequests = this._finishedRequests;
|
||||||
|
@ -255,4 +396,13 @@ Zotero.Sync.Storage.Queue.prototype.stop = function () {
|
||||||
request.stop();
|
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 () {
|
Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
var _queues = {};
|
var _queues = {};
|
||||||
var _conflicts = [];
|
var _currentQueues = [];
|
||||||
var _cancelled = false;
|
|
||||||
|
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
|
* @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
|
// Initialize the queue if it doesn't exist yet
|
||||||
if (!_queues[queueName]) {
|
if (!_queues[hash]) {
|
||||||
if (noInit) {
|
if (noInit) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var queue = new Zotero.Sync.Storage.Queue(queueName);
|
var queue = new Zotero.Sync.Storage.Queue(queueName, libraryID);
|
||||||
switch (queueName) {
|
switch (queueName) {
|
||||||
case 'download':
|
case 'download':
|
||||||
queue.maxConcurrentRequests =
|
queue.maxConcurrentRequests =
|
||||||
|
@ -56,22 +117,36 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
default:
|
default:
|
||||||
throw ("Invalid queue '" + queueName + "' in Zotero.Sync.Storage.QueueManager.get()");
|
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 = [];
|
var queues = [];
|
||||||
for each(var queue in _queues) {
|
for each(var queue in _queues) {
|
||||||
|
if (typeof libraryID == 'undefined' || queue.libraryID === libraryID) {
|
||||||
queues.push(queue);
|
queues.push(queue);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return queues;
|
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
|
* Stop all queues
|
||||||
*
|
*
|
||||||
|
@ -81,7 +156,6 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
*/
|
*/
|
||||||
this.cancel = function (skipStorageFinish) {
|
this.cancel = function (skipStorageFinish) {
|
||||||
Zotero.debug("Stopping all storage queues");
|
Zotero.debug("Stopping all storage queues");
|
||||||
_cancelled = true;
|
|
||||||
for each(var queue in _queues) {
|
for each(var queue in _queues) {
|
||||||
if (queue.isRunning() && !queue.isStopping()) {
|
if (queue.isRunning() && !queue.isStopping()) {
|
||||||
queue.stop();
|
queue.stop();
|
||||||
|
@ -92,26 +166,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
|
|
||||||
this.finish = function () {
|
this.finish = function () {
|
||||||
Zotero.debug("All storage queues are finished");
|
Zotero.debug("All storage queues are finished");
|
||||||
|
_currentQueues = [];
|
||||||
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 = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,86 +187,32 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
activeRequests += queue.activeRequests;
|
activeRequests += queue.activeRequests;
|
||||||
}
|
}
|
||||||
if (activeRequests == 0) {
|
if (activeRequests == 0) {
|
||||||
this.updateProgressMeters(0);
|
_updateProgressMeters(0);
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
this.finish();
|
this.finish();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Percentage
|
var status = {};
|
||||||
var percentageSum = 0;
|
|
||||||
var numQueues = 0;
|
|
||||||
for each(var queue in _queues) {
|
for each(var queue in _queues) {
|
||||||
percentageSum += queue.percentage;
|
if (!this.hasCurrentQueue(queue)) {
|
||||||
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;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
meter.setAttribute("value", percentage);
|
if (!status[queue.libraryID]) {
|
||||||
box.hidden = false;
|
status[queue.libraryID] = {};
|
||||||
|
|
||||||
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][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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateProgressMeters(activeRequests, status);
|
||||||
this.addConflict = function (requestName, localData, remoteData) {
|
|
||||||
Zotero.debug('===========');
|
|
||||||
Zotero.debug(localData);
|
|
||||||
Zotero.debug(remoteData);
|
|
||||||
|
|
||||||
_conflicts.push({
|
|
||||||
name: requestName,
|
|
||||||
localData: localData,
|
|
||||||
remoteData: remoteData
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -226,26 +227,76 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
var unfinishedRequests = queue.unfinishedRequests;
|
var unfinishedRequests = queue.unfinishedRequests;
|
||||||
|
|
||||||
if (!unfinishedRequests) {
|
if (!unfinishedRequests) {
|
||||||
return Zotero.getString('sync.storage.none')
|
return Zotero.getString('sync.storage.none');
|
||||||
}
|
}
|
||||||
|
|
||||||
var kbRemaining = Zotero.getString(
|
if (remaining > 1000) {
|
||||||
'sync.storage.kbRemaining',
|
var bytesRemaining = Zotero.getString(
|
||||||
Zotero.Utilities.numberFormat(remaining / 1024, 0)
|
'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 totalRequests = queue.totalRequests;
|
||||||
var filesRemaining = Zotero.getString(
|
var filesRemaining = Zotero.getString(
|
||||||
'sync.storage.filesRemaining',
|
'sync.storage.filesRemaining',
|
||||||
[totalRequests - unfinishedRequests, totalRequests]
|
[totalRequests - unfinishedRequests, totalRequests]
|
||||||
);
|
);
|
||||||
var status = Zotero.localeJoin([kbRemaining, '(' + filesRemaining + ')']);
|
return bytesRemaining + ' (' + filesRemaining + ')';
|
||||||
return status;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = [];
|
var objectPairs = [];
|
||||||
for each(var conflict in _conflicts) {
|
for each(var conflict in conflicts) {
|
||||||
var item = Zotero.Sync.Storage.getItemFromRequestName(conflict.name);
|
var item = Zotero.Sync.Storage.getItemFromRequestName(conflict.name);
|
||||||
var item1 = item.clone(false, false, true);
|
var item1 = item.clone(false, false, true);
|
||||||
item1.setField('dateModified',
|
item1.setField('dateModified',
|
||||||
|
@ -279,8 +330,8 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
|
|
||||||
// Since we're only putting cloned items into the merge window,
|
// Since we're only putting cloned items into the merge window,
|
||||||
// we have to manually set the ids
|
// we have to manually set the ids
|
||||||
for (var i=0; i<_conflicts.length; i++) {
|
for (var i=0; i<conflicts.length; i++) {
|
||||||
io.dataOut[i].id = Zotero.Sync.Storage.getItemFromRequestName(_conflicts[i].name).id;
|
io.dataOut[i].id = Zotero.Sync.Storage.getItemFromRequestName(conflicts[i].name).id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return io.dataOut;
|
return io.dataOut;
|
||||||
|
@ -292,8 +343,6 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.resyncOnFinish = true;
|
|
||||||
|
|
||||||
for each(var mergeItem in data) {
|
for each(var mergeItem in data) {
|
||||||
var itemID = mergeItem.id;
|
var itemID = mergeItem.id;
|
||||||
var dateModified = mergeItem.ref.getField('dateModified');
|
var dateModified = mergeItem.ref.getField('dateModified');
|
||||||
|
|
|
@ -29,12 +29,11 @@
|
||||||
*
|
*
|
||||||
* @param {String} name Identifier for request (e.g., "[libraryID]/[key]")
|
* @param {String} name Identifier for request (e.g., "[libraryID]/[key]")
|
||||||
* @param {Function} onStart Callback to run when request starts
|
* @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.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.name = name;
|
||||||
this.channel = null;
|
this.channel = null;
|
||||||
|
@ -42,10 +41,13 @@ Zotero.Sync.Storage.Request = function (name, callbacks) {
|
||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
this.progressMax = 0;
|
this.progressMax = 0;
|
||||||
|
|
||||||
|
this._deferred = Q.defer();
|
||||||
this._running = false;
|
this._running = false;
|
||||||
this._percentage = 0;
|
this._percentage = 0;
|
||||||
this._remaining = null;
|
this._remaining = null;
|
||||||
|
this._maxSize = null;
|
||||||
this._finished = false;
|
this._finished = false;
|
||||||
|
this._changesMade = false;
|
||||||
|
|
||||||
for (var func in callbacks) {
|
for (var func in callbacks) {
|
||||||
if (this.callbacks.indexOf(func) !== -1) {
|
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
|
* 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 () {
|
Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function () {
|
||||||
|
if (this._finished) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.progressMax == 0) {
|
if (this.progressMax == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -98,14 +114,14 @@ Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function ()
|
||||||
var percentage = Math.round((this.progress / this.progressMax) * 100);
|
var percentage = Math.round((this.progress / this.progressMax) * 100);
|
||||||
if (percentage < this._percentage) {
|
if (percentage < this._percentage) {
|
||||||
Zotero.debug(percentage + " is less than last percentage of "
|
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.progress);
|
||||||
Zotero.debug(this.progressMax);
|
Zotero.debug(this.progressMax);
|
||||||
percentage = this._percentage;
|
percentage = this._percentage;
|
||||||
}
|
}
|
||||||
else if (percentage > 100) {
|
else if (percentage > 100) {
|
||||||
Zotero.debug(percentage + " is greater than 100 for "
|
Zotero.debug(percentage + " is greater than 100 for "
|
||||||
+ this.name + " request", 2);
|
+ "request " + this.name, 2);
|
||||||
Zotero.debug(this.progress);
|
Zotero.debug(this.progress);
|
||||||
Zotero.debug(this.progressMax);
|
Zotero.debug(this.progressMax);
|
||||||
percentage = 100;
|
percentage = 100;
|
||||||
|
@ -119,7 +135,15 @@ Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function ()
|
||||||
|
|
||||||
|
|
||||||
Zotero.Sync.Storage.Request.prototype.__defineGetter__('remaining', function () {
|
Zotero.Sync.Storage.Request.prototype.__defineGetter__('remaining', function () {
|
||||||
|
if (this._finished) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.progressMax) {
|
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 + "'");
|
//Zotero.debug("Remaining not yet available for request '" + this.name + "'");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -151,22 +175,70 @@ Zotero.Sync.Storage.Request.prototype.setChannel = function (channel) {
|
||||||
|
|
||||||
Zotero.Sync.Storage.Request.prototype.start = function () {
|
Zotero.Sync.Storage.Request.prototype.start = function () {
|
||||||
if (!this.queue) {
|
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) {
|
if (this._running) {
|
||||||
throw ("Request '" + this.name + "' already running in "
|
throw new Error("Request " + this.name + " already running");
|
||||||
+ "Zotero.Sync.Storage.Request.start()");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Starting " + this.queue.name + " request '" + this.name + "'");
|
|
||||||
this._running = true;
|
this._running = true;
|
||||||
this.queue.activeRequests++;
|
this.queue.activeRequests++;
|
||||||
if (this._onStart) {
|
|
||||||
for each(var f in this._onStart) {
|
if (this.queue.type == 'download') {
|
||||||
f(this);
|
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)
|
* (usually total bytes)
|
||||||
*/
|
*/
|
||||||
Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress, progressMax) {
|
Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress, progressMax) {
|
||||||
|
Zotero.debug(progress + "/" + progressMax + " for request " + this.name);
|
||||||
|
|
||||||
if (!this._running) {
|
if (!this._running) {
|
||||||
Zotero.debug("Trying to update finished request " + this.name + " in "
|
Zotero.debug("Trying to update finished request " + this.name + " in "
|
||||||
+ "Zotero.Sync.Storage.Request.onProgress() "
|
+ "Zotero.Sync.Storage.Request.onProgress() "
|
||||||
|
@ -219,6 +293,10 @@ Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress,
|
||||||
this.progressMax = progressMax;
|
this.progressMax = progressMax;
|
||||||
this.queue.updateProgress();
|
this.queue.updateProgress();
|
||||||
|
|
||||||
|
if (this.queue.type == 'download') {
|
||||||
|
Zotero.Sync.Storage.setItemDownloadPercentage(this.name, this.percentage);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.onProgress) {
|
if (this.onProgress) {
|
||||||
for each(var f in this._onProgress) {
|
for each(var f in this._onProgress) {
|
||||||
f(progress, progressMax);
|
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
|
* Stop the request's underlying network request, if there is one
|
||||||
*/
|
*/
|
||||||
Zotero.Sync.Storage.Request.prototype.stop = function () {
|
Zotero.Sync.Storage.Request.prototype.stop = function () {
|
||||||
var finishNow = false;
|
|
||||||
try {
|
|
||||||
// If upload already finished, finish() will never be called otherwise
|
|
||||||
if (this.channel) {
|
if (this.channel) {
|
||||||
this.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
try {
|
||||||
// Throws error if request not finished
|
|
||||||
this.channel.requestSucceeded;
|
|
||||||
Zotero.debug("Channel is no longer running for request " + this.name);
|
|
||||||
Zotero.debug(this.channel.requestSucceeded);
|
|
||||||
finishNow = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {}
|
|
||||||
|
|
||||||
if (!this._running || !this.channel || finishNow) {
|
|
||||||
this.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug("Stopping request '" + this.name + "'");
|
Zotero.debug("Stopping request '" + this.name + "'");
|
||||||
this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
|
this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.debug(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark request as finished and notify queue that it's done
|
* Mark request as finished and notify queue that it's done
|
||||||
*/
|
*/
|
||||||
Zotero.Sync.Storage.Request.prototype.finish = function () {
|
Zotero.Sync.Storage.Request.prototype._finish = function () {
|
||||||
if (this._finished) {
|
|
||||||
throw ("Request '" + this.name + "' is already finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug("Finishing " + this.queue.name + " request '" + this.name + "'");
|
Zotero.debug("Finishing " + this.queue.name + " request '" + this.name + "'");
|
||||||
this._finished = true;
|
this._finished = true;
|
||||||
var active = this._running;
|
var active = this._running;
|
||||||
this._running = false;
|
this._running = false;
|
||||||
|
|
||||||
|
Zotero.Sync.Storage.setItemDownloadPercentage(this.name, false);
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
this.queue.activeRequests--;
|
this.queue.activeRequests--;
|
||||||
}
|
}
|
||||||
// mechanism for failures?
|
// TEMP: mechanism for failures?
|
||||||
|
try {
|
||||||
this.queue.finishedRequests++;
|
this.queue.finishedRequests++;
|
||||||
this.queue.updateProgress();
|
this.queue.updateProgress();
|
||||||
|
}
|
||||||
if (this._onStop) {
|
catch (e) {
|
||||||
for each(var f in this._onStop) {
|
Zotero.debug(e);
|
||||||
f();
|
Components.utils.reportError(e);
|
||||||
}
|
this._deferred.reject(e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
var _defaultError = "A WebDAV file sync error occurred. Please try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
|
var _defaultError = "A WebDAV file sync error occurred. Please try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
|
||||||
var _defaultErrorRestart = "A WebDAV file sync error occurred. Please restart " + Zotero.appName + " and try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
|
var _defaultErrorRestart = "A WebDAV file sync error occurred. Please restart " + Zotero.appName + " and try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
|
||||||
|
|
||||||
|
var _initialized = false;
|
||||||
var _parentURI;
|
var _parentURI;
|
||||||
var _rootURI;
|
var _rootURI;
|
||||||
var _cachedCredentials = false;
|
var _cachedCredentials = false;
|
||||||
|
@ -46,32 +47,25 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
* @param {Zotero.Item} item
|
* @param {Zotero.Item} item
|
||||||
* @param {Function} callback Callback f(item, mdate)
|
* @param {Function} callback Callback f(item, mdate)
|
||||||
*/
|
*/
|
||||||
function getStorageModificationTime(item, callback) {
|
function getStorageModificationTime(item) {
|
||||||
var uri = getItemPropertyURI(item);
|
var uri = getItemPropertyURI(item);
|
||||||
|
|
||||||
Zotero.HTTP.doGet(uri, function (req) {
|
return Zotero.HTTP.promise(
|
||||||
|
"GET", uri, { debug: true, successCodes: [200, 300, 404] }
|
||||||
|
)
|
||||||
|
.then(function (req) {
|
||||||
checkResponse(req);
|
checkResponse(req);
|
||||||
|
|
||||||
var funcName = "Zotero.Sync.Storage.WebDAV.getStorageModificationTime()";
|
var funcName = "Zotero.Sync.Storage.WebDAV.getStorageModificationTime()";
|
||||||
|
|
||||||
// mod_speling can return 300s for 404s with base name matches
|
// mod_speling can return 300s for 404s with base name matches
|
||||||
if (req.status == 404 || req.status == 300) {
|
if (req.status == 404 || req.status == 300) {
|
||||||
callback(item, false);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (req.status != 200) {
|
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
|
||||||
"Unexpected status code " + req.status + " in " + funcName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
|
|
||||||
// No modification time set
|
// No modification time set
|
||||||
if (!req.responseText) {
|
if (!req.responseText) {
|
||||||
callback(item, false);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -119,11 +113,17 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Zotero.debug(msg, 1);
|
Zotero.debug(msg, 1);
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
deleteStorageFiles([item.key + ".prop"]);
|
deleteStorageFiles([item.key + ".prop"]);
|
||||||
Zotero.Sync.Storage.EventManager.error(_defaultError);
|
throw _defaultError;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mdate = new Date(parseInt(mtime));
|
return new Date(parseInt(mtime));
|
||||||
callback(item, mdate);
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
Zotero.debug(req.responseText);
|
||||||
|
throw new Error("Unexpected status code " + e.status + " in " + funcName);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,9 +132,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
* Set mod time of file on storage server
|
* Set mod time of file on storage server
|
||||||
*
|
*
|
||||||
* @param {Zotero.Item} item
|
* @param {Zotero.Item} item
|
||||||
* @param {Function} callback Callback f(item, props)
|
|
||||||
*/
|
*/
|
||||||
function setStorageModificationTime(item, callback) {
|
function setStorageModificationTime(item) {
|
||||||
var uri = getItemPropertyURI(item);
|
var uri = getItemPropertyURI(item);
|
||||||
|
|
||||||
var mtime = item.attachmentModificationTime;
|
var mtime = item.attachmentModificationTime;
|
||||||
|
@ -145,19 +144,16 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
<hash>{hash}</hash>
|
<hash>{hash}</hash>
|
||||||
</properties>;
|
</properties>;
|
||||||
|
|
||||||
Zotero.HTTP.WebDAV.doPut(uri, prop.toXMLString(), function (req) {
|
return Zotero.HTTP.promise("PUT", uri, prop.toXMLString(),
|
||||||
switch (req.status) {
|
{ debug: true, successCodes: [200, 201, 204] })
|
||||||
case 200:
|
.then(function (req) {
|
||||||
case 201:
|
return { mtime: mtime, hash: hash };
|
||||||
case 204:
|
})
|
||||||
break;
|
.fail(function (e) {
|
||||||
|
throw new Error("Unexpected status code " + e.xmlhttp.status);
|
||||||
default:
|
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
throw new Error("Unexpected status code " + req.status);
|
|
||||||
}
|
|
||||||
callback(item, { mtime: mtime, hash: hash });
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,12 +171,12 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
var request = data.request;
|
var request = data.request;
|
||||||
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
||||||
|
|
||||||
getStorageModificationTime(item, function (item, mdate) {
|
return getStorageModificationTime(item)
|
||||||
try {
|
.then(function (mdate) {
|
||||||
if (!request.isRunning()) {
|
if (!request.isRunning()) {
|
||||||
Zotero.debug("Upload request '" + request.name
|
Zotero.debug("Upload request '" + request.name
|
||||||
+ "' is no longer running after getting mod time");
|
+ "' is no longer running after getting mod time");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for conflict
|
// Check for conflict
|
||||||
|
@ -222,9 +218,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime, true);
|
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime, true);
|
||||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
onChangesMade();
|
return true;
|
||||||
request.finish();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
|
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
|
||||||
|
@ -237,8 +231,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Zotero.debug("Conflict -- last synced file mod time "
|
Zotero.debug("Conflict -- last synced file mod time "
|
||||||
+ "does not match time on storage server"
|
+ "does not match time on storage server"
|
||||||
+ " (" + smtime + " != " + mtime + ")");
|
+ " (" + smtime + " != " + mtime + ")");
|
||||||
request.finish();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -271,13 +264,22 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
channel.setRequestHeader('Keep-Alive', '', false);
|
channel.setRequestHeader('Keep-Alive', '', false);
|
||||||
channel.setRequestHeader('Connection', '', false);
|
channel.setRequestHeader('Connection', '', false);
|
||||||
|
|
||||||
|
var deferred = Q.defer();
|
||||||
|
|
||||||
var listener = new Zotero.Sync.Storage.StreamListener(
|
var listener = new Zotero.Sync.Storage.StreamListener(
|
||||||
{
|
{
|
||||||
onProgress: function (a, b, c) {
|
onProgress: function (a, b, c) {
|
||||||
request.onProgress(a, b, c);
|
request.onProgress(a, b, c);
|
||||||
},
|
},
|
||||||
onStop: function (httpRequest, status, response, data) { onUploadComplete(httpRequest, status, response,data); },
|
onStop: function (httpRequest, status, response, data) {
|
||||||
onCancel: function (httpRequest, status, data) { onUploadCancel(httpRequest, status, data); },
|
deferred.resolve(
|
||||||
|
onUploadComplete(httpRequest, status, response, data)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onCancel: function (httpRequest, status, data) {
|
||||||
|
onUploadCancel(httpRequest, status, data);
|
||||||
|
deferred.resolve(false);
|
||||||
|
},
|
||||||
request: request,
|
request: request,
|
||||||
item: item,
|
item: item,
|
||||||
streams: [fis, bis]
|
streams: [fis, bis]
|
||||||
|
@ -292,10 +294,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Zotero.debug("HTTP PUT of " + file.leafName + " to " + dispURI.spec);
|
Zotero.debug("HTTP PUT of " + file.leafName + " to " + dispURI.spec);
|
||||||
|
|
||||||
channel.asyncOpen(listener, null);
|
channel.asyncOpen(listener, null);
|
||||||
}
|
|
||||||
catch (e) {
|
return deferred.promise;
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,30 +317,25 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
case 403:
|
case 403:
|
||||||
case 500:
|
case 500:
|
||||||
Zotero.debug(response);
|
Zotero.debug(response);
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
throw (Zotero.getString('sync.storage.error.fileUploadFailed') +
|
||||||
Zotero.getString('sync.storage.error.fileUploadFailed')
|
" " + Zotero.getString('sync.storage.error.checkFileSyncSettings'));
|
||||||
+ " " + Zotero.getString('sync.storage.error.checkFileSyncSettings')
|
|
||||||
);
|
|
||||||
|
|
||||||
case 507:
|
case 507:
|
||||||
Zotero.debug(response);
|
Zotero.debug(response);
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
throw Zotero.getString('sync.storage.error.webdav.insufficientSpace');
|
||||||
Zotero.getString('sync.storage.error.webdav.insufficientSpace')
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Zotero.debug(response);
|
Zotero.debug(response);
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
throw ("Unexpected file upload status " + status +
|
||||||
"Unexpected file upload status " + status
|
" in Zotero.Sync.Storage.WebDAV.onUploadComplete()");
|
||||||
+ " in Zotero.Sync.Storage.WebDAV.onUploadComplete()"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setStorageModificationTime(item, function (item, props) {
|
return setStorageModificationTime(item)
|
||||||
|
.then(function (props) {
|
||||||
if (!request.isRunning()) {
|
if (!request.isRunning()) {
|
||||||
Zotero.debug("Upload request '" + request.name
|
Zotero.debug("Upload request '" + request.name
|
||||||
+ "' is no longer running after getting mod time");
|
+ "' is no longer running after getting mod time");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
@ -360,8 +355,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangesMade();
|
return {
|
||||||
request.finish();
|
localChanges: true,
|
||||||
|
remoteChanges: true
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,9 +377,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
catch (e) {
|
catch (e) {
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -649,8 +643,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
throw e;
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
}
|
||||||
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
|
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
|
||||||
var msg = Zotero.getString('sync.storage.error.webdav.sslConnectionError', host) +
|
var msg = Zotero.getString('sync.storage.error.webdav.sslConnectionError', host) +
|
||||||
|
@ -667,7 +660,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -686,10 +679,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
});
|
});
|
||||||
obj.includeGroupItems = false;
|
obj.includeGroupItems = false;
|
||||||
|
|
||||||
Object.defineProperty(obj, "_enabled", {
|
|
||||||
get: function () this.includeUserFiles
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(obj, "_verified", {
|
Object.defineProperty(obj, "_verified", {
|
||||||
get: function () Zotero.Prefs.get("sync.storage.verified")
|
get: function () Zotero.Prefs.get("sync.storage.verified")
|
||||||
});
|
});
|
||||||
|
@ -755,7 +744,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Object.defineProperty(obj, "rootURI", {
|
Object.defineProperty(obj, "rootURI", {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!_rootURI) {
|
if (!_rootURI) {
|
||||||
throw new Error("Root URI not initialized");
|
this._init();
|
||||||
}
|
}
|
||||||
return _rootURI.clone();
|
return _rootURI.clone();
|
||||||
}
|
}
|
||||||
|
@ -764,62 +753,16 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Object.defineProperty(obj, "parentURI", {
|
Object.defineProperty(obj, "parentURI", {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!_parentURI) {
|
if (!_parentURI) {
|
||||||
throw new Error("Parent URI not initialized");
|
this._init();
|
||||||
}
|
}
|
||||||
return _parentURI.clone();
|
return _parentURI.clone();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
obj._init = function (url, dir, username, password) {
|
obj._init = function () {
|
||||||
if (!url) {
|
_rootURI = false;
|
||||||
var msg = "WebDAV URL not provided";
|
_parentURI = false;
|
||||||
Zotero.debug(msg);
|
|
||||||
throw ({
|
|
||||||
message: msg,
|
|
||||||
name: "Z_ERROR_NO_URL",
|
|
||||||
filename: "webdav.js",
|
|
||||||
toString: function () { return this.message; }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username && !password) {
|
|
||||||
var msg = "WebDAV password not provided";
|
|
||||||
Zotero.debug(msg);
|
|
||||||
throw ({
|
|
||||||
message: msg,
|
|
||||||
name: "Z_ERROR_NO_PASSWORD",
|
|
||||||
filename: "webdav.js",
|
|
||||||
toString: function () { return this.message; }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
|
||||||
getService(Components.interfaces.nsIIOService);
|
|
||||||
try {
|
|
||||||
var uri = ios.newURI(url, null, null);
|
|
||||||
if (username) {
|
|
||||||
uri.username = username;
|
|
||||||
uri.password = password;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.debug(e);
|
|
||||||
Components.utils.reportError(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!uri.spec.match(/\/$/)) {
|
|
||||||
uri.spec += "/";
|
|
||||||
}
|
|
||||||
_parentURI = uri;
|
|
||||||
|
|
||||||
var uri = uri.clone();
|
|
||||||
uri.spec += "zotero/";
|
|
||||||
_rootURI = uri;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
obj._initFromPrefs = function () {
|
|
||||||
var scheme = Zotero.Prefs.get('sync.storage.scheme');
|
var scheme = Zotero.Prefs.get('sync.storage.scheme');
|
||||||
switch (scheme) {
|
switch (scheme) {
|
||||||
case 'http':
|
case 'http':
|
||||||
|
@ -832,7 +775,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
|
|
||||||
var url = Zotero.Prefs.get('sync.storage.url');
|
var url = Zotero.Prefs.get('sync.storage.url');
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return false;
|
var msg = "WebDAV URL not provided";
|
||||||
|
Zotero.debug(msg);
|
||||||
|
throw ({
|
||||||
|
message: msg,
|
||||||
|
name: "Z_ERROR_NO_URL",
|
||||||
|
filename: "webdav.js",
|
||||||
|
toString: function () { return this.message; }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
url = scheme + '://' + url;
|
url = scheme + '://' + url;
|
||||||
|
@ -840,7 +790,41 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
var username = this._username;
|
var username = this._username;
|
||||||
var password = this._password;
|
var password = this._password;
|
||||||
|
|
||||||
return this._init(url, dir, username, password);
|
if (!username) {
|
||||||
|
var msg = "WebDAV username not provided";
|
||||||
|
Zotero.debug(msg);
|
||||||
|
throw ({
|
||||||
|
message: msg,
|
||||||
|
name: "Z_ERROR_NO_USERNAME",
|
||||||
|
filename: "webdav.js",
|
||||||
|
toString: function () { return this.message; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
var msg = "WebDAV password not provided";
|
||||||
|
Zotero.debug(msg);
|
||||||
|
throw ({
|
||||||
|
message: msg,
|
||||||
|
name: "Z_ERROR_NO_PASSWORD",
|
||||||
|
filename: "webdav.js",
|
||||||
|
toString: function () { return this.message; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||||
|
getService(Components.interfaces.nsIIOService);
|
||||||
|
var uri = ios.newURI(url, null, null);
|
||||||
|
uri.username = username;
|
||||||
|
uri.password = password;
|
||||||
|
if (!uri.spec.match(/\/$/)) {
|
||||||
|
uri.spec += "/";
|
||||||
|
}
|
||||||
|
_parentURI = uri;
|
||||||
|
|
||||||
|
var uri = uri.clone();
|
||||||
|
uri.spec += "zotero/";
|
||||||
|
_rootURI = uri;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -856,20 +840,20 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve modification time from server to store locally afterwards
|
// Retrieve modification time from server to store locally afterwards
|
||||||
getStorageModificationTime(item, function (item, mdate) {
|
return getStorageModificationTime(item)
|
||||||
|
.then(function (mdate) {
|
||||||
if (!request.isRunning()) {
|
if (!request.isRunning()) {
|
||||||
Zotero.debug("Download request '" + request.name
|
Zotero.debug("Download request '" + request.name
|
||||||
+ "' is no longer running after getting mod time");
|
+ "' is no longer running after getting mod time");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mdate) {
|
if (!mdate) {
|
||||||
Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item));
|
Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item));
|
||||||
request.finish();
|
request.finish();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
var syncModTime = mdate.getTime();
|
var syncModTime = mdate.getTime();
|
||||||
|
|
||||||
// Skip download if local file exists and matches mod time
|
// Skip download if local file exists and matches mod time
|
||||||
|
@ -883,9 +867,9 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
||||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
onChangesMade();
|
return {
|
||||||
request.finish();
|
localChanges: true
|
||||||
return;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri = getItemURI(item);
|
var uri = getItemURI(item);
|
||||||
|
@ -895,6 +879,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
destFile.remove(false);
|
destFile.remove(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var deferred = Q.defer();
|
||||||
|
|
||||||
var listener = new Zotero.Sync.Storage.StreamListener(
|
var listener = new Zotero.Sync.Storage.StreamListener(
|
||||||
{
|
{
|
||||||
onStart: function (request, data) {
|
onStart: function (request, data) {
|
||||||
|
@ -902,7 +888,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
Zotero.debug("Download request " + data.request.name
|
Zotero.debug("Download request " + data.request.name
|
||||||
+ " stopped before download started -- closing channel");
|
+ " stopped before download started -- closing channel");
|
||||||
request.cancel(0x804b0002); // NS_BINDING_ABORTED
|
request.cancel(0x804b0002); // NS_BINDING_ABORTED
|
||||||
return;
|
deferred.resolve(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress: function (a, b, c) {
|
onProgress: function (a, b, c) {
|
||||||
|
@ -917,7 +903,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
// Delete the orphaned prop file
|
// Delete the orphaned prop file
|
||||||
deleteStorageFiles([item.key + ".prop"]);
|
deleteStorageFiles([item.key + ".prop"]);
|
||||||
|
|
||||||
data.request.finish();
|
deferred.resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (status != 200) {
|
else if (status != 200) {
|
||||||
|
@ -926,26 +912,31 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
+ " in Zotero.Sync.Storage.WebDAV.downloadFile()";
|
+ " in Zotero.Sync.Storage.WebDAV.downloadFile()";
|
||||||
Zotero.debug(msg, 1);
|
Zotero.debug(msg, 1);
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
Zotero.Sync.Storage.EventManager.error(_defaultError);
|
deferred.reject(_defaultError);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't try to process if the request has been cancelled
|
// Don't try to process if the request has been cancelled
|
||||||
if (data.request.isFinished()) {
|
if (data.request.isFinished()) {
|
||||||
Zotero.debug("Download request " + data.request.name
|
Zotero.debug("Download request " + data.request.name
|
||||||
+ " is no longer running after file download");
|
+ " is no longer running after file download");
|
||||||
|
deferred.resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Finished download of " + destFile.path);
|
Zotero.debug("Finished download of " + destFile.path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Zotero.Sync.Storage.processDownload(data);
|
deferred.resolve(Zotero.Sync.Storage.processDownload(data));
|
||||||
data.request.finish();
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
deferred.reject(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onCancel: function (request, status, data) {
|
||||||
|
Zotero.debug("Request cancelled");
|
||||||
|
deferred.resolve(false);
|
||||||
|
},
|
||||||
request: request,
|
request: request,
|
||||||
item: item,
|
item: item,
|
||||||
compressed: true,
|
compressed: true,
|
||||||
|
@ -972,146 +963,134 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
// XXX Always use when we no longer support Firefox < 18
|
// XXX Always use when we no longer support Firefox < 18
|
||||||
wbp.saveURI(uri, null, null, null, null, destFile, null);
|
wbp.saveURI(uri, null, null, null, null, destFile, null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (e) {
|
return deferred.promise;
|
||||||
request.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._uploadFile = function (request) {
|
obj._uploadFile = function (request) {
|
||||||
Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); });
|
var deferred = Q.defer();
|
||||||
|
Zotero.Sync.Storage.createUploadFile(
|
||||||
|
request,
|
||||||
|
function (data) {
|
||||||
|
deferred.resolve(processUploadFile(data));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return deferred.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._getLastSyncTime = function (callback) {
|
obj._getLastSyncTime = function () {
|
||||||
// Cache the credentials at the root URI
|
// Cache the credentials at the root URI
|
||||||
var self = this;
|
var self = this;
|
||||||
this._cacheCredentials(function () {
|
return Q.fcall(function () {
|
||||||
try {
|
return self._cacheCredentials();
|
||||||
var uri = this.rootURI;
|
})
|
||||||
var successFileURI = uri.clone();
|
.then(function () {
|
||||||
successFileURI.spec += "lastsync";
|
var lastSyncURI = this.rootURI;
|
||||||
Zotero.HTTP.doGet(successFileURI, function (req) {
|
lastSyncURI.spec += "lastsync";
|
||||||
var ts = undefined;
|
return Zotero.HTTP.promise("GET", lastSyncURI,
|
||||||
try {
|
{ debug: true, successCodes: [200, 404] });
|
||||||
if (req.responseText) {
|
})
|
||||||
Zotero.debug(req.responseText);
|
.then(function (req) {
|
||||||
|
if (req.status == 404) {
|
||||||
|
Zotero.debug("No last WebDAV sync time");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
Zotero.debug(req.status);
|
|
||||||
|
|
||||||
if (req.status == 403) {
|
var lastModified = req.getResponseHeader("Last-Modified");
|
||||||
|
var date = new Date(lastModified);
|
||||||
|
Zotero.debug("Last successful WebDAV sync was " + date);
|
||||||
|
return Zotero.Date.toUnixTimestamp(date);
|
||||||
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
if (e.status == 403) {
|
||||||
Zotero.debug("Clearing WebDAV authentication credentials", 2);
|
Zotero.debug("Clearing WebDAV authentication credentials", 2);
|
||||||
_cachedCredentials = false;
|
_cachedCredentials = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.status != 200 && req.status != 404) {
|
|
||||||
var msg = "Unexpected status code " + req.status + " for HEAD request "
|
|
||||||
+ "in Zotero.Sync.Storage.WebDAV.getLastSyncTime()";
|
|
||||||
Zotero.debug(msg, 1);
|
|
||||||
Components.utils.reportError(msg);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(_defaultError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.status == 200) {
|
|
||||||
var lastModified = req.getResponseHeader("Last-Modified");
|
|
||||||
var date = new Date(lastModified);
|
|
||||||
Zotero.debug("Last successful storage sync was " + date);
|
|
||||||
ts = Zotero.Date.toUnixTimestamp(date);
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
ts = null;
|
throw("Unexpected status code " + e.status + " getting "
|
||||||
|
+ "WebDAV last sync time");
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(ts);
|
return Q.reject(e);
|
||||||
}
|
}
|
||||||
catch(e) {
|
// TODO: handle browser offline exception
|
||||||
Zotero.debug(e, 1);
|
else {
|
||||||
Components.utils.reportError(e);
|
throw (e);
|
||||||
Zotero.Sync.Storage.EventManager.error(_defaultError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.debug(e);
|
|
||||||
Components.utils.reportError(e);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(_defaultError);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._setLastSyncTime = function (callback) {
|
obj._setLastSyncTime = function (libraryID, localLastSyncTime) {
|
||||||
try {
|
if (libraryID) {
|
||||||
|
throw new Error("libraryID must be 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG: is this necessary for WebDAV?
|
||||||
|
if (localLastSyncTime) {
|
||||||
|
var sql = "REPLACE INTO version VALUES (?, ?)";
|
||||||
|
Zotero.DB.query(
|
||||||
|
sql, ['storage_webdav_' + libraryID, { int: localLastSyncTime }]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var uri = this.rootURI;
|
var uri = this.rootURI;
|
||||||
var successFileURI = uri.clone();
|
var successFileURI = uri.clone();
|
||||||
successFileURI.spec += "lastsync";
|
successFileURI.spec += "lastsync";
|
||||||
|
|
||||||
Zotero.HTTP.WebDAV.doPut(successFileURI, " ", function (req) {
|
var self = this;
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
Zotero.debug(req.status);
|
|
||||||
|
|
||||||
switch (req.status) {
|
return Zotero.HTTP.promise("PUT", successFileURI, " ",
|
||||||
case 200:
|
{ debug: true, successCodes: [200, 201, 204] })
|
||||||
case 201:
|
.then(function () {
|
||||||
case 204:
|
return self._getLastSyncTime()
|
||||||
getLastSyncTime(function (ts) {
|
.then(function (ts) {
|
||||||
if (ts) {
|
if (ts) {
|
||||||
var sql = "REPLACE INTO version VALUES ('storage_webdav', ?)";
|
var sql = "REPLACE INTO version VALUES (?, ?)";
|
||||||
Zotero.DB.query(sql, { int: ts });
|
Zotero.DB.query(
|
||||||
}
|
sql, ['storage_webdav_' + libraryID, { int: ts }]
|
||||||
if (callback) {
|
);
|
||||||
callback();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
})
|
||||||
}
|
.fail(function (e) {
|
||||||
|
|
||||||
var msg = "Unexpected error code " + req.status + " uploading storage success file";
|
var msg = "Unexpected error code " + req.status + " uploading storage success file";
|
||||||
Zotero.debug(msg, 2);
|
Zotero.debug(msg, 2);
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
if (callback) {
|
throw _defaultError;
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.debug(e);
|
|
||||||
Components.utils.reportError(e);
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._cacheCredentials = function (callback) {
|
obj._cacheCredentials = function () {
|
||||||
if (_cachedCredentials) {
|
if (_cachedCredentials) {
|
||||||
Zotero.debug("Credentials are already cached");
|
Zotero.debug("Credentials are already cached");
|
||||||
setTimeout(function () {
|
return;
|
||||||
callback();
|
|
||||||
}, 0);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.HTTP.doOptions(this.rootURI, function (req) {
|
return Zotero.HTTP.promise("OPTIONS", this.rootURI)
|
||||||
|
.then(function (req) {
|
||||||
|
// TODO: promisify
|
||||||
checkResponse(req);
|
checkResponse(req);
|
||||||
|
|
||||||
if (req.status != 200) {
|
|
||||||
var msg = "Unexpected status code " + req.status + " for OPTIONS request "
|
|
||||||
+ "in Zotero.Sync.Storage.WebDAV.getLastSyncTime()";
|
|
||||||
Zotero.debug(msg, 1);
|
|
||||||
Components.utils.reportError(msg);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(_defaultErrorRestart);
|
|
||||||
}
|
|
||||||
Zotero.debug("Credentials are cached");
|
Zotero.debug("Credentials are cached");
|
||||||
_cachedCredentials = true;
|
_cachedCredentials = true;
|
||||||
callback();
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
var msg = "Unexpected status code " + e.status + " "
|
||||||
|
+ "for OPTIONS request caching WebDAV credentials";
|
||||||
|
Zotero.debug(msg, 1);
|
||||||
|
Components.utils.reportError(msg);
|
||||||
|
throw new Error(_defaultErrorRestart);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1120,9 +1099,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
* @param {Object} errorCallbacks
|
* @param {Object} errorCallbacks
|
||||||
*/
|
*/
|
||||||
obj._checkServer = function (callback) {
|
obj._checkServer = function (callback) {
|
||||||
this._initFromPrefs();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Clear URIs
|
||||||
|
this.init();
|
||||||
|
|
||||||
var parentURI = this.parentURI;
|
var parentURI = this.parentURI;
|
||||||
var uri = this.rootURI;
|
var uri = this.rootURI;
|
||||||
}
|
}
|
||||||
|
@ -1132,6 +1112,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
callback(null, Zotero.Sync.Storage.ERROR_NO_URL);
|
callback(null, Zotero.Sync.Storage.ERROR_NO_URL);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 'Z_ERROR_NO_USERNAME':
|
||||||
|
callback(null, Zotero.Sync.Storage.ERROR_NO_USERNAME);
|
||||||
|
return;
|
||||||
|
|
||||||
case 'Z_ERROR_NO_PASSWORD':
|
case 'Z_ERROR_NO_PASSWORD':
|
||||||
callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD);
|
callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD);
|
||||||
return;
|
return;
|
||||||
|
@ -1399,6 +1383,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
var errorMessage = Zotero.getString('sync.storage.error.webdav.enterURL');
|
var errorMessage = Zotero.getString('sync.storage.error.webdav.enterURL');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Zotero.Sync.Storage.ERROR_NO_USERNAME:
|
||||||
|
var errorMessage = Zotero.getString('sync.error.usernameNotSet');
|
||||||
|
break;
|
||||||
|
|
||||||
case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
|
case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
|
||||||
var errorMessage = Zotero.getString('sync.error.enterPassword');
|
var errorMessage = Zotero.getString('sync.error.enterPassword');
|
||||||
break;
|
break;
|
||||||
|
@ -1525,7 +1513,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
*/
|
*/
|
||||||
obj._purgeDeletedStorageFiles = function (callback) {
|
obj._purgeDeletedStorageFiles = function (callback) {
|
||||||
if (!this._active) {
|
if (!this._active) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Purging deleted storage files");
|
Zotero.debug("Purging deleted storage files");
|
||||||
|
@ -1535,10 +1523,11 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
Zotero.Sync.Storage.EventManager.skip();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: promisify
|
||||||
|
|
||||||
// Add .zip extension
|
// Add .zip extension
|
||||||
var files = files.map(function (file) file + ".zip");
|
var files = files.map(function (file) file + ".zip");
|
||||||
|
|
||||||
|
@ -1582,16 +1571,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
||||||
const daysBeforeSyncTime = 1;
|
const daysBeforeSyncTime = 1;
|
||||||
|
|
||||||
if (!this._active) {
|
if (!this._active) {
|
||||||
Zotero.Sync.Storage.EventManager.skip();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If recently purged, skip
|
// If recently purged, skip
|
||||||
var lastpurge = Zotero.Prefs.get('lastWebDAVOrphanPurge');
|
var lastpurge = Zotero.Prefs.get('lastWebDAVOrphanPurge');
|
||||||
var days = 10;
|
var days = 10;
|
||||||
if (lastpurge && new Date(lastpurge * 1000) > (new Date() - (1000 * 60 * 60 * 24 * days))) {
|
if (lastpurge && new Date(lastpurge * 1000) > (new Date() - (1000 * 60 * 60 * 24 * days))) {
|
||||||
Zotero.Sync.Storage.EventManager.skip();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Purging orphaned storage files");
|
Zotero.debug("Purging orphaned storage files");
|
||||||
|
|
|
@ -28,7 +28,6 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
var _rootURI;
|
var _rootURI;
|
||||||
var _userURI;
|
var _userURI;
|
||||||
var _cachedCredentials = false;
|
var _cachedCredentials = false;
|
||||||
var _lastSyncTime = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file metadata on storage server
|
* Get file metadata on storage server
|
||||||
|
@ -36,23 +35,13 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
* @param {Zotero.Item} item
|
* @param {Zotero.Item} item
|
||||||
* @param {Function} callback Callback f(item, etag)
|
* @param {Function} callback Callback f(item, etag)
|
||||||
*/
|
*/
|
||||||
function getStorageFileInfo(item, callback) {
|
function getStorageFileInfo(item) {
|
||||||
var uri = getItemInfoURI(item);
|
|
||||||
|
|
||||||
Zotero.HTTP.doGet(uri, function (req) {
|
|
||||||
var funcName = "Zotero.Sync.Storage.ZFS.getStorageFileInfo()";
|
var funcName = "Zotero.Sync.Storage.ZFS.getStorageFileInfo()";
|
||||||
|
|
||||||
|
return Zotero.HTTP.promise("GET", getItemInfoURI(item), { successCodes: [200, 404] })
|
||||||
|
.then(function (req) {
|
||||||
if (req.status == 404) {
|
if (req.status == 404) {
|
||||||
callback(item, false);
|
return false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (req.status != 200) {
|
|
||||||
var msg = "Unexpected status code " + req.status + " in " + funcName
|
|
||||||
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
|
|
||||||
Zotero.debug(msg, 1);
|
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
Components.utils.reportError(msg);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = {};
|
var info = {};
|
||||||
|
@ -73,7 +62,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
var msg = "A file sync error occurred. Please restart " + Zotero.appName + " and/or your computer and try syncing again.\n\n"
|
var msg = "A file sync error occurred. Please restart " + Zotero.appName + " and/or your computer and try syncing again.\n\n"
|
||||||
+ "If the error persists, there may be a problem with either your computer or your network: security software, proxy server, VPN, etc. "
|
+ "If the error persists, there may be a problem with either your computer or your network: security software, proxy server, VPN, etc. "
|
||||||
+ "Try disabling any security/firewall software you're using or, if this is a laptop, try from a different network.";
|
+ "Try disabling any security/firewall software you're using or, if this is a laptop, try from a different network.";
|
||||||
Zotero.Sync.Storage.EventManager.error(msg);
|
throw msg;
|
||||||
}
|
}
|
||||||
info.filename = req.getResponseHeader('X-Zotero-Filename');
|
info.filename = req.getResponseHeader('X-Zotero-Filename');
|
||||||
var mtime = req.getResponseHeader('X-Zotero-Modification-Time');
|
var mtime = req.getResponseHeader('X-Zotero-Modification-Time');
|
||||||
|
@ -81,7 +70,19 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
info.compressed = req.getResponseHeader('X-Zotero-Compressed') == 'Yes';
|
info.compressed = req.getResponseHeader('X-Zotero-Compressed') == 'Yes';
|
||||||
Zotero.debug(info);
|
Zotero.debug(info);
|
||||||
|
|
||||||
callback(item, info);
|
return info;
|
||||||
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
var msg = "Unexpected status code " + e.xmlhttp.status
|
||||||
|
+ " getting storage file info";
|
||||||
|
Zotero.debug(msg, 1);
|
||||||
|
Zotero.debug(e.xmlhttp.responseText);
|
||||||
|
Components.utils.reportError(msg);
|
||||||
|
throw new Error(Zotero.Sync.Storage.defaultError);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,14 +102,14 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
|
|
||||||
var request = data.request;
|
var request = data.request;
|
||||||
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
||||||
getStorageFileInfo(item, function (item, info) {
|
return getStorageFileInfo(item)
|
||||||
|
.then(function (info) {
|
||||||
if (request.isFinished()) {
|
if (request.isFinished()) {
|
||||||
Zotero.debug("Upload request '" + request.name
|
Zotero.debug("Upload request '" + request.name
|
||||||
+ "' is no longer running after getting file info");
|
+ "' is no longer running after getting file info");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
// Check for conflict
|
// Check for conflict
|
||||||
if (Zotero.Sync.Storage.getSyncState(item.id)
|
if (Zotero.Sync.Storage.getSyncState(item.id)
|
||||||
!= Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
|
!= Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
|
||||||
|
@ -156,23 +157,25 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime);
|
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime);
|
||||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
Zotero.Sync.Storage.EventManager.changesMade();
|
return {
|
||||||
request.finish();
|
localChanges: true,
|
||||||
return;
|
remoteChanges: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
|
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
|
||||||
if (!useLocal && smtime != mtime) {
|
if (!useLocal && smtime != mtime) {
|
||||||
var localData = { modTime: fmtime };
|
|
||||||
var remoteData = { modTime: mtime };
|
|
||||||
Zotero.Sync.Storage.QueueManager.addConflict(
|
|
||||||
request.name, localData, remoteData
|
|
||||||
);
|
|
||||||
Zotero.debug("Conflict -- last synced file mod time "
|
Zotero.debug("Conflict -- last synced file mod time "
|
||||||
+ "does not match time on storage server"
|
+ "does not match time on storage server"
|
||||||
+ " (" + smtime + " != " + mtime + ")");
|
+ " (" + smtime + " != " + mtime + ")");
|
||||||
request.finish();
|
return {
|
||||||
return;
|
localChanges: false,
|
||||||
|
remoteChanges: false,
|
||||||
|
conflict: {
|
||||||
|
local: { modTime: fmtime },
|
||||||
|
remote: { modTime: mtime }
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -180,25 +183,19 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileUploadParameters(
|
return getFileUploadParameters(
|
||||||
item,
|
item,
|
||||||
function (item, target, uploadKey, params) {
|
function (item, target, uploadKey, params) {
|
||||||
try {
|
return postFile(request, item, target, uploadKey, params);
|
||||||
postFile(request, item, target, uploadKey, params);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
function () {
|
function () {
|
||||||
updateItemFileInfo(item);
|
updateItemFileInfo(item);
|
||||||
request.finish();
|
return {
|
||||||
|
localChanges: true,
|
||||||
|
remoteChanges: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +209,8 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
* on server and uploading isn't necessary
|
* on server and uploading isn't necessary
|
||||||
*/
|
*/
|
||||||
function getFileUploadParameters(item, uploadCallback, existsCallback) {
|
function getFileUploadParameters(item, uploadCallback, existsCallback) {
|
||||||
|
var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()";
|
||||||
|
|
||||||
var uri = getItemURI(item);
|
var uri = getItemURI(item);
|
||||||
|
|
||||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||||
|
@ -236,11 +235,39 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
body += "&zip=1";
|
body += "&zip=1";
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.HTTP.doPost(uri, body, function (req) {
|
return Zotero.HTTP.promise("POST", uri, { body: body, debug: true })
|
||||||
var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()";
|
.then(function (req) {
|
||||||
|
try {
|
||||||
|
// Strip XML declaration and convert to E4X
|
||||||
|
var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, '')));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error("Invalid response retrieving file upload parameters");
|
||||||
|
}
|
||||||
|
|
||||||
if (req.status == 413) {
|
if (xml.name() != 'upload' && xml.name() != 'exists') {
|
||||||
var retry = req.getResponseHeader('Retry-After');
|
throw new Error("Invalid response retrieving file upload parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
// File was already available, so uploading isn't required
|
||||||
|
if (xml.name() == 'exists') {
|
||||||
|
existsCallback();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = xml.url.toString();
|
||||||
|
var uploadKey = xml.key.toString();
|
||||||
|
var params = {}, p = '';
|
||||||
|
for each(var param in xml.params.children()) {
|
||||||
|
params[param.name()] = param.toString();
|
||||||
|
}
|
||||||
|
Zotero.debug(params);
|
||||||
|
return uploadCallback(item, url, uploadKey, params);
|
||||||
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
if (e.status == 413) {
|
||||||
|
var retry = e.xmlhttp.getResponseHeader('Retry-After');
|
||||||
if (retry) {
|
if (retry) {
|
||||||
var minutes = Math.round(retry / 60);
|
var minutes = Math.round(retry / 60);
|
||||||
// TODO: localize
|
// TODO: localize
|
||||||
|
@ -249,7 +276,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
+ "Please try again in " + minutes + " minutes.",
|
+ "Please try again in " + minutes + " minutes.",
|
||||||
"ZFS_UPLOAD_QUEUE_LIMIT"
|
"ZFS_UPLOAD_QUEUE_LIMIT"
|
||||||
);
|
);
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
var text, buttonText = null, buttonCallback;
|
var text, buttonText = null, buttonCallback;
|
||||||
|
@ -279,8 +306,6 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
// TODO: localize
|
// TODO: localize
|
||||||
text += "\n\n" + filename + " (" + Math.round(file.fileSize / 1024) + "KB)";
|
text += "\n\n" + filename + " (" + Math.round(file.fileSize / 1024) + "KB)";
|
||||||
|
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
|
|
||||||
var e = new Zotero.Error(
|
var e = new Zotero.Error(
|
||||||
"The file '" + filename + "' would exceed your Zotero File Storage quota",
|
"The file '" + filename + "' would exceed your Zotero File Storage quota",
|
||||||
"ZFS_OVER_QUOTA",
|
"ZFS_OVER_QUOTA",
|
||||||
|
@ -292,15 +317,12 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
);
|
);
|
||||||
Zotero.debug(e, 2);
|
Zotero.debug(e, 2);
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
// Stop uploads, log warning, and continue
|
// Stop uploads from this library, log warning, and continue
|
||||||
Zotero.Sync.Storage.QueueManager.get('upload').stop();
|
Zotero.Sync.Storage.QueueManager.get('upload', item.libraryID).stop();
|
||||||
Zotero.Sync.Storage.EventManager.warning(e);
|
Zotero.Sync.Storage.EventLog.warning(e, item.libraryID);
|
||||||
Zotero.Sync.Storage.EventManager.success();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (req.status == 403) {
|
else if (e.status == 403) {
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
|
|
||||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(item.libraryID);
|
var groupID = Zotero.Groups.getGroupIDFromLibraryID(item.libraryID);
|
||||||
var e = new Zotero.Error(
|
var e = new Zotero.Error(
|
||||||
"File editing denied for group",
|
"File editing denied for group",
|
||||||
|
@ -309,9 +331,9 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
groupID: groupID
|
groupID: groupID
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
throw e;
|
||||||
}
|
}
|
||||||
else if (req.status == 404) {
|
else if (e.status == 404) {
|
||||||
Components.utils.reportError("Unexpected status code 404 in " + funcName
|
Components.utils.reportError("Unexpected status code 404 in " + funcName
|
||||||
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")");
|
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")");
|
||||||
if (Zotero.Prefs.get('sync.debugNoAutoResetClient')) {
|
if (Zotero.Prefs.get('sync.debugNoAutoResetClient')) {
|
||||||
|
@ -324,49 +346,18 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
}
|
}
|
||||||
Zotero.Sync.Server.resetClient();
|
Zotero.Sync.Server.resetClient();
|
||||||
Zotero.Sync.Server.canAutoResetClient = false;
|
Zotero.Sync.Server.canAutoResetClient = false;
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
throw new Error(Zotero.Sync.Storage.defaultError);
|
||||||
}
|
}
|
||||||
else if (req.status != 200) {
|
|
||||||
var msg = "Unexpected status code " + req.status + " in " + funcName
|
var msg = "Unexpected status code " + e.status + " in " + funcName
|
||||||
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
|
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
|
||||||
Zotero.debug(msg, 1);
|
Zotero.debug(msg, 1);
|
||||||
Zotero.debug(req.responseText);
|
Zotero.debug(e.xmlhttp.getAllResponseHeaders());
|
||||||
Zotero.debug(req.getAllResponseHeaders());
|
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
throw new Error(Zotero.Sync.Storage.defaultError);
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug(req.responseText);
|
throw e;
|
||||||
|
|
||||||
try {
|
|
||||||
// Strip XML declaration and convert to E4X
|
|
||||||
var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, '')));
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
|
||||||
"Invalid response retrieving file upload parameters"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xml.name() != 'upload' && xml.name() != 'exists') {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
|
||||||
"Invalid response retrieving file upload parameters"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// File was already available, so uploading isn't required
|
|
||||||
if (xml.name() == 'exists') {
|
|
||||||
existsCallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = xml.url.toString();
|
|
||||||
var uploadKey = xml.key.toString();
|
|
||||||
var params = {}, p = '';
|
|
||||||
for each(var param in xml.params.children()) {
|
|
||||||
params[param.name()] = param.toString();
|
|
||||||
}
|
|
||||||
Zotero.debug(params);
|
|
||||||
uploadCallback(item, url, uploadKey, params);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +365,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
function postFile(request, item, url, uploadKey, params) {
|
function postFile(request, item, url, uploadKey, params) {
|
||||||
if (request.isFinished()) {
|
if (request.isFinished()) {
|
||||||
Zotero.debug("Upload request " + request.name + " is no longer running after getting upload parameters");
|
Zotero.debug("Upload request " + request.name + " is no longer running after getting upload parameters");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = getUploadFile(item);
|
var file = getUploadFile(item);
|
||||||
|
@ -458,13 +449,22 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
|
|
||||||
request.setChannel(channel);
|
request.setChannel(channel);
|
||||||
|
|
||||||
|
var deferred = Q.defer();
|
||||||
|
|
||||||
var listener = new Zotero.Sync.Storage.StreamListener(
|
var listener = new Zotero.Sync.Storage.StreamListener(
|
||||||
{
|
{
|
||||||
onProgress: function (a, b, c) {
|
onProgress: function (a, b, c) {
|
||||||
request.onProgress(a, b, c);
|
request.onProgress(a, b, c);
|
||||||
},
|
},
|
||||||
onStop: function (httpRequest, status, response, data) { onUploadComplete(httpRequest, status, response, data); },
|
onStop: function (httpRequest, status, response, data) {
|
||||||
onCancel: function (httpRequest, status, data) { onUploadCancel(httpRequest, status, data); },
|
deferred.resolve(
|
||||||
|
onUploadComplete(httpRequest, status, response, data)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onCancel: function (httpRequest, status, data) {
|
||||||
|
onUploadCancel(httpRequest, status, data)
|
||||||
|
deferred.resolve(false);
|
||||||
|
},
|
||||||
request: request,
|
request: request,
|
||||||
item: item,
|
item: item,
|
||||||
uploadKey: uploadKey,
|
uploadKey: uploadKey,
|
||||||
|
@ -480,6 +480,8 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.debug("HTTP POST of " + file.leafName + " to " + dispURI.spec);
|
Zotero.debug("HTTP POST of " + file.leafName + " to " + dispURI.spec);
|
||||||
|
|
||||||
channel.asyncOpen(listener, null);
|
channel.asyncOpen(listener, null);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,9 +500,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 500:
|
case 500:
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
throw new Error("File upload failed. Please try again.");
|
||||||
"File upload failed. Please try again."
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var msg = "Unexpected file upload status " + status
|
var msg = "Unexpected file upload status " + status
|
||||||
|
@ -509,29 +509,30 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.debug(msg, 1);
|
Zotero.debug(msg, 1);
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
Components.utils.reportError(response);
|
Components.utils.reportError(response);
|
||||||
Components.utils.reportError(msg);
|
throw new Error(Zotero.Sync.Storage.defaultError);
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri = getItemURI(item);
|
var uri = getItemURI(item);
|
||||||
var body = "update=" + uploadKey + "&mtime=" + item.attachmentModificationTime;
|
var body = "update=" + uploadKey + "&mtime=" + item.attachmentModificationTime;
|
||||||
|
|
||||||
// Register upload on server
|
// Register upload on server
|
||||||
Zotero.HTTP.doPost(uri, body, function (req) {
|
return Zotero.HTTP.promise("POST", uri, { body: body, successCodes: [204] })
|
||||||
if (req.status != 204) {
|
.then(function (req) {
|
||||||
var msg = "Unexpected file registration status " + req.status
|
updateItemFileInfo(item);
|
||||||
+ " in Zotero.Sync.Storage.ZFS.onUploadComplete()"
|
return {
|
||||||
|
localChanges: true,
|
||||||
|
remoteChanges: true
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
var msg = "Unexpected file registration status " + e.status
|
||||||
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
|
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
|
||||||
Zotero.debug(msg, 1);
|
Zotero.debug(msg, 1);
|
||||||
Zotero.debug(req.responseText);
|
Zotero.debug(e.xmlhttp.responseText);
|
||||||
Zotero.debug(req.getAllResponseHeaders());
|
Zotero.debug(e.xmlhttp.getAllResponseHeaders());
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
Components.utils.reportError(req.responseText);
|
Components.utils.reportError(e.xmlhttp.responseText);
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
throw new Error(Zotero.Sync.Storage.defaultError);
|
||||||
}
|
|
||||||
|
|
||||||
updateItemFileInfo(item);
|
|
||||||
request.finish();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,8 +564,6 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
catch (e) {
|
catch (e) {
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.EventManager.changesMade();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -584,8 +583,6 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
catch (e) {
|
catch (e) {
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -649,16 +646,12 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(obj, "_enabled", {
|
|
||||||
get: function () this.includeUserFiles || this.includeGroupFiles
|
|
||||||
});
|
|
||||||
|
|
||||||
obj._verified = true;
|
obj._verified = true;
|
||||||
|
|
||||||
Object.defineProperty(obj, "rootURI", {
|
Object.defineProperty(obj, "rootURI", {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!_rootURI) {
|
if (!_rootURI) {
|
||||||
throw ("Root URI not initialized in Zotero.Sync.Storage.ZFS.rootURI");
|
this._init();
|
||||||
}
|
}
|
||||||
return _rootURI.clone();
|
return _rootURI.clone();
|
||||||
}
|
}
|
||||||
|
@ -667,7 +660,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Object.defineProperty(obj, "userURI", {
|
Object.defineProperty(obj, "userURI", {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!_userURI) {
|
if (!_userURI) {
|
||||||
throw ("User URI not initialized in Zotero.Sync.Storage.ZFS.userURI");
|
this._init();
|
||||||
}
|
}
|
||||||
return _userURI.clone();
|
return _userURI.clone();
|
||||||
}
|
}
|
||||||
|
@ -675,35 +668,23 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
|
|
||||||
|
|
||||||
obj._init = function (url, username, password) {
|
obj._init = function (url, username, password) {
|
||||||
|
_rootURI = false;
|
||||||
|
_userURI = false;
|
||||||
|
|
||||||
|
var url = ZOTERO_CONFIG.API_URL;
|
||||||
|
var username = Zotero.Sync.Server.username;
|
||||||
|
var password = Zotero.Sync.Server.password;
|
||||||
|
|
||||||
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||||
getService(Components.interfaces.nsIIOService);
|
getService(Components.interfaces.nsIIOService);
|
||||||
try {
|
|
||||||
var uri = ios.newURI(url, null, null);
|
var uri = ios.newURI(url, null, null);
|
||||||
if (username) {
|
|
||||||
uri.username = username;
|
uri.username = username;
|
||||||
uri.password = password;
|
uri.password = password;
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.debug(e, 1);
|
|
||||||
Components.utils.reportError(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_rootURI = uri;
|
_rootURI = uri;
|
||||||
|
|
||||||
uri = uri.clone();
|
uri = uri.clone();
|
||||||
uri.spec += 'users/' + Zotero.userID + '/';
|
uri.spec += 'users/' + Zotero.userID + '/';
|
||||||
_userURI = uri;
|
_userURI = uri;
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
obj._initFromPrefs = function () {
|
|
||||||
var url = ZOTERO_CONFIG.API_URL;
|
|
||||||
var username = Zotero.Sync.Server.username;
|
|
||||||
var password = Zotero.Sync.Server.password;
|
|
||||||
return this._init(url, username, password);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -719,20 +700,19 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve file info from server to store locally afterwards
|
// Retrieve file info from server to store locally afterwards
|
||||||
getStorageFileInfo(item, function (item, info) {
|
return getStorageFileInfo(item)
|
||||||
|
.then(function (info) {
|
||||||
if (!request.isRunning()) {
|
if (!request.isRunning()) {
|
||||||
Zotero.debug("Download request '" + request.name
|
Zotero.debug("Download request '" + request.name
|
||||||
+ "' is no longer running after getting remote file info");
|
+ "' is no longer running after getting remote file info");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info) {
|
if (!info) {
|
||||||
Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key);
|
Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key);
|
||||||
request.finish();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
var syncModTime = info.mtime;
|
var syncModTime = info.mtime;
|
||||||
var syncHash = info.hash;
|
var syncHash = info.hash;
|
||||||
|
|
||||||
|
@ -749,9 +729,10 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
||||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
Zotero.Sync.Storage.EventManager.changesMade();
|
return {
|
||||||
request.finish();
|
localChanges: true,
|
||||||
return;
|
remoteChanges: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// If not compressed, check hash, in case only timestamp changed
|
// If not compressed, check hash, in case only timestamp changed
|
||||||
else if (!info.compressed && item.attachmentHash == syncHash) {
|
else if (!info.compressed && item.attachmentHash == syncHash) {
|
||||||
|
@ -767,9 +748,10 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
||||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
Zotero.Sync.Storage.EventManager.changesMade();
|
return {
|
||||||
request.finish();
|
localChanges: true,
|
||||||
return;
|
remoteChanges: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -799,6 +781,8 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.File.checkFileAccessError(e, destFile, 'create');
|
Zotero.File.checkFileAccessError(e, destFile, 'create');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var deferred = Q.defer();
|
||||||
|
|
||||||
var listener = new Zotero.Sync.Storage.StreamListener(
|
var listener = new Zotero.Sync.Storage.StreamListener(
|
||||||
{
|
{
|
||||||
onStart: function (request, data) {
|
onStart: function (request, data) {
|
||||||
|
@ -806,7 +790,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
Zotero.debug("Download request " + data.request.name
|
Zotero.debug("Download request " + data.request.name
|
||||||
+ " stopped before download started -- closing channel");
|
+ " stopped before download started -- closing channel");
|
||||||
request.cancel(0x804b0002); // NS_BINDING_ABORTED
|
request.cancel(0x804b0002); // NS_BINDING_ABORTED
|
||||||
return;
|
deferred.resolve(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress: function (a, b, c) {
|
onProgress: function (a, b, c) {
|
||||||
|
@ -819,26 +803,31 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
+ " in Zotero.Sync.Storage.ZFS.downloadFile()";
|
+ " in Zotero.Sync.Storage.ZFS.downloadFile()";
|
||||||
Zotero.debug(msg, 1);
|
Zotero.debug(msg, 1);
|
||||||
Components.utils.reportError(msg);
|
Components.utils.reportError(msg);
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
deferred.reject(Zotero.Sync.Storage.defaultError);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't try to process if the request has been cancelled
|
// Don't try to process if the request has been cancelled
|
||||||
if (data.request.isFinished()) {
|
if (data.request.isFinished()) {
|
||||||
Zotero.debug("Download request " + data.request.name
|
Zotero.debug("Download request " + data.request.name
|
||||||
+ " is no longer running after file download", 2);
|
+ " is no longer running after file download", 2);
|
||||||
|
deferred.resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Finished download of " + destFile.path);
|
Zotero.debug("Finished download of " + destFile.path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Zotero.Sync.Storage.processDownload(data);
|
deferred.resolve(Zotero.Sync.Storage.processDownload(data));
|
||||||
data.request.finish();
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
deferred.reject(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onCancel: function (request, status, data) {
|
||||||
|
Zotero.debug("Request cancelled");
|
||||||
|
deferred.resolve(false);
|
||||||
|
},
|
||||||
request: request,
|
request: request,
|
||||||
item: item,
|
item: item,
|
||||||
compressed: info.compressed,
|
compressed: info.compressed,
|
||||||
|
@ -868,10 +857,8 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
// XXX Always use when we no longer support Firefox < 18
|
// XXX Always use when we no longer support Firefox < 18
|
||||||
wbp.saveURI(uri, null, null, null, null, destFile, null);
|
wbp.saveURI(uri, null, null, null, null, destFile, null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (e) {
|
return deferred.promise;
|
||||||
Zotero.Sync.Storage.EventManager.error(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -879,140 +866,151 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
obj._uploadFile = function (request) {
|
obj._uploadFile = function (request) {
|
||||||
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
||||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||||
Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); });
|
var deferred = Q.defer();
|
||||||
|
Zotero.Sync.Storage.createUploadFile(
|
||||||
|
request,
|
||||||
|
function (data) {
|
||||||
|
deferred.resolve(processUploadFile(data));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
processUploadFile({ request: request });
|
return processUploadFile({ request: request });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._getLastSyncTime = function (callback) {
|
/**
|
||||||
var uri = this.userURI;
|
* @return {Promise} A promise for the last sync time
|
||||||
var successFileURI = uri.clone();
|
*/
|
||||||
successFileURI.spec += "laststoragesync?auth=1";
|
obj._getLastSyncTime = function (libraryID) {
|
||||||
|
var lastSyncURI = this._getLastSyncURI(libraryID);
|
||||||
|
|
||||||
// Cache the credentials at the root
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this._cacheCredentials(function () {
|
return Q.fcall(function () {
|
||||||
Zotero.HTTP.doGet(successFileURI, function (req) {
|
// Cache the credentials at the root
|
||||||
if (req.responseText) {
|
return self._cacheCredentials();
|
||||||
Zotero.debug(req.responseText);
|
})
|
||||||
}
|
.then(function () {
|
||||||
Zotero.debug(req.status);
|
return Zotero.HTTP.promise("GET", lastSyncURI,
|
||||||
|
{ debug: true, successCodes: [200, 404] });
|
||||||
if (req.status == 401 || req.status == 403) {
|
})
|
||||||
Zotero.debug("Clearing ZFS authentication credentials", 2);
|
.then(function (req) {
|
||||||
_cachedCredentials = false;
|
// Not yet synced
|
||||||
|
if (req.status == 404) {
|
||||||
|
Zotero.debug("No last sync time for library " + libraryID);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.status != 200 && req.status != 404) {
|
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
|
||||||
"Unexpected status code " + req.status + " getting "
|
|
||||||
+ "last file sync time"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.status == 200) {
|
|
||||||
var ts = req.responseText;
|
var ts = req.responseText;
|
||||||
var date = new Date(ts * 1000);
|
var date = new Date(ts * 1000);
|
||||||
Zotero.debug("Last successful storage sync was " + date);
|
Zotero.debug("Last successful ZFS sync for library "
|
||||||
_lastSyncTime = ts;
|
+ libraryID + " was " + date);
|
||||||
|
return ts;
|
||||||
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
if (e.status == 401 || e.status == 403) {
|
||||||
|
Zotero.debug("Clearing ZFS authentication credentials", 2);
|
||||||
|
_cachedCredentials = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Q.reject(e);
|
||||||
|
}
|
||||||
|
// TODO: handle browser offline exception
|
||||||
else {
|
else {
|
||||||
var ts = null;
|
throw e;
|
||||||
_lastSyncTime = null;
|
|
||||||
}
|
}
|
||||||
callback(ts);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._setLastSyncTime = function (callback, useLastSyncTime) {
|
obj._setLastSyncTime = function (libraryID, localLastSyncTime) {
|
||||||
if (useLastSyncTime) {
|
if (localLastSyncTime) {
|
||||||
if (!_lastSyncTime) {
|
var sql = "REPLACE INTO version VALUES (?, ?)";
|
||||||
if (callback) {
|
Zotero.DB.query(
|
||||||
callback();
|
sql, ['storage_zfs_' + libraryID, { int: localLastSyncTime }]
|
||||||
}
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)";
|
var lastSyncURI = this._getLastSyncURI(libraryID);
|
||||||
Zotero.DB.query(sql, { int: _lastSyncTime });
|
|
||||||
|
|
||||||
Zotero.debug("Clearing ZFS authentication credentials", 2);
|
|
||||||
_lastSyncTime = null;
|
|
||||||
_cachedCredentials = false;
|
|
||||||
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_lastSyncTime = null;
|
|
||||||
|
|
||||||
var uri = this.userURI;
|
|
||||||
var successFileURI = uri.clone();
|
|
||||||
successFileURI.spec += "laststoragesync?auth=1";
|
|
||||||
|
|
||||||
Zotero.HTTP.doPost(successFileURI, "", function (req) {
|
|
||||||
Zotero.debug(req.responseText);
|
|
||||||
Zotero.debug(req.status);
|
|
||||||
|
|
||||||
if (req.status != 200) {
|
|
||||||
var msg = "Unexpected status code " + req.status + " setting last file sync time";
|
|
||||||
Zotero.debug(msg, 1);
|
|
||||||
Components.utils.reportError(msg);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return Zotero.HTTP.promise("POST", lastSyncURI, { debug: true })
|
||||||
|
.then(function (req) {
|
||||||
var ts = req.responseText;
|
var ts = req.responseText;
|
||||||
|
|
||||||
var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)";
|
var sql = "REPLACE INTO version VALUES (?, ?)";
|
||||||
Zotero.DB.query(sql, { int: ts });
|
Zotero.DB.query(
|
||||||
|
sql, ['storage_zfs_' + libraryID, { int: ts }]
|
||||||
Zotero.debug("Clearing ZFS authentication credentials", 2);
|
);
|
||||||
_cachedCredentials = false;
|
})
|
||||||
|
.fail(function (e) {
|
||||||
if (callback) {
|
var msg = "Unexpected status code " + e.xmlhttp.status
|
||||||
callback();
|
+ " setting last file sync time";
|
||||||
}
|
Zotero.debug(msg, 1);
|
||||||
|
Components.utils.reportError(msg);
|
||||||
|
throw new Error(Zotero.Sync.Storage.defaultError);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
obj._cacheCredentials = function (callback) {
|
obj._getLastSyncURI = function (libraryID) {
|
||||||
|
if (libraryID === 0) {
|
||||||
|
var lastSyncURI = this.userURI;
|
||||||
|
}
|
||||||
|
else if (libraryID) {
|
||||||
|
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||||
|
getService(Components.interfaces.nsIIOService);
|
||||||
|
var uri = ios.newURI(Zotero.URI.getLibraryURI(libraryID), null, null);
|
||||||
|
var path = uri.path;
|
||||||
|
// We don't want the user URI, but it already has the right domain
|
||||||
|
// and credentials, so just start with that and replace the path
|
||||||
|
var lastSyncURI = this.userURI;
|
||||||
|
lastSyncURI.path = path + "/";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error("libraryID not specified");
|
||||||
|
}
|
||||||
|
lastSyncURI.spec += "laststoragesync";
|
||||||
|
return lastSyncURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
obj._cacheCredentials = function () {
|
||||||
if (_cachedCredentials) {
|
if (_cachedCredentials) {
|
||||||
Zotero.debug("Credentials are already cached");
|
Zotero.debug("Credentials are already cached");
|
||||||
setTimeout(function () {
|
return;
|
||||||
callback();
|
|
||||||
}, 0);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri = this.rootURI;
|
var uri = this.rootURI;
|
||||||
// TODO: move to root uri
|
// TODO: move to root uri
|
||||||
uri.spec += "?auth=1";
|
uri.spec += "?auth=1";
|
||||||
Zotero.HTTP.doGet(uri, function (req) {
|
|
||||||
if (req.status == 401) {
|
return Zotero.HTTP.promise("GET", uri).
|
||||||
// TODO: localize
|
then(function (req) {
|
||||||
var msg = "File sync login failed\n\nCheck your username and password in the Sync pane of the Zotero preferences.";
|
|
||||||
Zotero.Sync.Storage.EventManager.error(msg);
|
|
||||||
}
|
|
||||||
else if (req.status != 200) {
|
|
||||||
var msg = "Unexpected status code " + req.status + " caching "
|
|
||||||
+ "authentication credentials in Zotero.Sync.Storage.ZFS.cacheCredentials()";
|
|
||||||
Zotero.debug(msg, 1);
|
|
||||||
Components.utils.reportError(msg);
|
|
||||||
Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultErrorRestart);
|
|
||||||
}
|
|
||||||
Zotero.debug("Credentials are cached");
|
Zotero.debug("Credentials are cached");
|
||||||
_cachedCredentials = true;
|
_cachedCredentials = true;
|
||||||
callback();
|
})
|
||||||
|
.fail(function (e) {
|
||||||
|
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||||
|
if (e.status == 401) {
|
||||||
|
var msg = "File sync login failed\n\n"
|
||||||
|
+ "Check your username and password in the Sync "
|
||||||
|
+ "pane of the Zotero preferences.";
|
||||||
|
throw (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = "Unexpected status code " + e.status + " "
|
||||||
|
+ "caching ZFS credentials";
|
||||||
|
Zotero.debug(msg, 1);
|
||||||
|
throw (msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1022,17 +1020,17 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
obj._purgeDeletedStorageFiles = function (callback) {
|
obj._purgeDeletedStorageFiles = function (callback) {
|
||||||
// If we don't have a user id we've never synced and don't need to bother
|
// If we don't have a user id we've never synced and don't need to bother
|
||||||
if (!Zotero.userID) {
|
if (!Zotero.userID) {
|
||||||
Zotero.Sync.Storage.EventManager.skip();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "SELECT value FROM settings WHERE setting=? AND key=?";
|
var sql = "SELECT value FROM settings WHERE setting=? AND key=?";
|
||||||
var values = Zotero.DB.columnQuery(sql, ['storage', 'zfsPurge']);
|
var values = Zotero.DB.columnQuery(sql, ['storage', 'zfsPurge']);
|
||||||
if (!values) {
|
if (!values) {
|
||||||
Zotero.Sync.Storage.EventManager.skip();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: promisify
|
||||||
|
|
||||||
Zotero.debug("Unlinking synced files on ZFS");
|
Zotero.debug("Unlinking synced files on ZFS");
|
||||||
|
|
||||||
var uri = this.userURI;
|
var uri = this.userURI;
|
||||||
|
@ -1049,9 +1047,8 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
throw "Invalid zfsPurge value '" + value
|
||||||
"Invalid zfsPurge value '" + value + "' in ZFS purgeDeletedStorageFiles()"
|
+ "' in ZFS purgeDeletedStorageFiles()";
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uri.spec = uri.spec.substr(0, uri.spec.length - 1);
|
uri.spec = uri.spec.substr(0, uri.spec.length - 1);
|
||||||
|
@ -1061,9 +1058,7 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(false);
|
callback(false);
|
||||||
}
|
}
|
||||||
Zotero.Sync.Storage.EventManager.error(
|
throw "Unexpected status code " + xmlhttp.status + " purging ZFS files";
|
||||||
"Unexpected status code " + xmlhttp.status + " purging ZFS files"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "DELETE FROM settings WHERE setting=? AND key=?";
|
var sql = "DELETE FROM settings WHERE setting=? AND key=?";
|
||||||
|
@ -1072,8 +1067,6 @@ Zotero.Sync.Storage.ZFS = (function () {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(true);
|
callback(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Sync.Storage.EventManager.success();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -505,12 +505,12 @@ Zotero.Sync.Runner = new function () {
|
||||||
|
|
||||||
var _autoSyncTimer;
|
var _autoSyncTimer;
|
||||||
var _queue;
|
var _queue;
|
||||||
var _running;
|
|
||||||
var _background;
|
var _background;
|
||||||
|
|
||||||
var _lastSyncStatus;
|
var _lastSyncStatus;
|
||||||
var _currentSyncStatusLabel;
|
var _currentSyncStatusLabel;
|
||||||
var _currentLastSyncLabel;
|
var _currentLastSyncLabel;
|
||||||
|
var _errorsByLibrary = {};
|
||||||
|
|
||||||
var _warning = null;
|
var _warning = null;
|
||||||
|
|
||||||
|
@ -526,16 +526,9 @@ Zotero.Sync.Runner = new function () {
|
||||||
this.clearSyncTimeout(); // DEBUG: necessary?
|
this.clearSyncTimeout(); // DEBUG: necessary?
|
||||||
var msg = "Zotero cannot sync while " + Zotero.appName + " is in offline mode.";
|
var msg = "Zotero cannot sync while " + Zotero.appName + " is in offline mode.";
|
||||||
var e = new Zotero.Error(msg, 0, { dialogButtonText: null })
|
var e = new Zotero.Error(msg, 0, { dialogButtonText: null })
|
||||||
this.setSyncIcon('error', e);
|
Components.utils.reportError(e);
|
||||||
return false;
|
Zotero.debug(e, 1);
|
||||||
}
|
this.setSyncIcon(e);
|
||||||
|
|
||||||
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);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,7 +536,6 @@ Zotero.Sync.Runner = new function () {
|
||||||
Zotero.purgeDataObjects(true);
|
Zotero.purgeDataObjects(true);
|
||||||
|
|
||||||
_background = !!background;
|
_background = !!background;
|
||||||
_running = true;
|
|
||||||
this.setSyncIcon('animate');
|
this.setSyncIcon('animate');
|
||||||
|
|
||||||
var finalCallbacks = {
|
var finalCallbacks = {
|
||||||
|
@ -554,61 +546,30 @@ Zotero.Sync.Runner = new function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
var storageSync = function () {
|
var storageSync = function () {
|
||||||
var syncNeeded = false;
|
|
||||||
|
|
||||||
Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
|
Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
|
||||||
|
|
||||||
var zfsSync = function (skipSyncNeeded) {
|
Zotero.Sync.Storage.sync()
|
||||||
Zotero.Sync.Storage.ZFS.sync({
|
.then(function (results) {
|
||||||
// ZFS success
|
Zotero.debug("File sync is finished");
|
||||||
onSuccess: function () {
|
|
||||||
setTimeout(function () {
|
|
||||||
Zotero.Sync.Server.sync(finalCallbacks);
|
|
||||||
}, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ZFS skip
|
if (results.errors.length) {
|
||||||
onSkip: function () {
|
Zotero.Sync.Runner.setErrors(results.errors);
|
||||||
setTimeout(function () {
|
|
||||||
if (skipSyncNeeded) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.changesMade) {
|
||||||
|
Zotero.debug("Changes made during file sync "
|
||||||
|
+ "-- performing additional data sync");
|
||||||
Zotero.Sync.Server.sync(finalCallbacks);
|
Zotero.Sync.Server.sync(finalCallbacks);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Zotero.Sync.Runner.stop();
|
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
|
|
||||||
})
|
})
|
||||||
};
|
.fail(function (e) {
|
||||||
|
Zotero.debug("File sync failed", 1);
|
||||||
Zotero.Sync.Storage.WebDAV.sync({
|
Zotero.Sync.Runner.error(e);
|
||||||
// WebDAV success
|
|
||||||
onSuccess: function () {
|
|
||||||
zfsSync(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
// WebDAV skip
|
|
||||||
onSkip: function () {
|
|
||||||
zfsSync();
|
|
||||||
},
|
|
||||||
|
|
||||||
// WebDAV cancel
|
|
||||||
onStop: Zotero.Sync.Runner.stop,
|
|
||||||
|
|
||||||
// WebDAV failure
|
|
||||||
onError: Zotero.Sync.Runner.error
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -620,23 +581,26 @@ Zotero.Sync.Runner = new function () {
|
||||||
onSkip: storageSync,
|
onSkip: storageSync,
|
||||||
|
|
||||||
// Sync 1 stop
|
// Sync 1 stop
|
||||||
onStop: Zotero.Sync.Runner.stop,
|
onStop: function () {
|
||||||
|
Zotero.Sync.Runner.stop();
|
||||||
|
},
|
||||||
|
|
||||||
// Sync 1 error
|
// Sync 1 error
|
||||||
onError: Zotero.Sync.Runner.error
|
onError: function (e) {
|
||||||
|
Zotero.Sync.Runner.error(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.stop = function () {
|
this.stop = function () {
|
||||||
if (_warning) {
|
if (_warning) {
|
||||||
Zotero.Sync.Runner.setSyncIcon('warning', _warning);
|
Zotero.Sync.Runner.setSyncIcon(_warning);
|
||||||
_warning = null;
|
_warning = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Zotero.Sync.Runner.setSyncIcon();
|
Zotero.Sync.Runner.setSyncIcon();
|
||||||
}
|
}
|
||||||
_running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -644,14 +608,17 @@ Zotero.Sync.Runner = new function () {
|
||||||
* Log a warning, but don't throw an error
|
* Log a warning, but don't throw an error
|
||||||
*/
|
*/
|
||||||
this.warning = function (e) {
|
this.warning = function (e) {
|
||||||
|
Zotero.debug(e, 2);
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
|
e.status = 'warning';
|
||||||
_warning = e;
|
_warning = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.error = function (e) {
|
this.error = function (e) {
|
||||||
Zotero.Sync.Runner.setSyncIcon('error', e);
|
Components.utils.reportError(e);
|
||||||
_running = false;
|
Zotero.debug(e, 1);
|
||||||
|
Zotero.Sync.Runner.setSyncIcon(e);
|
||||||
throw (e);
|
throw (e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,60 +707,85 @@ Zotero.Sync.Runner = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.setSyncIcon = function (status, e) {
|
/**
|
||||||
var message;
|
* Trigger updating of the main sync icon, the sync error icon, and
|
||||||
var buttonText;
|
* library-specific sync error icons across all windows
|
||||||
var buttonCallback;
|
*/
|
||||||
var frontWindowOnly = false;
|
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) {
|
// Store other errors by libraryID to be shown in the source list
|
||||||
case '':
|
for each(var e in errors) {
|
||||||
case 'animate':
|
// Skip non-library-specific errors
|
||||||
case 'warning':
|
if (typeof e.libraryID == 'undefined') {
|
||||||
case 'error':
|
continue;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw ("Invalid sync icon status '" + status
|
|
||||||
+ "' in Zotero.Sync.Runner.setSyncIcon()");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e) {
|
if (!_errorsByLibrary[e.libraryID]) {
|
||||||
if (e.data) {
|
_errorsByLibrary[e.libraryID] = [];
|
||||||
if (e.data.dialogText) {
|
|
||||||
message = e.data.dialogText;
|
|
||||||
}
|
|
||||||
if (typeof e.data.dialogButtonText != 'undefined') {
|
|
||||||
buttonText = e.data.dialogButtonText;
|
|
||||||
buttonCallback = e.data.dialogButtonCallback;
|
|
||||||
}
|
|
||||||
if (e.data.frontWindowOnly) {
|
|
||||||
frontWindowOnly = e.data.frontWindowOnly;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!message) {
|
|
||||||
if (e.message) {
|
|
||||||
message = e.message;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
_errorsByLibrary[e.libraryID].push(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
var upgradeRequired = false;
|
// Refresh source list
|
||||||
|
Zotero.Notifier.trigger('redraw', 'collection', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.getErrors = function (libraryID) {
|
||||||
|
if (!_errorsByLibrary[libraryID]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _errorsByLibrary[libraryID];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.getPrimaryError = function (errors) {
|
||||||
|
errors = [this.parseSyncError(e) for each(e in errors)];
|
||||||
|
|
||||||
|
// Set highest priority error as the primary (sync error icon)
|
||||||
|
var statusPriorities = {
|
||||||
|
info: 1,
|
||||||
|
warning: 2,
|
||||||
|
error: 3,
|
||||||
|
upgrade: 4,
|
||||||
|
|
||||||
|
// Skip these
|
||||||
|
animate: -1
|
||||||
|
};
|
||||||
|
var primaryError = false;
|
||||||
|
for each(var error in errors) {
|
||||||
|
if (!error.status || statusPriorities[error.status] == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!primaryError || statusPriorities[error.status]
|
||||||
|
> statusPriorities[primaryError.status]) {
|
||||||
|
primaryError = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return primaryError;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the main sync error icon across all windows
|
||||||
|
*/
|
||||||
|
this.setSyncIcon = function (e) {
|
||||||
|
e = this.parseSyncError(e);
|
||||||
|
|
||||||
if (Zotero.Sync.Server.upgradeRequired) {
|
if (Zotero.Sync.Server.upgradeRequired) {
|
||||||
upgradeRequired = true;
|
e.status = 'upgrade';
|
||||||
Zotero.Sync.Server.upgradeRequired = false;
|
Zotero.Sync.Server.upgradeRequired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == 'error') {
|
if (e.frontWindowOnly) {
|
||||||
var errorsLogged = Zotero.getErrors().length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frontWindowOnly) {
|
|
||||||
// Fake an nsISimpleEnumerator with just the topmost window
|
// Fake an nsISimpleEnumerator with just the topmost window
|
||||||
var enumerator = {
|
var enumerator = {
|
||||||
_returned: false,
|
_returned: false,
|
||||||
|
@ -821,99 +813,16 @@ Zotero.Sync.Runner = new function () {
|
||||||
while (enumerator.hasMoreElements()) {
|
while (enumerator.hasMoreElements()) {
|
||||||
var win = enumerator.getNext();
|
var win = enumerator.getNext();
|
||||||
if (!win.ZoteroPane) continue;
|
if (!win.ZoteroPane) continue;
|
||||||
var warning = win.ZoteroPane.document.getElementById('zotero-tb-sync-warning');
|
var doc = win.ZoteroPane.document;
|
||||||
var icon = win.ZoteroPane.document.getElementById('zotero-tb-sync');
|
|
||||||
|
|
||||||
if (status == 'warning' || status == 'error') {
|
var button = doc.getElementById('zotero-tb-sync-error');
|
||||||
icon.setAttribute('status', '');
|
this.setErrorIcon(button, [e]);
|
||||||
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 syncIcon = doc.getElementById('zotero-tb-sync');
|
||||||
|
// Update sync icon state
|
||||||
|
syncIcon.setAttribute('status', e.status ? e.status : "");
|
||||||
// Disable button while spinning
|
// Disable button while spinning
|
||||||
icon.disabled = status == 'animate';
|
syncIcon.disabled = e.status == 'animate';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear status
|
// Clear status
|
||||||
|
@ -921,6 +830,9 @@ Zotero.Sync.Runner = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the sync icon tooltip message
|
||||||
|
*/
|
||||||
this.setSyncStatus = function (msg) {
|
this.setSyncStatus = function (msg) {
|
||||||
_lastSyncStatus = 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
|
* Register label in sync icon tooltip to receive updates
|
||||||
*
|
*
|
||||||
|
@ -1440,7 +1478,7 @@ Zotero.Sync.Server = new function () {
|
||||||
Zotero.suppressUIUpdates = true;
|
Zotero.suppressUIUpdates = true;
|
||||||
_updatesInProgress = true;
|
_updatesInProgress = true;
|
||||||
|
|
||||||
var errorHandler = function (e) {
|
var errorHandler = function (e, rethrow) {
|
||||||
Zotero.DB.rollbackTransaction();
|
Zotero.DB.rollbackTransaction();
|
||||||
|
|
||||||
Zotero.UnresponsiveScriptIndicator.enable();
|
Zotero.UnresponsiveScriptIndicator.enable();
|
||||||
|
@ -1451,6 +1489,9 @@ Zotero.Sync.Server = new function () {
|
||||||
Zotero.suppressUIUpdates = false;
|
Zotero.suppressUIUpdates = false;
|
||||||
_updatesInProgress = false;
|
_updatesInProgress = false;
|
||||||
|
|
||||||
|
if (rethrow) {
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
_error(e);
|
_error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1662,7 +1703,7 @@ Zotero.Sync.Server = new function () {
|
||||||
Zotero.pumpGenerator(gen, false, errorHandler);
|
Zotero.pumpGenerator(gen, false, errorHandler);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
errorHandler(e);
|
errorHandler(e, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
@ -2987,17 +3028,16 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
obj.attachmentSyncState =
|
obj.attachmentSyncState =
|
||||||
Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
|
Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
|
||||||
}
|
}
|
||||||
// Set existing attachments mtime update check
|
// Set existing attachments for mtime update check
|
||||||
else {
|
else {
|
||||||
var mtime = objectNode.getAttribute('storageModTime');
|
var mtime = objectNode.getAttribute('storageModTime');
|
||||||
if (mtime) {
|
if (mtime) {
|
||||||
var lk = Zotero.Items.getLibraryKeyHash(obj)
|
|
||||||
// Convert previously used Unix timestamps to ms-based timestamps
|
// Convert previously used Unix timestamps to ms-based timestamps
|
||||||
if (mtime < 10000000000) {
|
if (mtime < 10000000000) {
|
||||||
Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
|
Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
|
||||||
mtime = mtime * 1000;
|
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
|
// 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 they've been updated elsewhere and mark for download if so
|
||||||
if (type == 'item') {
|
if (type == 'item' && Object.keys(itemStorageModTimes).length) {
|
||||||
var ids = [];
|
Zotero.Sync.Storage.checkForUpdatedFiles(itemStorageModTimes);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,12 +83,9 @@ Zotero.URI = new function () {
|
||||||
* Get path portion of library URI (e.g., users/6 or groups/1)
|
* Get path portion of library URI (e.g., users/6 or groups/1)
|
||||||
*/
|
*/
|
||||||
this.getLibraryPath = function (libraryID) {
|
this.getLibraryPath = function (libraryID) {
|
||||||
if (libraryID) {
|
libraryID = libraryID ? parseInt(libraryID) : 0;
|
||||||
var libraryType = Zotero.Libraries.getType(libraryID);
|
var libraryType = Zotero.Libraries.getType(libraryID);
|
||||||
}
|
|
||||||
else {
|
|
||||||
libraryType = 'user';
|
|
||||||
}
|
|
||||||
switch (libraryType) {
|
switch (libraryType) {
|
||||||
case 'user':
|
case 'user':
|
||||||
var id = Zotero.userID;
|
var id = Zotero.userID;
|
||||||
|
|
|
@ -153,6 +153,7 @@ var ZoteroPane = new function()
|
||||||
var collectionsTree = document.getElementById('zotero-collections-tree');
|
var collectionsTree = document.getElementById('zotero-collections-tree');
|
||||||
collectionsTree.view = ZoteroPane_Local.collectionsView;
|
collectionsTree.view = ZoteroPane_Local.collectionsView;
|
||||||
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
|
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
|
||||||
|
collectionsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true);
|
||||||
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
|
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
|
||||||
|
|
||||||
var itemsTree = document.getElementById('zotero-items-tree');
|
var itemsTree = document.getElementById('zotero-items-tree');
|
||||||
|
@ -2509,11 +2510,32 @@ var ZoteroPane = new function()
|
||||||
var t = event.originalTarget;
|
var t = event.originalTarget;
|
||||||
var tree = t.parentNode;
|
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
|
// Automatically select all equivalent items when clicking on an item
|
||||||
// in duplicates view
|
// 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
|
// Trigger only on primary-button single clicks with modifiers
|
||||||
// (so that items can still be selected and deselected manually)
|
// (so that items can still be selected and deselected manually)
|
||||||
if (!event || event.detail != 1 || event.button != 0 || event.metaKey || event.shiftKey) {
|
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 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
|
// We care only about primary-button double and triple clicks
|
||||||
if (!event || (event.detail != 2 && event.detail != 3) || event.button != 0) {
|
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(),
|
// The Mozilla tree binding fires select() in mousedown(),
|
||||||
// but if when it gets to click() the selection differs from
|
// but if when it gets to click() the selection differs from
|
||||||
// what it expects (say, because multiple items had been
|
// what it expects (say, because multiple items had been
|
||||||
// selected during mousedown()), it fires select() again.
|
// selected during mousedown(), as is the case in duplicates mode),
|
||||||
// We prevent that here.
|
// it fires select() again. We prevent that here.
|
||||||
var itemGroup = ZoteroPane_Local.getItemGroup();
|
else if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
|
||||||
if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
|
|
||||||
if (event.metaKey || event.shiftKey) {
|
if (event.metaKey || event.shiftKey) {
|
||||||
return;
|
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') {
|
if (obj.value == 'twisty') {
|
||||||
return;
|
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'
|
// obj.value == 'cell'/'text'/'image'
|
||||||
if (!obj.value) {
|
if (!obj.value) {
|
||||||
return;
|
return;
|
||||||
|
@ -3424,6 +3473,8 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
|
|
||||||
function viewAttachment(itemIDs, event, noLocateOnMissing, forceExternalViewer) {
|
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
|
// If view isn't editable, don't show Locate button, since the updated
|
||||||
// path couldn't be sent back up
|
// path couldn't be sent back up
|
||||||
if (!this.collectionsView.editable) {
|
if (!this.collectionsView.editable) {
|
||||||
|
@ -3478,38 +3529,39 @@ var ZoteroPane = new function()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (item.isImportedAttachment() && Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
|
if (!item.isImportedAttachment() || !Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
|
||||||
let downloadedItem = item;
|
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
|
||||||
var started = Zotero.Sync.Storage.downloadFile(item, {
|
return;
|
||||||
onStart: function (request) {
|
|
||||||
if (!(request instanceof Zotero.Sync.Storage.Request)) {
|
|
||||||
throw new Error("Invalid request object");
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
onProgress: function (progress, progressMax) {
|
let downloadedItem = item;
|
||||||
|
Q.fcall(function () {
|
||||||
},
|
return Zotero.Sync.Storage.downloadFile(
|
||||||
|
downloadedItem,
|
||||||
onStop: function () {
|
{
|
||||||
|
onProgress: function (progress, progressMax) {}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
if (!downloadedItem.getFile()) {
|
if (!downloadedItem.getFile()) {
|
||||||
ZoteroPane_Local.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
|
ZoteroPane_Local.showAttachmentNotFoundDialog(downloadedItem.id, noLocateOnMissing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if unchanged?
|
// check if unchanged?
|
||||||
// maybe not necessary, since we'll get an error if there's an error
|
// 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);
|
ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
|
||||||
},
|
})
|
||||||
});
|
.fail(function (e) {
|
||||||
|
// TODO: show error somewhere else
|
||||||
if (started) {
|
Zotero.debug(e, 1);
|
||||||
continue;
|
ZoteroPane_Local.syncAlert(e);
|
||||||
}
|
})
|
||||||
}
|
.end();
|
||||||
|
|
||||||
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 () {
|
this.createParentItemsFromSelected = function () {
|
||||||
if (!this.canEdit()) {
|
if (!this.canEdit()) {
|
||||||
this.displayCannotEditLibraryMessage();
|
this.displayCannotEditLibraryMessage();
|
||||||
|
|
|
@ -192,32 +192,22 @@
|
||||||
value="0" tooltip="zotero-tb-sync-progress-tooltip">
|
value="0" tooltip="zotero-tb-sync-progress-tooltip">
|
||||||
</progressmeter>
|
</progressmeter>
|
||||||
<tooltip id="zotero-tb-sync-progress-tooltip" noautohide="true">
|
<tooltip id="zotero-tb-sync-progress-tooltip" noautohide="true">
|
||||||
<grid>
|
<hbox id="zotero-tb-sync-progress-tooltip-progress">
|
||||||
<columns>
|
|
||||||
<column/>
|
|
||||||
<column/>
|
|
||||||
</columns>
|
|
||||||
<rows>
|
|
||||||
<row>
|
|
||||||
<label value="&zotero.sync.storage.progress;"/>
|
<label value="&zotero.sync.storage.progress;"/>
|
||||||
<label id="zotero-tb-sync-progress-tooltip-progress"/>
|
<label/>
|
||||||
</row>
|
</hbox>
|
||||||
<row>
|
<zoterofilesyncstatus id="zotero-tb-sync-progress-status"/>
|
||||||
<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>
|
|
||||||
</tooltip>
|
</tooltip>
|
||||||
</hbox>
|
</hbox>
|
||||||
</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"
|
<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()">
|
oncommand="Zotero.Sync.Server.canAutoResetClient = true; Zotero.Sync.Server.manualSyncRequired = false; Zotero.Sync.Runner.sync()">
|
||||||
<tooltip
|
<tooltip
|
||||||
|
@ -292,6 +282,10 @@
|
||||||
|
|
||||||
<hbox id="zotero-trees" flex="1">
|
<hbox id="zotero-trees" flex="1">
|
||||||
<vbox id="zotero-collections-pane" zotero-persist="width">
|
<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
|
<!-- This extra vbox prevents the toolbar from getting compressed when resizing
|
||||||
the tag selector to max height -->
|
the tag selector to max height -->
|
||||||
<tree id="zotero-collections-tree" hidecolumnpicker="true" context="zotero-collectionmenu"
|
<tree id="zotero-collections-tree" hidecolumnpicker="true" context="zotero-collectionmenu"
|
||||||
|
@ -309,6 +303,9 @@
|
||||||
flex="1"
|
flex="1"
|
||||||
primary="true"
|
primary="true"
|
||||||
hideheader="true"/>
|
hideheader="true"/>
|
||||||
|
<treecol
|
||||||
|
id="zotero-collections-sync-status-column"
|
||||||
|
hideheader="true"/>
|
||||||
</treecols>
|
</treecols>
|
||||||
<treechildren/>
|
<treechildren/>
|
||||||
</tree>
|
</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.uploadAccepted = Upload accepted \u2014 waiting for sync server
|
||||||
sync.status.syncingFiles = Syncing files
|
sync.status.syncingFiles = Syncing files
|
||||||
|
|
||||||
|
sync.storage.mbRemaining = %SMB remaining
|
||||||
sync.storage.kbRemaining = %SKB remaining
|
sync.storage.kbRemaining = %SKB remaining
|
||||||
sync.storage.filesRemaining = %1$S/%2$S files
|
sync.storage.filesRemaining = %1$S/%2$S files
|
||||||
sync.storage.none = None
|
sync.storage.none = None
|
||||||
|
sync.storage.downloads = Downloads:
|
||||||
|
sync.storage.uploads = Uploads:
|
||||||
sync.storage.localFile = Local File
|
sync.storage.localFile = Local File
|
||||||
sync.storage.remoteFile = Remote File
|
sync.storage.remoteFile = Remote File
|
||||||
sync.storage.savedFile = Saved File
|
sync.storage.savedFile = Saved File
|
||||||
|
|
|
@ -20,11 +20,19 @@
|
||||||
min-height: 5.2em;
|
min-height: 5.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#zotero-collections-tree treechildren::-moz-tree-image
|
#zotero-collections-tree treechildren::-moz-tree-image(primary)
|
||||||
{
|
{
|
||||||
margin-right: 5px;
|
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) */
|
/* Set by setHighlightedRows() and getRowProperties() in collectionTreeView.js) */
|
||||||
#zotero-collections-tree treechildren::-moz-tree-row(highlighted)
|
#zotero-collections-tree treechildren::-moz-tree-row(highlighted)
|
||||||
{
|
{
|
||||||
|
@ -54,6 +62,145 @@
|
||||||
margin-right: 5px;
|
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 */
|
/* Set tag colors */
|
||||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFFF) { color:#FFFFFF }
|
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFFF) { color:#FFFFFF }
|
||||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCCC) { color:#FFCCCC }
|
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCCC) { color:#FFCCCC }
|
||||||
|
@ -380,28 +527,58 @@
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#zotero-tb-sync-progress-tooltip row label:first-child
|
#zotero-tb-sync-progress-tooltip-progress {
|
||||||
{
|
margin-bottom: 5px;
|
||||||
text-align: right;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#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);
|
list-style-image: url(chrome://zotero/skin/error.png);
|
||||||
margin-right: -5px;
|
margin-right: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#zotero-tb-sync-warning[mode=error]
|
#zotero-tb-sync-error[mode=error]
|
||||||
{
|
{
|
||||||
list-style-image: url(chrome://zotero/skin/exclamation.png);
|
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);
|
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 {
|
#zotero-tb-sync {
|
||||||
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
|
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
|
||||||
margin-left: -6px;
|
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');
|
-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 {
|
label.zotero-text-link {
|
||||||
-moz-binding: url('chrome://zotero/content/bindings/text-link.xml#text-link');
|
-moz-binding: url('chrome://zotero/content/bindings/text-link.xml#text-link');
|
||||||
-moz-user-focus: normal;
|
-moz-user-focus: normal;
|
||||||
|
|
|
@ -95,7 +95,7 @@ const xpcomFilesLocal = [
|
||||||
'sync',
|
'sync',
|
||||||
'storage',
|
'storage',
|
||||||
'storage/streamListener',
|
'storage/streamListener',
|
||||||
'storage/eventManager',
|
'storage/eventLog',
|
||||||
'storage/queueManager',
|
'storage/queueManager',
|
||||||
'storage/queue',
|
'storage/queue',
|
||||||
'storage/request',
|
'storage/request',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user