зеркало из https://github.com/mozilla/gecko-dev.git
Backout 138ea79ea71a (bug 854288) and aa9b27b090db (bug 852032) for xpcshell bustage on a CLOSED TREE
This commit is contained in:
Родитель
0a3ab6f20e
Коммит
dff5de0599
|
@ -63,7 +63,6 @@ EXTRA_JS_MODULES = \
|
|||
ColorConversion.js \
|
||||
ClusterLib.js \
|
||||
BookmarkJSONUtils.jsm \
|
||||
PlacesBackups.jsm \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_JS_MODULES = \
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: sw=2 ts=2 sts=2 expandtab filetype=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/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PlacesBackups"];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
this.PlacesBackups = {
|
||||
get _filenamesRegex() {
|
||||
// Get the localized backup filename, will be used to clear out
|
||||
// old backups with a localized name (bug 445704).
|
||||
let localizedFilename =
|
||||
PlacesUtils.getFormattedString("bookmarksArchiveFilename", [new Date()]);
|
||||
let localizedFilenamePrefix =
|
||||
localizedFilename.substr(0, localizedFilename.indexOf("-"));
|
||||
delete this._filenamesRegex;
|
||||
return this._filenamesRegex =
|
||||
new RegExp("^(bookmarks|" + localizedFilenamePrefix + ")-([0-9-]+)\.(json|html)");
|
||||
},
|
||||
|
||||
get folder() {
|
||||
let bookmarksBackupDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
|
||||
bookmarksBackupDir.append("bookmarkbackups");
|
||||
if (!bookmarksBackupDir.exists()) {
|
||||
bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8));
|
||||
if (!bookmarksBackupDir.exists())
|
||||
throw("Unable to create bookmarks backup folder");
|
||||
}
|
||||
delete this.folder;
|
||||
return this.folder = bookmarksBackupDir;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cache current backups in a sorted (by date DESC) array.
|
||||
*/
|
||||
get entries() {
|
||||
delete this.entries;
|
||||
this.entries = [];
|
||||
let files = this.folder.directoryEntries;
|
||||
while (files.hasMoreElements()) {
|
||||
let entry = files.getNext().QueryInterface(Ci.nsIFile);
|
||||
// A valid backup is any file that matches either the localized or
|
||||
// not-localized filename (bug 445704).
|
||||
let matches = entry.leafName.match(this._filenamesRegex);
|
||||
if (!entry.isHidden() && matches) {
|
||||
// Remove bogus backups in future dates.
|
||||
if (this.getDateForFile(entry) > new Date()) {
|
||||
entry.remove(false);
|
||||
continue;
|
||||
}
|
||||
this.entries.push(entry);
|
||||
}
|
||||
}
|
||||
this.entries.sort((a, b) => {
|
||||
let aDate = this.getDateForFile(a);
|
||||
let bDate = this.getDateForFile(b);
|
||||
return aDate < bDate ? 1 : aDate > bDate ? -1 : 0;
|
||||
});
|
||||
return this.entries;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a filename for bookmarks backup files.
|
||||
*
|
||||
* @param [optional] aDateObj
|
||||
* Date object used to build the filename.
|
||||
* Will use current date if empty.
|
||||
* @return A bookmarks backup filename.
|
||||
*/
|
||||
getFilenameForDate: function PB_getFilenameForDate(aDateObj) {
|
||||
let dateObj = aDateObj || new Date();
|
||||
// Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters
|
||||
// and makes the alphabetical order of multiple backup files more useful.
|
||||
return "bookmarks-" + dateObj.toLocaleFormat("%Y-%m-%d") + ".json";
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a Date object from a backup file. The date is the backup
|
||||
* creation date.
|
||||
*
|
||||
* @param aBackupFile
|
||||
* nsIFile of the backup.
|
||||
* @return A Date object for the backup's creation time.
|
||||
*/
|
||||
getDateForFile: function PB_getDateForFile(aBackupFile) {
|
||||
let filename = aBackupFile.leafName;
|
||||
let matches = filename.match(this._filenamesRegex);
|
||||
if (!matches)
|
||||
throw("Invalid backup file name: " + filename);
|
||||
return new Date(matches[2].replace(/-/g, "/"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the most recent backup file.
|
||||
*
|
||||
* @param [optional] aFileExt
|
||||
* Force file extension. Either "html" or "json".
|
||||
* Will check for both if not defined.
|
||||
* @returns nsIFile backup file
|
||||
*/
|
||||
getMostRecent: function PB_getMostRecent(aFileExt) {
|
||||
let fileExt = aFileExt || "(json|html)";
|
||||
for (let i = 0; i < this.entries.length; i++) {
|
||||
let rx = new RegExp("\." + fileExt + "$");
|
||||
if (this.entries[i].leafName.match(rx))
|
||||
return this.entries[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes bookmarks using JSON, and writes to the supplied file.
|
||||
* Note: any item that should not be backed up must be annotated with
|
||||
* "places/excludeFromBackup".
|
||||
*
|
||||
* @param aFile
|
||||
* nsIFile where to save JSON backup.
|
||||
* @return {Promise}
|
||||
*/
|
||||
saveBookmarksToJSONFile: function PB_saveBookmarksToJSONFile(aFile) {
|
||||
return Task.spawn(function() {
|
||||
if (!aFile.exists())
|
||||
aFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0600", 8));
|
||||
if (!aFile.exists() || !aFile.isWritable()) {
|
||||
throw new Error("Unable to create bookmarks backup file: " + aFile.leafName);
|
||||
}
|
||||
|
||||
yield BookmarkJSONUtils.exportToFile(aFile);
|
||||
|
||||
if (aFile.parent.equals(this.folder)) {
|
||||
// Update internal cache.
|
||||
this.entries.push(aFile);
|
||||
} else {
|
||||
// If we are saving to a folder different than our backups folder, then
|
||||
// we also want to copy this new backup to it.
|
||||
// This way we ensure the latest valid backup is the same saved by the
|
||||
// user. See bug 424389.
|
||||
let latestBackup = this.getMostRecent("json");
|
||||
if (!latestBackup || latestBackup != aFile) {
|
||||
let name = this.getFilenameForDate();
|
||||
let file = this.folder.clone();
|
||||
file.append(name);
|
||||
if (file.exists()) {
|
||||
file.remove(false);
|
||||
} else {
|
||||
// Update internal cache if we are not replacing an existing
|
||||
// backup file.
|
||||
this.entries.push(file);
|
||||
}
|
||||
aFile.copyTo(this.folder, name);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a dated backup in <profile>/bookmarkbackups.
|
||||
* Stores the bookmarks using JSON.
|
||||
* Note: any item that should not be backed up must be annotated with
|
||||
* "places/excludeFromBackup".
|
||||
*
|
||||
* @param [optional] int aMaxBackups
|
||||
* The maximum number of backups to keep.
|
||||
* @param [optional] bool aForceBackup
|
||||
* Forces creating a backup even if one was already
|
||||
* created that day (overwrites).
|
||||
* @return {Promise}
|
||||
*/
|
||||
create: function PB_create(aMaxBackups, aForceBackup) {
|
||||
return Task.spawn(function() {
|
||||
// Construct the new leafname.
|
||||
let newBackupFilename = this.getFilenameForDate();
|
||||
let mostRecentBackupFile = this.getMostRecent();
|
||||
|
||||
if (!aForceBackup) {
|
||||
let numberOfBackupsToDelete = 0;
|
||||
if (aMaxBackups !== undefined && aMaxBackups > -1)
|
||||
numberOfBackupsToDelete = this.entries.length - aMaxBackups;
|
||||
|
||||
if (numberOfBackupsToDelete > 0) {
|
||||
// If we don't have today's backup, remove one more so that
|
||||
// the total backups after this operation does not exceed the
|
||||
// number specified in the pref.
|
||||
if (!mostRecentBackupFile ||
|
||||
mostRecentBackupFile.leafName != newBackupFilename)
|
||||
numberOfBackupsToDelete++;
|
||||
|
||||
while (numberOfBackupsToDelete--) {
|
||||
let oldestBackup = this.entries.pop();
|
||||
oldestBackup.remove(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Do nothing if we already have this backup or we don't want backups.
|
||||
if (aMaxBackups === 0 ||
|
||||
(mostRecentBackupFile &&
|
||||
mostRecentBackupFile.leafName == newBackupFilename))
|
||||
return;
|
||||
}
|
||||
|
||||
let newBackupFile = this.folder.clone();
|
||||
newBackupFile.append(newBackupFilename);
|
||||
|
||||
if (aForceBackup && newBackupFile.exists())
|
||||
newBackupFile.remove(false);
|
||||
|
||||
if (newBackupFile.exists())
|
||||
return;
|
||||
|
||||
yield this.saveBookmarksToJSONFile(newBackupFile);
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
|
@ -1162,6 +1162,121 @@ this.PlacesUtils = {
|
|||
return urls;
|
||||
},
|
||||
|
||||
/**
|
||||
* Import bookmarks from a JSON string.
|
||||
* Note: any item annotated with "places/excludeFromBackup" won't be removed
|
||||
* before executing the restore.
|
||||
*
|
||||
* @param aString
|
||||
* JSON string of serialized bookmark data.
|
||||
* @param aReplace
|
||||
* Boolean if true, replace existing bookmarks, else merge.
|
||||
*/
|
||||
restoreBookmarksFromJSONString:
|
||||
function PU_restoreBookmarksFromJSONString(aString, aReplace) {
|
||||
// convert string to JSON
|
||||
var nodes = this.unwrapNodes(aString, this.TYPE_X_MOZ_PLACE_CONTAINER);
|
||||
|
||||
if (nodes.length == 0 || !nodes[0].children ||
|
||||
nodes[0].children.length == 0)
|
||||
return; // nothing to restore
|
||||
|
||||
// ensure tag folder gets processed last
|
||||
nodes[0].children.sort(function sortRoots(aNode, bNode) {
|
||||
return (aNode.root && aNode.root == "tagsFolder") ? 1 :
|
||||
(bNode.root && bNode.root == "tagsFolder") ? -1 : 0;
|
||||
});
|
||||
|
||||
var batch = {
|
||||
nodes: nodes[0].children,
|
||||
runBatched: function restore_runBatched() {
|
||||
if (aReplace) {
|
||||
// Get roots excluded from the backup, we will not remove them
|
||||
// before restoring.
|
||||
var excludeItems = PlacesUtils.annotations.getItemsWithAnnotation(
|
||||
PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO
|
||||
);
|
||||
// delete existing children of the root node, excepting:
|
||||
// 1. special folders: delete the child nodes
|
||||
// 2. tags folder: untag via the tagging api
|
||||
var query = PlacesUtils.history.getNewQuery();
|
||||
query.setFolders([PlacesUtils.placesRootId], 1);
|
||||
var options = PlacesUtils.history.getNewQueryOptions();
|
||||
options.expandQueries = false;
|
||||
var root = PlacesUtils.history.executeQuery(query, options).root;
|
||||
root.containerOpen = true;
|
||||
var childIds = [];
|
||||
for (var i = 0; i < root.childCount; i++) {
|
||||
var childId = root.getChild(i).itemId;
|
||||
if (excludeItems.indexOf(childId) == -1 &&
|
||||
childId != PlacesUtils.tagsFolderId)
|
||||
childIds.push(childId);
|
||||
}
|
||||
root.containerOpen = false;
|
||||
|
||||
for (var i = 0; i < childIds.length; i++) {
|
||||
var rootItemId = childIds[i];
|
||||
if (PlacesUtils.isRootItem(rootItemId))
|
||||
PlacesUtils.bookmarks.removeFolderChildren(rootItemId);
|
||||
else
|
||||
PlacesUtils.bookmarks.removeItem(rootItemId);
|
||||
}
|
||||
}
|
||||
|
||||
var searchIds = [];
|
||||
var folderIdMap = [];
|
||||
|
||||
this.nodes.forEach(function(node) {
|
||||
if (!node.children || node.children.length == 0)
|
||||
return; // nothing to restore for this root
|
||||
|
||||
if (node.root) {
|
||||
var container = this.placesRootId; // default to places root
|
||||
switch (node.root) {
|
||||
case "bookmarksMenuFolder":
|
||||
container = this.bookmarksMenuFolderId;
|
||||
break;
|
||||
case "tagsFolder":
|
||||
container = this.tagsFolderId;
|
||||
break;
|
||||
case "unfiledBookmarksFolder":
|
||||
container = this.unfiledBookmarksFolderId;
|
||||
break;
|
||||
case "toolbarFolder":
|
||||
container = this.toolbarFolderId;
|
||||
break;
|
||||
}
|
||||
|
||||
// insert the data into the db
|
||||
node.children.forEach(function(child) {
|
||||
var index = child.index;
|
||||
var [folders, searches] = this.importJSONNode(child, container, index, 0);
|
||||
for (var i = 0; i < folders.length; i++) {
|
||||
if (folders[i])
|
||||
folderIdMap[i] = folders[i];
|
||||
}
|
||||
searchIds = searchIds.concat(searches);
|
||||
}, this);
|
||||
}
|
||||
else {
|
||||
this.importJSONNode(node, this.placesRootId, node.index, 0);
|
||||
}
|
||||
}, PlacesUtils);
|
||||
|
||||
// fixup imported place: uris that contain folders
|
||||
searchIds.forEach(function(aId) {
|
||||
var oldURI = this.bookmarks.getBookmarkURI(aId);
|
||||
var uri = this._fixupQuery(this.bookmarks.getBookmarkURI(aId),
|
||||
folderIdMap);
|
||||
if (!uri.equals(oldURI))
|
||||
this.bookmarks.changeBookmarkURI(aId, uri);
|
||||
}, PlacesUtils);
|
||||
}
|
||||
};
|
||||
|
||||
this.bookmarks.runInBatchMode(batch, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a JSON-serialized node and inserts it into the db.
|
||||
*
|
||||
|
@ -1307,6 +1422,25 @@ this.PlacesUtils = {
|
|||
return [folderIdMap, searchIds];
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces imported folder ids with their local counterparts in a place: URI.
|
||||
*
|
||||
* @param aURI
|
||||
* A place: URI with folder ids.
|
||||
* @param aFolderIdMap
|
||||
* An array mapping old folder id to new folder ids.
|
||||
* @returns the fixed up URI if all matched. If some matched, it returns
|
||||
* the URI with only the matching folders included. If none matched it
|
||||
* returns the input URI unchanged.
|
||||
*/
|
||||
_fixupQuery: function PU__fixupQuery(aQueryURI, aFolderIdMap) {
|
||||
function convert(str, p1, offset, s) {
|
||||
return "folder=" + aFolderIdMap[p1];
|
||||
}
|
||||
var stringURI = aQueryURI.spec.replace(/folder=([0-9]+)/g, convert);
|
||||
return this._uri(stringURI);
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes the given node (and all its descendents) as JSON
|
||||
* and writes the serialization to the given output stream.
|
||||
|
@ -1519,6 +1653,13 @@ this.PlacesUtils = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Serialize a JS object to JSON
|
||||
*/
|
||||
toJSONString: function PU_toJSONString(aObj) {
|
||||
return JSON.stringify(aObj);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores bookmarks and tags from a JSON file.
|
||||
* WARNING: This method *removes* any bookmarks in the collection before
|
||||
|
@ -1529,11 +1670,50 @@ this.PlacesUtils = {
|
|||
*/
|
||||
restoreBookmarksFromJSONFile:
|
||||
function PU_restoreBookmarksFromJSONFile(aFile) {
|
||||
Deprecated.warning(
|
||||
"restoreBookmarksFromJSONFile is deprecated and will be removed in a future version",
|
||||
"https://bugzilla.mozilla.org/show_bug.cgi?id=854388");
|
||||
const RESTORE_NSIOBSERVER_DATA = "json";
|
||||
|
||||
BookmarkJSONUtils.importFromFile(aFile, true);
|
||||
let failed = false;
|
||||
Services.obs.notifyObservers(null,
|
||||
this.TOPIC_BOOKMARKS_RESTORE_BEGIN,
|
||||
RESTORE_NSIOBSERVER_DATA);
|
||||
|
||||
try {
|
||||
// open file stream
|
||||
var stream = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
stream.init(aFile, 0x01, 0, 0);
|
||||
var converted = Cc["@mozilla.org/intl/converter-input-stream;1"].
|
||||
createInstance(Ci.nsIConverterInputStream);
|
||||
converted.init(stream, "UTF-8", 8192,
|
||||
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
|
||||
// read in contents
|
||||
var str = {};
|
||||
var jsonStr = "";
|
||||
while (converted.readString(8192, str) != 0)
|
||||
jsonStr += str.value;
|
||||
converted.close();
|
||||
|
||||
if (jsonStr.length == 0)
|
||||
return; // empty file
|
||||
|
||||
this.restoreBookmarksFromJSONString(jsonStr, true);
|
||||
}
|
||||
catch (exc) {
|
||||
failed = true;
|
||||
Services.obs.notifyObservers(null,
|
||||
this.TOPIC_BOOKMARKS_RESTORE_FAILED,
|
||||
RESTORE_NSIOBSERVER_DATA);
|
||||
Cu.reportError("Bookmarks JSON restore failed: " + exc);
|
||||
throw exc;
|
||||
}
|
||||
finally {
|
||||
if (!failed) {
|
||||
Services.obs.notifyObservers(null,
|
||||
this.TOPIC_BOOKMARKS_RESTORE_SUCCESS,
|
||||
RESTORE_NSIOBSERVER_DATA);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче