toolkit/components/osfile/modules/osfile_shared_front.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,576 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +/**
     1.9 + * Code shared by OS.File front-ends.
    1.10 + *
    1.11 + * This code is meant to be included by another library. It is also meant to
    1.12 + * be executed only on a worker thread.
    1.13 + */
    1.14 +
    1.15 +if (typeof Components != "undefined") {
    1.16 +  throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
    1.17 +}
    1.18 +(function(exports) {
    1.19 +
    1.20 +let SharedAll =
    1.21 +  require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
    1.22 +let Path = require("resource://gre/modules/osfile/ospath.jsm");
    1.23 +let Lz4 =
    1.24 +  require("resource://gre/modules/workers/lz4.js");
    1.25 +let LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end");
    1.26 +let clone = SharedAll.clone;
    1.27 +
    1.28 +/**
    1.29 + * Code shared by implementations of File.
    1.30 + *
    1.31 + * @param {*} fd An OS-specific file handle.
    1.32 + * @param {string} path File path of the file handle, used for error-reporting.
    1.33 + * @constructor
    1.34 + */
    1.35 +let AbstractFile = function AbstractFile(fd, path) {
    1.36 +  this._fd = fd;
    1.37 +  if (!path) {
    1.38 +    throw new TypeError("path is expected");
    1.39 +  }
    1.40 +  this._path = path;
    1.41 +};
    1.42 +
    1.43 +AbstractFile.prototype = {
    1.44 +  /**
    1.45 +   * Return the file handle.
    1.46 +   *
    1.47 +   * @throw OS.File.Error if the file has been closed.
    1.48 +   */
    1.49 +  get fd() {
    1.50 +    if (this._fd) {
    1.51 +      return this._fd;
    1.52 +    }
    1.53 +    throw OS.File.Error.closed("accessing file", this._path);
    1.54 +  },
    1.55 +  /**
    1.56 +   * Read bytes from this file to a new buffer.
    1.57 +   *
    1.58 +   * @param {number=} bytes If unspecified, read all the remaining bytes from
    1.59 +   * this file. If specified, read |bytes| bytes, or less if the file does notclone
    1.60 +   * contain that many bytes.
    1.61 +   * @param {JSON} options
    1.62 +   * @return {Uint8Array} An array containing the bytes read.
    1.63 +   */
    1.64 +  read: function read(bytes, options = {}) {
    1.65 +    options = clone(options);
    1.66 +    options.bytes = bytes == null ? this.stat().size : bytes;
    1.67 +    let buffer = new Uint8Array(options.bytes);
    1.68 +    let size = this.readTo(buffer, options);
    1.69 +    if (size == options.bytes) {
    1.70 +      return buffer;
    1.71 +    } else {
    1.72 +      return buffer.subarray(0, size);
    1.73 +    }
    1.74 +  },
    1.75 +
    1.76 +  /**
    1.77 +   * Read bytes from this file to an existing buffer.
    1.78 +   *
    1.79 +   * Note that, by default, this function may perform several I/O
    1.80 +   * operations to ensure that the buffer is as full as possible.
    1.81 +   *
    1.82 +   * @param {Typed Array | C pointer} buffer The buffer in which to
    1.83 +   * store the bytes. The buffer must be large enough to
    1.84 +   * accomodate |bytes| bytes.
    1.85 +   * @param {*=} options Optionally, an object that may contain the
    1.86 +   * following fields:
    1.87 +   * - {number} bytes The number of |bytes| to write from the buffer. If
    1.88 +   * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
    1.89 +   * if |buffer| is a C pointer.
    1.90 +   *
    1.91 +   * @return {number} The number of bytes actually read, which may be
    1.92 +   * less than |bytes| if the file did not contain that many bytes left.
    1.93 +   */
    1.94 +  readTo: function readTo(buffer, options = {}) {
    1.95 +    let {ptr, bytes} = SharedAll.normalizeToPointer(buffer, options.bytes);
    1.96 +    let pos = 0;
    1.97 +    while (pos < bytes) {
    1.98 +      let chunkSize = this._read(ptr, bytes - pos, options);
    1.99 +      if (chunkSize == 0) {
   1.100 +        break;
   1.101 +      }
   1.102 +      pos += chunkSize;
   1.103 +      ptr = SharedAll.offsetBy(ptr, chunkSize);
   1.104 +    }
   1.105 +
   1.106 +    return pos;
   1.107 +  },
   1.108 +
   1.109 +  /**
   1.110 +   * Write bytes from a buffer to this file.
   1.111 +   *
   1.112 +   * Note that, by default, this function may perform several I/O
   1.113 +   * operations to ensure that the buffer is fully written.
   1.114 +   *
   1.115 +   * @param {Typed array | C pointer} buffer The buffer in which the
   1.116 +   * the bytes are stored. The buffer must be large enough to
   1.117 +   * accomodate |bytes| bytes.
   1.118 +   * @param {*=} options Optionally, an object that may contain the
   1.119 +   * following fields:
   1.120 +   * - {number} bytes The number of |bytes| to write from the buffer. If
   1.121 +   * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
   1.122 +   * if |buffer| is a C pointer.
   1.123 +   *
   1.124 +   * @return {number} The number of bytes actually written.
   1.125 +   */
   1.126 +  write: function write(buffer, options = {}) {
   1.127 +
   1.128 +    let {ptr, bytes} =
   1.129 +      SharedAll.normalizeToPointer(buffer, options.bytes || undefined);
   1.130 +
   1.131 +    let pos = 0;
   1.132 +    while (pos < bytes) {
   1.133 +      let chunkSize = this._write(ptr, bytes - pos, options);
   1.134 +      pos += chunkSize;
   1.135 +      ptr = SharedAll.offsetBy(ptr, chunkSize);
   1.136 +    }
   1.137 +    return pos;
   1.138 +  }
   1.139 +};
   1.140 +
   1.141 +/**
   1.142 + * 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.
   1.143 + *
   1.144 + * @param {string} path The path to the file.
   1.145 + * @param {*=} options Additional options for file opening. This
   1.146 + * implementation interprets the following fields:
   1.147 + *
   1.148 + * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
   1.149 + *  If |false| use HEX numbers ie: filename-A65BC0.ext
   1.150 + * - {number} maxReadableNumber Used to limit the amount of tries after a failed
   1.151 + *  file creation. Default is 20.
   1.152 + *
   1.153 + * @return {Object} contains A file object{file} and the path{path}.
   1.154 + * @throws {OS.File.Error} If the file could not be opened.
   1.155 + */
   1.156 +AbstractFile.openUnique = function openUnique(path, options = {}) {
   1.157 +  let mode = {
   1.158 +    create : true
   1.159 +  };
   1.160 +
   1.161 +  let dirName = Path.dirname(path);
   1.162 +  let leafName = Path.basename(path);
   1.163 +  let lastDotCharacter = leafName.lastIndexOf('.');
   1.164 +  let fileName = leafName.substring(0, lastDotCharacter != -1 ? lastDotCharacter : leafName.length);
   1.165 +  let suffix = (lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : "");
   1.166 +  let uniquePath = "";
   1.167 +  let maxAttempts = options.maxAttempts || 99;
   1.168 +  let humanReadable = !!options.humanReadable;
   1.169 +  const HEX_RADIX = 16;
   1.170 +  // We produce HEX numbers between 0 and 2^24 - 1.
   1.171 +  const MAX_HEX_NUMBER = 16777215;
   1.172 +
   1.173 +  try {
   1.174 +    return {
   1.175 +      path: path,
   1.176 +      file: OS.File.open(path, mode)
   1.177 +    };
   1.178 +  } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
   1.179 +    for (let i = 0; i < maxAttempts; ++i) {
   1.180 +      try {
   1.181 +        if (humanReadable) {
   1.182 +          uniquePath = Path.join(dirName, fileName + "-" + (i + 1) + suffix);
   1.183 +        } else {
   1.184 +          let hexNumber = Math.floor(Math.random() * MAX_HEX_NUMBER).toString(HEX_RADIX);
   1.185 +          uniquePath = Path.join(dirName, fileName + "-" + hexNumber + suffix);
   1.186 +        }
   1.187 +        return {
   1.188 +          path: uniquePath,
   1.189 +          file: OS.File.open(uniquePath, mode)
   1.190 +        };
   1.191 +      } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
   1.192 +        // keep trying ...
   1.193 +      }
   1.194 +    }
   1.195 +    throw OS.File.Error.exists("could not find an unused file name.", path);
   1.196 +  }
   1.197 +};
   1.198 +
   1.199 +/**
   1.200 + * Code shared by iterators.
   1.201 + */
   1.202 +AbstractFile.AbstractIterator = function AbstractIterator() {
   1.203 +};
   1.204 +AbstractFile.AbstractIterator.prototype = {
   1.205 +  /**
   1.206 +   * Allow iterating with |for|
   1.207 +   */
   1.208 +  __iterator__: function __iterator__() {
   1.209 +    return this;
   1.210 +  },
   1.211 +  /**
   1.212 +   * Apply a function to all elements of the directory sequentially.
   1.213 +   *
   1.214 +   * @param {Function} cb This function will be applied to all entries
   1.215 +   * of the directory. It receives as arguments
   1.216 +   *  - the OS.File.Entry corresponding to the entry;
   1.217 +   *  - the index of the entry in the enumeration;
   1.218 +   *  - the iterator itself - calling |close| on the iterator stops
   1.219 +   *   the loop.
   1.220 +   */
   1.221 +  forEach: function forEach(cb) {
   1.222 +    let index = 0;
   1.223 +    for (let entry in this) {
   1.224 +      cb(entry, index++, this);
   1.225 +    }
   1.226 +  },
   1.227 +  /**
   1.228 +   * Return several entries at once.
   1.229 +   *
   1.230 +   * Entries are returned in the same order as a walk with |forEach| or
   1.231 +   * |for(...)|.
   1.232 +   *
   1.233 +   * @param {number=} length If specified, the number of entries
   1.234 +   * to return. If unspecified, return all remaining entries.
   1.235 +   * @return {Array} An array containing the next |length| entries, or
   1.236 +   * less if the iteration contains less than |length| entries left.
   1.237 +   */
   1.238 +  nextBatch: function nextBatch(length) {
   1.239 +    let array = [];
   1.240 +    let i = 0;
   1.241 +    for (let entry in this) {
   1.242 +      array.push(entry);
   1.243 +      if (++i >= length) {
   1.244 +        return array;
   1.245 +      }
   1.246 +    }
   1.247 +    return array;
   1.248 +  }
   1.249 +};
   1.250 +
   1.251 +/**
   1.252 + * Utility function shared by implementations of |OS.File.open|:
   1.253 + * extract read/write/trunc/create/existing flags from a |mode|
   1.254 + * object.
   1.255 + *
   1.256 + * @param {*=} mode An object that may contain fields |read|,
   1.257 + * |write|, |truncate|, |create|, |existing|. These fields
   1.258 + * are interpreted only if true-ish.
   1.259 + * @return {{read:bool, write:bool, trunc:bool, create:bool,
   1.260 + * existing:bool}} an object recapitulating the options set
   1.261 + * by |mode|.
   1.262 + * @throws {TypeError} If |mode| contains other fields, or
   1.263 + * if it contains both |create| and |truncate|, or |create|
   1.264 + * and |existing|.
   1.265 + */
   1.266 +AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
   1.267 +  let result = {
   1.268 +    read: false,
   1.269 +    write: false,
   1.270 +    trunc: false,
   1.271 +    create: false,
   1.272 +    existing: false,
   1.273 +    append: true
   1.274 +  };
   1.275 +  for (let key in mode) {
   1.276 +    let val = !!mode[key]; // bool cast.
   1.277 +    switch (key) {
   1.278 +    case "read":
   1.279 +      result.read = val;
   1.280 +      break;
   1.281 +    case "write":
   1.282 +      result.write = val;
   1.283 +      break;
   1.284 +    case "truncate": // fallthrough
   1.285 +    case "trunc":
   1.286 +      result.trunc = val;
   1.287 +      result.write |= val;
   1.288 +      break;
   1.289 +    case "create":
   1.290 +      result.create = val;
   1.291 +      result.write |= val;
   1.292 +      break;
   1.293 +    case "existing": // fallthrough
   1.294 +    case "exist":
   1.295 +      result.existing = val;
   1.296 +      break;
   1.297 +    case "append":
   1.298 +      result.append = val;
   1.299 +      break;
   1.300 +    default:
   1.301 +      throw new TypeError("Mode " + key + " not understood");
   1.302 +    }
   1.303 +  }
   1.304 +  // Reject opposite modes
   1.305 +  if (result.existing && result.create) {
   1.306 +    throw new TypeError("Cannot specify both existing:true and create:true");
   1.307 +  }
   1.308 +  if (result.trunc && result.create) {
   1.309 +    throw new TypeError("Cannot specify both trunc:true and create:true");
   1.310 +  }
   1.311 +  // Handle read/write
   1.312 +  if (!result.write) {
   1.313 +    result.read = true;
   1.314 +  }
   1.315 +  return result;
   1.316 +};
   1.317 +
   1.318 +/**
   1.319 + * Return the contents of a file.
   1.320 + *
   1.321 + * @param {string} path The path to the file.
   1.322 + * @param {number=} bytes Optionally, an upper bound to the number of bytes
   1.323 + * to read. DEPRECATED - please use options.bytes instead.
   1.324 + * @param {object=} options Optionally, an object with some of the following
   1.325 + * fields:
   1.326 + * - {number} bytes An upper bound to the number of bytes to read.
   1.327 + * - {string} compression If "lz4" and if the file is compressed using the lz4
   1.328 + *   compression algorithm, decompress the file contents on the fly.
   1.329 + *
   1.330 + * @return {Uint8Array} A buffer holding the bytes
   1.331 + * and the number of bytes read from the file.
   1.332 + */
   1.333 +AbstractFile.read = function read(path, bytes, options = {}) {
   1.334 +  if (bytes && typeof bytes == "object") {
   1.335 +    options = bytes;
   1.336 +    bytes = options.bytes || null;
   1.337 +  }
   1.338 +  if ("encoding" in options && typeof options.encoding != "string") {
   1.339 +    throw new TypeError("Invalid type for option encoding");
   1.340 +  }
   1.341 +  if ("compression" in options && typeof options.compression != "string") {
   1.342 +    throw new TypeError("Invalid type for option compression: " + options.compression);
   1.343 +  }
   1.344 +  if ("bytes" in options && typeof options.bytes != "number") {
   1.345 +    throw new TypeError("Invalid type for option bytes");
   1.346 +  }
   1.347 +  let file = exports.OS.File.open(path);
   1.348 +  try {
   1.349 +    let buffer = file.read(bytes, options);
   1.350 +    if ("compression" in options) {
   1.351 +      if (options.compression == "lz4") {
   1.352 +        buffer = Lz4.decompressFileContent(buffer, options);
   1.353 +      } else {
   1.354 +        throw OS.File.Error.invalidArgument("Compression");
   1.355 +      }
   1.356 +    }
   1.357 +    if (!("encoding" in options)) {
   1.358 +      return buffer;
   1.359 +    }
   1.360 +    let decoder;
   1.361 +    try {
   1.362 +      decoder = new TextDecoder(options.encoding);
   1.363 +    } catch (ex if ex instanceof TypeError) {
   1.364 +      throw OS.File.Error.invalidArgument("Decode");
   1.365 +    }
   1.366 +    return decoder.decode(buffer);
   1.367 +  } finally {
   1.368 +    file.close();
   1.369 +  }
   1.370 +};
   1.371 +
   1.372 +/**
   1.373 + * Write a file, atomically.
   1.374 + *
   1.375 + * By opposition to a regular |write|, this operation ensures that,
   1.376 + * until the contents are fully written, the destination file is
   1.377 + * not modified.
   1.378 + *
   1.379 + * Limitation: In a few extreme cases (hardware failure during the
   1.380 + * write, user unplugging disk during the write, etc.), data may be
   1.381 + * corrupted. If your data is user-critical (e.g. preferences,
   1.382 + * application data, etc.), you may wish to consider adding options
   1.383 + * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
   1.384 + * detailed below. Note that no combination of options can be
   1.385 + * guaranteed to totally eliminate the risk of corruption.
   1.386 + *
   1.387 + * @param {string} path The path of the file to modify.
   1.388 + * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
   1.389 + * @param {*=} options Optionally, an object determining the behavior
   1.390 + * of this function. This object may contain the following fields:
   1.391 + * - {number} bytes The number of bytes to write. If unspecified,
   1.392 + * |buffer.byteLength|. Required if |buffer| is a C pointer.
   1.393 + * - {string} tmpPath If |null| or unspecified, write all data directly
   1.394 + * to |path|. If specified, write all data to a temporary file called
   1.395 + * |tmpPath| and, once this write is complete, rename the file to
   1.396 + * replace |path|. Performing this additional operation is a little
   1.397 + * slower but also a little safer.
   1.398 + * - {bool} noOverwrite - If set, this function will fail if a file already
   1.399 + * exists at |path|.
   1.400 + * - {bool} flush - If |false| or unspecified, return immediately once the
   1.401 + * write is complete. If |true|, before writing, force the operating system
   1.402 + * to write its internal disk buffers to the disk. This is considerably slower
   1.403 + * (not just for the application but for the whole system) but also safer:
   1.404 + * if the system shuts down improperly (typically due to a kernel freeze
   1.405 + * or a power failure) or if the device is disconnected before the buffer
   1.406 + * is flushed, the file has more chances of not being corrupted.
   1.407 + * - {string} compression - If empty or unspecified, do not compress the file.
   1.408 + * If "lz4", compress the contents of the file atomically using lz4. For the
   1.409 + * time being, the container format is specific to Mozilla and cannot be read
   1.410 + * by means other than OS.File.read(..., { compression: "lz4"})
   1.411 + * - {string} backupTo - If specified, backup the destination file as |backupTo|.
   1.412 + * Note that this function renames the destination file before overwriting it.
   1.413 + * If the process or the operating system freezes or crashes
   1.414 + * during the short window between these operations,
   1.415 + * the destination file will have been moved to its backup.
   1.416 + *
   1.417 + * @return {number} The number of bytes actually written.
   1.418 + */
   1.419 +AbstractFile.writeAtomic =
   1.420 +     function writeAtomic(path, buffer, options = {}) {
   1.421 +
   1.422 +  // Verify that path is defined and of the correct type
   1.423 +  if (typeof path != "string" || path == "") {
   1.424 +    throw new TypeError("File path should be a (non-empty) string");
   1.425 +  }
   1.426 +  let noOverwrite = options.noOverwrite;
   1.427 +  if (noOverwrite && OS.File.exists(path)) {
   1.428 +    throw OS.File.Error.exists("writeAtomic", path);
   1.429 +  }
   1.430 +
   1.431 +  if (typeof buffer == "string") {
   1.432 +    // Normalize buffer to a C buffer by encoding it
   1.433 +    let encoding = options.encoding || "utf-8";
   1.434 +    buffer = new TextEncoder(encoding).encode(buffer);
   1.435 +  }
   1.436 +
   1.437 +  if ("compression" in options && options.compression == "lz4") {
   1.438 +    buffer = Lz4.compressFileContent(buffer, options);
   1.439 +    options = Object.create(options);
   1.440 +    options.bytes = buffer.byteLength;
   1.441 +  }
   1.442 +
   1.443 +  let bytesWritten = 0;
   1.444 +
   1.445 +  if (!options.tmpPath) {
   1.446 +    if (options.backupTo) {
   1.447 +      try {
   1.448 +        OS.File.move(path, options.backupTo, {noCopy: true});
   1.449 +      } catch (ex if ex.becauseNoSuchFile) {
   1.450 +        // The file doesn't exist, nothing to backup.
   1.451 +      }
   1.452 +    }
   1.453 +    // Just write, without any renaming trick
   1.454 +    let dest = OS.File.open(path, {write: true, truncate: true});
   1.455 +    try {
   1.456 +      bytesWritten = dest.write(buffer, options);
   1.457 +      if (options.flush) {
   1.458 +        dest.flush();
   1.459 +      }
   1.460 +    } finally {
   1.461 +      dest.close();
   1.462 +    }
   1.463 +    return bytesWritten;
   1.464 +  }
   1.465 +
   1.466 +  let tmpFile = OS.File.open(options.tmpPath, {write: true, truncate: true});
   1.467 +  try {
   1.468 +    bytesWritten = tmpFile.write(buffer, options);
   1.469 +    if (options.flush) {
   1.470 +      tmpFile.flush();
   1.471 +    }
   1.472 +  } catch (x) {
   1.473 +    OS.File.remove(options.tmpPath);
   1.474 +    throw x;
   1.475 +  } finally {
   1.476 +    tmpFile.close();
   1.477 +  }
   1.478 +
   1.479 +  if (options.backupTo) {
   1.480 +    try {
   1.481 +      OS.File.move(path, options.backupTo, {noCopy: true});
   1.482 +    } catch (ex if ex.becauseNoSuchFile) {
   1.483 +      // The file doesn't exist, nothing to backup.
   1.484 +    }
   1.485 +  }
   1.486 +
   1.487 +  OS.File.move(options.tmpPath, path, {noCopy: true});
   1.488 +  return bytesWritten;
   1.489 +};
   1.490 +
   1.491 +/**
   1.492 + * This function is used by removeDir to avoid calling lstat for each
   1.493 + * files under the specified directory. External callers should not call
   1.494 + * this function directly.
   1.495 + */
   1.496 +AbstractFile.removeRecursive = function(path, options = {}) {
   1.497 +  let iterator = new OS.File.DirectoryIterator(path);
   1.498 +  if (!iterator.exists()) {
   1.499 +    if (!("ignoreAbsent" in options) || options.ignoreAbsent) {
   1.500 +      return;
   1.501 +    }
   1.502 +  }
   1.503 +
   1.504 +  try {
   1.505 +    for (let entry in iterator) {
   1.506 +      if (entry.isDir) {
   1.507 +        if (entry.isLink) {
   1.508 +          // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
   1.509 +          // directories are directories themselves. OS.File.remove()
   1.510 +          // will not work for them.
   1.511 +          OS.File.removeEmptyDir(entry.path, options);
   1.512 +        } else {
   1.513 +          // Normal directories.
   1.514 +          AbstractFile.removeRecursive(entry.path, options);
   1.515 +        }
   1.516 +      } else {
   1.517 +        // NTFS symlinks to files, Unix symlinks, or regular files.
   1.518 +        OS.File.remove(entry.path, options);
   1.519 +      }
   1.520 +    }
   1.521 +  } finally {
   1.522 +    iterator.close();
   1.523 +  }
   1.524 +
   1.525 +  OS.File.removeEmptyDir(path);
   1.526 +};
   1.527 +
   1.528 +/**
   1.529 + * Create a directory and, optionally, its parent directories.
   1.530 + *
   1.531 + * @param {string} path The name of the directory.
   1.532 + * @param {*=} options Additional options.
   1.533 + *
   1.534 + * - {string} from If specified, the call to |makeDir| creates all the
   1.535 + * ancestors of |path| that are descendants of |from|. Note that |path|
   1.536 + * must be a descendant of |from|, and that |from| and its existing
   1.537 + * subdirectories present in |path|  must be user-writeable.
   1.538 + * Example:
   1.539 + *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
   1.540 + *  creates directories profileDir/foo, profileDir/foo/bar
   1.541 + * - {bool} ignoreExisting If |false|, throw an error if the directory
   1.542 + * already exists. |true| by default. Ignored if |from| is specified.
   1.543 + * - {number} unixMode Under Unix, if specified, a file creation mode,
   1.544 + * as per libc function |mkdir|. If unspecified, dirs are
   1.545 + * created with a default mode of 0700 (dir is private to
   1.546 + * the user, the user can read, write and execute). Ignored under Windows
   1.547 + * or if the file system does not support file creation modes.
   1.548 + * - {C pointer} winSecurity Under Windows, if specified, security
   1.549 + * attributes as per winapi function |CreateDirectory|. If
   1.550 + * unspecified, use the default security descriptor, inherited from
   1.551 + * the parent directory. Ignored under Unix or if the file system
   1.552 + * does not support security descriptors.
   1.553 + */
   1.554 +AbstractFile.makeDir = function(path, options = {}) {
   1.555 +  if (!options.from) {
   1.556 +    return OS.File._makeDir(path, options);
   1.557 +  }
   1.558 +  if (!path.startsWith(options.from)) {
   1.559 +    throw new Error("Incorrect use of option |from|: " + path + " is not a descendant of " + options.from);
   1.560 +  }
   1.561 +  let innerOptions = Object.create(options, {
   1.562 +    ignoreExisting: {
   1.563 +      value: true
   1.564 +    }
   1.565 +  });
   1.566 +  // Compute the elements that appear in |path| but not in |options.from|.
   1.567 +  let items = Path.split(path).components.slice(Path.split(options.from).components.length);
   1.568 +  let current = options.from;
   1.569 +  for (let item of items) {
   1.570 +    current = Path.join(current, item);
   1.571 +    OS.File._makeDir(current, innerOptions);
   1.572 +  }
   1.573 +};
   1.574 +
   1.575 +if (!exports.OS.Shared) {
   1.576 +  exports.OS.Shared = {};
   1.577 +}
   1.578 +exports.OS.Shared.AbstractFile = AbstractFile;
   1.579 +})(this);

mercurial