diff --git a/common/content/browser.js b/common/content/browser.js index f522cbcc..842aa185 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -177,7 +177,7 @@ function Browser() //{{{ { if (/^file:\/|^\//.test(url)) { - let file = io.getFile(url); + let file = io.File(url); return file.exists() && file.isDirectory(); } else diff --git a/common/content/buffer.js b/common/content/buffer.js index 95606569..3a435dc3 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -181,7 +181,7 @@ function Buffer() //{{{ function openUploadPrompt(elem) { commandline.input("Upload file: ", function (path) { - let file = io.getFile(path); + let file = io.File(path); if (!file.exists()) return void liberator.beep(); @@ -541,7 +541,7 @@ function Buffer() //{{{ if (arg) { options.setPref("print.print_to_file", "true"); - options.setPref("print.print_to_filename", io.getFile(arg.substr(1)).path); + options.setPref("print.print_to_filename", io.File(arg.substr(1)).path); liberator.echomsg("Printing to file: " + arg.substr(1)); } else @@ -617,7 +617,7 @@ function Buffer() //{{{ if (filename) { - let file = io.getFile(filename); + let file = io.File(filename); if (file.exists() && !args.bang) return void liberator.echoerr("E13: File exists (add ! to override)"); diff --git a/common/content/editor.js b/common/content/editor.js index fe0c26dc..3bd5bfe1 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -892,16 +892,16 @@ function Editor() //{{{ textBox.style.backgroundColor = "#bbbbbb"; } - if (!io.writeFile(tmpfile, text)) - throw "Input contains characters not valid in the current " + - "file encoding"; + if (!tmpfile.write(text)) + throw Error("Input contains characters not valid in the current " + + "file encoding"); this.editFileExternally(tmpfile.path); if (textBox) textBox.removeAttribute("readonly"); - let val = io.readFile(tmpfile); + let val = tmpfile.read(); if (textBox) textBox.value = val; else diff --git a/common/content/events.js b/common/content/events.js index 0ea683f2..bf80129c 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -563,21 +563,20 @@ function Events() //{{{ for (let [, dir] in Iterator(dirs)) { liberator.echomsg('Searching for "macros/*" in "' + dir.path + '"', 2); - liberator.log("Sourcing macros directory: " + dir.path + "...", 3); let files = io.readDirectory(dir.path); + for (let file in dir.iterDirectory()) + { + if (file.exists() && !file.isDirectory() && file.isReadable() && + /^[\w_-]+(\.vimp)?$/i.test(file.leafName)) + { + let name = file.leafName.replace(/\.vimp$/i, ""); + macros.set(name, file.read().split("\n")[0]); - files.forEach(function (file) { - if (!file.exists() || file.isDirectory() || - !file.isReadable() || !/^[\w_-]+(\.vimp)?$/i.test(file.leafName)) - return; - - let name = file.leafName.replace(/\.vimp$/i, ""); - macros.set(name, io.readFile(file).split("\n")[0]); - - liberator.log("Macro " + name + " added: " + macros.get(name), 5); - }); + liberator.log("Macro " + name + " added: " + macros.get(name), 5); + } + } } } else diff --git a/common/content/io.js b/common/content/io.js index e3ba52a0..b9e626f8 100644 --- a/common/content/io.js +++ b/common/content/io.js @@ -77,17 +77,16 @@ function IO() //{{{ { if (!list) return []; - else - // empty list item means the current directory - return list.replace(/,$/, "").split(",") - .map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir); + // empty list item means the current directory + return list.replace(/,$/, "").split(",") + .map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir); } function replacePathSep(path) path.replace("/", IO.PATH_SEP, "g"); function joinPaths(head, tail) { - let path = self.getFile(head); + let path = self.File(head); try { path.appendRelativePath(self.expandPath(tail, true)); // FIXME: should only expand env vars and normalise path separators @@ -257,7 +256,7 @@ function IO() //{{{ return void liberator.echoerr("E172: Only one file name allowed"); let filename = args[0] || io.getRCFile(null, true).path; - let file = io.getFile(filename); + let file = io.File(filename); if (file.exists() && !args.bang) return void liberator.echoerr("E189: \"" + filename + "\" exists (add ! to override)"); @@ -281,7 +280,7 @@ function IO() //{{{ try { - io.writeFile(file, lines.join("\n")); + file.write(lines.join("\n")); } catch (e) { @@ -372,7 +371,7 @@ function IO() //{{{ /////////////////////////////////////////////////////////////////////////////{{{ liberator.registerObserver("load_completion", function () { - completion.setFunctionCompleter([self.getFile, self.expandPath], + completion.setFunctionCompleter([self.File, self.expandPath], [function (context, obj, args) { context.quote[2] = ""; completion.file(context, true); @@ -451,10 +450,10 @@ function IO() //{{{ for (let [, dirName] in Iterator(dirNames)) { - let dir = io.getFile(dirName); + let dir = io.File(dirName); if (dir.exists() && dir.isDirectory()) { - commands.push([[file.leafName, dir.path] for ([i, file] in Iterator(io.readDirectory(dir))) + commands.push([[file.leafName, dir.path] for (file in dir.iterDirectory()) if (file.isFile() && file.isExecutable())]); } } @@ -466,11 +465,201 @@ function IO() //{{{ completion.addUrlCompleter("f", "Local files", completion.file); }); + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// File //////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + /** + * @class File A class to wrap nsIFile objects and simplify operations + * thereon. + * + * @param {nsIFile|string} path Expanded according to {@link IO#expandPath} + * @param {boolean} checkPWD Whether to allow expansion relative to the + * current directory. @default true + */ + function File(path, checkPWD) + { + let self = { __proto__: File.prototype } + if (arguments.length < 2) + checkPWD = true; + + self.file = services.create("file"); + + if (path instanceof Ci.nsIFile) + self.file = path; + else if (/file:\/\//.test(path)) + self.file = services.create("file:").getFileFromURLSpec(path); + else + { + let expandedPath = io.expandPath(path); + + if (!isAbsolutePath(expandedPath) && checkPWD) + self = joinPaths(io.getCurrentDirectory().path, expandedPath); + else + self.file.initWithPath(expandedPath); + } + self.wrappedNative = self.file; + return self; + } + File.prototype = { + __noSuchMethod__: function (meth, args) + { + return this.wrappedNative[meth].apply(this.wrappedNative, + args.map(function (a) a instanceof File ? a.wrappedNative : a)); + }, + + /** + * Iterates over the objects in this directory. + */ + iterDirectory: function () + { + if (!this.file.isDirectory()) + throw Error("Not a directory"); + let entries = this.file.directoryEntries; + while (entries.hasMoreElements()) + yield File(entries.getNext().QueryInterface(Ci.nsIFile)); + }, + /** + * Returns the list of files in this directory. + * + * @param {boolean} sort Whether to sort the returned directory + * entries. + * @returns {nsIFile[]} + */ + readDirectory: function (sort) + { + if (!this.file.isDirectory()) + throw Error("Not a directory"); + + let array = [e for (e in this.iterDirectory())]; + if (sort) + array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path)); + return array; + }, + + /** + * Reads this file's entire contents in "text" mode and returns the + * content as a string. + * + * @param {string} encoding The encoding from which to decode the file. + * @default options["fileencoding"] + * @returns {string} + */ + read: function (encoding) + { + let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); + let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream); + + if (!encoding) + encoding = options["fileencoding"]; + + ifstream.init(this.file, -1, 0, 0); + icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering + + let buffer = []; + let str = {}; + while (icstream.readString(4096, str) != 0) + buffer.push(str.value); + + icstream.close(); + ifstream.close(); + return buffer.join(""); + }, + + /** + * Writes the string buf to this file. + * + * @param {string} buf The file content. + * @param {string|number} mode The file access mode, a bitwise OR of + * the following flags: + * {@link #MODE_RDONLY}: 0x01 + * {@link #MODE_WRONLY}: 0x02 + * {@link #MODE_RDWR}: 0x04 + * {@link #MODE_CREATE}: 0x08 + * {@link #MODE_APPEND}: 0x10 + * {@link #MODE_TRUNCATE}: 0x20 + * {@link #MODE_SYNC}: 0x40 + * Alternatively, the following abbreviations may be used: + * ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE} + * ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND} + * @default ">" + * @param {number} perms The file mode bits of the created file. This + * is only used when creating a new file and does not change + * permissions if the file exists. + * @default 0644 + * @param {string} encoding The encoding to used to write the file. + * @default options["fileencoding"] + */ + write: function (buf, mode, perms, encoding) + { + let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); + function getStream(defaultChar) + { + let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream); + stream.init(ofstream, encoding, 0, defaultChar); + return stream; + } + + if (!encoding) + encoding = options["fileencoding"]; + + if (mode == ">>") + mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_APPEND; + else if (!mode || mode == ">") + mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_TRUNCATE; + + if (!perms) + perms = 0644; + + ofstream.init(this.file, mode, perms, 0); + let ocstream = getStream(0); + try + { + ocstream.writeString(buf); + } + catch (e) + { + liberator.dump(e); + if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) + { + ocstream = getStream("?".charCodeAt(0)); + ocstream.writeString(buf); + return false; + } + else + throw e; + } + finally + { + try + { + ocstream.close(); + } + catch (e) {} + ofstream.close(); + } + return true; + }, + }; + /* It would be nice if there were a simpler way to do this. */ + ("leafName nativeLeafName permissions permissionsOfLink lastModifiedTime " + + "lastModifiedTimeOfLink fileSize fileSizeOfLink target nativeTarget path " + + "nativePath parent directoryEntries").split(" ").forEach(function (p) { + File.prototype.__defineGetter__(p, function () this.file[p]); + File.prototype.__defineSetter__(p, function (val) this.file[p] = val); + }); + + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ const self = { + /** + * @property {function} File class. + * @final + */ + File: File, /** * @property {number} Open for reading only. @@ -562,7 +751,7 @@ function IO() //{{{ */ getCurrentDirectory: function () { - let dir = self.getFile(cwd.path); + let dir = self.File(cwd.path); // NOTE: the directory could have been deleted underneath us so // fallback to the process's CWD @@ -586,7 +775,7 @@ function IO() //{{{ [cwd, oldcwd] = [oldcwd, this.getCurrentDirectory()]; else { - let dir = self.getFile(newDir); + let dir = self.File(newDir); if (!dir.exists() || !dir.isDirectory()) { @@ -612,7 +801,6 @@ function IO() //{{{ dirs = dirs.map(function (dir) joinPaths(dir, name)) .filter(function (dir) dir.exists() && dir.isDirectory() && dir.isReadable()); - return dirs; }, @@ -644,44 +832,11 @@ function IO() //{{{ return null; }, - // return a nsILocalFile for path where you can call isDirectory(), etc. on - // caller must check with .exists() if the returned file really exists - // also expands relative paths - /** - * Returns an nsIFile object for path, which is expanded - * according to {@link #expandPath}. - * - * @param {string} path The path used to create the file object. - * @param {boolean} noCheckPWD Whether to allow a relative path. - * @returns {nsIFile} - */ - getFile: function (path, noCheckPWD) - { - let file = services.create("file"); - - if (/file:\/\//.test(path)) - { - file = Cc["@mozilla.org/network/protocol;1?name=file"].createInstance(Ci.nsIFileProtocolHandler) - .getFileFromURLSpec(path); - } - else - { - let expandedPath = self.expandPath(path); - - if (!isAbsolutePath(expandedPath) && !noCheckPWD) - file = joinPaths(self.getCurrentDirectory().path, expandedPath); - else - file.initWithPath(expandedPath); - } - - return file; - }, - // TODO: make secure /** * Creates a temporary file. * - * @returns {nsIFile} + * @returns {File} */ createTempFile: function () { @@ -690,148 +845,9 @@ function IO() //{{{ file.append(config.tempFile); file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); - return file; + return self.File(file); }, - /** - * Returns the list of files in dir. - * - * @param {nsIFile|string} dir The directory to read, either a full - * pathname or an instance of nsIFile. - * @param {boolean} sort Whether to sort the returned directory - * entries. - * @returns {nsIFile[]} - */ - readDirectory: function (dir, sort) - { - if (typeof dir == "string") - dir = self.getFile(dir); - - if (dir.isDirectory()) - { - let entries = dir.directoryEntries; - let array = []; - while (entries.hasMoreElements()) - { - let entry = entries.getNext(); - array.push(entry.QueryInterface(Ci.nsIFile)); - } - if (sort) - array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path)); - return array; - } - else - return []; // XXX: or should it throw an error, probably yes? - // Yes, though frankly this should be a precondition so... --djk - }, - - /** - * Reads a file in "text" mode and returns the content as a string. - * - * @param {nsIFile|string} file The file to read, either a full - * pathname or an instance of nsIFile. - * @returns {string} - */ - readFile: function (file, encoding) - { - let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); - let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream); - - if (!encoding) - encoding = options["fileencoding"]; - if (typeof file == "string") - file = self.getFile(file); - - ifstream.init(file, -1, 0, 0); - icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering - - let buffer = ""; - let str = {}; - while (icstream.readString(4096, str) != 0) - buffer += str.value; - - icstream.close(); - ifstream.close(); - - return buffer; - }, - - /** - * Writes the string buf to a file. - * - * @param {nsIFile|string} file The file to write, either a full - * pathname or an instance of nsIFile. - * @param {string} buf The file content. - * @param {string|number} mode The file access mode, a bitwise OR of - * the following flags: - * {@link #MODE_RDONLY}: 0x01 - * {@link #MODE_WRONLY}: 0x02 - * {@link #MODE_RDWR}: 0x04 - * {@link #MODE_CREATE}: 0x08 - * {@link #MODE_APPEND}: 0x10 - * {@link #MODE_TRUNCATE}: 0x20 - * {@link #MODE_SYNC}: 0x40 - * Alternatively, the following abbreviations may be used: - * ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE} - * ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND} - * @default ">" - * @param {number} perms The file mode bits of the created file. This - * is only used when creating a new file and does not change - * permissions if the file exists. - * @default 0644 - */ - writeFile: function (file, buf, mode, perms, encoding) - { - let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); - function getStream(defaultChar) - { - let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream); - stream.init(ofstream, encoding, 0, defaultChar); - return stream; - } - - if (!encoding) - encoding = options["fileencoding"]; - if (typeof file == "string") - file = self.getFile(file); - - if (mode == ">>") - mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_APPEND; - else if (!mode || mode == ">") - mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_TRUNCATE; - - if (!perms) - perms = 0644; - - ofstream.init(file, mode, perms, 0); - let ocstream = getStream(0); - try - { - ocstream.writeString(buf); - } - catch (e) - { - liberator.dump(e); - if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) - { - ocstream = getStream("?".charCodeAt(0)); - ocstream.writeString(buf); - return false; - } - else - throw e; - } - finally - { - try - { - ocstream.close(); - } - catch (e) {} - ofstream.close(); - } - return true; - }, /** * Runs an external program. @@ -849,7 +865,7 @@ function IO() //{{{ let file; if (isAbsolutePath(program)) - file = self.getFile(program, true); + file = self.File(program, true); else { let dirs = services.get("environment").get("PATH").split(WINDOWS ? ";" : ":"); @@ -891,7 +907,7 @@ lookup: let process = services.create("process"); - process.init(file); + process.init(file.wrappedNative); process.run(blocking, args.map(String), args.length); return process.exitValue; @@ -952,7 +968,7 @@ lookup: let wasSourcing = self.sourcing; try { - var file = self.getFile(filename); + var file = self.File(filename); self.sourcing = { file: file.path, line: 0 @@ -975,8 +991,8 @@ lookup: liberator.echomsg("sourcing \"" + filename + "\"", 2); - let str = self.readFile(file); - let uri = services.get("io").newFileURI(file); + let str = file.read(); + let uri = services.get("io").newFileURI(file.wrappedNative); // handle pure JavaScript files specially if (/\.js$/.test(filename)) @@ -990,7 +1006,7 @@ lookup: let err = new Error(); for (let [k, v] in Iterator(e)) err[k] = v; - err.echoerr = file.path + ":" + e.lineNumber + ": " + e; + err.echoerr = <>{file.path}:{e.lineNumber}: {e}; throw err; } } @@ -1076,8 +1092,8 @@ lookup: } catch (e) { - let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); liberator.reportError(e); + let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); if (!silent) liberator.echoerr(message); } @@ -1105,7 +1121,7 @@ lookup: return this.withTempFiles(function (stdin, stdout, cmd) { if (input) - this.writeFile(stdin, input); + stdin.write(input); // TODO: implement 'shellredir' if (WINDOWS) @@ -1115,13 +1131,13 @@ lookup: } else { - this.writeFile(cmd, "cd " + escape(cwd.path) + "\n" + + cmd.write("cd " + escape(cwd.path) + "\n" + ["exec", ">" + escape(stdout.path), "2>&1", "<" + escape(stdin.path), escape(options["shell"]), options["shellcmdflag"], escape(command)].join(" ")); res = this.run("/bin/sh", ["-e", cmd.path], true); } - let output = self.readFile(stdout); + let output = stdout.read(); if (res > 0) output += "\nshell returned " + res; // if there is only one \n at the end, chop it off diff --git a/common/content/liberator.js b/common/content/liberator.js index 475d03d6..f4f4df7f 100644 --- a/common/content/liberator.js +++ b/common/content/liberator.js @@ -402,10 +402,10 @@ const liberator = (function () //{{{ "Install an extension", function (args) { - let file = io.getFile(args[0]); + let file = io.File(args[0]); if (file.exists() && file.isReadable() && file.isFile()) - services.get("extensionManager").installItemFromFile(file, "app-profile"); + services.get("extensionManager").installItemFromFile(file.file, "app-profile"); else { if (file.exists() && file.isDirectory()) @@ -1483,7 +1483,7 @@ const liberator = (function () //{{{ return void liberator.echoerr("E484: Can't open file " + dir.path); liberator.log("Sourcing plugin directory: " + dir.path + "...", 3); - io.readDirectory(dir.path, true).forEach(function (file) { + dir.readDirectory(true).forEach(function (file) { if (file.isFile() && /\.(js|vimp)$/i.test(file.path) && !(file.path in liberator.pluginFiles)) { try @@ -1879,7 +1879,7 @@ const liberator = (function () //{{{ { let filename = liberator.commandLineOptions.rcFile; if (!/^(NONE|NORC)$/.test(filename)) - io.source(io.getFile(filename).path, false); // let io.source handle any read failure like Vim + io.source(io.File(filename).path, false); // let io.source handle any read failure like Vim } else { diff --git a/common/content/services.js b/common/content/services.js index 47d0058e..9cc54026 100644 --- a/common/content/services.js +++ b/common/content/services.js @@ -114,6 +114,7 @@ function Services() self.add("xulAppInfo", "@mozilla.org/xre/app-info;1", Ci.nsIXULAppInfo); self.addClass("file", "@mozilla.org/file/local;1", Ci.nsILocalFile); + self.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler); self.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind); self.addClass("process", "@mozilla.org/process/util;1", Ci.nsIProcess); diff --git a/common/content/style.js b/common/content/style.js index 1c09ab2c..a28afc2f 100644 --- a/common/content/style.js +++ b/common/content/style.js @@ -825,17 +825,13 @@ liberator.registerObserver("load_completion", function () { ]); completion.colorScheme = function colorScheme(context) { - let colors = []; - - io.getRuntimeDirectories("colors").forEach(function (dir) { - io.readDirectory(dir).forEach(function (file) { - if (/\.vimp$/.test(file.leafName) && !colors.some(function (c) c.leafName == file.leafName)) - colors.push(file); - }); - }); - context.title = ["Color Scheme", "Runtime Path"]; - context.completions = [[c.leafName.replace(/\.vimp$/, ""), c.parent.path] for ([, c] in Iterator(colors))] + context.keys = { text: function (f) f.leafName.replace(/\.vimp$/, ""), description: "parent.path" }; + context.completions = util.Array.flatten( + io.getRuntimeDirectories("colors").map( + function (dir) dir.readDirectory().filter( + function (file) /\.vimp$/.test(file.leafName) && !colors.some(function (c) c.leafName == file.leafName)))) + }; completion.highlightGroup = function highlightGroup(context) { diff --git a/common/content/util.js b/common/content/util.js index 07530b7c..f79a8905 100644 --- a/common/content/util.js +++ b/common/content/util.js @@ -688,9 +688,9 @@ const util = { //{{{ try { // Try to find a matching file. - let file = io.getFile(url); + let file = io.File(url); if (file.exists() && file.isReadable()) - return services.get("io").newFileURI(file).spec; + return services.get("io").newFileURI(file.wrappedNative).spec; } catch (e) {}