Bug 934283 - Add option to OS.File.makeDir to recursively make directories;r=froydnj
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Fri, 28 Mar 2014 17:33:36 -0700
changeset 194515 083cbb15c2a53f74b4d9caa14799e735637d4fd8
parent 194514 8fde70fb3b9b46e9667eb283b874bcbaa7c62847
child 194516 0779a3cf6075cad283d06b1628912780a2d9a8dd
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs934283
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 934283 - Add option to OS.File.makeDir to recursively make directories;r=froydnj
toolkit/components/osfile/modules/osfile_async_front.jsm
toolkit/components/osfile/modules/osfile_shared_front.jsm
toolkit/components/osfile/modules/osfile_unix_front.jsm
toolkit/components/osfile/modules/osfile_win_front.jsm
toolkit/components/osfile/tests/xpcshell/test_makeDir.js
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -966,28 +966,40 @@ File.removeEmptyDir = function removeEmp
 File.remove = function remove(path) {
   return Scheduler.post("remove",
     [Type.path.toMsg(path)]);
 };
 
 
 
 /**
- * Create a directory.
+ * Create a directory and, optionally, its parent directories.
  *
  * @param {string} path The name of the directory.
  * @param {*=} options Additional options.
- * Implementations may interpret the following fields:
  *
- * - {C pointer} winSecurity If specified, security attributes
- * as per winapi function |CreateDirectory|. If unspecified,
- * use the default security descriptor, inherited from the
- * parent directory.
- * - {bool} ignoreExisting If |true|, do not fail if the
- * directory already exists.
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |path|
+ * must be a descendant of |from|, and that |from| and its existing
+ * subdirectories present in |path|  must be user-writeable.
+ * Example:
+ *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ *  creates directories profileDir/foo, profileDir/foo/bar
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default. Ignored if |from| is specified.
+ * - {number} unixMode Under Unix, if specified, a file creation mode,
+ * as per libc function |mkdir|. If unspecified, dirs are
+ * created with a default mode of 0700 (dir is private to
+ * the user, the user can read, write and execute). Ignored under Windows
+ * or if the file system does not support file creation modes.
+ * - {C pointer} winSecurity Under Windows, if specified, security
+ * attributes as per winapi function |CreateDirectory|. If
+ * unspecified, use the default security descriptor, inherited from
+ * the parent directory. Ignored under Unix or if the file system
+ * does not support security descriptors.
  */
 File.makeDir = function makeDir(path, options) {
   return Scheduler.post("makeDir",
     [Type.path.toMsg(path), options], path);
 };
 
 /**
  * Return the contents of a file
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -11,16 +11,17 @@
 
 if (typeof Components != "undefined") {
   throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
 }
 (function(exports) {
 
 let SharedAll =
   require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+let Path = require("resource://gre/modules/osfile/ospath.jsm");
 let Lz4 =
   require("resource://gre/modules/workers/lz4.js");
 let LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end");
 let clone = SharedAll.clone;
 
 /**
  * Code shared by implementations of File.
  *
@@ -149,18 +150,18 @@ AbstractFile.prototype = {
  * @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 dirName = Path.dirname(path);
+  let leafName = 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.
@@ -170,20 +171,20 @@ AbstractFile.openUnique = function openU
     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);
+          uniquePath = 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);
+          uniquePath = 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 ...
       }
@@ -516,13 +517,60 @@ AbstractFile.removeRecursive = function(
     }
   } finally {
     iterator.close();
   }
 
   OS.File.removeEmptyDir(path);
 };
 
+/**
+ * Create a directory and, optionally, its parent directories.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ *
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |path|
+ * must be a descendant of |from|, and that |from| and its existing
+ * subdirectories present in |path|  must be user-writeable.
+ * Example:
+ *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ *  creates directories profileDir/foo, profileDir/foo/bar
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default. Ignored if |from| is specified.
+ * - {number} unixMode Under Unix, if specified, a file creation mode,
+ * as per libc function |mkdir|. If unspecified, dirs are
+ * created with a default mode of 0700 (dir is private to
+ * the user, the user can read, write and execute). Ignored under Windows
+ * or if the file system does not support file creation modes.
+ * - {C pointer} winSecurity Under Windows, if specified, security
+ * attributes as per winapi function |CreateDirectory|. If
+ * unspecified, use the default security descriptor, inherited from
+ * the parent directory. Ignored under Unix or if the file system
+ * does not support security descriptors.
+ */
+AbstractFile.makeDir = function(path, options = {}) {
+  if (!options.from) {
+    return OS.File._makeDir(path, options);
+  }
+  if (!path.startsWith(options.from)) {
+    throw new Error("Incorrect use of option |from|: " + path + " is not a descendant of " + options.from);
+  }
+  let innerOptions = Object.create(options, {
+    ignoreExisting: {
+      value: true
+    }
+  });
+  // Compute the elements that appear in |path| but not in |options.from|.
+  let items = Path.split(path).components.slice(Path.split(options.from).components.length);
+  let current = options.from;
+  for (let item of items) {
+    current = Path.join(current, item);
+    OS.File._makeDir(current, innerOptions);
+  }
+};
+
 if (!exports.OS.Shared) {
   exports.OS.Shared = {};
 }
 exports.OS.Shared.AbstractFile = AbstractFile;
 })(this);
