Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | /** |
michael@0 | 6 | * Code shared by OS.File front-ends. |
michael@0 | 7 | * |
michael@0 | 8 | * This code is meant to be included by another library. It is also meant to |
michael@0 | 9 | * be executed only on a worker thread. |
michael@0 | 10 | */ |
michael@0 | 11 | |
michael@0 | 12 | if (typeof Components != "undefined") { |
michael@0 | 13 | throw new Error("osfile_shared_front.jsm cannot be used from the main thread"); |
michael@0 | 14 | } |
michael@0 | 15 | (function(exports) { |
michael@0 | 16 | |
michael@0 | 17 | let SharedAll = |
michael@0 | 18 | require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); |
michael@0 | 19 | let Path = require("resource://gre/modules/osfile/ospath.jsm"); |
michael@0 | 20 | let Lz4 = |
michael@0 | 21 | require("resource://gre/modules/workers/lz4.js"); |
michael@0 | 22 | let LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end"); |
michael@0 | 23 | let clone = SharedAll.clone; |
michael@0 | 24 | |
michael@0 | 25 | /** |
michael@0 | 26 | * Code shared by implementations of File. |
michael@0 | 27 | * |
michael@0 | 28 | * @param {*} fd An OS-specific file handle. |
michael@0 | 29 | * @param {string} path File path of the file handle, used for error-reporting. |
michael@0 | 30 | * @constructor |
michael@0 | 31 | */ |
michael@0 | 32 | let AbstractFile = function AbstractFile(fd, path) { |
michael@0 | 33 | this._fd = fd; |
michael@0 | 34 | if (!path) { |
michael@0 | 35 | throw new TypeError("path is expected"); |
michael@0 | 36 | } |
michael@0 | 37 | this._path = path; |
michael@0 | 38 | }; |
michael@0 | 39 | |
michael@0 | 40 | AbstractFile.prototype = { |
michael@0 | 41 | /** |
michael@0 | 42 | * Return the file handle. |
michael@0 | 43 | * |
michael@0 | 44 | * @throw OS.File.Error if the file has been closed. |
michael@0 | 45 | */ |
michael@0 | 46 | get fd() { |
michael@0 | 47 | if (this._fd) { |
michael@0 | 48 | return this._fd; |
michael@0 | 49 | } |
michael@0 | 50 | throw OS.File.Error.closed("accessing file", this._path); |
michael@0 | 51 | }, |
michael@0 | 52 | /** |
michael@0 | 53 | * Read bytes from this file to a new buffer. |
michael@0 | 54 | * |
michael@0 | 55 | * @param {number=} bytes If unspecified, read all the remaining bytes from |
michael@0 | 56 | * this file. If specified, read |bytes| bytes, or less if the file does notclone |
michael@0 | 57 | * contain that many bytes. |
michael@0 | 58 | * @param {JSON} options |
michael@0 | 59 | * @return {Uint8Array} An array containing the bytes read. |
michael@0 | 60 | */ |
michael@0 | 61 | read: function read(bytes, options = {}) { |
michael@0 | 62 | options = clone(options); |
michael@0 | 63 | options.bytes = bytes == null ? this.stat().size : bytes; |
michael@0 | 64 | let buffer = new Uint8Array(options.bytes); |
michael@0 | 65 | let size = this.readTo(buffer, options); |
michael@0 | 66 | if (size == options.bytes) { |
michael@0 | 67 | return buffer; |
michael@0 | 68 | } else { |
michael@0 | 69 | return buffer.subarray(0, size); |
michael@0 | 70 | } |
michael@0 | 71 | }, |
michael@0 | 72 | |
michael@0 | 73 | /** |
michael@0 | 74 | * Read bytes from this file to an existing buffer. |
michael@0 | 75 | * |
michael@0 | 76 | * Note that, by default, this function may perform several I/O |
michael@0 | 77 | * operations to ensure that the buffer is as full as possible. |
michael@0 | 78 | * |
michael@0 | 79 | * @param {Typed Array | C pointer} buffer The buffer in which to |
michael@0 | 80 | * store the bytes. The buffer must be large enough to |
michael@0 | 81 | * accomodate |bytes| bytes. |
michael@0 | 82 | * @param {*=} options Optionally, an object that may contain the |
michael@0 | 83 | * following fields: |
michael@0 | 84 | * - {number} bytes The number of |bytes| to write from the buffer. If |
michael@0 | 85 | * unspecified, this is |buffer.byteLength|. Note that |bytes| is required |
michael@0 | 86 | * if |buffer| is a C pointer. |
michael@0 | 87 | * |
michael@0 | 88 | * @return {number} The number of bytes actually read, which may be |
michael@0 | 89 | * less than |bytes| if the file did not contain that many bytes left. |
michael@0 | 90 | */ |
michael@0 | 91 | readTo: function readTo(buffer, options = {}) { |
michael@0 | 92 | let {ptr, bytes} = SharedAll.normalizeToPointer(buffer, options.bytes); |
michael@0 | 93 | let pos = 0; |
michael@0 | 94 | while (pos < bytes) { |
michael@0 | 95 | let chunkSize = this._read(ptr, bytes - pos, options); |
michael@0 | 96 | if (chunkSize == 0) { |
michael@0 | 97 | break; |
michael@0 | 98 | } |
michael@0 | 99 | pos += chunkSize; |
michael@0 | 100 | ptr = SharedAll.offsetBy(ptr, chunkSize); |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | return pos; |
michael@0 | 104 | }, |
michael@0 | 105 | |
michael@0 | 106 | /** |
michael@0 | 107 | * Write bytes from a buffer to this file. |
michael@0 | 108 | * |
michael@0 | 109 | * Note that, by default, this function may perform several I/O |
michael@0 | 110 | * operations to ensure that the buffer is fully written. |
michael@0 | 111 | * |
michael@0 | 112 | * @param {Typed array | C pointer} buffer The buffer in which the |
michael@0 | 113 | * the bytes are stored. The buffer must be large enough to |
michael@0 | 114 | * accomodate |bytes| bytes. |
michael@0 | 115 | * @param {*=} options Optionally, an object that may contain the |
michael@0 | 116 | * following fields: |
michael@0 | 117 | * - {number} bytes The number of |bytes| to write from the buffer. If |
michael@0 | 118 | * unspecified, this is |buffer.byteLength|. Note that |bytes| is required |
michael@0 | 119 | * if |buffer| is a C pointer. |
michael@0 | 120 | * |
michael@0 | 121 | * @return {number} The number of bytes actually written. |
michael@0 | 122 | */ |
michael@0 | 123 | write: function write(buffer, options = {}) { |
michael@0 | 124 | |
michael@0 | 125 | let {ptr, bytes} = |
michael@0 | 126 | SharedAll.normalizeToPointer(buffer, options.bytes || undefined); |
michael@0 | 127 | |
michael@0 | 128 | let pos = 0; |
michael@0 | 129 | while (pos < bytes) { |
michael@0 | 130 | let chunkSize = this._write(ptr, bytes - pos, options); |
michael@0 | 131 | pos += chunkSize; |
michael@0 | 132 | ptr = SharedAll.offsetBy(ptr, chunkSize); |
michael@0 | 133 | } |
michael@0 | 134 | return pos; |
michael@0 | 135 | } |
michael@0 | 136 | }; |
michael@0 | 137 | |
michael@0 | 138 | /** |
michael@0 | 139 | * 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. |
michael@0 | 140 | * |
michael@0 | 141 | * @param {string} path The path to the file. |
michael@0 | 142 | * @param {*=} options Additional options for file opening. This |
michael@0 | 143 | * implementation interprets the following fields: |
michael@0 | 144 | * |
michael@0 | 145 | * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext. |
michael@0 | 146 | * If |false| use HEX numbers ie: filename-A65BC0.ext |
michael@0 | 147 | * - {number} maxReadableNumber Used to limit the amount of tries after a failed |
michael@0 | 148 | * file creation. Default is 20. |
michael@0 | 149 | * |
michael@0 | 150 | * @return {Object} contains A file object{file} and the path{path}. |
michael@0 | 151 | * @throws {OS.File.Error} If the file could not be opened. |
michael@0 | 152 | */ |
michael@0 | 153 | AbstractFile.openUnique = function openUnique(path, options = {}) { |
michael@0 | 154 | let mode = { |
michael@0 | 155 | create : true |
michael@0 | 156 | }; |
michael@0 | 157 | |
michael@0 | 158 | let dirName = Path.dirname(path); |
michael@0 | 159 | let leafName = Path.basename(path); |
michael@0 | 160 | let lastDotCharacter = leafName.lastIndexOf('.'); |
michael@0 | 161 | let fileName = leafName.substring(0, lastDotCharacter != -1 ? lastDotCharacter : leafName.length); |
michael@0 | 162 | let suffix = (lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : ""); |
michael@0 | 163 | let uniquePath = ""; |
michael@0 | 164 | let maxAttempts = options.maxAttempts || 99; |
michael@0 | 165 | let humanReadable = !!options.humanReadable; |
michael@0 | 166 | const HEX_RADIX = 16; |
michael@0 | 167 | // We produce HEX numbers between 0 and 2^24 - 1. |
michael@0 | 168 | const MAX_HEX_NUMBER = 16777215; |
michael@0 | 169 | |
michael@0 | 170 | try { |
michael@0 | 171 | return { |
michael@0 | 172 | path: path, |
michael@0 | 173 | file: OS.File.open(path, mode) |
michael@0 | 174 | }; |
michael@0 | 175 | } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { |
michael@0 | 176 | for (let i = 0; i < maxAttempts; ++i) { |
michael@0 | 177 | try { |
michael@0 | 178 | if (humanReadable) { |
michael@0 | 179 | uniquePath = Path.join(dirName, fileName + "-" + (i + 1) + suffix); |
michael@0 | 180 | } else { |
michael@0 | 181 | let hexNumber = Math.floor(Math.random() * MAX_HEX_NUMBER).toString(HEX_RADIX); |
michael@0 | 182 | uniquePath = Path.join(dirName, fileName + "-" + hexNumber + suffix); |
michael@0 | 183 | } |
michael@0 | 184 | return { |
michael@0 | 185 | path: uniquePath, |
michael@0 | 186 | file: OS.File.open(uniquePath, mode) |
michael@0 | 187 | }; |
michael@0 | 188 | } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { |
michael@0 | 189 | // keep trying ... |
michael@0 | 190 | } |
michael@0 | 191 | } |
michael@0 | 192 | throw OS.File.Error.exists("could not find an unused file name.", path); |
michael@0 | 193 | } |
michael@0 | 194 | }; |
michael@0 | 195 | |
michael@0 | 196 | /** |
michael@0 | 197 | * Code shared by iterators. |
michael@0 | 198 | */ |
michael@0 | 199 | AbstractFile.AbstractIterator = function AbstractIterator() { |
michael@0 | 200 | }; |
michael@0 | 201 | AbstractFile.AbstractIterator.prototype = { |
michael@0 | 202 | /** |
michael@0 | 203 | * Allow iterating with |for| |
michael@0 | 204 | */ |
michael@0 | 205 | __iterator__: function __iterator__() { |
michael@0 | 206 | return this; |
michael@0 | 207 | }, |
michael@0 | 208 | /** |
michael@0 | 209 | * Apply a function to all elements of the directory sequentially. |
michael@0 | 210 | * |
michael@0 | 211 | * @param {Function} cb This function will be applied to all entries |
michael@0 | 212 | * of the directory. It receives as arguments |
michael@0 | 213 | * - the OS.File.Entry corresponding to the entry; |
michael@0 | 214 | * - the index of the entry in the enumeration; |
michael@0 | 215 | * - the iterator itself - calling |close| on the iterator stops |
michael@0 | 216 | * the loop. |
michael@0 | 217 | */ |
michael@0 | 218 | forEach: function forEach(cb) { |
michael@0 | 219 | let index = 0; |
michael@0 | 220 | for (let entry in this) { |
michael@0 | 221 | cb(entry, index++, this); |
michael@0 | 222 | } |
michael@0 | 223 | }, |
michael@0 | 224 | /** |
michael@0 | 225 | * Return several entries at once. |
michael@0 | 226 | * |
michael@0 | 227 | * Entries are returned in the same order as a walk with |forEach| or |
michael@0 | 228 | * |for(...)|. |
michael@0 | 229 | * |
michael@0 | 230 | * @param {number=} length If specified, the number of entries |
michael@0 | 231 | * to return. If unspecified, return all remaining entries. |
michael@0 | 232 | * @return {Array} An array containing the next |length| entries, or |
michael@0 | 233 | * less if the iteration contains less than |length| entries left. |
michael@0 | 234 | */ |
michael@0 | 235 | nextBatch: function nextBatch(length) { |
michael@0 | 236 | let array = []; |
michael@0 | 237 | let i = 0; |
michael@0 | 238 | for (let entry in this) { |
michael@0 | 239 | array.push(entry); |
michael@0 | 240 | if (++i >= length) { |
michael@0 | 241 | return array; |
michael@0 | 242 | } |
michael@0 | 243 | } |
michael@0 | 244 | return array; |
michael@0 | 245 | } |
michael@0 | 246 | }; |
michael@0 | 247 | |
michael@0 | 248 | /** |
michael@0 | 249 | * Utility function shared by implementations of |OS.File.open|: |
michael@0 | 250 | * extract read/write/trunc/create/existing flags from a |mode| |
michael@0 | 251 | * object. |
michael@0 | 252 | * |
michael@0 | 253 | * @param {*=} mode An object that may contain fields |read|, |
michael@0 | 254 | * |write|, |truncate|, |create|, |existing|. These fields |
michael@0 | 255 | * are interpreted only if true-ish. |
michael@0 | 256 | * @return {{read:bool, write:bool, trunc:bool, create:bool, |
michael@0 | 257 | * existing:bool}} an object recapitulating the options set |
michael@0 | 258 | * by |mode|. |
michael@0 | 259 | * @throws {TypeError} If |mode| contains other fields, or |
michael@0 | 260 | * if it contains both |create| and |truncate|, or |create| |
michael@0 | 261 | * and |existing|. |
michael@0 | 262 | */ |
michael@0 | 263 | AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) { |
michael@0 | 264 | let result = { |
michael@0 | 265 | read: false, |
michael@0 | 266 | write: false, |
michael@0 | 267 | trunc: false, |
michael@0 | 268 | create: false, |
michael@0 | 269 | existing: false, |
michael@0 | 270 | append: true |
michael@0 | 271 | }; |
michael@0 | 272 | for (let key in mode) { |
michael@0 | 273 | let val = !!mode[key]; // bool cast. |
michael@0 | 274 | switch (key) { |
michael@0 | 275 | case "read": |
michael@0 | 276 | result.read = val; |
michael@0 | 277 | break; |
michael@0 | 278 | case "write": |
michael@0 | 279 | result.write = val; |
michael@0 | 280 | break; |
michael@0 | 281 | case "truncate": // fallthrough |
michael@0 | 282 | case "trunc": |
michael@0 | 283 | result.trunc = val; |
michael@0 | 284 | result.write |= val; |
michael@0 | 285 | break; |
michael@0 | 286 | case "create": |
michael@0 | 287 | result.create = val; |
michael@0 | 288 | result.write |= val; |
michael@0 | 289 | break; |
michael@0 | 290 | case "existing": // fallthrough |
michael@0 | 291 | case "exist": |
michael@0 | 292 | result.existing = val; |
michael@0 | 293 | break; |
michael@0 | 294 | case "append": |
michael@0 | 295 | result.append = val; |
michael@0 | 296 | break; |
michael@0 | 297 | default: |
michael@0 | 298 | throw new TypeError("Mode " + key + " not understood"); |
michael@0 | 299 | } |
michael@0 | 300 | } |
michael@0 | 301 | // Reject opposite modes |
michael@0 | 302 | if (result.existing && result.create) { |
michael@0 | 303 | throw new TypeError("Cannot specify both existing:true and create:true"); |
michael@0 | 304 | } |
michael@0 | 305 | if (result.trunc && result.create) { |
michael@0 | 306 | throw new TypeError("Cannot specify both trunc:true and create:true"); |
michael@0 | 307 | } |
michael@0 | 308 | // Handle read/write |
michael@0 | 309 | if (!result.write) { |
michael@0 | 310 | result.read = true; |
michael@0 | 311 | } |
michael@0 | 312 | return result; |
michael@0 | 313 | }; |
michael@0 | 314 | |
michael@0 | 315 | /** |
michael@0 | 316 | * Return the contents of a file. |
michael@0 | 317 | * |
michael@0 | 318 | * @param {string} path The path to the file. |
michael@0 | 319 | * @param {number=} bytes Optionally, an upper bound to the number of bytes |
michael@0 | 320 | * to read. DEPRECATED - please use options.bytes instead. |
michael@0 | 321 | * @param {object=} options Optionally, an object with some of the following |
michael@0 | 322 | * fields: |
michael@0 | 323 | * - {number} bytes An upper bound to the number of bytes to read. |
michael@0 | 324 | * - {string} compression If "lz4" and if the file is compressed using the lz4 |
michael@0 | 325 | * compression algorithm, decompress the file contents on the fly. |
michael@0 | 326 | * |
michael@0 | 327 | * @return {Uint8Array} A buffer holding the bytes |
michael@0 | 328 | * and the number of bytes read from the file. |
michael@0 | 329 | */ |
michael@0 | 330 | AbstractFile.read = function read(path, bytes, options = {}) { |
michael@0 | 331 | if (bytes && typeof bytes == "object") { |
michael@0 | 332 | options = bytes; |
michael@0 | 333 | bytes = options.bytes || null; |
michael@0 | 334 | } |
michael@0 | 335 | if ("encoding" in options && typeof options.encoding != "string") { |
michael@0 | 336 | throw new TypeError("Invalid type for option encoding"); |
michael@0 | 337 | } |
michael@0 | 338 | if ("compression" in options && typeof options.compression != "string") { |
michael@0 | 339 | throw new TypeError("Invalid type for option compression: " + options.compression); |
michael@0 | 340 | } |
michael@0 | 341 | if ("bytes" in options && typeof options.bytes != "number") { |
michael@0 | 342 | throw new TypeError("Invalid type for option bytes"); |
michael@0 | 343 | } |
michael@0 | 344 | let file = exports.OS.File.open(path); |
michael@0 | 345 | try { |
michael@0 | 346 | let buffer = file.read(bytes, options); |
michael@0 | 347 | if ("compression" in options) { |
michael@0 | 348 | if (options.compression == "lz4") { |
michael@0 | 349 | buffer = Lz4.decompressFileContent(buffer, options); |
michael@0 | 350 | } else { |
michael@0 | 351 | throw OS.File.Error.invalidArgument("Compression"); |
michael@0 | 352 | } |
michael@0 | 353 | } |
michael@0 | 354 | if (!("encoding" in options)) { |
michael@0 | 355 | return buffer; |
michael@0 | 356 | } |
michael@0 | 357 | let decoder; |
michael@0 | 358 | try { |
michael@0 | 359 | decoder = new TextDecoder(options.encoding); |
michael@0 | 360 | } catch (ex if ex instanceof TypeError) { |
michael@0 | 361 | throw OS.File.Error.invalidArgument("Decode"); |
michael@0 | 362 | } |
michael@0 | 363 | return decoder.decode(buffer); |
michael@0 | 364 | } finally { |
michael@0 | 365 | file.close(); |
michael@0 | 366 | } |
michael@0 | 367 | }; |
michael@0 | 368 | |
michael@0 | 369 | /** |
michael@0 | 370 | * Write a file, atomically. |
michael@0 | 371 | * |
michael@0 | 372 | * By opposition to a regular |write|, this operation ensures that, |
michael@0 | 373 | * until the contents are fully written, the destination file is |
michael@0 | 374 | * not modified. |
michael@0 | 375 | * |
michael@0 | 376 | * Limitation: In a few extreme cases (hardware failure during the |
michael@0 | 377 | * write, user unplugging disk during the write, etc.), data may be |
michael@0 | 378 | * corrupted. If your data is user-critical (e.g. preferences, |
michael@0 | 379 | * application data, etc.), you may wish to consider adding options |
michael@0 | 380 | * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as |
michael@0 | 381 | * detailed below. Note that no combination of options can be |
michael@0 | 382 | * guaranteed to totally eliminate the risk of corruption. |
michael@0 | 383 | * |
michael@0 | 384 | * @param {string} path The path of the file to modify. |
michael@0 | 385 | * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write. |
michael@0 | 386 | * @param {*=} options Optionally, an object determining the behavior |
michael@0 | 387 | * of this function. This object may contain the following fields: |
michael@0 | 388 | * - {number} bytes The number of bytes to write. If unspecified, |
michael@0 | 389 | * |buffer.byteLength|. Required if |buffer| is a C pointer. |
michael@0 | 390 | * - {string} tmpPath If |null| or unspecified, write all data directly |
michael@0 | 391 | * to |path|. If specified, write all data to a temporary file called |
michael@0 | 392 | * |tmpPath| and, once this write is complete, rename the file to |
michael@0 | 393 | * replace |path|. Performing this additional operation is a little |
michael@0 | 394 | * slower but also a little safer. |
michael@0 | 395 | * - {bool} noOverwrite - If set, this function will fail if a file already |
michael@0 | 396 | * exists at |path|. |
michael@0 | 397 | * - {bool} flush - If |false| or unspecified, return immediately once the |
michael@0 | 398 | * write is complete. If |true|, before writing, force the operating system |
michael@0 | 399 | * to write its internal disk buffers to the disk. This is considerably slower |
michael@0 | 400 | * (not just for the application but for the whole system) but also safer: |
michael@0 | 401 | * if the system shuts down improperly (typically due to a kernel freeze |
michael@0 | 402 | * or a power failure) or if the device is disconnected before the buffer |
michael@0 | 403 | * is flushed, the file has more chances of not being corrupted. |
michael@0 | 404 | * - {string} compression - If empty or unspecified, do not compress the file. |
michael@0 | 405 | * If "lz4", compress the contents of the file atomically using lz4. For the |
michael@0 | 406 | * time being, the container format is specific to Mozilla and cannot be read |
michael@0 | 407 | * by means other than OS.File.read(..., { compression: "lz4"}) |
michael@0 | 408 | * - {string} backupTo - If specified, backup the destination file as |backupTo|. |
michael@0 | 409 | * Note that this function renames the destination file before overwriting it. |
michael@0 | 410 | * If the process or the operating system freezes or crashes |
michael@0 | 411 | * during the short window between these operations, |
michael@0 | 412 | * the destination file will have been moved to its backup. |
michael@0 | 413 | * |
michael@0 | 414 | * @return {number} The number of bytes actually written. |
michael@0 | 415 | */ |
michael@0 | 416 | AbstractFile.writeAtomic = |
michael@0 | 417 | function writeAtomic(path, buffer, options = {}) { |
michael@0 | 418 | |
michael@0 | 419 | // Verify that path is defined and of the correct type |
michael@0 | 420 | if (typeof path != "string" || path == "") { |
michael@0 | 421 | throw new TypeError("File path should be a (non-empty) string"); |
michael@0 | 422 | } |
michael@0 | 423 | let noOverwrite = options.noOverwrite; |
michael@0 | 424 | if (noOverwrite && OS.File.exists(path)) { |
michael@0 | 425 | throw OS.File.Error.exists("writeAtomic", path); |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | if (typeof buffer == "string") { |
michael@0 | 429 | // Normalize buffer to a C buffer by encoding it |
michael@0 | 430 | let encoding = options.encoding || "utf-8"; |
michael@0 | 431 | buffer = new TextEncoder(encoding).encode(buffer); |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | if ("compression" in options && options.compression == "lz4") { |
michael@0 | 435 | buffer = Lz4.compressFileContent(buffer, options); |
michael@0 | 436 | options = Object.create(options); |
michael@0 | 437 | options.bytes = buffer.byteLength; |
michael@0 | 438 | } |
michael@0 | 439 | |
michael@0 | 440 | let bytesWritten = 0; |
michael@0 | 441 | |
michael@0 | 442 | if (!options.tmpPath) { |
michael@0 | 443 | if (options.backupTo) { |
michael@0 | 444 | try { |
michael@0 | 445 | OS.File.move(path, options.backupTo, {noCopy: true}); |
michael@0 | 446 | } catch (ex if ex.becauseNoSuchFile) { |
michael@0 | 447 | // The file doesn't exist, nothing to backup. |
michael@0 | 448 | } |
michael@0 | 449 | } |
michael@0 | 450 | // Just write, without any renaming trick |
michael@0 | 451 | let dest = OS.File.open(path, {write: true, truncate: true}); |
michael@0 | 452 | try { |
michael@0 | 453 | bytesWritten = dest.write(buffer, options); |
michael@0 | 454 | if (options.flush) { |
michael@0 | 455 | dest.flush(); |
michael@0 | 456 | } |
michael@0 | 457 | } finally { |
michael@0 | 458 | dest.close(); |
michael@0 | 459 | } |
michael@0 | 460 | return bytesWritten; |
michael@0 | 461 | } |
michael@0 | 462 | |
michael@0 | 463 | let tmpFile = OS.File.open(options.tmpPath, {write: true, truncate: true}); |
michael@0 | 464 | try { |
michael@0 | 465 | bytesWritten = tmpFile.write(buffer, options); |
michael@0 | 466 | if (options.flush) { |
michael@0 | 467 | tmpFile.flush(); |
michael@0 | 468 | } |
michael@0 | 469 | } catch (x) { |
michael@0 | 470 | OS.File.remove(options.tmpPath); |
michael@0 | 471 | throw x; |
michael@0 | 472 | } finally { |
michael@0 | 473 | tmpFile.close(); |
michael@0 | 474 | } |
michael@0 | 475 | |
michael@0 | 476 | if (options.backupTo) { |
michael@0 | 477 | try { |
michael@0 | 478 | OS.File.move(path, options.backupTo, {noCopy: true}); |
michael@0 | 479 | } catch (ex if ex.becauseNoSuchFile) { |
michael@0 | 480 | // The file doesn't exist, nothing to backup. |
michael@0 | 481 | } |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | OS.File.move(options.tmpPath, path, {noCopy: true}); |
michael@0 | 485 | return bytesWritten; |
michael@0 | 486 | }; |
michael@0 | 487 | |
michael@0 | 488 | /** |
michael@0 | 489 | * This function is used by removeDir to avoid calling lstat for each |
michael@0 | 490 | * files under the specified directory. External callers should not call |
michael@0 | 491 | * this function directly. |
michael@0 | 492 | */ |
michael@0 | 493 | AbstractFile.removeRecursive = function(path, options = {}) { |
michael@0 | 494 | let iterator = new OS.File.DirectoryIterator(path); |
michael@0 | 495 | if (!iterator.exists()) { |
michael@0 | 496 | if (!("ignoreAbsent" in options) || options.ignoreAbsent) { |
michael@0 | 497 | return; |
michael@0 | 498 | } |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | try { |
michael@0 | 502 | for (let entry in iterator) { |
michael@0 | 503 | if (entry.isDir) { |
michael@0 | 504 | if (entry.isLink) { |
michael@0 | 505 | // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to |
michael@0 | 506 | // directories are directories themselves. OS.File.remove() |
michael@0 | 507 | // will not work for them. |
michael@0 | 508 | OS.File.removeEmptyDir(entry.path, options); |
michael@0 | 509 | } else { |
michael@0 | 510 | // Normal directories. |
michael@0 | 511 | AbstractFile.removeRecursive(entry.path, options); |
michael@0 | 512 | } |
michael@0 | 513 | } else { |
michael@0 | 514 | // NTFS symlinks to files, Unix symlinks, or regular files. |
michael@0 | 515 | OS.File.remove(entry.path, options); |
michael@0 | 516 | } |
michael@0 | 517 | } |
michael@0 | 518 | } finally { |
michael@0 | 519 | iterator.close(); |
michael@0 | 520 | } |
michael@0 | 521 | |
michael@0 | 522 | OS.File.removeEmptyDir(path); |
michael@0 | 523 | }; |
michael@0 | 524 | |
michael@0 | 525 | /** |
michael@0 | 526 | * Create a directory and, optionally, its parent directories. |
michael@0 | 527 | * |
michael@0 | 528 | * @param {string} path The name of the directory. |
michael@0 | 529 | * @param {*=} options Additional options. |
michael@0 | 530 | * |
michael@0 | 531 | * - {string} from If specified, the call to |makeDir| creates all the |
michael@0 | 532 | * ancestors of |path| that are descendants of |from|. Note that |path| |
michael@0 | 533 | * must be a descendant of |from|, and that |from| and its existing |
michael@0 | 534 | * subdirectories present in |path| must be user-writeable. |
michael@0 | 535 | * Example: |
michael@0 | 536 | * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir }); |
michael@0 | 537 | * creates directories profileDir/foo, profileDir/foo/bar |
michael@0 | 538 | * - {bool} ignoreExisting If |false|, throw an error if the directory |
michael@0 | 539 | * already exists. |true| by default. Ignored if |from| is specified. |
michael@0 | 540 | * - {number} unixMode Under Unix, if specified, a file creation mode, |
michael@0 | 541 | * as per libc function |mkdir|. If unspecified, dirs are |
michael@0 | 542 | * created with a default mode of 0700 (dir is private to |
michael@0 | 543 | * the user, the user can read, write and execute). Ignored under Windows |
michael@0 | 544 | * or if the file system does not support file creation modes. |
michael@0 | 545 | * - {C pointer} winSecurity Under Windows, if specified, security |
michael@0 | 546 | * attributes as per winapi function |CreateDirectory|. If |
michael@0 | 547 | * unspecified, use the default security descriptor, inherited from |
michael@0 | 548 | * the parent directory. Ignored under Unix or if the file system |
michael@0 | 549 | * does not support security descriptors. |
michael@0 | 550 | */ |
michael@0 | 551 | AbstractFile.makeDir = function(path, options = {}) { |
michael@0 | 552 | if (!options.from) { |
michael@0 | 553 | return OS.File._makeDir(path, options); |
michael@0 | 554 | } |
michael@0 | 555 | if (!path.startsWith(options.from)) { |
michael@0 | 556 | throw new Error("Incorrect use of option |from|: " + path + " is not a descendant of " + options.from); |
michael@0 | 557 | } |
michael@0 | 558 | let innerOptions = Object.create(options, { |
michael@0 | 559 | ignoreExisting: { |
michael@0 | 560 | value: true |
michael@0 | 561 | } |
michael@0 | 562 | }); |
michael@0 | 563 | // Compute the elements that appear in |path| but not in |options.from|. |
michael@0 | 564 | let items = Path.split(path).components.slice(Path.split(options.from).components.length); |
michael@0 | 565 | let current = options.from; |
michael@0 | 566 | for (let item of items) { |
michael@0 | 567 | current = Path.join(current, item); |
michael@0 | 568 | OS.File._makeDir(current, innerOptions); |
michael@0 | 569 | } |
michael@0 | 570 | }; |
michael@0 | 571 | |
michael@0 | 572 | if (!exports.OS.Shared) { |
michael@0 | 573 | exports.OS.Shared = {}; |
michael@0 | 574 | } |
michael@0 | 575 | exports.OS.Shared.AbstractFile = AbstractFile; |
michael@0 | 576 | })(this); |