gecko-dev/toolkit/components/osfile/modules/osfile_async_worker.js

408 строки
13 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
if (this.Components) {
throw new Error("This worker can only be loaded from a worker thread");
}
// Worker thread for osfile asynchronous front-end
(function(exports) {
"use strict";
// Timestamps, for use in Telemetry.
// The object is set to |null| once it has been sent
// to the main thread.
let timeStamps = {
entered: Date.now(),
loaded: null
};
importScripts("resource://gre/modules/osfile.jsm");
let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
let LOG = SharedAll.LOG.bind(SharedAll, "Agent");
let worker = new PromiseWorker.AbstractWorker();
worker.dispatch = function(method, args = []) {
return Agent[method](...args);
},
worker.log = LOG;
worker.postMessage = function(message, ...transfers) {
if (timeStamps) {
message.timeStamps = timeStamps;
timeStamps = null;
}
self.postMessage(message, ...transfers);
};
worker.close = function() {
self.close();
};
let Meta = PromiseWorker.Meta;
self.addEventListener("message", msg => worker.handleMessage(msg));
/**
* A data structure used to track opened resources
*/
let ResourceTracker = function ResourceTracker() {
// A number used to generate ids
this._idgen = 0;
// A map from id to resource
this._map = new Map();
};
ResourceTracker.prototype = {
/**
* Get a resource from its unique identifier.
*/
get: function(id) {
let result = this._map.get(id);
if (result == null) {
return result;
}
return result.resource;
},
/**
* Remove a resource from its unique identifier.
*/
remove: function(id) {
if (!this._map.has(id)) {
throw new Error("Cannot find resource id " + id);
}
this._map.delete(id);
},
/**
* Add a resource, return a new unique identifier
*
* @param {*} resource A resource.
* @param {*=} info Optional information. For debugging purposes.
*
* @return {*} A unique identifier. For the moment, this is a number,
* but this might not remain the case forever.
*/
add: function(resource, info) {
let id = this._idgen++;
this._map.set(id, {resource:resource, info:info});
return id;
},
/**
* Return a list of all open resources i.e. the ones still present in
* ResourceTracker's _map.
*/
listOpenedResources: function listOpenedResources() {
return Array.from(this._map, ([id, resource]) => resource.info.path);
}
};
/**
* A map of unique identifiers to opened files.
*/
let OpenedFiles = new ResourceTracker();
/**
* Execute a function in the context of a given file.
*
* @param {*} id A unique identifier, as used by |OpenFiles|.
* @param {Function} f A function to call.
* @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception.
* @return The return value of |f()|
*
* This function attempts to get the file matching |id|. If
* the file exists, it executes |f| within the |this| set
* to the corresponding file. Otherwise, it throws an error.
*/
let withFile = function withFile(id, f, ignoreAbsent) {
let file = OpenedFiles.get(id);
if (file == null) {
if (!ignoreAbsent) {
throw OS.File.Error.closed("accessing file");
}
return undefined;
}
return f.call(file);
};
let OpenedDirectoryIterators = new ResourceTracker();
let withDir = function withDir(fd, f, ignoreAbsent) {
let file = OpenedDirectoryIterators.get(fd);
if (file == null) {
if (!ignoreAbsent) {
throw OS.File.Error.closed("accessing directory");
}
return undefined;
}
if (!(file instanceof File.DirectoryIterator)) {
throw new Error("file is not a directory iterator " + file.__proto__.toSource());
}
return f.call(file);
};
let Type = exports.OS.Shared.Type;
let File = exports.OS.File;
/**
* The agent.
*
* It is in charge of performing method-specific deserialization
* of messages, calling the function/method of OS.File and serializing
* back the results.
*/
let Agent = {
// Update worker's OS.Shared.DEBUG flag message from controller.
SET_DEBUG: function(aDEBUG) {
SharedAll.Config.DEBUG = aDEBUG;
},
// Return worker's current OS.Shared.DEBUG value to controller.
// Note: This is used for testing purposes.
GET_DEBUG: function() {
return SharedAll.Config.DEBUG;
},
/**
* Execute shutdown sequence, returning data on leaked file descriptors.
*
* @param {bool} If |true|, kill the worker if this would not cause
* leaks.
*/
Meta_shutdown: function(kill) {
let result = {
openedFiles: OpenedFiles.listOpenedResources(),
openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
killed: false // Placeholder
};
// Is it safe to kill the worker?
let safe = result.openedFiles.length == 0
&& result.openedDirectoryIterators.length == 0;
result.killed = safe && kill;
return new Meta(result, {shutdown: result.killed});
},
// Functions of OS.File
stat: function stat(path, options) {
return exports.OS.File.Info.toMsg(
exports.OS.File.stat(Type.path.fromMsg(path), options));
},
setPermissions: function setPermissions(path, options = {}) {
return exports.OS.File.setPermissions(Type.path.fromMsg(path), options);
},
setDates: function setDates(path, accessDate, modificationDate) {
return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate,
modificationDate);
},
getCurrentDirectory: function getCurrentDirectory() {
return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
},
setCurrentDirectory: function setCurrentDirectory(path) {
File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path));
},
copy: function copy(sourcePath, destPath, options) {
return File.copy(Type.path.fromMsg(sourcePath),
Type.path.fromMsg(destPath), options);
},
move: function move(sourcePath, destPath, options) {
return File.move(Type.path.fromMsg(sourcePath),
Type.path.fromMsg(destPath), options);
},
getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) {
return Type.uint64_t.toMsg(
File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath)));
},
makeDir: function makeDir(path, options) {
return File.makeDir(Type.path.fromMsg(path), options);
},
removeEmptyDir: function removeEmptyDir(path, options) {
return File.removeEmptyDir(Type.path.fromMsg(path), options);
},
remove: function remove(path, options) {
return File.remove(Type.path.fromMsg(path), options);
},
open: function open(path, mode, options) {
let filePath = Type.path.fromMsg(path);
let file = File.open(filePath, mode, options);
return OpenedFiles.add(file, {
// Adding path information to keep track of opened files
// to report leaks when debugging.
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);
if (typeof data == "string") {
return data;
}
return new Meta({
buffer: data.buffer,
byteOffset: data.byteOffset,
byteLength: data.byteLength
}, {
transfers: [data.buffer]
});
},
exists: function exists(path) {
return File.exists(Type.path.fromMsg(path));
},
writeAtomic: function writeAtomic(path, buffer, options) {
if (options.tmpPath) {
options.tmpPath = Type.path.fromMsg(options.tmpPath);
}
return File.writeAtomic(Type.path.fromMsg(path),
Type.voidptr_t.fromMsg(buffer),
options
);
},
removeDir: function(path, options) {
return File.removeDir(Type.path.fromMsg(path), options);
},
new_DirectoryIterator: function new_DirectoryIterator(path, options) {
let directoryPath = Type.path.fromMsg(path);
let iterator = new File.DirectoryIterator(directoryPath, options);
return OpenedDirectoryIterators.add(iterator, {
// Adding path information to keep track of opened directory
// iterators to report leaks when debugging.
path: directoryPath
});
},
// Methods of OS.File
File_prototype_close: function close(fd) {
return withFile(fd,
function do_close() {
try {
return this.close();
} finally {
OpenedFiles.remove(fd);
}
});
},
File_prototype_stat: function stat(fd) {
return withFile(fd,
function do_stat() {
return exports.OS.File.Info.toMsg(this.stat());
});
},
File_prototype_setPermissions: function setPermissions(fd, options = {}) {
return withFile(fd,
function do_setPermissions() {
return this.setPermissions(options);
});
},
File_prototype_setDates: function setDates(fd, accessTime, modificationTime) {
return withFile(fd,
function do_setDates() {
return this.setDates(accessTime, modificationTime);
});
},
File_prototype_read: function read(fd, nbytes, options) {
return withFile(fd,
function do_read() {
let data = this.read(nbytes, options);
return new Meta({
buffer: data.buffer,
byteOffset: data.byteOffset,
byteLength: data.byteLength
}, {
transfers: [data.buffer]
});
}
);
},
File_prototype_readTo: function readTo(fd, buffer, options) {
return withFile(fd,
function do_readTo() {
return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
options);
});
},
File_prototype_write: function write(fd, buffer, options) {
return withFile(fd,
function do_write() {
return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
options);
});
},
File_prototype_setPosition: function setPosition(fd, pos, whence) {
return withFile(fd,
function do_setPosition() {
return this.setPosition(pos, whence);
});
},
File_prototype_getPosition: function getPosition(fd) {
return withFile(fd,
function do_getPosition() {
return this.getPosition();
});
},
File_prototype_flush: function flush(fd) {
return withFile(fd,
function do_flush() {
return this.flush();
});
},
// Methods of OS.File.DirectoryIterator
DirectoryIterator_prototype_next: function next(dir) {
return withDir(dir,
function do_next() {
try {
return File.DirectoryIterator.Entry.toMsg(this.next());
} catch (x) {
if (x == StopIteration) {
OpenedDirectoryIterators.remove(dir);
}
throw x;
}
}, false);
},
DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) {
return withDir(dir,
function do_nextBatch() {
let result;
try {
result = this.nextBatch(size);
} catch (x) {
OpenedDirectoryIterators.remove(dir);
throw x;
}
return result.map(File.DirectoryIterator.Entry.toMsg);
}, false);
},
DirectoryIterator_prototype_close: function close(dir) {
return withDir(dir,
function do_close() {
this.close();
OpenedDirectoryIterators.remove(dir);
}, true);// ignore error to support double-closing |DirectoryIterator|
},
DirectoryIterator_prototype_exists: function exists(dir) {
return withDir(dir,
function do_exists() {
return this.exists();
});
}
};
if (!SharedAll.Constants.Win) {
Agent.unixSymLink = function unixSymLink(sourcePath, destPath) {
return File.unixSymLink(Type.path.fromMsg(sourcePath),
Type.path.fromMsg(destPath));
};
}
timeStamps.loaded = Date.now();
})(this);