--- a/toolkit/components/osfile/modules/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -391,18 +391,25 @@
       * implementation interprets the following fields:
       *
       * - {number} unixMode If specified, a file creation mode,
       * as per libc function |mkdir|. If unspecified, dirs are
       * created with a default mode of 0700 (dir is private to
       * the user, the user can read, write and execute).
       * - {bool} ignoreExisting If |false|, throw error if the directory
       * already exists. |true| by default
-      */
-     File.makeDir = function makeDir(path, options = {}) {
+      * - {string} from If specified, the call to |makeDir| creates all the
+      * ancestors of |path| that are descendants of |from|. Note that |from|
+      * and its existing descendants must be user-writeable and that |path|
+      * must be a descendant of |from|.
+      * Example:
+      *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+      *  creates directories profileDir/foo, profileDir/foo/bar
+       */
+     File._makeDir = function makeDir(path, options = {}) {
        let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR;
        let result = UnixFile.mkdir(path, omode);
        if (result == -1) {
          if ((!("ignoreExisting" in options) || options.ignoreExisting) &&
              (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) {
            return;
          }
          throw new File.Error("makeDir", ctypes.errno, path);
@@ -930,16 +937,17 @@
        throw_on_negative("setDates",
                          UnixFile.utimes(path, gTimevalsPtr),
                          path);
      };
 
      File.read = exports.OS.Shared.AbstractFile.read;
      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
      File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+     File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
 
      /**
       * Remove an existing directory and its contents.
       *
       * @param {string} path The name of the directory.
       * @param {*=} options Additional options.
       *   - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
       *     exist. |true| by default.
--- a/toolkit/components/osfile/modules/osfile_win_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_front.jsm
@@ -428,30 +428,37 @@
              ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
            return;
          }
          throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
        }
      };
 
      /**
-      * Create a directory.
+      * Create a directory and, optionally, its parent directories.
       *
       * @param {string} path The name of the directory.
       * @param {*=} options Additional options. This
       * implementation interprets the following fields:
       *
       * - {C pointer} winSecurity If specified, security attributes
       * as per winapi function |CreateDirectory|. If unspecified,
       * use the default security descriptor, inherited from the
       * parent directory.
       * - {bool} ignoreExisting If |false|, throw an error if the directory
       * already exists. |true| by default
-      */
-     File.makeDir = function makeDir(path, options = {}) {
+      * - {string} from If specified, the call to |makeDir| creates all the
+      * ancestors of |path| that are descendants of |from|. Note that |from|
+      * and its existing descendants must be user-writeable and that |path|
+      * must be a descendant of |from|.
+      * Example:
+      *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+      *  creates directories profileDir/foo, profileDir/foo/bar
+       */
+     File._makeDir = function makeDir(path, options = {}) {
        let security = options.winSecurity || null;
        let result = WinFile.CreateDirectory(path, security);
 
        if (result) {
          return;
        }
 
        if (("ignoreExisting" in options) && !options.ignoreExisting) {
@@ -969,16 +976,17 @@
        // Directories can only be opened with backup semantics(!)
        winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
        winDisposition: Const.OPEN_EXISTING
      };
 
      File.read = exports.OS.Shared.AbstractFile.read;
      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
      File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+     File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
 
      /**
       * Remove an existing directory and its contents.
       *
       * @param {string} path The name of the directory.
       * @param {*=} options Additional options.
       *   - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
       *     exist. |true| by default.
--- a/toolkit/components/osfile/tests/xpcshell/test_makeDir.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_makeDir.js
@@ -2,46 +2,100 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 Components.utils.import("resource://gre/modules/osfile.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
+let Path = OS.Path;
+let profileDir;
+
 do_register_cleanup(function() {
   Services.prefs.setBoolPref("toolkit.osfile.log", false);
 });
 
 function run_test() {
-  Services.prefs.setBoolPref("toolkit.osfile.log", true);
-
   run_next_test();
 }
 
 /**
  * Test OS.File.makeDir
  */
-add_task(function() {
+
+add_task(function init() {
   // Set up profile. We create the directory in the profile, because the profile
   // is removed after every test run.
   do_get_profile();
+  profileDir = OS.Constants.Path.profileDir;
+  Services.prefs.setBoolPref("toolkit.osfile.log", true);
+});
 
-  let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory");
+/**
+ * Basic use
+ */
+
+add_task(function* test_basic() {
+  let dir = Path.join(profileDir, "directory");
 
   // Sanity checking for the test
   do_check_false((yield OS.File.exists(dir)));
 
   // Make a directory
   yield OS.File.makeDir(dir);
 
   //check if the directory exists
   yield OS.File.stat(dir);
 
-  // Make a directory that already exists
+  // Make a directory that already exists, this should succeed
+  yield OS.File.makeDir(dir);
+
+  // Make a directory with ignoreExisting
+  yield OS.File.makeDir(dir, {ignoreExisting: true});
+
+  // Make a directory with ignoreExisting false
+  let exception = null;
+  try {
+    yield OS.File.makeDir(dir, {ignoreExisting: false});
+  } catch (ex) {
+    exception = ex;
+  }
+
+  do_check_true(!!exception);
+  do_check_true(exception instanceof OS.File.Error);
+  do_check_true(exception.becauseExists);
+});
+
+// Make a root directory that already exists
+add_task(function* test_root() {
+  if (OS.Constants.Win) {
+    yield OS.File.makeDir("C:");
+    yield OS.File.makeDir("C:\\");
+  } else {
+    yield OS.File.makeDir("/");
+  }
+});
+
+/**
+ * Creating subdirectories
+ */
+add_task(function test_option_from() {
+  let dir = Path.join(profileDir, "a", "b", "c");
+
+  // Sanity checking for the test
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Make a directory
+  yield OS.File.makeDir(dir, {from: profileDir});
+
+  //check if the directory exists
+  yield OS.File.stat(dir);
+
+  // Make a directory that already exists, this should succeed
   yield OS.File.makeDir(dir);
 
   // Make a directory with ignoreExisting
   yield OS.File.makeDir(dir, {ignoreExisting: true});
 
   // Make a directory with ignoreExisting false
   let exception = null;
   try {
@@ -49,16 +103,21 @@ add_task(function() {
   } catch (ex) {
     exception = ex;
   }
 
   do_check_true(!!exception);
   do_check_true(exception instanceof OS.File.Error);
   do_check_true(exception.becauseExists);
 
-  // Make a root directory that already exists
-  if (OS.Constants.Win) {
-    yield OS.File.makeDir("C:");
-    yield OS.File.makeDir("C:\\");
-  } else {
-    yield OS.File.makeDir("/");
+  // Make a directory without |from| and fail
+  let dir2 = Path.join(profileDir, "g", "h", "i");
+  exception = null;
+  try {
+    yield OS.File.makeDir(dir2);
+  } catch (ex) {
+    exception = ex;
   }
+
+  do_check_true(!!exception);
+  do_check_true(exception instanceof OS.File.Error);
+  do_check_true(exception.becauseNoSuchFile);
 });