2015-10-29 07:41:54 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
describe("Zotero.Sync.Storage.Local", function () {
|
|
|
|
beforeEach(function* () {
|
|
|
|
yield resetDB({
|
2015-11-15 22:49:06 +00:00
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
2015-10-29 07:41:54 +00:00
|
|
|
})
|
|
|
|
})
|
2017-07-01 10:20:27 +00:00
|
|
|
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
describe("#checkForUpdatedFiles()", function () {
|
|
|
|
it("should flag modified file for upload and return it", function* () {
|
|
|
|
// Create attachment
|
2017-07-01 10:20:27 +00:00
|
|
|
let item = yield importTextAttachment();
|
2015-10-29 07:41:54 +00:00
|
|
|
var hash = yield item.attachmentHash;
|
|
|
|
// Set file mtime to the past (without milliseconds, which aren't used on OS X)
|
|
|
|
var mtime = (Math.floor(new Date().getTime() / 1000) * 1000) - 1000;
|
|
|
|
yield OS.File.setDates((yield item.getFilePathAsync()), null, mtime);
|
|
|
|
|
|
|
|
// Mark as synced, so it will be checked
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
item.attachmentSyncedModificationTime = mtime;
|
|
|
|
item.attachmentSyncedHash = hash;
|
|
|
|
item.attachmentSyncState = "in_sync";
|
|
|
|
yield item.saveTx({ skipAll: true });
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
// Update mtime and contents
|
|
|
|
var path = yield item.getFilePathAsync();
|
|
|
|
yield OS.File.setDates(path);
|
|
|
|
yield Zotero.File.putContentsAsync(path, Zotero.Utilities.randomString());
|
|
|
|
|
|
|
|
// File should be returned
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
|
|
|
|
|
|
|
|
yield item.eraseTx();
|
|
|
|
|
|
|
|
assert.equal(changed, true);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD);
|
2015-10-29 07:41:54 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it("should skip a file if mod time hasn't changed", function* () {
|
|
|
|
// Create attachment
|
2017-07-01 10:20:27 +00:00
|
|
|
let item = yield importTextAttachment();
|
2015-10-29 07:41:54 +00:00
|
|
|
var hash = yield item.attachmentHash;
|
|
|
|
var mtime = yield item.attachmentModificationTime;
|
|
|
|
|
|
|
|
// Mark as synced, so it will be checked
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
item.attachmentSyncedModificationTime = mtime;
|
|
|
|
item.attachmentSyncedHash = hash;
|
|
|
|
item.attachmentSyncState = "in_sync";
|
|
|
|
yield item.saveTx({ skipAll: true });
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
var syncState = item.attachmentSyncState;
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
yield item.eraseTx();
|
|
|
|
|
|
|
|
assert.isFalse(changed);
|
2015-12-23 09:52:09 +00:00
|
|
|
assert.equal(syncState, Zotero.Sync.Storage.Local.SYNC_STATE_IN_SYNC);
|
2015-10-29 07:41:54 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it("should skip a file if mod time has changed but contents haven't", function* () {
|
|
|
|
// Create attachment
|
2017-07-01 10:20:27 +00:00
|
|
|
let item = yield importTextAttachment();
|
2015-10-29 07:41:54 +00:00
|
|
|
var hash = yield item.attachmentHash;
|
|
|
|
// Set file mtime to the past (without milliseconds, which aren't used on OS X)
|
|
|
|
var mtime = (Math.floor(new Date().getTime() / 1000) * 1000) - 1000;
|
|
|
|
yield OS.File.setDates((yield item.getFilePathAsync()), null, mtime);
|
|
|
|
|
|
|
|
// Mark as synced, so it will be checked
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
item.attachmentSyncedModificationTime = mtime;
|
|
|
|
item.attachmentSyncedHash = hash;
|
|
|
|
item.attachmentSyncState = "in_sync";
|
|
|
|
yield item.saveTx({ skipAll: true });
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
// Update mtime, but not contents
|
|
|
|
var path = yield item.getFilePathAsync();
|
|
|
|
yield OS.File.setDates(path);
|
|
|
|
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
var syncState = item.attachmentSyncState;
|
|
|
|
var syncedModTime = item.attachmentSyncedModificationTime;
|
2015-10-29 07:41:54 +00:00
|
|
|
var newModTime = yield item.attachmentModificationTime;
|
|
|
|
|
|
|
|
yield item.eraseTx();
|
|
|
|
|
|
|
|
assert.isFalse(changed);
|
2015-12-23 09:52:09 +00:00
|
|
|
assert.equal(syncState, Zotero.Sync.Storage.Local.SYNC_STATE_IN_SYNC);
|
2015-10-29 07:41:54 +00:00
|
|
|
assert.equal(syncedModTime, newModTime);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2017-07-01 10:26:22 +00:00
|
|
|
describe("#updateSyncStates()", function () {
|
|
|
|
it("should update attachment sync states to 'to_upload'", function* () {
|
|
|
|
var attachment1 = yield importFileAttachment('test.png');
|
|
|
|
attachment1.attachmentSyncState = 'in_sync';
|
|
|
|
yield attachment1.saveTx();
|
|
|
|
var attachment2 = yield importFileAttachment('test.png');
|
|
|
|
attachment2.attachmentSyncState = 'in_sync';
|
|
|
|
yield attachment2.saveTx();
|
|
|
|
|
|
|
|
var local = Zotero.Sync.Storage.Local;
|
|
|
|
yield local.updateSyncStates([attachment1, attachment2], 'to_upload');
|
|
|
|
|
|
|
|
for (let attachment of [attachment1, attachment2]) {
|
|
|
|
assert.strictEqual(attachment.attachmentSyncState, local.SYNC_STATE_TO_UPLOAD);
|
|
|
|
let state = yield Zotero.DB.valueQueryAsync(
|
|
|
|
"SELECT syncState FROM itemAttachments WHERE itemID=?", attachment.id
|
|
|
|
);
|
|
|
|
assert.strictEqual(state, local.SYNC_STATE_TO_UPLOAD);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-12-22 15:00:40 +00:00
|
|
|
describe("#resetAllSyncStates()", function () {
|
2016-06-26 09:29:51 +00:00
|
|
|
it("should reset attachment sync states to 'to_upload'", function* () {
|
|
|
|
var attachment = yield importFileAttachment('test.png');
|
|
|
|
attachment.attachmentSyncState = 'in_sync';
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
|
|
|
var local = Zotero.Sync.Storage.Local;
|
2017-12-08 05:27:29 +00:00
|
|
|
yield local.resetAllSyncStates(attachment.libraryID)
|
2016-06-26 09:29:51 +00:00
|
|
|
assert.strictEqual(attachment.attachmentSyncState, local.SYNC_STATE_TO_UPLOAD);
|
|
|
|
var state = yield Zotero.DB.valueQueryAsync(
|
|
|
|
"SELECT syncState FROM itemAttachments WHERE itemID=?", attachment.id
|
|
|
|
);
|
|
|
|
assert.strictEqual(state, local.SYNC_STATE_TO_UPLOAD);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-10-29 07:41:54 +00:00
|
|
|
describe("#processDownload()", function () {
|
2017-03-07 03:04:56 +00:00
|
|
|
describe("single file", function () {
|
|
|
|
it("should download a single file into the attachment directory", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
var key = Zotero.DataObjectUtilities.generateKey();
|
|
|
|
var fileContents = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
var oldFilename = "Old File";
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var tmpFile = OS.Path.join(tmpDir, key + '.tmp');
|
|
|
|
yield Zotero.File.putContentsAsync(tmpFile, fileContents);
|
|
|
|
|
|
|
|
// Create an existing attachment directory to replace
|
|
|
|
var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path;
|
|
|
|
yield OS.File.makeDir(
|
|
|
|
dir,
|
|
|
|
{
|
|
|
|
unixMode: 0o755
|
|
|
|
}
|
|
|
|
);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), '');
|
|
|
|
|
|
|
|
var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(tmpFile));
|
|
|
|
var mtime = 1445667239000;
|
|
|
|
|
|
|
|
var json = {
|
|
|
|
key,
|
|
|
|
version: 10,
|
|
|
|
itemType: 'attachment',
|
|
|
|
linkMode: 'imported_url',
|
|
|
|
url: 'https://example.com/foo.txt',
|
|
|
|
filename: 'foo.txt',
|
|
|
|
contentType: 'text/plain',
|
|
|
|
charset: 'utf-8',
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
};
|
|
|
|
yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]);
|
|
|
|
|
|
|
|
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
|
|
|
yield Zotero.Sync.Storage.Local.processDownload({
|
|
|
|
item,
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
});
|
|
|
|
yield OS.File.remove(tmpFile);
|
|
|
|
|
|
|
|
var storageDir = Zotero.Attachments.getStorageDirectory(item).path;
|
|
|
|
|
|
|
|
// Make sure previous files don't exist
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename)));
|
|
|
|
|
|
|
|
// Make sure main file matches attachment hash and mtime
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents)
|
|
|
|
);
|
|
|
|
yield assert.eventually.equal(item.attachmentModificationTime, mtime);
|
2015-10-29 07:41:54 +00:00
|
|
|
});
|
|
|
|
|
2016-06-02 07:39:26 +00:00
|
|
|
|
2017-03-07 03:04:56 +00:00
|
|
|
it("should download and rename a single file with invalid filename into the attachment directory", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
var key = Zotero.DataObjectUtilities.generateKey();
|
|
|
|
var fileContents = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
var oldFilename = "Old File";
|
|
|
|
var newFilename = " ab — c \\:.txt.";
|
|
|
|
var filteredFilename = " ab — c .txt.";
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var tmpFile = OS.Path.join(tmpDir, key + '.tmp');
|
|
|
|
yield Zotero.File.putContentsAsync(tmpFile, fileContents);
|
|
|
|
|
|
|
|
// Create an existing attachment directory to replace
|
|
|
|
var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path;
|
|
|
|
yield OS.File.makeDir(
|
|
|
|
dir,
|
|
|
|
{
|
|
|
|
unixMode: 0o755
|
|
|
|
}
|
|
|
|
);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), '');
|
|
|
|
|
|
|
|
var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(tmpFile));
|
|
|
|
var mtime = 1445667239000;
|
|
|
|
|
|
|
|
var json = {
|
|
|
|
key,
|
|
|
|
version: 10,
|
|
|
|
itemType: 'attachment',
|
|
|
|
linkMode: 'imported_url',
|
|
|
|
url: 'https://example.com/foo.txt',
|
|
|
|
filename: newFilename,
|
|
|
|
contentType: 'text/plain',
|
|
|
|
charset: 'utf-8',
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
};
|
|
|
|
yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]);
|
|
|
|
|
|
|
|
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
|
|
|
yield Zotero.Sync.Storage.Local.processDownload({
|
|
|
|
item,
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
});
|
|
|
|
yield OS.File.remove(tmpFile);
|
|
|
|
|
|
|
|
var storageDir = Zotero.Attachments.getStorageDirectory(item).path;
|
|
|
|
|
|
|
|
// Make sure previous file doesn't exist
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename)));
|
|
|
|
// And new one does
|
|
|
|
assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, filteredFilename)));
|
|
|
|
|
|
|
|
// Make sure main file matches attachment hash and mtime
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents)
|
|
|
|
);
|
|
|
|
yield assert.eventually.equal(item.attachmentModificationTime, mtime);
|
|
|
|
});
|
2016-06-02 07:39:26 +00:00
|
|
|
|
|
|
|
|
2017-03-07 03:04:56 +00:00
|
|
|
it("should download and rename a single file with invalid filename using Windows parsing rules into the attachment directory", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
var key = Zotero.DataObjectUtilities.generateKey();
|
|
|
|
var fileContents = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
var oldFilename = "Old File";
|
|
|
|
var newFilename = "a:b.txt";
|
|
|
|
var filteredFilename = "ab.txt";
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var tmpFile = OS.Path.join(tmpDir, key + '.tmp');
|
|
|
|
yield Zotero.File.putContentsAsync(tmpFile, fileContents);
|
|
|
|
|
|
|
|
// Create an existing attachment directory to replace
|
|
|
|
var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path;
|
|
|
|
yield OS.File.makeDir(
|
|
|
|
dir,
|
|
|
|
{
|
|
|
|
unixMode: 0o755
|
|
|
|
}
|
|
|
|
);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), '');
|
|
|
|
|
|
|
|
var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(tmpFile));
|
|
|
|
var mtime = 1445667239000;
|
|
|
|
|
|
|
|
var json = {
|
|
|
|
key,
|
|
|
|
version: 10,
|
|
|
|
itemType: 'attachment',
|
|
|
|
linkMode: 'imported_url',
|
|
|
|
url: 'https://example.com/foo.txt',
|
|
|
|
filename: 'a:b.txt',
|
|
|
|
contentType: 'text/plain',
|
|
|
|
charset: 'utf-8',
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
};
|
|
|
|
yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]);
|
|
|
|
|
|
|
|
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
|
|
|
|
|
|
|
// Stub functions to simulate OS.Path.basename() behavior on Windows
|
|
|
|
var basenameOrigFunc = OS.Path.basename.bind(OS.Path);
|
2017-05-31 15:44:35 +00:00
|
|
|
var basenameStub = sinon.stub(OS.Path, "basename").callsFake((path) => {
|
2017-03-07 03:04:56 +00:00
|
|
|
// Split on colon
|
|
|
|
if (path.endsWith("a:b.txt")) {
|
|
|
|
return "b.txt";
|
|
|
|
}
|
|
|
|
return basenameOrigFunc(path);
|
|
|
|
});
|
|
|
|
var pathToFileOrigFunc = Zotero.File.pathToFile.bind(Zotero.File);
|
2017-05-31 15:44:35 +00:00
|
|
|
var pathToFileStub = sinon.stub(Zotero.File, "pathToFile").callsFake((path) => {
|
2017-03-07 03:04:56 +00:00
|
|
|
if (path.includes(":")) {
|
|
|
|
throw new Error("Path contains colon");
|
|
|
|
}
|
|
|
|
return pathToFileOrigFunc(path);
|
|
|
|
});
|
|
|
|
|
|
|
|
yield Zotero.Sync.Storage.Local.processDownload({
|
|
|
|
item,
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
});
|
|
|
|
yield OS.File.remove(tmpFile);
|
|
|
|
|
|
|
|
var storageDir = Zotero.Attachments.getStorageDirectory(item).path;
|
|
|
|
|
|
|
|
basenameStub.restore();
|
|
|
|
pathToFileStub.restore();
|
|
|
|
|
|
|
|
// Make sure path is set correctly
|
|
|
|
assert.equal(item.getFilePath(), OS.Path.join(storageDir, filteredFilename));
|
|
|
|
// Make sure previous files don't exist
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename)));
|
|
|
|
// And new one does
|
|
|
|
assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, filteredFilename)));
|
|
|
|
|
|
|
|
// Make sure main file matches attachment hash and mtime
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents)
|
|
|
|
);
|
|
|
|
yield assert.eventually.equal(item.attachmentModificationTime, mtime);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("ZIP", function () {
|
|
|
|
it("should download and extract a ZIP file into the attachment directory", function* () {
|
|
|
|
var file1Name = 'index.html';
|
|
|
|
var file1Contents = '<html><body>Test</body></html>';
|
|
|
|
var file2Name = 'aux1.txt';
|
|
|
|
var file2Contents = 'Test 1';
|
|
|
|
var subDirName = 'sub';
|
|
|
|
var file3Name = 'aux2';
|
|
|
|
var file3Contents = 'Test 2';
|
|
|
|
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
var key = Zotero.DataObjectUtilities.generateKey();
|
|
|
|
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var zipFile = OS.Path.join(tmpDir, key + '.tmp');
|
|
|
|
|
|
|
|
// Create ZIP file with subdirectory
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var zipDir = yield getTempDirectory();
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, file1Name), file1Contents);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, file2Name), file2Contents);
|
|
|
|
var subDir = OS.Path.join(zipDir, subDirName);
|
|
|
|
yield OS.File.makeDir(subDir);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(subDir, file3Name), file3Contents);
|
|
|
|
yield Zotero.File.zipDirectory(zipDir, zipFile);
|
|
|
|
yield removeDir(zipDir);
|
|
|
|
|
|
|
|
// Create an existing attachment directory (and subdirectory) to replace
|
|
|
|
var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path;
|
|
|
|
yield OS.File.makeDir(
|
|
|
|
OS.Path.join(dir, 'subdir'),
|
|
|
|
{
|
|
|
|
from: Zotero.DataDirectory.dir,
|
|
|
|
unixMode: 0o755
|
|
|
|
}
|
|
|
|
);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, 'A'), '');
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, 'subdir', 'B'), '');
|
|
|
|
|
|
|
|
var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(zipFile));
|
|
|
|
var mtime = 1445667239000;
|
|
|
|
|
|
|
|
var json = {
|
|
|
|
key,
|
|
|
|
version: 10,
|
|
|
|
itemType: 'attachment',
|
|
|
|
linkMode: 'imported_url',
|
|
|
|
url: 'https://example.com',
|
|
|
|
filename: file1Name,
|
|
|
|
contentType: 'text/html',
|
|
|
|
charset: 'utf-8',
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
};
|
|
|
|
yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]);
|
|
|
|
|
|
|
|
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
|
|
|
yield Zotero.Sync.Storage.Local.processDownload({
|
|
|
|
item,
|
|
|
|
md5,
|
|
|
|
mtime,
|
|
|
|
compressed: true
|
|
|
|
});
|
|
|
|
yield OS.File.remove(zipFile);
|
|
|
|
|
|
|
|
var storageDir = Zotero.Attachments.getStorageDirectory(item).path;
|
|
|
|
|
|
|
|
// Make sure previous files don't exist
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, 'A')));
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, 'subdir')));
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, 'subdir', 'B')));
|
|
|
|
|
|
|
|
// Make sure main file matches attachment hash and mtime
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
item.attachmentHash, Zotero.Utilities.Internal.md5(file1Contents)
|
|
|
|
);
|
|
|
|
yield assert.eventually.equal(item.attachmentModificationTime, mtime);
|
|
|
|
|
|
|
|
// Check second file
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
Zotero.File.getContentsAsync(OS.Path.join(storageDir, file2Name)),
|
|
|
|
file2Contents
|
|
|
|
);
|
|
|
|
|
|
|
|
// Check subdirectory and file
|
|
|
|
assert.isTrue((yield OS.File.stat(OS.Path.join(storageDir, subDirName))).isDir);
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
Zotero.File.getContentsAsync(OS.Path.join(storageDir, subDirName, file3Name)),
|
|
|
|
file3Contents
|
|
|
|
);
|
|
|
|
});
|
2016-06-02 07:39:26 +00:00
|
|
|
|
2017-03-07 03:04:56 +00:00
|
|
|
|
|
|
|
it("should download and rename a ZIP file with invalid filename using Windows parsing rules into the attachment directory", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
var key = Zotero.DataObjectUtilities.generateKey();
|
|
|
|
|
|
|
|
var oldFilename = "Old File";
|
|
|
|
var oldAuxFilename = "a.gif";
|
|
|
|
var newFilename = "a:b.html";
|
|
|
|
var fileContents = Zotero.Utilities.randomString();
|
|
|
|
var newAuxFilename = "b.gif";
|
|
|
|
var filteredFilename = "ab.html";
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var zipFile = OS.Path.join(tmpDir, key + '.tmp');
|
|
|
|
|
|
|
|
// Create ZIP file
|
|
|
|
var tmpDir = Zotero.getTempDirectory().path;
|
|
|
|
var zipDir = yield getTempDirectory();
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, newFilename), fileContents);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, newAuxFilename), '');
|
|
|
|
yield Zotero.File.zipDirectory(zipDir, zipFile);
|
|
|
|
yield removeDir(zipDir);
|
|
|
|
|
|
|
|
// Create an existing attachment directory to replace
|
|
|
|
var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path;
|
|
|
|
yield OS.File.makeDir(
|
|
|
|
dir,
|
|
|
|
{
|
|
|
|
unixMode: 0o755
|
|
|
|
}
|
|
|
|
);
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), '');
|
|
|
|
yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldAuxFilename), '');
|
|
|
|
|
|
|
|
var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(zipFile));
|
|
|
|
var mtime = 1445667239000;
|
|
|
|
|
|
|
|
var json = {
|
|
|
|
key,
|
|
|
|
version: 10,
|
|
|
|
itemType: 'attachment',
|
|
|
|
linkMode: 'imported_url',
|
|
|
|
url: 'https://example.com/foo.html',
|
|
|
|
filename: 'a:b.html',
|
|
|
|
contentType: 'text/plain',
|
|
|
|
charset: 'utf-8',
|
|
|
|
md5,
|
|
|
|
mtime
|
|
|
|
};
|
|
|
|
yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]);
|
|
|
|
|
|
|
|
var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
|
|
|
|
|
|
|
|
// Stub functions to simulate OS.Path.basename() behavior on Windows
|
|
|
|
var basenameOrigFunc = OS.Path.basename.bind(OS.Path);
|
2017-05-31 15:44:35 +00:00
|
|
|
var basenameStub = sinon.stub(OS.Path, "basename").callsFake((path) => {
|
2017-03-07 03:04:56 +00:00
|
|
|
// Split on colon
|
|
|
|
if (path.endsWith("a:b.html")) {
|
|
|
|
return "b.html";
|
|
|
|
}
|
|
|
|
return basenameOrigFunc(path);
|
|
|
|
});
|
|
|
|
var pathToFileOrigFunc = Zotero.File.pathToFile.bind(Zotero.File);
|
2017-05-31 15:44:35 +00:00
|
|
|
var pathToFileStub = sinon.stub(Zotero.File, "pathToFile").callsFake((path) => {
|
2017-03-07 03:04:56 +00:00
|
|
|
if (path.includes(":")) {
|
|
|
|
throw new Error("Path contains colon");
|
|
|
|
}
|
|
|
|
return pathToFileOrigFunc(path);
|
|
|
|
});
|
|
|
|
|
|
|
|
yield Zotero.Sync.Storage.Local.processDownload({
|
|
|
|
item,
|
|
|
|
md5,
|
|
|
|
mtime,
|
|
|
|
compressed: true
|
|
|
|
});
|
|
|
|
yield OS.File.remove(zipFile);
|
|
|
|
|
|
|
|
var storageDir = Zotero.Attachments.getStorageDirectory(item).path;
|
|
|
|
|
|
|
|
basenameStub.restore();
|
|
|
|
pathToFileStub.restore();
|
|
|
|
|
|
|
|
// Make sure path is set correctly
|
|
|
|
assert.equal(item.getFilePath(), OS.Path.join(storageDir, filteredFilename));
|
|
|
|
// Make sure previous files don't exist
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename)));
|
|
|
|
assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldAuxFilename)));
|
|
|
|
// And new ones do
|
|
|
|
assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, filteredFilename)));
|
|
|
|
assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, newAuxFilename)));
|
|
|
|
|
|
|
|
// Make sure main file matches attachment hash and mtime
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents)
|
|
|
|
);
|
|
|
|
yield assert.eventually.equal(item.attachmentModificationTime, mtime);
|
|
|
|
});
|
|
|
|
});
|
2015-10-29 07:41:54 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
describe("#getConflicts()", function () {
|
|
|
|
it("should return an array of objects for attachments in conflict", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
|
|
|
|
var item1 = yield importFileAttachment('test.png');
|
|
|
|
item1.version = 10;
|
|
|
|
yield item1.saveTx();
|
2017-07-01 10:20:27 +00:00
|
|
|
var item2 = yield importTextAttachment();
|
|
|
|
var item3 = yield importHTMLAttachment();
|
2015-10-29 07:41:54 +00:00
|
|
|
item3.version = 11;
|
|
|
|
yield item3.saveTx();
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
var json1 = item1.toJSON();
|
|
|
|
var json3 = item3.toJSON();
|
2015-10-29 07:41:54 +00:00
|
|
|
// Change remote mtimes
|
|
|
|
// Round to nearest second because OS X doesn't support ms resolution
|
|
|
|
var now = Math.round(new Date().getTime() / 1000) * 1000;
|
|
|
|
json1.mtime = now - 10000;
|
|
|
|
json3.mtime = now - 20000;
|
|
|
|
yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1, json3]);
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
item1.attachmentSyncState = "in_conflict";
|
|
|
|
yield item1.saveTx({ skipAll: true });
|
|
|
|
item3.attachmentSyncState = "in_conflict";
|
|
|
|
yield item3.saveTx({ skipAll: true });
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
var conflicts = yield Zotero.Sync.Storage.Local.getConflicts(libraryID);
|
|
|
|
assert.lengthOf(conflicts, 2);
|
|
|
|
|
|
|
|
var item1Conflict = conflicts.find(x => x.left.key == item1.key);
|
|
|
|
assert.equal(
|
|
|
|
item1Conflict.left.dateModified,
|
|
|
|
Zotero.Date.dateToISO(new Date(yield item1.attachmentModificationTime))
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
item1Conflict.right.dateModified,
|
|
|
|
Zotero.Date.dateToISO(new Date(json1.mtime))
|
|
|
|
);
|
|
|
|
|
|
|
|
var item3Conflict = conflicts.find(x => x.left.key == item3.key);
|
|
|
|
assert.equal(
|
|
|
|
item3Conflict.left.dateModified,
|
|
|
|
Zotero.Date.dateToISO(new Date(yield item3.attachmentModificationTime))
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
item3Conflict.right.dateModified,
|
|
|
|
Zotero.Date.dateToISO(new Date(json3.mtime))
|
|
|
|
);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#resolveConflicts()", function () {
|
2017-07-01 10:20:27 +00:00
|
|
|
var win;
|
|
|
|
|
|
|
|
before(function* () {
|
|
|
|
win = yield loadBrowserWindow();
|
|
|
|
});
|
|
|
|
|
|
|
|
after(function () {
|
|
|
|
if (win) {
|
|
|
|
win.close();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2015-10-29 07:41:54 +00:00
|
|
|
it("should show the conflict resolution window on attachment conflicts", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
|
|
|
|
var item1 = yield importFileAttachment('test.png');
|
|
|
|
item1.version = 10;
|
|
|
|
yield item1.saveTx();
|
2017-07-01 10:20:27 +00:00
|
|
|
var item2 = yield importTextAttachment();
|
|
|
|
var item3 = yield importHTMLAttachment();
|
2015-10-29 07:41:54 +00:00
|
|
|
item3.version = 11;
|
|
|
|
yield item3.saveTx();
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
var json1 = item1.toJSON();
|
|
|
|
var json3 = item3.toJSON();
|
2017-01-29 12:50:46 +00:00
|
|
|
// Change remote mtimes and hashes
|
2015-10-29 07:41:54 +00:00
|
|
|
json1.mtime = new Date().getTime() + 10000;
|
2017-01-29 12:50:46 +00:00
|
|
|
json1.md5 = 'f4ce1167f3a854896c257a0cc1ac387f';
|
2015-10-29 07:41:54 +00:00
|
|
|
json3.mtime = new Date().getTime() - 10000;
|
2017-01-29 12:50:46 +00:00
|
|
|
json3.md5 = 'fcd080b1c2cad562237823ec27671bbd';
|
2015-10-29 07:41:54 +00:00
|
|
|
yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1, json3]);
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
item1.attachmentSyncState = "in_conflict";
|
|
|
|
yield item1.saveTx({ skipAll: true });
|
|
|
|
item3.attachmentSyncState = "in_conflict";
|
|
|
|
yield item3.saveTx({ skipAll: true });
|
2015-10-29 07:41:54 +00:00
|
|
|
|
2023-04-29 07:13:53 +00:00
|
|
|
var promise = waitForWindow('chrome://zotero/content/merge.xhtml', async function (dialog) {
|
2015-10-29 07:41:54 +00:00
|
|
|
var doc = dialog.document;
|
2023-04-29 07:13:53 +00:00
|
|
|
var wizard = doc.querySelector('wizard');
|
|
|
|
var mergeGroup = wizard.getElementsByTagName('merge-group')[0];
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
// 1 (remote)
|
|
|
|
// Later remote version should be selected
|
2023-04-29 07:13:53 +00:00
|
|
|
assert.equal(mergeGroup.rightPane.getAttribute('selected'), 'true');
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
// Check checkbox text
|
|
|
|
assert.equal(
|
|
|
|
doc.getElementById('resolve-all').label,
|
|
|
|
Zotero.getString('sync.conflict.resolveAllRemote')
|
|
|
|
);
|
|
|
|
|
|
|
|
// Select local object
|
2023-04-29 07:13:53 +00:00
|
|
|
mergeGroup.leftPane.click();
|
|
|
|
assert.equal(mergeGroup.leftPane.getAttribute('selected'), 'true');
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
wizard.getButton('next').click();
|
|
|
|
|
|
|
|
// 2 (local)
|
|
|
|
// Later local version should be selected
|
2023-04-29 07:13:53 +00:00
|
|
|
assert.equal(mergeGroup.leftPane.getAttribute('selected'), 'true');
|
2015-10-29 07:41:54 +00:00
|
|
|
// Select remote object
|
2023-04-29 07:13:53 +00:00
|
|
|
mergeGroup.rightPane.click();
|
|
|
|
assert.equal(mergeGroup.rightPane.getAttribute('selected'), 'true');
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
if (Zotero.isMac) {
|
|
|
|
assert.isTrue(wizard.getButton('next').hidden);
|
|
|
|
assert.isFalse(wizard.getButton('finish').hidden);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
wizard.getButton('finish').click();
|
|
|
|
})
|
|
|
|
yield Zotero.Sync.Storage.Local.resolveConflicts(libraryID);
|
|
|
|
yield promise;
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
assert.equal(item1.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD);
|
2017-01-29 12:50:46 +00:00
|
|
|
assert.equal(item1.attachmentSyncedModificationTime, json1.mtime);
|
|
|
|
assert.equal(item1.attachmentSyncedHash, json1.md5);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
assert.equal(item3.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_DOWNLOAD);
|
2017-01-29 12:50:46 +00:00
|
|
|
assert.isNull(item3.attachmentSyncedModificationTime);
|
|
|
|
assert.isNull(item3.attachmentSyncedHash);
|
2019-09-21 05:59:09 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("should handle attachment conflicts with no remote mtime/md5", function* () {
|
|
|
|
var libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
|
|
|
|
var item1 = yield importFileAttachment('test.png');
|
|
|
|
item1.version = 10;
|
|
|
|
yield item1.saveTx();
|
|
|
|
|
|
|
|
var json1 = item1.toJSON();
|
|
|
|
yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1]);
|
|
|
|
|
|
|
|
item1.attachmentSyncState = "in_conflict";
|
|
|
|
yield item1.saveTx({ skipAll: true });
|
|
|
|
|
2023-04-29 07:13:53 +00:00
|
|
|
var promise = waitForWindow('chrome://zotero/content/merge.xhtml', async function (dialog) {
|
2019-09-21 05:59:09 +00:00
|
|
|
var doc = dialog.document;
|
2023-04-29 07:13:53 +00:00
|
|
|
var wizard = doc.querySelector('wizard');
|
|
|
|
var mergeGroup = wizard.getElementsByTagName('merge-group')[0];
|
2019-09-21 05:59:09 +00:00
|
|
|
|
|
|
|
// 1 (remote)
|
|
|
|
// Identical, so remote version should be selected
|
2023-04-29 07:13:53 +00:00
|
|
|
assert.equal(mergeGroup.rightPane.getAttribute('selected'), 'true');
|
2019-09-21 05:59:09 +00:00
|
|
|
|
|
|
|
// Select local object
|
2023-04-29 07:13:53 +00:00
|
|
|
mergeGroup.leftPane.click();
|
|
|
|
assert.equal(mergeGroup.leftPane.getAttribute('selected'), 'true');
|
2019-09-21 05:59:09 +00:00
|
|
|
|
|
|
|
if (Zotero.isMac) {
|
|
|
|
assert.isTrue(wizard.getButton('next').hidden);
|
|
|
|
assert.isFalse(wizard.getButton('finish').hidden);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
wizard.getButton('finish').click();
|
|
|
|
});
|
|
|
|
yield Zotero.Sync.Storage.Local.resolveConflicts(libraryID);
|
|
|
|
yield promise;
|
|
|
|
|
|
|
|
assert.equal(item1.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD);
|
|
|
|
assert.isNull(item1.attachmentSyncedModificationTime);
|
|
|
|
assert.isNull(item1.attachmentSyncedHash);
|
|
|
|
});
|
2015-10-29 07:41:54 +00:00
|
|
|
})
|
|
|
|
})
|