Bug 866571 - [OS.File] Add createUnique method. r=yoric

This commit is contained in:
Marcos Aruj 2013-10-16 08:21:39 -04:00
parent f092674a68
commit be4277a463
7 changed files with 194 additions and 0 deletions

View File

@ -507,6 +507,35 @@ File.open = function open(path, mode, options) {
);
};
/**
* Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
*
* @param {string} path The path to the file.
* @param {*=} options Additional options for file opening. This
* implementation interprets the following fields:
*
* - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
* If |false| use HEX numbers ie: filename-A65BC0.ext
* - {number} maxReadableNumber Used to limit the amount of tries after a failed
* file creation. Default is 20.
*
* @return {Object} contains A file object{file} and the path{path}.
* @throws {OS.File.Error} If the file could not be opened.
*/
File.openUnique = function openUnique(path, options) {
return Scheduler.post(
"openUnique", [Type.path.toMsg(path), options],
path
).then(
function onSuccess(msg) {
return {
path: msg.path,
file: new File(msg.file)
};
}
);
};
/**
* Get the information on the file.
*

View File

@ -270,6 +270,20 @@ if (this.Components) {
path: filePath
});
},
openUnique: function openUnique(path, options) {
let filePath = Type.path.fromMsg(path);
let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options);
let resourceId = OpenedFiles.add(openedFile.file, {
// Adding path information to keep track of opened files
// to report leaks when debugging.
path: openedFile.path
});
return {
path: openedFile.path,
file: resourceId
};
},
read: function read(path, bytes, options) {
let data = File.read(Type.path.fromMsg(path), bytes, options);
return new Transfer({buffer: data.buffer, byteOffset: data.byteOffset, byteLength: data.byteLength}, [data.buffer]);

View File

@ -127,6 +127,64 @@ AbstractFile.prototype = {
}
};
/**
* Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
*
* @param {string} path The path to the file.
* @param {*=} options Additional options for file opening. This
* implementation interprets the following fields:
*
* - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
* If |false| use HEX numbers ie: filename-A65BC0.ext
* - {number} maxReadableNumber Used to limit the amount of tries after a failed
* file creation. Default is 20.
*
* @return {Object} contains A file object{file} and the path{path}.
* @throws {OS.File.Error} If the file could not be opened.
*/
AbstractFile.openUnique = function openUnique(path, options = {}) {
let mode = {
create : true
};
let dirName = OS.Path.dirname(path);
let leafName = OS.Path.basename(path);
let lastDotCharacter = leafName.lastIndexOf('.');
let fileName = leafName.substring(0, lastDotCharacter != -1 ? lastDotCharacter : leafName.length);
let suffix = (lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : "");
let uniquePath = "";
let maxAttempts = options.maxAttempts || 99;
let humanReadable = !!options.humanReadable;
const HEX_RADIX = 16;
// We produce HEX numbers between 0 and 2^24 - 1.
const MAX_HEX_NUMBER = 16777215;
try {
return {
path: path,
file: OS.File.open(path, mode)
};
} catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
for (let i = 0; i < maxAttempts; ++i) {
try {
if (humanReadable) {
uniquePath = OS.Path.join(dirName, fileName + "-" + (i + 1) + suffix);
} else {
let hexNumber = Math.floor(Math.random() * MAX_HEX_NUMBER).toString(HEX_RADIX);
uniquePath = OS.Path.join(dirName, fileName + "-" + hexNumber + suffix);
}
return {
path: uniquePath,
file: OS.File.open(uniquePath, mode)
};
} catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
// keep trying ...
}
}
throw OS.File.Error.exists("could not find an unused file name.");
}
};
/**
* Utility function used to normalize a Typed Array or C
* pointer into a uint8_t C pointer.

View File

@ -824,6 +824,7 @@
File.read = exports.OS.Shared.AbstractFile.read;
File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
File.removeDir = exports.OS.Shared.AbstractFile.removeDir;
/**

View File

@ -756,6 +756,7 @@
File.read = exports.OS.Shared.AbstractFile.read;
File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
File.removeDir = exports.OS.Shared.AbstractFile.removeDir;
/**

View File

@ -0,0 +1,90 @@
"use strict";
Components.utils.import("resource://gre/modules/osfile.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");
function run_test() {
run_next_test();
}
function testFiles(filename) {
return Task.spawn(function() {
const MAX_TRIES = 10;
let currentDir = yield OS.File.getCurrentDirectory();
let path = OS.Path.join(currentDir, filename);
let exists = yield OS.File.exists(path);
// Check a file with the same name doesn't exist already
do_check_false(exists);
// Ensure that openUnique() uses the file name if there is no file with that name already.
let openedFile = yield OS.File.openUnique(path);
do_print("\nCreate new file: " + openedFile.path);
yield openedFile.file.close();
exists = yield OS.File.exists(openedFile.path);
do_check_true(exists);
do_check_eq(path, openedFile.path);
let fileInfo = yield OS.File.stat(openedFile.path);
do_check_true(fileInfo.size == 0);
// Ensure that openUnique() creates a new file name using a HEX number, as the original name is already taken.
openedFile = yield OS.File.openUnique(path);
do_print("\nCreate unique HEX file: " + openedFile.path);
yield openedFile.file.close();
exists = yield OS.File.exists(openedFile.path);
do_check_true(exists);
let fileInfo = yield OS.File.stat(openedFile.path);
do_check_true(fileInfo.size == 0);
// Ensure that openUnique() generates different file names each time, using the HEX number algorithm
let filenames = new Set();
for (let i=0; i < MAX_TRIES; i++) {
openedFile = yield OS.File.openUnique(path);
yield openedFile.file.close();
filenames.add(openedFile.path);
}
do_check_eq(filenames.size, MAX_TRIES);
// Ensure that openUnique() creates a new human readable file name using, as the original name is already taken.
openedFile = yield OS.File.openUnique(path, {humanReadable : true});
do_print("\nCreate unique Human Readable file: " + openedFile.path);
yield openedFile.file.close();
exists = yield OS.File.exists(openedFile.path);
do_check_true(exists);
let fileInfo = yield OS.File.stat(openedFile.path);
do_check_true(fileInfo.size == 0);
// Ensure that openUnique() generates different human readable file names each time
filenames = new Set();
for (let i=0; i < MAX_TRIES; i++) {
openedFile = yield OS.File.openUnique(path, {humanReadable : true});
yield openedFile.file.close();
filenames.add(openedFile.path);
}
do_check_eq(filenames.size, MAX_TRIES);
let exn;
try {
for (let i=0; i < 100; i++) {
openedFile = yield OS.File.openUnique(path, {humanReadable : true});
yield openedFile.file.close();
}
} catch (ex) {
exn = ex;
}
do_print("Ensure that this raises the correct error");
do_check_true(!!exn);
do_check_true(exn instanceof OS.File.Error);
do_check_true(exn.becauseExists);
});
}
add_task(function test_unique() {
OS.Shared.DEBUG = true;
// Tests files with extension
yield testFiles("dummy_unique_file.txt");
// Tests files with no extension
yield testFiles("dummy_unique_file_no_ext");
});

View File

@ -12,3 +12,4 @@ tail =
[test_exception.js]
[test_path_constants.js]
[test_removeDir.js]
[test_unique.js]