mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 561472 - Add a new DownloadPaths JavaScript module. r=sdwilsh
This commit is contained in:
parent
2e5b6f9223
commit
b471c5525e
121
toolkit/mozapps/downloads/DownloadPaths.jsm
Normal file
121
toolkit/mozapps/downloads/DownloadPaths.jsm
Normal file
@ -0,0 +1,121 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Download Manager Utility Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Paolo Amadini <http://www.amadzone.org/>.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"DownloadPaths",
|
||||
];
|
||||
|
||||
/**
|
||||
* This module provides the DownloadPaths object which contains methods for
|
||||
* giving names and paths to files being downloaded.
|
||||
*
|
||||
* List of methods:
|
||||
*
|
||||
* nsILocalFile
|
||||
* createNiceUniqueFile(nsILocalFile aLocalFile)
|
||||
*
|
||||
* [string base, string ext]
|
||||
* splitBaseNameAndExtension(string aLeafName)
|
||||
*/
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
const DownloadPaths = {
|
||||
/**
|
||||
* Creates a uniquely-named file starting from the name of the provided file.
|
||||
* If a file with the provided name already exists, the function attempts to
|
||||
* create nice alternatives, like "base(1).ext" (instead of "base-1.ext").
|
||||
*
|
||||
* If a unique name cannot be found, the function throws the XPCOM exception
|
||||
* NS_ERROR_FILE_TOO_BIG. Other exceptions, like NS_ERROR_FILE_ACCESS_DENIED,
|
||||
* can also be expected.
|
||||
*
|
||||
* @param aTemplateFile
|
||||
* nsILocalFile whose leaf name is going to be used as a template. The
|
||||
* provided object is not modified.
|
||||
* @returns A new instance of an nsILocalFile object pointing to the newly
|
||||
* created empty file. On platforms that support permission bits, the
|
||||
* file is created with permissions 600.
|
||||
*/
|
||||
createNiceUniqueFile: function DP_createNiceUniqueFile(aTemplateFile) {
|
||||
// Work on a clone of the provided template file object.
|
||||
var curFile = aTemplateFile.clone().QueryInterface(Ci.nsILocalFile);
|
||||
var [base, ext] = DownloadPaths.splitBaseNameAndExtension(curFile.leafName);
|
||||
// Try other file names, for example "base(1).txt" or "base(1).tar.gz",
|
||||
// only if the file name initially set already exists.
|
||||
for (let i = 1; i < 10000 && curFile.exists(); i++) {
|
||||
curFile.leafName = base + "(" + i + ")" + ext;
|
||||
}
|
||||
// At this point we hand off control to createUnique, which will create the
|
||||
// file with the name we chose, if it is valid. If not, createUnique will
|
||||
// attempt to modify it again, for example it will shorten very long names
|
||||
// that can't be created on some platforms, and for which a normal call to
|
||||
// nsIFile.create would result in NS_ERROR_FILE_NOT_FOUND. This can result
|
||||
// very rarely in strange names like "base(9999).tar-1.gz" or "ba-1.gz".
|
||||
curFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
|
||||
return curFile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Separates the base name from the extension in a file name, recognizing some
|
||||
* double extensions like ".tar.gz".
|
||||
*
|
||||
* @param aLeafName
|
||||
* The full leaf name to be parsed. Be careful when processing names
|
||||
* containing leading or trailing dots or spaces.
|
||||
* @returns [base, ext]
|
||||
* The base name of the file, which can be empty, and its extension,
|
||||
* which always includes the leading dot unless it's an empty string.
|
||||
* Concatenating the two items always results in the original name.
|
||||
*/
|
||||
splitBaseNameAndExtension: function DP_splitBaseNameAndExtension(aLeafName) {
|
||||
// The following regular expression is built from these key parts:
|
||||
// .*? Matches the base name non-greedily.
|
||||
// \.[A-Z0-9]{1,3} Up to three letters or numbers preceding a
|
||||
// double extension.
|
||||
// \.(?:gz|bz2|Z) The second part of common double extensions.
|
||||
// \.[^.]* Matches any extension or a single trailing dot.
|
||||
var [, base, ext] = /(.*?)(\.[A-Z0-9]{1,3}\.(?:gz|bz2|Z)|\.[^.]*)?$/i
|
||||
.exec(aLeafName);
|
||||
// Return an empty string instead of undefined if no extension is found.
|
||||
return [base, ext || ""];
|
||||
}
|
||||
};
|
@ -48,6 +48,7 @@ EXTRA_PP_COMPONENTS = nsHelperAppDlg.js
|
||||
|
||||
EXTRA_JS_MODULES = \
|
||||
DownloadLastDir.jsm \
|
||||
DownloadPaths.jsm \
|
||||
DownloadUtils.jsm \
|
||||
$(NULL)
|
||||
|
||||
|
@ -78,6 +78,7 @@ const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
|
||||
const nsITimer = Components.interfaces.nsITimer;
|
||||
|
||||
Components.utils.import("resource://gre/modules/DownloadLastDir.jsm");
|
||||
Components.utils.import("resource://gre/modules/DownloadPaths.jsm");
|
||||
|
||||
/* ctor
|
||||
*/
|
||||
@ -313,7 +314,7 @@ nsUnknownContentTypeDialog.prototype = {
|
||||
aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
|
||||
aLocalFile.append(aLeafName);
|
||||
|
||||
this.makeFileUnique(aLocalFile);
|
||||
var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFile);
|
||||
|
||||
#ifdef XP_WIN
|
||||
let ext;
|
||||
@ -324,66 +325,17 @@ nsUnknownContentTypeDialog.prototype = {
|
||||
|
||||
// Append a file extension if it's an executable that doesn't have one
|
||||
// but make sure we actually have an extension to add
|
||||
let leaf = aLocalFile.leafName;
|
||||
if (aLocalFile.isExecutable() && ext &&
|
||||
leaf.substring(leaf.length - ext.length) != ext) {
|
||||
let f = aLocalFile.clone();
|
||||
let leaf = createdFile.leafName;
|
||||
if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) {
|
||||
createdFile.remove(false);
|
||||
aLocalFile.leafName = leaf + ext;
|
||||
|
||||
f.remove(false);
|
||||
this.makeFileUnique(aLocalFile);
|
||||
createdFile = DownloadPaths.createNiceUniqueFile(aLocalFile);
|
||||
}
|
||||
#endif
|
||||
|
||||
return aLocalFile;
|
||||
return createdFile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates and returns a uniquely-named file from aLocalFile. If
|
||||
* aLocalFile does not exist, it will be the file returned; otherwise, a
|
||||
* file whose name is similar to that of aLocalFile will be returned.
|
||||
*/
|
||||
makeFileUnique: function (aLocalFile)
|
||||
{
|
||||
try {
|
||||
// Note - this code is identical to that in
|
||||
// toolkit/content/contentAreaUtils.js.
|
||||
// If you are updating this code, update that code too! We can't share code
|
||||
// here since this is called in a js component.
|
||||
var collisionCount = 0;
|
||||
while (aLocalFile.exists()) {
|
||||
collisionCount++;
|
||||
if (collisionCount == 1) {
|
||||
// Append "(2)" before the last dot in (or at the end of) the filename
|
||||
// special case .ext.gz etc files so we don't wind up with .tar(2).gz
|
||||
if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) {
|
||||
aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
|
||||
}
|
||||
else {
|
||||
aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// replace the last (n) in the filename with (n+1)
|
||||
aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
|
||||
}
|
||||
}
|
||||
aLocalFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
|
||||
}
|
||||
catch (e) {
|
||||
dump("*** exception in validateLeafName: " + e + "\n");
|
||||
|
||||
if (e.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED)
|
||||
throw e;
|
||||
|
||||
if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
|
||||
aLocalFile.append("unnamed");
|
||||
if (aLocalFile.exists())
|
||||
aLocalFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ---------- implementation methods ----------
|
||||
|
||||
// Web progress listener so we can detect errors while mLauncher is
|
||||
|
131
toolkit/mozapps/downloads/tests/unit/test_DownloadPaths.js
Normal file
131
toolkit/mozapps/downloads/tests/unit/test_DownloadPaths.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Tests for the "DownloadPaths.jsm" JavaScript module.
|
||||
*/
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/DownloadPaths.jsm");
|
||||
|
||||
/**
|
||||
* Provides a temporary save directory.
|
||||
*
|
||||
* @returns nsIFile pointing to the new or existing directory.
|
||||
*/
|
||||
function createTemporarySaveDirectory()
|
||||
{
|
||||
var saveDir = Cc["@mozilla.org/file/directory_service;1"].
|
||||
getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
|
||||
saveDir.append("testsavedir");
|
||||
if (!saveDir.exists()) {
|
||||
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
|
||||
}
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
function testSplitBaseNameAndExtension(aLeafName, [aBase, aExt])
|
||||
{
|
||||
var [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
|
||||
do_check_eq(base, aBase);
|
||||
do_check_eq(ext, aExt);
|
||||
|
||||
// If we modify the base name and concatenate it with the extension again,
|
||||
// another roundtrip through the function should give a consistent result.
|
||||
// The only exception is when we introduce an extension in a file name that
|
||||
// didn't have one or that ended with one of the special cases like ".gz". If
|
||||
// we avoid using a dot and we introduce at least another special character,
|
||||
// the results are always consistent.
|
||||
[base, ext] = DownloadPaths.splitBaseNameAndExtension("(" + base + ")" + ext);
|
||||
do_check_eq(base, "(" + aBase + ")");
|
||||
do_check_eq(ext, aExt);
|
||||
}
|
||||
|
||||
function testCreateNiceUniqueFile(aTempFile, aExpectedLeafName)
|
||||
{
|
||||
var createdFile = DownloadPaths.createNiceUniqueFile(aTempFile);
|
||||
do_check_eq(createdFile.leafName, aExpectedLeafName);
|
||||
}
|
||||
|
||||
function run_test()
|
||||
{
|
||||
// Usual file names.
|
||||
testSplitBaseNameAndExtension("base", ["base", ""]);
|
||||
testSplitBaseNameAndExtension("base.ext", ["base", ".ext"]);
|
||||
testSplitBaseNameAndExtension("base.application", ["base", ".application"]);
|
||||
testSplitBaseNameAndExtension("base.x.Z", ["base", ".x.Z"]);
|
||||
testSplitBaseNameAndExtension("base.ext.Z", ["base", ".ext.Z"]);
|
||||
testSplitBaseNameAndExtension("base.ext.gz", ["base", ".ext.gz"]);
|
||||
testSplitBaseNameAndExtension("base.ext.Bz2", ["base", ".ext.Bz2"]);
|
||||
testSplitBaseNameAndExtension("base..ext", ["base.", ".ext"]);
|
||||
testSplitBaseNameAndExtension("base..Z", ["base.", ".Z"]);
|
||||
testSplitBaseNameAndExtension("base. .Z", ["base. ", ".Z"]);
|
||||
testSplitBaseNameAndExtension("base.base.Bz2", ["base.base", ".Bz2"]);
|
||||
testSplitBaseNameAndExtension("base .ext", ["base ", ".ext"]);
|
||||
|
||||
// Corner cases. A name ending with a dot technically has no extension, but
|
||||
// we consider the ending dot separately from the base name so that modifying
|
||||
// the latter never results in an extension being introduced accidentally.
|
||||
// Names beginning with a dot are hidden files on Unix-like platforms and if
|
||||
// their name doesn't contain another dot they should have no extension, but
|
||||
// on Windows the whole name is considered as an extension.
|
||||
testSplitBaseNameAndExtension("base.", ["base", "."]);
|
||||
testSplitBaseNameAndExtension(".ext", ["", ".ext"]);
|
||||
|
||||
// Unusual file names (not recommended as input to the function).
|
||||
testSplitBaseNameAndExtension("base. ", ["base", ". "]);
|
||||
testSplitBaseNameAndExtension("base ", ["base ", ""]);
|
||||
testSplitBaseNameAndExtension("", ["", ""]);
|
||||
testSplitBaseNameAndExtension(" ", [" ", ""]);
|
||||
testSplitBaseNameAndExtension(" . ", [" ", ". "]);
|
||||
testSplitBaseNameAndExtension(" .. ", [" .", ". "]);
|
||||
testSplitBaseNameAndExtension(" .ext", [" ", ".ext"]);
|
||||
testSplitBaseNameAndExtension(" .ext. ", [" .ext", ". "]);
|
||||
testSplitBaseNameAndExtension(" .ext.gz ", [" .ext", ".gz "]);
|
||||
|
||||
var destDir = createTemporarySaveDirectory();
|
||||
try {
|
||||
// Single extension.
|
||||
var tempFile = destDir.clone();
|
||||
tempFile.append("test.txt");
|
||||
testCreateNiceUniqueFile(tempFile, "test.txt");
|
||||
testCreateNiceUniqueFile(tempFile, "test(1).txt");
|
||||
testCreateNiceUniqueFile(tempFile, "test(2).txt");
|
||||
|
||||
// Double extension.
|
||||
tempFile.leafName = "test.tar.gz";
|
||||
testCreateNiceUniqueFile(tempFile, "test.tar.gz");
|
||||
testCreateNiceUniqueFile(tempFile, "test(1).tar.gz");
|
||||
testCreateNiceUniqueFile(tempFile, "test(2).tar.gz");
|
||||
|
||||
// Test automatic shortening of long file names. We don't know exactly how
|
||||
// many characters are removed, because it depends on the name of the folder
|
||||
// where the file is located.
|
||||
tempFile.leafName = new Array(256).join("T") + ".txt";
|
||||
var newFile = DownloadPaths.createNiceUniqueFile(tempFile);
|
||||
do_check_true(newFile.leafName.length < tempFile.leafName.length);
|
||||
do_check_eq(newFile.leafName.slice(-4), ".txt");
|
||||
|
||||
// Creating a valid file name from an invalid one is not always possible.
|
||||
tempFile.append("file-under-long-directory.txt");
|
||||
try {
|
||||
DownloadPaths.createNiceUniqueFile(tempFile);
|
||||
do_throw("Exception expected with a long parent directory name.")
|
||||
} catch (e) {
|
||||
// An exception is expected, but we don't know which one exactly.
|
||||
}
|
||||
} finally {
|
||||
// Clean up the temporary directory.
|
||||
destDir.remove(true);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user