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:
parent
1ff1fabb31
commit
0964277a37
3 changed files with 61 additions and 23 deletions
|
@ -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))) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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* () {
|
||||||
|
|
Loading…
Reference in a new issue