zotero/chrome/content/zotero/osfile.mjs
2024-05-21 01:56:38 -04:00

418 lines
No EOL
9.5 KiB
JavaScript

//
// Compatibility shims from the Mozilla codebase
//
const isWin = Services.appinfo.OS == 'WINNT';
export let OS = {
Constants: {
Path: {
get homeDir() {
return FileUtils.getDir("Home", []).path;
},
get libDir() {
return FileUtils.getDir("GreBinD", []).path;
},
get profileDir() {
return FileUtils.getDir("ProfD", []).path;
},
get tmpDir() {
return FileUtils.getDir("TmpD", []).path;
},
}
},
File: {
DirectoryIterator: function (path) {
var initialized = false;
var paths = [];
async function init() {
paths.push(...await IOUtils.getChildren(path));
initialized = true;
}
async function getEntry(path) {
var info = await IOUtils.stat(path);
return {
name: PathUtils.filename(path),
path,
isDir: info.type == 'directory'
};
}
this.nextBatch = async function (num) {
if (!initialized) {
await init();
}
var entries = [];
while (paths.length && num > 0) {
entries.push(await getEntry(paths.shift()));
num--;
}
return entries;
};
this.forEach = async function (func) {
if (!initialized) {
await init();
}
var i = 0;
while (paths.length) {
let entry = await getEntry(paths.shift());
await func(entry, i++, this);
}
};
this.close = function () {};
},
Error: function (msg) {
this.message = msg;
this.stack = new Error().stack;
},
copy: wrapWrite(async function (src, dest) {
return IOUtils.copy(src, dest);
}),
exists: async function (path) {
try {
return await IOUtils.exists(path);
}
catch (e) {
if (e.message.includes('NS_ERROR_FILE_UNRECOGNIZED_PATH')) {
dump(e.message + "\n\n" + e.stack + "\n\n");
Components.utils.reportError(e);
return false;
}
}
},
makeDir: wrapWrite(async function (path, options = {}) {
try {
return await IOUtils.makeDirectory(
path,
{
ignoreExisting: options.ignoreExisting !== false,
createAncestors: !!options.from,
permissions: options.unixMode
}
);
}
catch (e) {
// Broken symlink
if (e.name == 'InvalidAccessError') {
if (/Could not create directory because the target file(.+) exists and is not a directory/.test(e.message)) {
let osFileError = new OS.File.Error(e.message);
osFileError.becauseExists = true;
throw osFileError;
}
}
}
}),
move: wrapWrite(async function (src, dest, options = {}) {
if (options.noCopy) {
throw new Error("noCopy is no longer supported");
}
// Check noOverwrite
var destFileInfo = null;
try {
destFileInfo = await IOUtils.stat(dest)
}
catch (e) {
if (e.name != 'NotFoundError') {
throw e;
}
}
if (destFileInfo) {
if (destFileInfo.type == 'directory') {
throw new Error("OS.File.move() destination cannot be a directory -- use IOUtils.move()");
}
if (options.noOverwrite) {
let e = new OS.File.Error;
e.becauseExists = true;
throw e;
}
}
return IOUtils.move(src, dest, options);
}),
read: async function (path, options = {}) {
if (options.encoding) {
if (!/^utf\-?8$/i.test(options.encoding)) {
throw new Error("Can only read UTF-8");
}
return IOUtils.readUTF8(path);
}
return IOUtils.read(
path,
{
maxBytes: options.bytes
}
);
},
remove: async function (path, options = {}) {
return IOUtils.remove(path, options);
},
removeDir: async function (path, options = {}) {
return IOUtils.remove(
path,
{
recursive: true,
// OS.File.removeDir defaulted to ignoreAbsent: true
ignoreAbsent: options.ignoreAbsent !== false
}
);
},
removeEmptyDir: async function (path) {
return IOUtils.remove(path);
},
setDates: async function (path, atime, mtime) {
if (atime) {
await IOUtils.setAccessTime(path, atime.valueOf());
}
return await IOUtils.setModificationTime(path, mtime ? mtime.valueOf() : undefined);
},
setPermissions: async function (path, { unixMode, winAttributes } = {}) {
await IOUtils.setPermissions(path, unixMode);
if (winAttributes && isWin) {
let { readOnly, hidden, system } = winAttributes;
await IOUtils.setWindowsAttributes(path, { readOnly, hidden, system });
}
},
stat: async function stat(path) {
var info;
try {
info = await IOUtils.stat(path);
}
catch (e) {
if (e.name == 'NotFoundError') {
let osFileError = new this.Error("File not found");
osFileError.becauseNoSuchFile = true;
throw osFileError;
}
throw e;
}
return {
isDir: info.type == 'directory',
isSymLink: true, // Supposedly was broken in Firefox
size: info.size,
lastAccessDate: new Date(info.lastAccessed),
lastModificationDate: new Date(info.lastModified),
unixMode: !isWin ? info.permissions : undefined,
};
},
unixSymLink: async function (pathTarget, pathCreate) {
if (await IOUtils.exists(pathCreate)) {
let osFileError = new this.Error(pathCreate + " already exists");
osFileError.becauseExists = true;
throw osFileError;
}
// Copy of Zotero.File.createSymlink
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
try {
if (Services.appinfo.OS === "Darwin") {
const libc = ctypes.open(
Services.appinfo.OS === "Darwin" ? "libSystem.B.dylib" : "libc.so"
);
const symlink = libc.declare(
"symlink",
ctypes.default_abi,
ctypes.int, // return value
ctypes.char.ptr, // target
ctypes.char.ptr //linkpath
);
if (symlink(pathTarget, pathCreate)) {
throw new Error("Failed to create symlink at " + pathCreate);
}
}
// The above is failing with "invalid ELF header" for libc.so on GitHub Actions, so
// just use ln -s on non-macOS systems
else {
let ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
ln.initWithPath("/bin/ln");
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(ln);
let args = ["-s", pathTarget, pathCreate];
process.run(true, args, args.length);
}
}
catch (e) {
dump(e.message + "\n\n");
throw new Error("Failed to create symlink at " + pathCreate);
}
},
writeAtomic: async function (path, bytes, options = {}) {
if (options.backupTo) {
options.backupFile = options.backupTo;
}
if (options.noOverwrite) {
options.mode = 'create';
}
if (options.encoding == 'utf-8') {
return IOUtils.writeUTF8(path, bytes, options);
}
return IOUtils.write(path, bytes, options);
},
},
Path: {
basename: function (path) {
return PathUtils.filename(path);
},
dirname: function (path) {
return PathUtils.parent(path);
},
fromFileURI: function (uri) {
let url = new URL(uri);
if (url.protocol != "file:") {
throw new Error("fromFileURI expects a file URI");
}
let path = this.normalize(decodeURIComponent(url.pathname));
return path;
},
join: function (path, ...args) {
var platformSlash = Services.appinfo.OS == 'WINNT' ? '\\' : '/';
try {
if (args.length == 0) {
return path;
}
if (args.length == 1 && args[0].includes(platformSlash)) {
return PathUtils.joinRelative(path, ...args);
}
return PathUtils.join(path, ...args);
}
catch (e) {
if (e.message.includes('NS_ERROR_FILE_UNRECOGNIZED_PATH')) {
Cu.reportError("WARNING: " + e.message + " -- update for IOUtils");
return [path, ...args].join(platformSlash);
}
throw e;
}
},
// From Firefox 102
normalize: function (path) {
let stack = [];
let absolute;
if (path.length >= 0 && path[0] == "/") {
absolute = true;
}
else {
absolute = false;
}
path.split("/").forEach(function (v) {
switch (v) {
case "":
case ".": // fallthrough
break;
case "..":
if (!stack.length) {
if (absolute) {
throw new Error("Path is ill-formed: attempting to go past root");
}
else {
stack.push("..");
}
}
else if (stack[stack.length - 1] == "..") {
stack.push("..");
}
else {
stack.pop();
}
break;
default:
stack.push(v);
}
});
let string = stack.join("/");
return absolute ? "/" + string : string;
},
split: function (path) {
if (Services.appinfo.OS == "WINNT") {
// winIsAbsolute()
let index = path.indexOf(":");
let absolute = path.length > index + 1 && path[index + 1] == "\\";
return {
absolute,
winDrive: winGetDrive(path),
components: path.split("\\")
};
}
return {
absolute: path.length && path[0] == "/",
components: path.split("/")
};
},
toFileURI: function (path) {
return PathUtils.toFileURI(path);
},
}
};
// From Fx60 ospath_win.jsm
var winGetDrive = function (path) {
if (path == null) {
throw new TypeError("path is invalid");
}
if (path.startsWith("\\\\")) {
// UNC path
if (path.length == 2) {
return null;
}
let index = path.indexOf("\\", 2);
if (index == -1) {
return path;
}
return path.slice(0, index);
}
// Non-UNC path
let index = path.indexOf(":");
if (index <= 0) return null;
return path.slice(0, index + 1);
};
function wrapWrite(func) {
return async function () {
try {
return await func(...arguments);
}
catch (e) {
if (DOMException.isInstance(e)) {
if (e.name == 'NoModificationAllowedError') {
e.becauseExists = true;
}
}
throw e;
}
};
}