Use OS.File.move() for data-dir migration on Windows, and make automatic

Previously on Windows, where we don't have /bin/mv, we were recursing
into the data directory and copying files individually, which is very
slow, so automatic migration was disabled. Instead, try moving
directories with OS.File.move() with the `noCopy` flag. Moving
directories is technically unsupported by OS.File, but probably only
because of the possibility of a cross-volume copy (which is only
implemented for some platforms), and using `noCopy` hopefully prevents
that. If someone does have their data directory or storage directory on
a different volume, the migration might be quite slow, but leaving a
data directory behind in the Firefox profile directory (where it can be
easily misplaced with a seemingly unrelated Firefox reset) is worse.
This commit is contained in:
Dan Stillman 2017-02-22 04:56:49 -05:00
parent 1ff1fabb31
commit 0964277a37
3 changed files with 61 additions and 23 deletions

View file

@ -484,14 +484,7 @@ Zotero.DataDirectory = {
} }
let automatic = false; let automatic = false;
if (!exists) { if (!exists) {
// Migrate automatically on macOS and Linux -- this should match the check in automatic = true;
// Zotero.File.moveDirectory()
if (!Zotero.isWin && (yield OS.File.exists("/bin/mv"))) {
automatic = true;
}
else {
return false;
}
// Skip automatic migration if there's a non-empty directory at the new location // Skip automatic migration if there's a non-empty directory at the new location
if ((yield OS.File.exists(newDir)) && !(yield Zotero.File.directoryIsEmpty(newDir))) { if ((yield OS.File.exists(newDir)) && !(yield Zotero.File.directoryIsEmpty(newDir))) {

View file

@ -497,11 +497,18 @@ Zotero.File = new function(){
* *
* Currently this means using /bin/mv, which only works on macOS and Linux * Currently this means using /bin/mv, which only works on macOS and Linux
*/ */
this.canMoveDirectoryAtomic = Zotero.lazy(function () { this.canMoveDirectoryWithCommand = Zotero.lazy(function () {
var cmd = "/bin/mv"; var cmd = "/bin/mv";
return !Zotero.isWin && this.pathToFile(cmd).exists(); return !Zotero.isWin && this.pathToFile(cmd).exists();
}); });
/**
* For tests
*/
this.canMoveDirectoryWithFunction = Zotero.lazy(function () {
return true;
});
/** /**
* Move directory (using mv on macOS/Linux, recursively on Windows) * Move directory (using mv on macOS/Linux, recursively on Windows)
* *
@ -513,7 +520,8 @@ Zotero.File = new function(){
this.moveDirectory = Zotero.Promise.coroutine(function* (oldDir, newDir, options = {}) { this.moveDirectory = Zotero.Promise.coroutine(function* (oldDir, newDir, options = {}) {
var maxDepth = options.maxDepth || 10; var maxDepth = options.maxDepth || 10;
var cmd = "/bin/mv"; var cmd = "/bin/mv";
var useCmd = this.canMoveDirectoryAtomic(); var useCmd = this.canMoveDirectoryWithCommand();
var useFunction = this.canMoveDirectoryWithFunction();
if (!options.allowExistingTarget && (yield OS.File.exists(newDir))) { if (!options.allowExistingTarget && (yield OS.File.exists(newDir))) {
throw new Error(newDir + " exists"); throw new Error(newDir + " exists");
@ -591,6 +599,7 @@ Zotero.File = new function(){
let moved = false; let moved = false;
if (useCmd && !(yield OS.File.exists(dest))) { if (useCmd && !(yield OS.File.exists(dest))) {
Zotero.debug(`Moving ${entry.path} with ${cmd}`);
let args = [entry.path, dest]; let args = [entry.path, dest];
try { try {
yield Zotero.Utilities.Internal.exec(cmd, args); yield Zotero.Utilities.Internal.exec(cmd, args);
@ -598,7 +607,29 @@ Zotero.File = new function(){
} }
catch (e) { catch (e) {
checkError(e); checkError(e);
addError(e); Zotero.debug(e, 1);
}
}
// If can't use command, try moving with OS.File.move(). Technically this is
// unsupported for directories, but it works on all platforms as long as noCopy
// is set (and on some platforms regardless)
if (!moved && useFunction) {
Zotero.debug(`Moving ${entry.path} with OS.File`);
try {
yield OS.File.move(
entry.path,
dest,
{
noCopy: true
}
);
moved = true;
}
catch (e) {
checkError(e);
Zotero.debug(e, 1);
} }
} }

View file

@ -59,19 +59,33 @@ describe("Zotero.DataDirectory", function () {
stubs.pipeExists.restore(); stubs.pipeExists.restore();
}); });
// Force non-mv mode
var disableCommandMode = function () { var disableCommandMode = function () {
// Force non-mv mode if (!stubs.canMoveDirectoryWithCommand) {
var origFunc = OS.File.exists; stubs.canMoveDirectoryWithCommand = sinon.stub(Zotero.File, "canMoveDirectoryWithCommand")
if (!stubs.canMoveDirectoryAtomic) { .returns(false);
stubs.canMoveDirectoryAtomic = sinon.stub(Zotero.File, "canMoveDirectoryAtomic") }
};
// Force non-OS.File.move() mode
var disableFunctionMode = function () {
if (!stubs.canMoveDirectoryWithFunction) {
stubs.canMoveDirectoryWithFunction = sinon.stub(Zotero.File, "canMoveDirectoryWithFunction")
.returns(false); .returns(false);
} }
}; };
var resetCommandMode = function () { var resetCommandMode = function () {
if (stubs.canMoveDirectoryAtomic) { if (stubs.canMoveDirectoryWithCommand) {
stubs.canMoveDirectoryAtomic.restore(); stubs.canMoveDirectoryWithCommand.restore();
stubs.canMoveDirectoryAtomic = undefined; stubs.canMoveDirectoryWithCommand = undefined;
}
};
var resetFunctionMode = function () {
if (stubs.canMoveDirectoryWithFunction) {
stubs.canMoveDirectoryWithFunction.restore();
stubs.canMoveDirectoryWithFunction = undefined;
} }
}; };
@ -144,10 +158,12 @@ describe("Zotero.DataDirectory", function () {
beforeEach(function () { beforeEach(function () {
disableCommandMode(); disableCommandMode();
disableFunctionMode();
}); });
after(function () { after(function () {
resetCommandMode(); resetCommandMode();
resetFunctionMode();
}); });
var tests = []; var tests = [];
@ -157,11 +173,7 @@ describe("Zotero.DataDirectory", function () {
it("should skip automatic migration if target directory exists and is non-empty", function* () { it("should skip automatic migration if target directory exists and is non-empty", function* () {
resetCommandMode(); resetCommandMode();
resetFunctionMode();
// No automatic migration without atomic directory move
if (!Zotero.File.canMoveDirectoryAtomic()) {
this.skip();
}
yield populateDataDirectory(oldDir); yield populateDataDirectory(oldDir);
yield OS.File.remove(oldMigrationMarker); yield OS.File.remove(oldMigrationMarker);
@ -347,10 +359,12 @@ describe("Zotero.DataDirectory", function () {
before(function () { before(function () {
disableCommandMode(); disableCommandMode();
disableFunctionMode();
}); });
after(function () { after(function () {
resetCommandMode(); resetCommandMode();
resetFunctionMode();
}); });
it("should handle partial failure", function* () { it("should handle partial failure", function* () {