commit bd070f7b63e85454afd50c9bfc9d9a4422297fb0
parent a1277ccaa9a227fac0c85285f5b4728e3092bff4
Author: Dan Stillman <dstillman@zotero.org>
Date: Thu, 15 Jan 2009 06:58:06 +0000
- Adds file conflict resolution -- not particularly attractive at the moment, and no Apply to All button, but possibly functional
- Fixes file syncing after editing a file locally
- Fixes a few storage bugs that could result in eternal spinning, invalid percentages, and other unpleasantries
- Made the attachment display box more flexible, including a filename field that we may or may not want to keep in the main view
Diffstat:
6 files changed, 396 insertions(+), 55 deletions(-)
diff --git a/chrome/content/zotero/bindings/attachmentbox.xml b/chrome/content/zotero/bindings/attachmentbox.xml
@@ -41,6 +41,7 @@
<field name="displayGoButtons">false</field>
<field name="clickableLink">false</field>
<field name="displayButton">false</field>
+ <field name="displayNote">false</field>
<field name="buttonCaption"/>
<field name="clickHandler"/>
@@ -52,31 +53,52 @@
<![CDATA[
this.editable = false;
this.displayGoButtons = false;
+ this.displayURL = false;
+ this.displayFileName = false;
this.clickableLink = false;
- this.displayIndexed = false;
+ this.displayAccessed = false;
this.displayPages = false;
+ this.displayDateModified = false;
+ this.displayIndexed = false;
+ this.displayNote = false;
switch (val) {
case 'view':
this.displayGoButtons = true;
+ this.displayURL = true;
+ this.displayFileName = true;
this.clickableLink = true;
- this.displayIndexed = true;
+ this.displayAccessed = true;
this.displayPages = true;
+ this.displayIndexed = true;
+ this.displayNote = true;
break;
case 'edit':
this.editable = true;
this.displayGoButtons = true;
+ this.displayURL = true;
+ this.displayFileName = true;
this.clickableLink = true;
- this.displayIndexed = true;
+ this.displayAccessed = true;
this.displayPages = true;
+ this.displayIndexed = true;
+ this.displayNote = true;
break;
case 'merge':
+ this.displayURL = true;
+ this.displayFileName = true;
+ this.displayAccessed = true;
+ this.displayNote = true;
this.displayButton = true;
break;
case 'mergeedit':
+ this.displayURL = true;
+ this.displayFileName = true;
+ this.displayAccessed = true;
+ this.displayNote = true;
break;
default:
@@ -114,9 +136,11 @@
var goButtons = this._id('go-buttons');
var viewButton = this._id('view');
var showButton = this._id('show');
+ var fileNameRow = this._id('fileNameRow');
var urlField = this._id('url');
var accessed = this._id('accessed');
var pagesRow = this._id('pages');
+ var dateModifiedRow = this._id('dateModified');
var indexBox = this._id('index-box');
var selectButton = this._id('select-button');
@@ -180,36 +204,74 @@
var str = Zotero.getString('pane.item.attachments.view.link');
}
- showButton.setAttribute('hidden', !isImportedURL);
+ showButton.hidden = !isImportedURL;
// URL
- var urlSpec = this.item.getField('url');
- urlField.setAttribute('value', urlSpec);
- urlField.setAttribute('hidden', false);
- if (this.clickableLink) {
- urlField.onclick = function (event) {
- ZoteroPane.loadURI(this.value, event)
- };
- urlField.className = 'text-link';
+ if (this.displayURL) {
+ var urlSpec = this.item.getField('url');
+ urlField.setAttribute('value', urlSpec);
+ urlField.setAttribute('hidden', false);
+ if (this.clickableLink) {
+ urlField.onclick = function (event) {
+ ZoteroPane.loadURI(this.value, event)
+ };
+ urlField.className = 'text-link';
+ }
+ else {
+ urlField.className = '';
+ }
+ urlField.hidden = false;
}
else {
- urlField.className = '';
+ urlField.hidden = true;
}
// Access date
- accessed.setAttribute('value',
- Zotero.getString('itemFields.accessDate') + ': '
- + Zotero.Date.sqlToDate(this.item.getField('accessDate'), true).toLocaleString());
- accessed.setAttribute('hidden', false);
+ if (this.displayAccessed) {
+ accessed.value = Zotero.localeJoin([
+ Zotero.getString('itemFields.accessDate'),
+ ': ',
+ Zotero.Date.sqlToDate(
+ this.item.getField('accessDate'), true
+ ).toLocaleString()
+ ], '');
+ accessed.hidden = false;
+ }
+ else {
+ accessed.hidden = true;
+ }
}
// Metadata for files
else {
var str = Zotero.getString('pane.item.attachments.view.file');
- showButton.setAttribute('hidden', false);
- urlField.setAttribute('hidden', true);
- accessed.setAttribute('hidden', true);
+ showButton.hidden = false;
+ urlField.hidden = true;
+ accessed.hidden = true;
}
+ if (this.item.attachmentLinkMode
+ != Zotero.Attachments.LINK_MODE_LINKED_URL
+ && this.displayFileName) {
+ // TODO: localize
+ var file = this.item.getFile(false, true);
+ var fileName = file.leafName;
+ if (fileName) {
+ fileNameRow.value = Zotero.localeJoin([
+ 'Filename', // TODO: localize
+ ': ', // TODO: probably needs to be in localized string
+ fileName
+ ], '');
+ fileNameRow.hidden = false;
+ }
+ else {
+ fileNameRow.hidden = true;
+ }
+ }
+ else {
+ fileNameRow.hidden = true;
+ }
+
+
viewButton.setAttribute('label', str);
// Page count
@@ -217,16 +279,35 @@
var pages = Zotero.Fulltext.getPages(this.item.id);
var pages = pages ? pages.total : null;
if (pages) {
- var str = Zotero.getString('itemFields.pages') + ': ' + pages;
- pagesRow.setAttribute('value', str);
- pagesRow.setAttribute('hidden', false);
+ var str = Zotero.localeJoin([
+ Zotero.getString('itemFields.pages'),
+ ': ',
+ pages
+ ], '');
+ pagesRow.value = str;
+ pagesRow.hidden = false;
}
else {
- pagesRow.setAttribute('hidden', true);
+ pagesRow.hidden = true;
}
}
else {
- pagesRow.setAttribute('hidden', true);
+ pagesRow.hidden = true;
+ }
+
+ if (this.displayDateModified) {
+ var str = Zotero.localeJoin([
+ Zotero.getString('itemFields.dateModified'),
+ ': ',
+ Zotero.Date.sqlToDate(
+ this.item.getField('dateModified'), true
+ ).toLocaleString()
+ ], '');
+ dateModifiedRow.value = str;
+ dateModifiedRow.hidden = false;
+ }
+ else {
+ dateModifiedRow.hidden = true;
}
// Full-text index information
@@ -240,16 +321,24 @@
// Note editor
var noteEditor = this._id('note-editor');
- // Don't make note editable (at least for now)
- if (this.mode == 'merge' || this.mode == 'mergeedit') {
- noteEditor.mode = 'merge';
- noteEditor.displayButton = false;
+ if (this.displayNote) {
+ noteEditor.hidden = false;
+
+ // Don't make note editable (at least for now)
+ if (this.mode == 'merge' || this.mode == 'mergeedit') {
+ noteEditor.mode = 'merge';
+ noteEditor.displayButton = false;
+ }
+ else {
+ noteEditor.mode = this.mode;
+ }
+ noteEditor.parent = null;
+ noteEditor.item = this.item;
}
else {
- noteEditor.mode = this.mode;
+ noteEditor.hidden = true;
}
- noteEditor.parent = null;
- noteEditor.item = this.item;
+
if (this.displayButton) {
selectButton.label = this.buttonCaption;
@@ -412,9 +501,10 @@
<button id="show" label="&zotero.item.attachment.file.show;" flex="1"/>
</hbox>
<label id="url" crop="end"/>
-
+ <label id="fileNameRow"/>
<label id="accessed"/>
<label id="pages"/>
+ <label id="dateModified"/>
<hbox id="index-box">
<label id="index-status"/>
diff --git a/chrome/content/zotero/bindings/merge.xml b/chrome/content/zotero/bindings/merge.xml
@@ -134,6 +134,7 @@
if (this._leftpane.ref != 'deleted'
&& this._rightpane.ref != 'deleted') {
+
var dm1 = this._leftpane.ref.getField('dateModified');
if (dm1) {
dm1 = Zotero.Date.sqlToDate(dm1);
@@ -318,6 +319,10 @@
elementName = 'zoteronoteeditor';
break;
+ case 'storagefile':
+ elementName = 'zoterostoragefilebox';
+ break;
+
default:
throw ("Object type '" + this.type
+ "' not supported in <zoteromergepane>.ref");
@@ -349,6 +354,7 @@
switch (this.type) {
case 'attachment':
case 'note':
+ case 'storagefile':
objbox.buttonCaption = 'Choose this version';
break;
}
diff --git a/chrome/content/zotero/bindings/storagefilebox.xml b/chrome/content/zotero/bindings/storagefilebox.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (c) 2006 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://chnm.gmu.edu
+
+ Licensed under the Educational Community License, Version 1.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.opensource.org/licenses/ecl1.php
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ ***** END LICENSE BLOCK *****
+-->
+
+<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="storage-file-box"
+ extends="chrome://zotero/content/bindings/attachmentbox.xml#attachment-box">
+
+ <implementation>
+ <property name="mode" onget="return this._mode;">
+ <setter>
+ <![CDATA[
+ this.editable = false;
+ this.displayGoButtons = false;
+ this.clickableLink = false;
+ this.displayIndexed = false;
+ this.displayPages = false;
+ this.displayNote = false;
+
+ switch (val) {
+ case 'merge':
+ this.displayFileName = true;
+ this.displayButton = true;
+ this.displayDateModified = true;
+ break;
+
+ case 'mergeedit':
+ this.displayFileName = true;
+ this.displayDateModified = true;
+ break;
+
+ default:
+ throw ("Invalid mode '" + val + "' in storagefilebox.xml");
+ }
+
+ this._mode = val;
+ document.getAnonymousNodes(this)[0].setAttribute('mode', val);
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/chrome/content/zotero/merge.js b/chrome/content/zotero/merge.js
@@ -42,6 +42,7 @@ var Zotero_Merge_Window = new function () {
switch (_mergeGroup.type) {
case 'item':
+ case 'storagefile':
break;
default:
@@ -127,6 +128,8 @@ var Zotero_Merge_Window = new function () {
}
}
catch (e) {
+ Zotero.debug(e);
+
var prompt = Components.classes["@mozilla.org/network/default-prompt;1"]
.createInstance(Components.interfaces.nsIPrompt);
prompt.alert(Zotero.getString('general.error'), e);
diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js
@@ -5,6 +5,8 @@ Zotero.Sync.Storage = new function () {
this.SYNC_STATE_TO_UPLOAD = 0;
this.SYNC_STATE_TO_DOWNLOAD = 1;
this.SYNC_STATE_IN_SYNC = 2;
+ this.SYNC_STATE_FORCE_UPLOAD = 3;
+ this.SYNC_STATE_FORCE_DOWNLOAD = 4;
this.SUCCESS = 1;
this.ERROR_NO_URL = -1;
@@ -22,7 +24,6 @@ Zotero.Sync.Storage = new function () {
this.ERROR_NOT_ALLOWED = -14;
this.ERROR_UNKNOWN = -15;
-
//
// Public properties
//
@@ -242,7 +243,6 @@ Zotero.Sync.Storage = new function () {
}
Zotero.debug("Beginning storage sync");
- Zotero.Sync.Runner.setSyncIcon('animate');
_syncInProgress = true;
_changesMade = false;
@@ -297,6 +297,8 @@ Zotero.Sync.Storage = new function () {
case this.SYNC_STATE_TO_UPLOAD:
case this.SYNC_STATE_TO_DOWNLOAD:
case this.SYNC_STATE_IN_SYNC:
+ case this.SYNC_STATE_FORCE_UPLOAD:
+ case this.SYNC_STATE_FORCE_DOWNLOAD:
break;
default:
@@ -608,6 +610,10 @@ Zotero.Sync.Storage = new function () {
* @return {Boolean}
*/
this.downloadFiles = function () {
+ if (!_syncInProgress) {
+ _syncInProgress = true;
+ }
+
// Check for active operations?
var queue = Zotero.Sync.Storage.QueueManager.get('download');
if (queue.isRunning()) {
@@ -624,7 +630,9 @@ Zotero.Sync.Storage = new function () {
for each(var itemID in downloadFileIDs) {
var item = Zotero.Items.get(itemID);
- if (this.isFileModified(itemID)) {
+ if (Zotero.Sync.Storage.getSyncState(itemID) !=
+ Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
+ && this.isFileModified(itemID)) {
Zotero.debug("File for attachment " + itemID + " has been modified");
this.setSyncState(itemID, this.SYNC_STATE_TO_UPLOAD);
continue;
@@ -671,6 +679,24 @@ Zotero.Sync.Storage = new function () {
try {
var syncModTime = Zotero.Date.toUnixTimestamp(mdate);
+
+ // Skip download if local file exists and matches mod time
+ var file = item.getFile();
+ if (file && file.exists()
+ && syncModTime == Math.round(file.lastModifiedTime / 1000)) {
+ Zotero.debug("Stored file mod time matches remote file -- skipping download");
+
+ Zotero.DB.beginTransaction();
+ var syncState = Zotero.Sync.Storage.getSyncState(item.id);
+ var updateItem = syncState != 1;
+ Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, true);
+ Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
+ Zotero.DB.commitTransaction();
+ _changesMade = true;
+ request.finish();
+ return;
+ }
+
var uri = _getItemURI(item);
var destFile = Zotero.getTempDirectory();
destFile.append(item.key + '.zip.tmp');
@@ -715,6 +741,10 @@ Zotero.Sync.Storage = new function () {
* @return {Boolean}
*/
this.uploadFiles = function () {
+ if (!_syncInProgress) {
+ _syncInProgress = true;
+ }
+
// Check for active operations?
var queue = Zotero.Sync.Storage.QueueManager.get('upload');
if (queue.isRunning()) {
@@ -946,9 +976,24 @@ Zotero.Sync.Storage = new function () {
}
- this.resetAllSyncStates = function () {
+ this.resetAllSyncStates = function (syncState) {
+ if (!syncState) {
+ syncState = this.SYNC_STATE_TO_UPLOAD;
+ }
+
+ switch (syncState) {
+ case this.SYNC_STATE_TO_UPLOAD:
+ case this.SYNC_STATE_TO_DOWNLOAD:
+ case this.SYNC_STATE_IN_SYNC:
+ break;
+
+ default:
+ throw ("Invalid sync state '" + syncState + "' in "
+ + "Zotero.Sync.Storage.resetAllSyncStates()");
+ }
+
var sql = "UPDATE itemAttachments SET syncState=?";
- Zotero.DB.query(sql, [this.SYNC_STATE_TO_UPLOAD]);
+ Zotero.DB.query(sql, [syncState]);
}
@@ -1217,18 +1262,28 @@ Zotero.Sync.Storage = new function () {
try {
// Check for conflict
- if (mdate) {
- var file = item.getFile();
- var mtime = Zotero.Date.toUnixTimestamp(mdate);
- var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
- if (mtime != smtime) {
- request.error("Conflict! Last known mod time does not match remote time!"
- + " (" + mtime + " != " + smtime + ")");
- return;
+ if (Zotero.Sync.Storage.getSyncState(item.id)
+ != Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
+ if (mdate) {
+ var file = item.getFile();
+ var mtime = Zotero.Date.toUnixTimestamp(mdate);
+ var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
+ if (mtime != smtime) {
+ var localData = { modTime: smtime };
+ var remoteData = { modTime: mtime };
+ Zotero.Sync.Storage.QueueManager.addConflict(
+ request.name, localData, remoteData
+ );
+ Zotero.debug("File conflict -- last known mod time "
+ + "does not match remote time"
+ + " (" + mtime + " != " + smtime + ")");
+ request.finish();
+ return;
+ }
+ }
+ else {
+ Zotero.debug("Remote file not found for item " + item.id);
}
- }
- else {
- Zotero.debug("Remote file not found for item " + item.id);
}
var file = Zotero.getTempDirectory();
@@ -1364,8 +1419,13 @@ Zotero.Sync.Storage = new function () {
* @return {Number[]} Array of attachment itemIDs
*/
function _getFilesToDownload() {
- var sql = "SELECT itemID FROM itemAttachments WHERE syncState=?";
- return Zotero.DB.columnQuery(sql, Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD);
+ var sql = "SELECT itemID FROM itemAttachments WHERE syncState IN (?,?)";
+ return Zotero.DB.columnQuery(sql,
+ [
+ Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD,
+ Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
+ ]
+ );
}
@@ -1376,11 +1436,12 @@ Zotero.Sync.Storage = new function () {
* @return {Number[]} Array of attachment itemIDs
*/
function _getFilesToUpload() {
- var sql = "SELECT itemID FROM itemAttachments WHERE syncState=? "
+ var sql = "SELECT itemID FROM itemAttachments WHERE syncState IN (?,?) "
+ "AND linkMode IN (?,?)";
return Zotero.DB.columnQuery(sql,
[
Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD,
+ Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD,
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL
]
@@ -1890,6 +1951,13 @@ Zotero.Sync.Storage = new function () {
Zotero.debug("Storage sync is complete");
_syncInProgress = false;
+ if (!cancelled && this.resyncOnFinish) {
+ Zotero.debug("Force-resyncing items in conflict");
+ this.resyncOnFinish = false;
+ this.sync();
+ return;
+ }
+
if (cancelled || !_changesMade) {
if (!_changesMade) {
Zotero.debug("No changes made during storage sync");
@@ -1991,6 +2059,7 @@ Zotero.Sync.Storage = new function () {
Zotero.Sync.Storage.QueueManager = new function () {
var _queues = {};
+ var _conflicts = [];
/**
@@ -2028,6 +2097,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
for each(var queue in _queues) {
queue.stop();
}
+ _conflicts = [];
}
@@ -2035,6 +2105,14 @@ Zotero.Sync.Storage.QueueManager = new function () {
* Tell the storage system that we're finished
*/
this.finish = function () {
+ if (_conflicts.length) {
+ var data = _reconcileConflicts();
+ if (data) {
+ _processMergeData(data);
+ }
+ _conflicts = [];
+ }
+
Zotero.Sync.Storage.finish(this._cancelled);
this._cancelled = false;
}
@@ -2075,8 +2153,10 @@ Zotero.Sync.Storage.QueueManager = new function () {
//Zotero.debug("Total percentage is " + percentage);
// Remaining KB
- var downloadStatus = _getQueueStatus(_queues.download);
- var uploadStatus = _getQueueStatus(_queues.upload);
+ var downloadStatus = _queues.download ?
+ _getQueueStatus(_queues.download) : 0;
+ var uploadStatus = _queues.upload ?
+ _getQueueStatus(_queues.upload) : 0;
this.updateProgressMeters(
activeRequests, percentage, downloadStatus, uploadStatus
@@ -2124,6 +2204,15 @@ Zotero.Sync.Storage.QueueManager = new function () {
}
+ this.addConflict = function (requestName, localData, remoteData) {
+ _conflicts.push({
+ name: requestName,
+ localData: localData,
+ remoteData: remoteData
+ });
+ }
+
+
/**
* Get a status string for a queue
*
@@ -2155,6 +2244,77 @@ Zotero.Sync.Storage.QueueManager = new function () {
var status = Zotero.localeJoin([kbRemaining, '(' + filesRemaining + ')']);
return status;
}
+
+
+ function _reconcileConflicts() {
+ var objectPairs = [];
+ for each(var conflict in _conflicts) {
+ var item = Zotero.Items.getByKey(conflict.name);
+ var item1 = item.clone();
+ item1.setField('dateModified',
+ Zotero.Date.dateToSQL(new Date(conflict.localData.modTime * 1000), true));
+ var item2 = item.clone();
+ item2.setField('dateModified',
+ Zotero.Date.dateToSQL(new Date(conflict.remoteData.modTime * 1000), true));
+ objectPairs.push([item1, item2]);
+ }
+
+ var io = {
+ dataIn: {
+ type: 'storagefile',
+ captions: [
+ // TODO: localize
+ 'Local File',
+ 'Remote File',
+ 'Saved File'
+ ],
+ objects: objectPairs
+ }
+ };
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var lastWin = wm.getMostRecentWindow("navigator:browser");
+ lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
+
+ if (!io.dataOut) {
+ return false;
+ }
+
+ // Since we're only putting cloned items into the merge window,
+ // we have to manually set the ids
+ for (var i=0; i<_conflicts.length; i++) {
+ io.dataOut[i].id = Zotero.Items.getByKey(_conflicts[i].name).id;
+ }
+
+ return io.dataOut;
+ }
+
+
+ function _processMergeData(data) {
+ if (!data.length) {
+ return false;
+ }
+
+ Zotero.Sync.Storage.resyncOnFinish = true;
+
+ for each(var mergeItem in data) {
+ var itemID = mergeItem.id;
+ var dateModified = mergeItem.ref.getField('dateModified');
+ // Local
+ if (dateModified == mergeItem.left.getField('dateModified')) {
+ Zotero.Sync.Storage.setSyncState(
+ itemID, Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD
+ );
+ }
+ // Remote
+ else {
+ Zotero.Sync.Storage.setSyncState(
+ itemID, Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
+ );
+ }
+ }
+ }
}
@@ -2229,6 +2389,10 @@ Zotero.Sync.Storage.Queue = function (name) {
return remaining;
});
this.__defineGetter__('percentage', function () {
+ if (this.totalRequests == 0) {
+ return 0;
+ }
+
var completedRequests = 0;
for each(var request in this._requests) {
completedRequests += request.percentage / 100;
@@ -2374,8 +2538,14 @@ Zotero.Sync.Storage.Queue.prototype.stop = function () {
Zotero.debug(this.Name + " queue is already finished");
return;
}
- this._stopping = true;
+ // If no requests, finish manually
+ if (this.activeRequests == 0) {
+ this._finishedRequests = this._finishedRequests;
+ return;
+ }
+
+ this._stopping = true;
for each(var request in this._requests) {
if (!request.isFinished()) {
request.stop();
@@ -2461,6 +2631,7 @@ Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function ()
if (this.progressMax == 0) {
return 0;
}
+
var percentage = Math.round((this.progress / this.progressMax) * 100);
if (percentage < this._percentage) {
Zotero.debug(percentage + " is less than last percentage of "
diff --git a/chrome/skin/default/zotero/zotero.css b/chrome/skin/default/zotero/zotero.css
@@ -73,6 +73,10 @@ zoteroattachmentbox
-moz-binding: url('chrome://zotero/content/bindings/attachmentbox.xml#attachment-box');
}
+zoterostoragefilebox
+{
+ -moz-binding: url('chrome://zotero/content/bindings/storagefilebox.xml#storage-file-box');
+}
zoteronoteeditor
{