Bug 965196 - [OS.File] Add an option |backupTo| to writeAtomic. r=Yoric

This commit is contained in:
Peiyong Lin 2014-02-06 15:10:08 -05:00
parent 1798ca1941
commit 5a907508c4
4 changed files with 169 additions and 0 deletions

View File

@ -903,6 +903,11 @@ File.exists = function exists(path) {
* if the system shuts down improperly (typically due to a kernel freeze
* or a power failure) or if the device is disconnected before the buffer
* is flushed, the file has more chances of not being corrupted.
* - {string} backupTo - If specified, backup the destination file as |backupTo|.
* Note that this function renames the destination file before overwriting it.
* If the process or the operating system freezes or crashes
* during the short window between these operations,
* the destination file will have been moved to its backup.
*
* @return {promise}
* @resolves {number} The number of bytes actually written.

View File

@ -378,6 +378,11 @@ AbstractFile.read = function read(path, bytes, options = {}) {
* If "lz4", compress the contents of the file atomically using lz4. For the
* time being, the container format is specific to Mozilla and cannot be read
* by means other than OS.File.read(..., { compression: "lz4"})
* - {string} backupTo - If specified, backup the destination file as |backupTo|.
* Note that this function renames the destination file before overwriting it.
* If the process or the operating system freezes or crashes
* during the short window between these operations,
* the destination file will have been moved to its backup.
*
* @return {number} The number of bytes actually written.
*/
@ -408,6 +413,13 @@ AbstractFile.writeAtomic =
let bytesWritten = 0;
if (!options.tmpPath) {
if (options.backupTo) {
try {
OS.File.move(path, options.backupTo, {noCopy: true});
} catch (ex if ex.becauseNoSuchFile) {
// The file doesn't exist, nothing to backup.
}
}
// Just write, without any renaming trick
let dest = OS.File.open(path, {write: true, truncate: true});
try {
@ -434,6 +446,14 @@ AbstractFile.writeAtomic =
tmpFile.close();
}
if (options.backupTo) {
try {
OS.File.move(path, options.backupTo, {noCopy: true});
} catch (ex if ex.becauseNoSuchFile) {
// The file doesn't exist, nothing to backup.
}
}
OS.File.move(options.tmpPath, path, {noCopy: true});
return bytesWritten;
};

View File

@ -0,0 +1,143 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let {OS: {File, Path, Constants}} = Components.utils.import("resource://gre/modules/osfile.jsm", {});
Components.utils.import("resource://gre/modules/Task.jsm");
/**
* Remove all temporary files and back up files, including
* test_backupTo_option_with_tmpPath.tmp
* test_backupTo_option_with_tmpPath.tmp.backup
* test_backupTo_option_without_tmpPath.tmp
* test_backupTo_option_without_tmpPath.tmp.backup
* test_non_backupTo_option.tmp
* test_non_backupTo_option.tmp.backup
* test_backupTo_option_without_destination_file.tmp
* test_backupTo_option_without_destination_file.tmp.backup
* test_backupTo_option_with_backup_file.tmp
* test_backupTo_option_with_backup_file.tmp.backup
*/
function clearFiles() {
return Task.spawn(function () {
let files = ["test_backupTo_option_with_tmpPath.tmp",
"test_backupTo_option_without_tmpPath.tmp",
"test_non_backupTo_option.tmp",
"test_backupTo_option_without_destination_file.tmp",
"test_backupTo_option_with_backup_file.tmp"];
for (let file of files) {
let path = Path.join(Constants.Path.tmpDir, file);
yield File.remove(path);
yield File.remove(path + ".backup");
}
});
}
function run_test() {
run_next_test();
}
add_task(function* init() {
yield clearFiles();
});
/**
* test when
* |backupTo| specified
* |tmpPath| specified
* destination file exists
* @result destination file will be backed up
*/
add_task(function* test_backupTo_option_with_tmpPath() {
let DEFAULT_CONTENTS = "default contents" + Math.random();
let WRITE_CONTENTS = "abc" + Math.random();
let path = Path.join(Constants.Path.tmpDir,
"test_backupTo_option_with_tmpPath.tmp");
yield File.writeAtomic(path, DEFAULT_CONTENTS);
yield File.writeAtomic(path, WRITE_CONTENTS, { tmpPath: path + ".tmp",
backupTo: path + ".backup" });
do_check_true((yield File.exists(path + ".backup")));
let contents = yield File.read(path + ".backup");
do_check_eq(DEFAULT_CONTENTS, (new TextDecoder()).decode(contents));
});
/**
* test when
* |backupTo| specified
* |tmpPath| not specified
* destination file exists
* @result destination file will be backed up
*/
add_task(function* test_backupTo_option_without_tmpPath() {
let DEFAULT_CONTENTS = "default contents" + Math.random();
let WRITE_CONTENTS = "abc" + Math.random();
let path = Path.join(Constants.Path.tmpDir,
"test_backupTo_option_without_tmpPath.tmp");
yield File.writeAtomic(path, DEFAULT_CONTENTS);
yield File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" });
do_check_true((yield File.exists(path + ".backup")));
let contents = yield File.read(path + ".backup");
do_check_eq(DEFAULT_CONTENTS, (new TextDecoder()).decode(contents));
});
/**
* test when
* |backupTo| not specified
* |tmpPath| not specified
* destination file exists
* @result destination file will not be backed up
*/
add_task(function* test_non_backupTo_option() {
let DEFAULT_CONTENTS = "default contents" + Math.random();
let WRITE_CONTENTS = "abc" + Math.random();
let path = Path.join(Constants.Path.tmpDir,
"test_non_backupTo_option.tmp");
yield File.writeAtomic(path, DEFAULT_CONTENTS);
yield File.writeAtomic(path, WRITE_CONTENTS);
do_check_false((yield File.exists(path + ".backup")));
});
/**
* test when
* |backupTo| specified
* |tmpPath| not specified
* destination file not exists
* @result no back up file exists
*/
add_task(function* test_backupTo_option_without_destination_file() {
let DEFAULT_CONTENTS = "default contents" + Math.random();
let WRITE_CONTENTS = "abc" + Math.random();
let path = Path.join(Constants.Path.tmpDir,
"test_backupTo_option_without_destination_file.tmp");
yield File.remove(path);
yield File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" });
do_check_false((yield File.exists(path + ".backup")));
});
/**
* test when
* |backupTo| specified
* |tmpPath| not specified
* backup file exists
* destination file exists
* @result destination file will be backed up
*/
add_task(function* test_backupTo_option_with_backup_file() {
let DEFAULT_CONTENTS = "default contents" + Math.random();
let WRITE_CONTENTS = "abc" + Math.random();
let path = Path.join(Constants.Path.tmpDir,
"test_backupTo_option_with_backup_file.tmp");
yield File.writeAtomic(path, DEFAULT_CONTENTS);
yield File.writeAtomic(path + ".backup", new Uint8Array(1000));
yield File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" });
do_check_true((yield File.exists(path + ".backup")));
let contents = yield File.read(path + ".backup");
do_check_eq(DEFAULT_CONTENTS, (new TextDecoder()).decode(contents));
});
add_task(function* cleanup() {
yield clearFiles();
});

View File

@ -25,3 +25,4 @@ tail =
[test_telemetry.js]
[test_duration.js]
[test_compression.js]
[test_osfile_writeAtomic_backupTo_option.js]