PageRenderTime 71ms CodeModel.GetById 16ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 0ms

/services/sync/tps/extensions/tps/modules/bookmarks.jsm

http://github.com/zpao/v8monkey
Unknown | 1037 lines | 977 code | 60 blank | 0 comment | 0 complexity | 2e6ffaeef13e0ef68aa389f7a2ecd2f2 MD5 | raw file
   1/* ***** BEGIN LICENSE BLOCK *****
   2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
   3 *
   4 * The contents of this file are subject to the Mozilla Public License Version
   5 * 1.1 (the "License"); you may not use this file except in compliance with
   6 * the License. You may obtain a copy of the License at
   7 * http://www.mozilla.org/MPL/
   8 *
   9 * Software distributed under the License is distributed on an "AS IS" basis,
  10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11 * for the specific language governing rights and limitations under the
  12 * License.
  13 *
  14 * The Original Code is Crossweave.
  15 *
  16 * The Initial Developer of the Original Code is Mozilla.
  17 * Portions created by the Initial Developer are Copyright (C) 2010
  18 * the Initial Developer. All Rights Reserved.
  19 *
  20 * Contributor(s):
  21 *   Jonathan Griffin <jgriffin@mozilla.com>
  22 *   Philipp von Weitershausen <philipp@weitershausen.de>
  23 *
  24 * Alternatively, the contents of this file may be used under the terms of
  25 * either the GNU General Public License Version 2 or later (the "GPL"), or
  26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27 * in which case the provisions of the GPL or the LGPL are applicable instead
  28 * of those above. If you wish to allow use of your version of this file only
  29 * under the terms of either the GPL or the LGPL, and not to allow others to
  30 * use your version of this file under the terms of the MPL, indicate your
  31 * decision by deleting the provisions above and replace them with the notice
  32 * and other provisions required by the GPL or the LGPL. If you do not delete
  33 * the provisions above, a recipient may use your version of this file under
  34 * the terms of any one of the MPL, the GPL or the LGPL.
  35 *
  36 * ***** END LICENSE BLOCK ***** */
  37
  38 /* This is a JavaScript module (JSM) to be imported via
  39  * Components.utils.import() and acts as a singleton. Only the following
  40  * listed symbols will exposed on import, and only when and where imported.
  41  */
  42
  43var EXPORTED_SYMBOLS = ["PlacesItem", "Bookmark", "Separator", "Livemark",
  44                        "BookmarkFolder", "DumpBookmarks"];
  45
  46const CC = Components.classes;
  47const CI = Components.interfaces;
  48const CU = Components.utils;
  49
  50CU.import("resource://tps/logger.jsm");
  51CU.import("resource://gre/modules/Services.jsm");
  52CU.import("resource://gre/modules/PlacesUtils.jsm");
  53
  54
  55var DumpBookmarks = function TPS_Bookmarks__DumpBookmarks() {
  56  let writer = {
  57    value: "",
  58    write: function PlacesItem__dump__write(aStr, aLen) {
  59      this.value += aStr;
  60    }
  61  };
  62
  63  let options = PlacesUtils.history.getNewQueryOptions();
  64  options.queryType = options.QUERY_TYPE_BOOKMARKS;
  65  let query = PlacesUtils.history.getNewQuery();
  66  query.setFolders([PlacesUtils.placesRootId], 1);
  67  let root = PlacesUtils.history.executeQuery(query, options).root;
  68  root.containerOpen = true;
  69  PlacesUtils.serializeNodeAsJSONToOutputStream(root, writer, true, false);
  70  let value = JSON.parse(writer.value);
  71  Logger.logInfo("dumping bookmarks\n\n" + JSON.stringify(value, null, ' ') + "\n\n");
  72};
  73
  74/**
  75 * extend, causes a child object to inherit from a parent
  76 */
  77function extend(child, supertype)
  78{
  79   child.prototype.__proto__ = supertype.prototype;
  80}
  81
  82/**
  83 * PlacesItemProps object, holds properties for places items
  84 */
  85function PlacesItemProps(props) {
  86  this.location = null;
  87  this.uri = null;
  88  this.loadInSidebar = null;
  89  this.keyword = null;
  90  this.title = null;
  91  this.description = null;
  92  this.after = null;
  93  this.before = null;
  94  this.folder = null;
  95  this.position = null;
  96  this.delete = false;
  97  this.siteUri = null;
  98  this.feedUri = null;
  99  this.livemark = null;
 100  this.tags = null;
 101  this.last_item_pos = null;
 102  this.type = null;
 103
 104  for (var prop in props) {
 105    if (prop in this)
 106      this[prop] = props[prop];
 107  }
 108}
 109
 110/**
 111 * PlacesItem object.  Base class for places items.
 112 */
 113function PlacesItem(props) {
 114  this.props = new PlacesItemProps(props);
 115  if (this.props.location == null)
 116    this.props.location = "menu";
 117  if ("changes" in props)
 118    this.updateProps = new PlacesItemProps(props.changes);
 119  else
 120    this.updateProps = null;
 121}
 122
 123/**
 124 * Instance methods for generic places items.
 125 */
 126PlacesItem.prototype = {
 127  // an array of possible root folders for places items
 128  _bookmarkFolders: {
 129    "places": "placesRoot",
 130    "menu": "bookmarksMenuFolder",
 131    "tags": "tagFolder",
 132    "unfiled": "unfiledBookmarksFolder",
 133    "toolbar": "toolbarFolder",
 134  },
 135
 136  toString: function() {
 137    var that = this;
 138    var props = ['uri', 'title', 'location', 'folder', 'feedUri', 'siteUri', 'livemark'];
 139    var string = (this.props.type ? this.props.type + " " : "") +
 140      "(" +
 141      (function() {
 142        var ret = [];
 143        for (var i in props) {
 144          if (that.props[props[i]]) {
 145            ret.push(props[i] + ": " + that.props[props[i]])
 146          }
 147        }
 148        return ret;
 149      })().join(", ") + ")";
 150    return string;
 151  },
 152
 153  /**
 154   * GetPlacesNodeId
 155   *
 156   * Finds the id of the an item with the specified properties in the places
 157   * database.
 158   *
 159   * @param folder The id of the folder to search
 160   * @param type The type of the item to find, or null to match any item;
 161   *        this is one of the values listed at
 162   *        https://developer.mozilla.org/en/nsINavHistoryResultNode#Constants
 163   * @param title The title of the item to find, or null to match any title
 164   * @param uri The uri of the item to find, or null to match any uri
 165   *
 166   * @return the node id if the item was found, otherwise -1
 167   */
 168  GetPlacesNodeId: function (folder, type, title, uri) {
 169    let node_id = -1;
 170
 171    let options = PlacesUtils.history.getNewQueryOptions();
 172    let query = PlacesUtils.history.getNewQuery();
 173    query.setFolders([folder], 1);
 174    let result = PlacesUtils.history.executeQuery(query, options);
 175    let rootNode = result.root;
 176    rootNode.containerOpen = true;
 177
 178    for (let j = 0; j < rootNode.childCount; j ++) {
 179      let node = rootNode.getChild(j);
 180      if (node.title == title) {
 181        if (type == null || type == undefined || node.type == type)
 182          if (uri == undefined || uri == null || node.uri.spec == uri.spec)
 183            node_id = node.itemId;
 184      }
 185    }
 186    rootNode.containerOpen = false;
 187
 188    return node_id;
 189  },
 190
 191  /**
 192   * IsAdjacentTo
 193   *
 194   * Determines if this object is immediately adjacent to another.
 195   *
 196   * @param itemName The name of the other object; this may be any kind of
 197   *        places item
 198   * @param relativePos The relative position of the other object.  If -1,
 199   *        it means the other object should precede this one, if +1,
 200   *        the other object should come after this one
 201   * @return true if this object is immediately adjacent to the other object,
 202   *         otherwise false
 203   */
 204  IsAdjacentTo: function(itemName, relativePos) {
 205    Logger.AssertTrue(this.props.folder_id != -1 && this.props.item_id != -1,
 206      "Either folder_id or item_id was invalid");
 207    let other_id = this.GetPlacesNodeId(this.props.folder_id, null, itemName);
 208    Logger.AssertTrue(other_id != -1, "item " + itemName + " not found");
 209    let other_pos = PlacesUtils.bookmarks.getItemIndex(other_id);
 210    let this_pos = PlacesUtils.bookmarks.getItemIndex(this.props.item_id);
 211    if (other_pos + relativePos != this_pos) {
 212      Logger.logPotentialError("Invalid position - " +
 213       (this.props.title ? this.props.title : this.props.folder) +
 214      " not " + (relativePos == 1 ? "after " : "before ") + itemName +
 215      " for " + this.toString());
 216      return false;
 217    }
 218    return true;
 219  },
 220
 221  /**
 222   * GetItemIndex
 223   *
 224   * Gets the item index for this places item.
 225   *
 226   * @return the item index, or -1 if there's an error
 227   */
 228  GetItemIndex: function() {
 229    if (this.props.item_id == -1)
 230      return -1;
 231    return PlacesUtils.bookmarks.getItemIndex(this.props.item_id);
 232  },
 233
 234  /**
 235   * GetFolder
 236   *
 237   * Gets the folder id for the specified bookmark folder
 238   *
 239   * @param location The full path of the folder, which must begin
 240   *        with one of the bookmark root folders
 241   * @return the folder id if the folder is found, otherwise -1
 242   */
 243  GetFolder: function(location) {
 244    let folder_parts = location.split("/");
 245    if (!(folder_parts[0] in this._bookmarkFolders)) {
 246      return -1;
 247    }
 248    let folder_id = PlacesUtils.bookmarks[this._bookmarkFolders[folder_parts[0]]];
 249    for (let i = 1; i < folder_parts.length; i++) {
 250      let subfolder_id = this.GetPlacesNodeId(
 251        folder_id,
 252        CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
 253        folder_parts[i]);
 254      if (subfolder_id == -1) {
 255        return -1;
 256      }
 257      else {
 258        folder_id = subfolder_id;
 259      }
 260    }
 261    return folder_id;
 262  },
 263
 264  /**
 265   * CreateFolder
 266   *
 267   * Creates a bookmark folder.
 268   *
 269   * @param location The full path of the folder, which must begin
 270   *        with one of the bookmark root folders
 271   * @return the folder id if the folder was created, otherwise -1
 272   */
 273  CreateFolder: function(location) {
 274    let folder_parts = location.split("/");
 275    if (!(folder_parts[0] in this._bookmarkFolders)) {
 276      return -1;
 277    }
 278    let folder_id = PlacesUtils.bookmarks[this._bookmarkFolders[folder_parts[0]]];
 279    for (let i = 1; i < folder_parts.length; i++) {
 280      let subfolder_id = this.GetPlacesNodeId(
 281        folder_id,
 282        CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
 283        folder_parts[i]);
 284      if (subfolder_id == -1) {
 285        folder_id = PlacesUtils.bookmarks.createFolder(folder_id,
 286                                                 folder_parts[i], -1);
 287      }
 288      else {
 289        folder_id = subfolder_id;
 290      }
 291    }
 292    return folder_id;
 293  },
 294
 295  /**
 296   * GetOrCreateFolder
 297   *
 298   * Locates the specified folder; if not found it is created.
 299   *
 300   * @param location The full path of the folder, which must begin
 301   *        with one of the bookmark root folders
 302   * @return the folder id if the folder was found or created, otherwise -1
 303   */
 304  GetOrCreateFolder: function(location) {
 305    folder_id = this.GetFolder(location);
 306    if (folder_id == -1)
 307      folder_id = this.CreateFolder(location);
 308    return folder_id;
 309  },
 310
 311  /**
 312   * CheckDescription
 313   *
 314   * Compares the description of this places item with an expected
 315   * description.
 316   *
 317   * @param expectedDescription The description this places item is
 318   *        expected to have
 319   * @return true if the actual and expected descriptions match, or if
 320   *         there is no expected description; otherwise false
 321   */
 322  CheckDescription: function(expectedDescription) {
 323    if (expectedDescription != null) {
 324      let description = "";
 325      if (PlacesUtils.annotations.itemHasAnnotation(this.props.item_id,
 326          "bookmarkProperties/description")) {
 327        description = PlacesUtils.annotations.getItemAnnotation(
 328          this.props.item_id, "bookmarkProperties/description");
 329      }
 330      if (description != expectedDescription) {
 331        Logger.logPotentialError("Invalid description, expected: " +
 332          expectedDescription + ", actual: " + description + " for " +
 333          this.toString());
 334        return false;
 335      }
 336    }
 337    return true;
 338  },
 339
 340  /**
 341   * CheckPosition
 342   *
 343   * Verifies the position of this places item.
 344   *
 345   * @param before The name of the places item that this item should be
 346            before, or null if this check should be skipped
 347   * @param after The name of the places item that this item should be
 348            after, or null if this check should be skipped
 349   * @param last_item_pos The index of the places item above this one,
 350   *        or null if this check should be skipped
 351   * @return true if this item is in the correct position, otherwise false
 352   */
 353  CheckPosition: function(before, after, last_item_pos) {
 354    if (after)
 355      if (!this.IsAdjacentTo(after, 1)) return false;
 356    if (before)
 357      if (!this.IsAdjacentTo(before, -1)) return false;
 358    if (last_item_pos != null && last_item_pos > -1) {
 359      if (this.GetItemIndex() != last_item_pos + 1) {
 360        Logger.logPotentialError("Item not found at the expected index, got " +
 361          this.GetItemIndex() + ", expected " + (last_item_pos + 1) + " for " +
 362          this.toString());
 363        return false;
 364      }
 365    }
 366    return true;
 367  },
 368
 369  /**
 370   * SetLocation
 371   *
 372   * Moves this places item to a different folder.
 373   *
 374   * @param location The full path of the folder to which to move this
 375   *        places item, which must begin with one of the bookmark root
 376   *        folders; if null, no changes are made
 377   * @return nothing if successful, otherwise an exception is thrown
 378   */
 379  SetLocation: function(location) {
 380    if (location != null) {
 381      let newfolder_id = this.GetOrCreateFolder(location);
 382      Logger.AssertTrue(newfolder_id != -1, "Location " + location +
 383                        " doesn't exist; can't change item's location");
 384      PlacesUtils.bookmarks.moveItem(this.props.item_id, newfolder_id, -1);
 385      this.props.folder_id = newfolder_id;
 386    }
 387  },
 388
 389  /**
 390   * SetDescription
 391   *
 392   * Updates the description for this places item.
 393   *
 394   * @param description The new description to set; if null, no changes are
 395   *        made
 396   * @return nothing
 397   */
 398  SetDescription: function(description) {
 399    if (description != null) {
 400      if (description != "")
 401        PlacesUtils.annotations.setItemAnnotation(this.props.item_id,
 402                                      "bookmarkProperties/description",
 403                                      description,
 404                                      0,
 405                                      PlacesUtils.annotations.EXPIRE_NEVER);
 406      else
 407        PlacesUtils.annotations.removeItemAnnotation(this.props.item_id,
 408                                         "bookmarkProperties/description");
 409    }
 410  },
 411
 412  /**
 413   * SetPosition
 414   *
 415   * Updates the position of this places item within this item's current
 416   * folder.  Use SetLocation to change folders.
 417   *
 418   * @param position The new index this item should be moved to; if null,
 419   *        no changes are made; if -1, this item is moved to the bottom of
 420   *        the current folder
 421   * @return nothing if successful, otherwise an exception is thrown
 422   */
 423  SetPosition: function(position) {
 424    if (position != null) {
 425      let newposition = -1;
 426      if (position != -1) {
 427        newposition = this.GetPlacesNodeId(this.props.folder_id,
 428                                           null, position);
 429        Logger.AssertTrue(newposition != -1, "position " + position +
 430                          " is invalid; unable to change position");
 431        newposition = PlacesUtils.bookmarks.getItemIndex(newposition);
 432      }
 433      PlacesUtils.bookmarks.moveItem(this.props.item_id,
 434                               this.props.folder_id, newposition);
 435    }
 436  },
 437
 438  /**
 439   * Update the title of this places item
 440   *
 441   * @param title The new title to set for this item; if null, no changes
 442   *        are made
 443   * @return nothing
 444   */
 445  SetTitle: function(title) {
 446    if (title != null) {
 447      PlacesUtils.bookmarks.setItemTitle(this.props.item_id, title);
 448    }
 449  },
 450};
 451
 452/**
 453 * Bookmark class constructor.  Initializes instance properties.
 454 */
 455function Bookmark(props) {
 456  PlacesItem.call(this, props);
 457  if (this.props.title == null)
 458    this.props.title = this.props.uri;
 459  this.props.type = "bookmark";
 460}
 461
 462/**
 463 * Bookmark instance methods.
 464 */
 465Bookmark.prototype = {
 466  /**
 467   * SetKeyword
 468   *
 469   * Update this bookmark's keyword.
 470   *
 471   * @param keyword The keyword to set for this bookmark; if null, no
 472   *        changes are made
 473   * @return nothing
 474   */
 475  SetKeyword: function(keyword) {
 476    if (keyword != null)
 477      PlacesUtils.bookmarks.setKeywordForBookmark(this.props.item_id, keyword);
 478  },
 479
 480  /**
 481   * SetLoadInSidebar
 482   *
 483   * Updates this bookmark's loadInSidebar property.
 484   *
 485   * @param loadInSidebar if true, the loadInSidebar property will be set,
 486   *        if false, it will be cleared, and any other value will result
 487   *        in no change
 488   * @return nothing
 489   */
 490  SetLoadInSidebar: function(loadInSidebar) {
 491    if (loadInSidebar == true)
 492      PlacesUtils.annotations.setItemAnnotation(this.props.item_id,
 493                                    "bookmarkProperties/loadInSidebar",
 494                                    true,
 495                                    0,
 496                                    PlacesUtils.annotations.EXPIRE_NEVER);
 497    else if (loadInSidebar == false)
 498      PlacesUtils.annotations.removeItemAnnotation(this.props.item_id,
 499                                       "bookmarkProperties/loadInSidebar");
 500  },
 501
 502  /**
 503   * SetTitle
 504   *
 505   * Updates this bookmark's title.
 506   *
 507   * @param title The new title to set for this boomark; if null, no changes
 508   *        are made
 509   * @return nothing
 510   */
 511  SetTitle: function(title) {
 512    if (title)
 513      PlacesUtils.bookmarks.setItemTitle(this.props.item_id, title);
 514  },
 515
 516  /**
 517   * SetUri
 518   *
 519   * Updates this bookmark's URI.
 520   *
 521   * @param uri The new URI to set for this boomark; if null, no changes
 522   *        are made
 523   * @return nothing
 524   */
 525  SetUri: function(uri) {
 526    if (uri) {
 527      let newURI = Services.io.newURI(uri, null, null);
 528      PlacesUtils.bookmarks.changeBookmarkURI(this.props.item_id, newURI);
 529    }
 530  },
 531
 532  /**
 533   * SetTags
 534   *
 535   * Updates this bookmark's tags.
 536   *
 537   * @param tags An array of tags which should be associated with this
 538   *        bookmark; any previous tags are removed; if this param is null,
 539   *        no changes are made.  If this param is an empty array, all
 540   *        tags are removed from this bookmark.
 541   * @return nothing
 542   */
 543  SetTags: function(tags) {
 544    if (tags != null) {
 545      let URI = Services.io.newURI(this.props.uri, null, null);
 546      PlacesUtils.tagging.untagURI(URI, null);
 547      if (tags.length > 0)
 548        PlacesUtils.tagging.tagURI(URI, tags);
 549    }
 550  },
 551
 552  /**
 553   * Create
 554   *
 555   * Creates the bookmark described by this object's properties.
 556   *
 557   * @return the id of the created bookmark
 558   */
 559  Create: function() {
 560    this.props.folder_id = this.GetOrCreateFolder(this.props.location);
 561    Logger.AssertTrue(this.props.folder_id != -1, "Unable to create " +
 562      "bookmark, error creating folder " + this.props.location);
 563    let bookmarkURI = Services.io.newURI(this.props.uri, null, null);
 564    this.props.item_id = PlacesUtils.bookmarks.insertBookmark(this.props.folder_id,
 565                                                        bookmarkURI,
 566                                                        -1,
 567                                                        this.props.title);
 568    this.SetKeyword(this.props.keyword);
 569    this.SetDescription(this.props.description);
 570    this.SetLoadInSidebar(this.props.loadInSidebar);
 571    this.SetTags(this.props.tags);
 572    return this.props.item_id;
 573  },
 574
 575  /**
 576   * Update
 577   *
 578   * Updates this bookmark's properties according the properties on this
 579   * object's 'updateProps' property.
 580   *
 581   * @return nothing
 582   */
 583  Update: function() {
 584    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
 585      "Invalid item_id during Remove");
 586    this.SetKeyword(this.updateProps.keyword);
 587    this.SetDescription(this.updateProps.description);
 588    this.SetLoadInSidebar(this.updateProps.loadInSidebar);
 589    this.SetTitle(this.updateProps.title);
 590    this.SetUri(this.updateProps.uri);
 591    this.SetTags(this.updateProps.tags);
 592    this.SetLocation(this.updateProps.location);
 593    this.SetPosition(this.updateProps.position);
 594  },
 595
 596  /**
 597   * Find
 598   *
 599   * Locates the bookmark which corresponds to this object's properties.
 600   *
 601   * @return the bookmark id if the bookmark was found, otherwise -1
 602   */
 603  Find: function() {
 604    this.props.folder_id = this.GetFolder(this.props.location);
 605    if (this.props.folder_id == -1) {
 606      Logger.logError("Unable to find folder " + this.props.location);
 607      return -1;
 608    }
 609    let bookmarkTitle = this.props.title;
 610    this.props.item_id = this.GetPlacesNodeId(this.props.folder_id,
 611                                              null,
 612                                              bookmarkTitle,
 613                                              this.props.uri);
 614
 615    if (this.props.item_id == -1) {
 616      Logger.logPotentialError(this.toString() + " not found");
 617      return -1;
 618    }
 619    if (!this.CheckDescription(this.props.description))
 620      return -1;
 621    if (this.props.keyword != null) {
 622      let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this.props.item_id);
 623      if (keyword != this.props.keyword) {
 624        Logger.logPotentialError("Incorrect keyword - expected: " +
 625          this.props.keyword + ", actual: " + keyword +
 626          " for " + this.toString());
 627        return -1;
 628      }
 629    }
 630    let loadInSidebar = PlacesUtils.annotations.itemHasAnnotation(
 631      this.props.item_id,
 632      "bookmarkProperties/loadInSidebar");
 633    if (loadInSidebar)
 634      loadInSidebar = PlacesUtils.annotations.getItemAnnotation(
 635        this.props.item_id,
 636        "bookmarkProperties/loadInSidebar");
 637    if (this.props.loadInSidebar != null &&
 638        loadInSidebar != this.props.loadInSidebar) {
 639      Logger.logPotentialError("Incorrect loadInSidebar setting - expected: " +
 640        this.props.loadInSidebar + ", actual: " + loadInSidebar +
 641        " for " + this.toString());
 642      return -1;
 643    }
 644    if (this.props.tags != null) {
 645      try {
 646        let URI = Services.io.newURI(this.props.uri, null, null);
 647        let tags = PlacesUtils.tagging.getTagsForURI(URI, {});
 648        tags.sort();
 649        this.props.tags.sort();
 650        if (JSON.stringify(tags) != JSON.stringify(this.props.tags)) {
 651          Logger.logPotentialError("Wrong tags - expected: " +
 652            JSON.stringify(this.props.tags) + ", actual: " +
 653            JSON.stringify(tags) + " for " + this.toString());
 654          return -1;
 655        }
 656      }
 657      catch (e) {
 658        Logger.logPotentialError("error processing tags " + e);
 659        return -1;
 660      }
 661    }
 662    if (!this.CheckPosition(this.props.before,
 663                            this.props.after,
 664                            this.props.last_item_pos))
 665      return -1;
 666    return this.props.item_id;
 667  },
 668
 669  /**
 670   * Remove
 671   *
 672   * Removes this bookmark.  The bookmark should have been located previously
 673   * by a call to Find.
 674   *
 675   * @return nothing
 676   */
 677  Remove: function() {
 678    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
 679      "Invalid item_id during Remove");
 680    PlacesUtils.bookmarks.removeItem(this.props.item_id);
 681  },
 682};
 683
 684extend(Bookmark, PlacesItem);
 685
 686/**
 687 * BookmarkFolder class constructor. Initializes instance properties.
 688 */
 689function BookmarkFolder(props) {
 690  PlacesItem.call(this, props);
 691  this.props.type = "folder";
 692}
 693
 694/**
 695 * BookmarkFolder instance methods
 696 */
 697BookmarkFolder.prototype = {
 698  /**
 699   * Create
 700   *
 701   * Creates the bookmark folder described by this object's properties.
 702   *
 703   * @return the id of the created bookmark folder
 704   */
 705  Create: function() {
 706    this.props.folder_id = this.GetOrCreateFolder(this.props.location);
 707    Logger.AssertTrue(this.props.folder_id != -1, "Unable to create " +
 708      "folder, error creating parent folder " + this.props.location);
 709    this.props.item_id = PlacesUtils.bookmarks.createFolder(this.props.folder_id,
 710                                                      this.props.folder,
 711                                                      -1);
 712    this.SetDescription(this.props.description);
 713    return this.props.folder_id;
 714  },
 715
 716  /**
 717   * Find
 718   *
 719   * Locates the bookmark folder which corresponds to this object's
 720   * properties.
 721   *
 722   * @return the folder id if the folder was found, otherwise -1
 723   */
 724  Find: function() {
 725    this.props.folder_id = this.GetFolder(this.props.location);
 726    if (this.props.folder_id == -1) {
 727      Logger.logError("Unable to find folder " + this.props.location);
 728      return -1;
 729    }
 730    this.props.item_id = this.GetPlacesNodeId(
 731                              this.props.folder_id,
 732                              CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
 733                              this.props.folder);
 734    if (!this.CheckDescription(this.props.description))
 735      return -1;
 736    if (!this.CheckPosition(this.props.before,
 737                            this.props.after,
 738                            this.props.last_item_pos))
 739      return -1;
 740    return this.props.item_id;
 741  },
 742
 743  /**
 744   * Remove
 745   *
 746   * Removes this folder.  The folder should have been located previously
 747   * by a call to Find.
 748   *
 749   * @return nothing
 750   */
 751  Remove: function() {
 752    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
 753      "Invalid item_id during Remove");
 754    PlacesUtils.bookmarks.removeFolderChildren(this.props.item_id);
 755    PlacesUtils.bookmarks.removeItem(this.props.item_id);
 756  },
 757
 758  /**
 759   * Update
 760   *
 761   * Updates this bookmark's properties according the properties on this
 762   * object's 'updateProps' property.
 763   *
 764   * @return nothing
 765   */
 766  Update: function() {
 767    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
 768      "Invalid item_id during Update");
 769    this.SetLocation(this.updateProps.location);
 770    this.SetPosition(this.updateProps.position);
 771    this.SetTitle(this.updateProps.folder);
 772    this.SetDescription(this.updateProps.description);
 773  },
 774};
 775
 776extend(BookmarkFolder, PlacesItem);
 777
 778/**
 779 * Livemark class constructor. Initialzes instance properties.
 780 */
 781function Livemark(props) {
 782  PlacesItem.call(this, props);
 783  this.props.type = "livemark";
 784}
 785
 786/**
 787 * Livemark instance methods
 788 */
 789Livemark.prototype = {
 790  /**
 791   * Create
 792   *
 793   * Creates the livemark described by this object's properties.
 794   *
 795   * @return the id of the created livemark
 796   */
 797  Create: function() {
 798    this.props.folder_id = this.GetOrCreateFolder(this.props.location);
 799    Logger.AssertTrue(this.props.folder_id != -1, "Unable to create " +
 800      "folder, error creating parent folder " + this.props.location);
 801    let siteURI = null;
 802    if (this.props.siteUri != null)
 803      siteURI = Services.io.newURI(this.props.siteUri, null, null);
 804    this.props.item_id = PlacesUtils.livemarks.createLivemark(
 805        this.props.folder_id,
 806        this.props.livemark,
 807        siteURI,
 808        Services.io.newURI(this.props.feedUri, null, null),
 809        -1);
 810    return this.props.item_id;
 811  },
 812
 813  /**
 814   * Find
 815   *
 816   * Locates the livemark which corresponds to this object's
 817   * properties.
 818   *
 819   * @return the item id if the livemark was found, otherwise -1
 820   */
 821  Find: function() {
 822    this.props.folder_id = this.GetFolder(this.props.location);
 823    if (this.props.folder_id == -1) {
 824      Logger.logError("Unable to find folder " + this.props.location);
 825      return -1;
 826    }
 827    this.props.item_id = this.GetPlacesNodeId(
 828                              this.props.folder_id,
 829                              CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
 830                              this.props.livemark);
 831    if (!PlacesUtils.livemarks.isLivemark(this.props.item_id)) {
 832      Logger.logPotentialError("livemark folder found, but it's just a regular folder, for " +
 833        this.toString());
 834      this.props.item_id = -1;
 835      return -1;
 836    }
 837    let feedURI = Services.io.newURI(this.props.feedUri, null, null);
 838    let lmFeedURI = PlacesUtils.livemarks.getFeedURI(this.props.item_id);
 839    if (feedURI.spec != lmFeedURI.spec) {
 840      Logger.logPotentialError("livemark feed uri not correct, expected: " +
 841        this.props.feedUri + ", actual: " + lmFeedURI.spec +
 842        " for " + this.toString());
 843      return -1;
 844    }
 845    if (this.props.siteUri != null) {
 846      let siteURI = Services.io.newURI(this.props.siteUri, null, null);
 847      let lmSiteURI = PlacesUtils.livemarks.getSiteURI(this.props.item_id);
 848      if (siteURI.spec != lmSiteURI.spec) {
 849        Logger.logPotentialError("livemark site uri not correct, expected: " +
 850        this.props.siteUri + ", actual: " + lmSiteURI.spec + " for " +
 851        this.toString());
 852        return -1;
 853      }
 854    }
 855    if (!this.CheckPosition(this.props.before,
 856                            this.props.after,
 857                            this.props.last_item_pos))
 858      return -1;
 859    return this.props.item_id;
 860  },
 861
 862  /**
 863   * SetSiteUri
 864   *
 865   * Sets the siteURI property for this livemark.
 866   *
 867   * @param siteUri the URI to set; if null, no changes are made
 868   * @return nothing
 869   */
 870  SetSiteUri: function(siteUri) {
 871    if (siteUri) {
 872      let siteURI = Services.io.newURI(siteUri, null, null);
 873      PlacesUtils.livemarks.setSiteURI(this.props.item_id, siteURI);
 874    }
 875  },
 876
 877  /**
 878   * SetFeedUri
 879   *
 880   * Sets the feedURI property for this livemark.
 881   *
 882   * @param feedUri the URI to set; if null, no changes are made
 883   * @return nothing
 884   */
 885  SetFeedUri: function(feedUri) {
 886    if (feedUri) {
 887      let feedURI = Services.io.newURI(feedUri, null, null);
 888      PlacesUtils.livemarks.setFeedURI(this.props.item_id, feedURI);
 889    }
 890  },
 891
 892  /**
 893   * Update
 894   *
 895   * Updates this livemark's properties according the properties on this
 896   * object's 'updateProps' property.
 897   *
 898   * @return nothing
 899   */
 900  Update: function() {
 901    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
 902      "Invalid item_id during Update");
 903    this.SetLocation(this.updateProps.location);
 904    this.SetPosition(this.updateProps.position);
 905    this.SetSiteUri(this.updateProps.siteUri);
 906    this.SetFeedUri(this.updateProps.feedUri);
 907    this.SetTitle(this.updateProps.livemark);
 908    return true;
 909  },
 910
 911  /**
 912   * Remove
 913   *
 914   * Removes this livemark.  The livemark should have been located previously
 915   * by a call to Find.
 916   *
 917   * @return nothing
 918   */
 919  Remove: function() {
 920    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
 921      "Invalid item_id during Remove");
 922    PlacesUtils.bookmarks.removeItem(this.props.item_id);
 923  },
 924};
 925
 926extend(Livemark, PlacesItem);
 927
 928/**
 929 * Separator class constructor. Initializes instance properties.
 930 */
 931function Separator(props) {
 932  PlacesItem.call(this, props);
 933  this.props.type = "separator";
 934}
 935
 936/**
 937 * Separator instance methods.
 938 */
 939Separator.prototype = {
 940  /**
 941   * Create
 942   *
 943   * Creates the bookmark separator described by this object's properties.
 944   *
 945   * @return the id of the created separator
 946   */
 947  Create: function () {
 948    this.props.folder_id = this.GetOrCreateFolder(this.props.location);
 949    Logger.AssertTrue(this.props.folder_id != -1, "Unable to create " +
 950      "folder, error creating parent folder " + this.props.location);
 951    this.props.item_id = PlacesUtils.bookmarks.insertSeparator(this.props.folder_id,
 952                                                         -1);
 953    return this.props.item_id;
 954  },
 955
 956  /**
 957   * Find
 958   *
 959   * Locates the bookmark separator which corresponds to this object's
 960   * properties.
 961   *
 962   * @return the item id if the separator was found, otherwise -1
 963   */
 964  Find: function () {
 965    this.props.folder_id = this.GetFolder(this.props.location);
 966    if (this.props.folder_id == -1) {
 967      Logger.logError("Unable to find folder " + this.props.location);
 968      return -1;
 969    }
 970    if (this.props.before == null && this.props.last_item_pos == null) {
 971      Logger.logPotentialError("Separator requires 'before' attribute if it's the" +
 972        "first item in the list");
 973      return -1;
 974    }
 975    let expected_pos = -1;
 976    if (this.props.before) {
 977      other_id = this.GetPlacesNodeId(this.props.folder_id,
 978                                      null,
 979                                      this.props.before);
 980      if (other_id == -1) {
 981        Logger.logPotentialError("Can't find places item " + this.props.before +
 982          " for locating separator");
 983        return -1;
 984      }
 985      expected_pos = PlacesUtils.bookmarks.getItemIndex(other_id) - 1;
 986    }
 987    else {
 988      expected_pos = this.props.last_item_pos + 1;
 989    }
 990    this.props.item_id = PlacesUtils.bookmarks.getIdForItemAt(this.props.folder_id,
 991                                                        expected_pos);
 992    if (this.props.item_id == -1) {
 993      Logger.logPotentialError("No separator found at position " + expected_pos);
 994    }
 995    else {
 996      if (PlacesUtils.bookmarks.getItemType(this.props.item_id) !=
 997          PlacesUtils.bookmarks.TYPE_SEPARATOR) {
 998        Logger.logPotentialError("Places item at position " + expected_pos +
 999          " is not a separator");
1000        return -1;
1001      }
1002    }
1003    return this.props.item_id;
1004  },
1005
1006  /**
1007   * Update
1008   *
1009   * Updates this separator's properties according the properties on this
1010   * object's 'updateProps' property.
1011   *
1012   * @return nothing
1013   */
1014  Update: function() {
1015    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
1016      "Invalid item_id during Update");
1017    this.SetLocation(this.updateProps.location);
1018    this.SetPosition(this.updateProps.position);
1019    return true;
1020  },
1021
1022  /**
1023   * Remove
1024   *
1025   * Removes this separator.  The separator should have been located
1026   * previously by a call to Find.
1027   *
1028   * @return nothing
1029   */
1030  Remove: function() {
1031    Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null,
1032      "Invalid item_id during Update");
1033    PlacesUtils.bookmarks.removeItem(this.props.item_id);
1034  },
1035};
1036
1037extend(Separator, PlacesItem);