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 gContactSync. 15 * 16 * The Initial Developer of the Original Code is 17 * Josh Geenen <gcontactsync@pirules.org>. 18 * Portions created by the Initial Developer are Copyright (C) 2008-2009 19 * the Initial Developer. All Rights Reserved. 20 * 21 * Contributor(s): 22 * 23 * Alternatively, the contents of this file may be used under the terms of 24 * either the GNU General Public License Version 2 or later (the "GPL"), or 25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 26 * in which case the provisions of the GPL or the LGPL are applicable instead 27 * of those above. If you wish to allow use of your version of this file only 28 * under the terms of either the GPL or the LGPL, and not to allow others to 29 * use your version of this file under the terms of the MPL, indicate your 30 * decision by deleting the provisions above and replace them with the notice 31 * and other provisions required by the GPL or the LGPL. If you do not delete 32 * the provisions above, a recipient may use your version of this file under 33 * the terms of any one of the MPL, the GPL or the LGPL. 34 * 35 * ***** END LICENSE BLOCK ***** */ 36 37 if (!com) var com = {}; // A generic wrapper variable 38 // A wrapper for all GCS functions and variables 39 if (!com.gContactSync) com.gContactSync = {}; 40 41 /** 42 * A class for a Thunderbird Address Book with methods to add, modify, obtain, 43 * and delete cards. 44 * @param aDirectory {nsIAbDirectory} The actual directory. 45 * @constructor 46 * @class 47 */ 48 com.gContactSync.AddressBook = function gCS_AddressBook(aDirectory) { 49 this.mDirectory = aDirectory; 50 // make sure the directory is valid 51 if (!this.isDirectoryValid(this.mDirectory)) 52 throw "Invalid directory supplied to the AddressBook constructor" + 53 "\nCalled by: " + this.caller + 54 com.gContactSync.StringBundle.getStr("pleaseReport"); 55 // get the directory's URI 56 if (this.mDirectory.URI) 57 this.mURI = this.mDirectory.URI; 58 else { 59 this.mDirectory.QueryInterface(Components.interfaces.nsIAbMDBDirectory); 60 this.mURI = this.mDirectory.getDirUri(); 61 } 62 }; 63 64 com.gContactSync.AddressBook.prototype = { 65 /** The Uniform Resource Identifier (URI) of the directory */ 66 mURI: {}, 67 /** The cards within this address book */ 68 mContacts: [], 69 /** set to true when mContacts should be updated */ 70 mContactsUpdate: false, 71 /** 72 * Adds the contact to this address book and returns the added contact. 73 * @param aContact {TBContact} The contact to add. 74 * @returns {TBContact} The newly-added contact. 75 */ 76 addContact: function AddressBook_addContact(aContact) { 77 if (!(aContact instanceof com.gContactSync.TBContact)) { 78 throw "Invalid aContact sent to AddressBook.addContact"; 79 } 80 try { 81 var newContact = new com.gContactSync.TBContact(this.mDirectory.addCard(aContact.mContact), 82 this); 83 this.mContacts.push(newContact); 84 return newContact; 85 } 86 catch (e) { 87 com.gContactSync.LOGGER.LOG_ERROR("Unable to add card to the directory with URI: " + 88 this.mURI, e); 89 } 90 return null; 91 }, 92 /** 93 * Returns an array of all of the cards in this Address Book. 94 * @returns {array} An array of the TBContacts in this Address Book. 95 */ 96 getAllContacts: function AddressBook_getAllContacts() { 97 this.mContacts = []; 98 var iter = this.mDirectory.childCards, 99 data; 100 if (iter instanceof Components.interfaces.nsISimpleEnumerator) { // Thunderbird 3 101 while (iter.hasMoreElements()) { 102 data = iter.getNext(); 103 if (data instanceof Components.interfaces.nsIAbCard && !data.isMailList) 104 this.mContacts.push(new com.gContactSync.TBContact(data, this)); 105 } 106 } 107 else if (iter instanceof Components.interfaces.nsIEnumerator) { // TB 2 108 // use nsIEnumerator... 109 try { 110 iter.first(); 111 do { 112 data = iter.currentItem(); 113 if (data instanceof Components.interfaces.nsIAbCard && 114 !data.isMailList) 115 this.mContacts.push(new com.gContactSync.TBContact(data, this)); 116 iter.next(); 117 } while (Components.lastResult === 0); 118 // An error is expected when finished 119 } 120 catch (e) { 121 com.gContactSync.LOGGER.VERBOSE_LOG("(This error is expected): " + e); 122 } 123 } 124 else { 125 com.gContactSync.LOGGER.LOG_ERROR("Could not iterate through an address book's contacts"); 126 throw "Couldn't find an address book's contacts"; 127 } 128 return this.mContacts; 129 }, 130 /** 131 * Returns an an object containing MailList objects whose attribute name is 132 * the name of the mail list. 133 * @param skipGetCards {boolean} True to skip getting the cards of each list. 134 * @returns An object containing MailList objects. 135 */ 136 getAllLists: function AddressBook_getAllLists(skipGetCards) { 137 // same in Thunderbird 2 and 3 138 com.gContactSync.LOGGER.VERBOSE_LOG("Searching for mailing lists:"); 139 var iter = this.mDirectory.childNodes, 140 obj = {}, 141 list, 142 id, 143 data; 144 while (iter.hasMoreElements()) { 145 data = iter.getNext(); 146 if (data instanceof Components.interfaces.nsIAbDirectory && data.isMailList) { 147 list = this.newListObj(data, this, skipGetCards); 148 obj.push(list); 149 com.gContactSync.LOGGER.VERBOSE_LOG(" * " + list.getName() + " - " + id); 150 } 151 } 152 return obj; 153 }, 154 /** 155 * Finds and returns the first Mail List that matches the given nickname in 156 * this address book. 157 * @param aNickName {string} The nickname to search for. If null then this 158 * function returns nothing. 159 * @returns {MailList} A new MailList object containing a list that matches the 160 * nickname or nothing if the list wasn't found. 161 */ 162 getListByNickName: function AddressBook_getListByNickName(aNickName) { 163 if (!aNickName) 164 return null; 165 // same in Thunderbird 2 and 3 166 var iter = this.mDirectory.childNodes, 167 data; 168 while (iter.hasMoreElements()) { 169 data = iter.getNext(); 170 if (data instanceof Components.interfaces.nsIAbDirectory && data.isMailList && 171 data.listNickName === aNickName) { 172 return this.newListObj(data, this, true); 173 } 174 } 175 return null; 176 }, 177 /** 178 * Creates a new mail list, adds it to the address book, and returns a 179 * MailList object containing the list. 180 * @param aName {string} The new name for the mail list. 181 * @param aNickName {string} The nickname for the mail list. 182 * @returns {MailList} A new MailList object containing the newly-made List 183 * with the given name and nickname. 184 */ 185 addList: function AddressBook_addList(aName, aNickName) { 186 if (!aName) 187 throw "Error - aName sent to addList is invalid"; 188 if (!aNickName) 189 throw "Error - aNickName sent to addList is invalid"; 190 var list = Components.classes["@mozilla.org/addressbook/directoryproperty;1"] 191 .createInstance(Components.interfaces.nsIAbDirectory), 192 realList; 193 list.isMailList = true; 194 list.dirName = aName; 195 list.listNickName = aNickName; 196 this.mDirectory.addMailList(list); 197 // list can't be QI'd to an MDBDirectory, so the new list has to be found... 198 realList = this.getListByNickName(aNickName); 199 return realList; 200 }, 201 /** 202 * Deletes the nsIAbCards from the nsIAbDirectory Address Book. If the cards 203 * aren't in the book nothing will happen. 204 * @param aContacts {array} The cards to delete from the directory 205 */ 206 deleteContacts: function AddressBook_deleteContacts(aContacts) { 207 if (!(aContacts && aContacts.length > 0)) 208 return; 209 var arr, 210 i = 0; 211 if (com.gContactSync.AbManager.mVersion === 3) { // TB 3 212 arr = Components.classes["@mozilla.org/array;1"] 213 .createInstance(Components.interfaces.nsIMutableArray); 214 for (; i < aContacts.length; i++) { 215 if (aContacts[i] instanceof com.gContactSync.TBContact) { 216 arr.appendElement(aContacts[i].mContact, false); 217 } 218 else { 219 com.gContactSync.LOGGER.LOG_WARNING("Found an invalid contact sent " + 220 "AddressBook.deleteContacts"); 221 } 222 } 223 } 224 else { // TB 2 225 arr = Components.classes["@mozilla.org/supports-array;1"] 226 .createInstance(Components.interfaces.nsISupportsArray); 227 for (; i < aContacts.length; i++) { 228 if (aContacts[i] instanceof com.gContactSync.TBContact) { 229 arr.AppendElement(aContacts[i].mContact, false); 230 } 231 else { 232 com.gContactSync.LOGGER.LOG_WARNING("Found an invalid contact sent " + 233 "AddressBook.deleteContacts"); 234 } 235 } 236 } 237 try { 238 if (arr) { // make sure arr isn't null (mailnews bug 448165) 239 this.mContactsUpdate = true; // update mContacts when used 240 this.mDirectory.deleteCards(arr); 241 } 242 } 243 catch (e) { 244 com.gContactSync.LOGGER.LOG_WARNING("Error while deleting cards from an AB", e); 245 } 246 }, 247 /** 248 * Updates a card (commits changes) in this address book. 249 * @param aContact {TBContact} The card to update. 250 */ 251 updateContact: function AddressBook_updateContact(aContact) { 252 if (!(aContact instanceof com.gContactSync.TBContact)) { 253 throw "Invalid aContact sent to AddressBook.updateContact"; 254 } 255 this.mContactsUpdate = true; 256 if (this.mDirectory && this.mDirectory.modifyCard) 257 this.mDirectory.modifyCard(aContact.mContact); 258 else if (aContact.mContact.editCardToDatabase) 259 aContact.mContact.editCardToDatabase(this.mURI); 260 }, 261 /** 262 * Checks the validity of a mailing list and throws an error if it is invalid. 263 * @param aList {nsIAbDirectory} An object that should be a mailing list. 264 * @param aMethodName {string} The name of the method calling checkList (used 265 * when throwing the error) 266 */ 267 checkList: function AddressBook_checkList(aList, aMethodName) { 268 // if it is a MailList object, get it's actual list 269 var list = aList && aList.mList ? aList.mList : aList; 270 if (!list || !(list instanceof Components.interfaces.nsIAbDirectory) || !list.isMailList) { 271 throw "Invalid list: " + aList + " sent to the '" + aMethodName + 272 "' method" + com.gContactSync.StringBundle.getStr("pleaseReport"); 273 } 274 }, 275 /** 276 * Checks the validity of a directory and throws an error if it is invalid. 277 * @param aDirectory {nsIAbDirectory} The directory to check. 278 * @param aMethodName {strong} The name of the method calling checkDirectory 279 * (used when throwing the error) 280 */ 281 checkDirectory: function AddressBook_checkDirectory(aDirectory, aMethodName) { 282 if (!this.isDirectoryValid(aDirectory)) 283 throw "Invalid Directory: " + aDirectory + " sent to the '" + 284 aMethodName + "' method" + 285 com.gContactSync.StringBundle.getStr("pleaseReport"); 286 }, 287 /** 288 * Checks the validity of a directory and returns false if it is invalid. 289 * @param aDirectory {nsIAbDirectory} The directory to check. 290 */ 291 isDirectoryValid: function AddressBook_isDirectoryValid(aDirectory) { 292 return aDirectory && aDirectory instanceof Components.interfaces.nsIAbDirectory && 293 aDirectory.dirName !== "" && 294 (com.gContactSync.AbManager.mVersion === 3 || 295 aDirectory instanceof Components.interfaces.nsIAbMDBDirectory); 296 }, 297 /** 298 * Creates and returns a new TBContact in this address book. 299 * NOTE: The contact is already added to this address book. 300 * @returns {TBContact} A new TBContact in this address book. 301 */ 302 newContact: function AddressBook_newContact() { 303 return this.addContact(new com.gContactSync 304 .TBContact(Components.classes["@mozilla.org/addressbook/cardproperty;1"] 305 .createInstance(Components.interfaces.nsIAbCard), 306 this)); 307 }, 308 /** 309 * Returns true if the directory passed in is the same as the directory 310 * stored by this AddressBook object. Two directories are considered the same 311 * if and only if their Uniform Resource Identifiers (URIs) are the same. 312 * @param aOtherDir The directory to compare with this object's directory. 313 * @returns {boolean} True if the URI of the passed directory is the same as 314 * the URI of the directory stored by this object. 315 */ 316 equals: function AddressBook_equals(aOtherDir) { 317 // return false if the directory isn't valid 318 if (!this.isDirectoryValid(aOtherDir)) 319 return false; 320 // compare the URIs 321 if (this.mDirectory.URI) 322 return this.mDirectory.URI === aOtherDir.URI; 323 return this.mDirectory.getDirUri() === aOtherDir.getDirUri(); 324 }, 325 /** 326 * Returns the card in this directory, if any, with the same (not-null) 327 * value for the GoogleID attribute, or, if the GoogleID is null, if the 328 * display name, primary, and second emails are the same. 329 * @param aContact {TBContact} The card being searched for. 330 * @returns {TBContact} The card in this AB, if any, with the same, and 331 * non-null value for its GoogleID attribute, or, if the 332 * GoogleID is null, if the display name, primary, and 333 * second emails are the same. 334 */ 335 hasContact: function AddressBook_hasContact(aContact) { 336 if (!(aContact instanceof com.gContactSync.TBContact)) { 337 throw "Invalid aContact sent to AddressBook.hasContact"; 338 } 339 // get all of the cards in this list again, if necessary 340 if (this.mContactsUpdate || this.mContacts.length === 0) { 341 this.getAllContacts(); 342 } 343 for (var i = 0, length = this.mContacts.length; i < length; i++) { 344 var contact = this.mContacts[i], 345 aContactID = aContact.getID(); 346 // if it is an old card (has id) compare IDs 347 if (aContactID) { 348 if (aContactID === contact.getID()) { 349 return contact; 350 } 351 } 352 // else check that display name, primary and second email are equal 353 else if (aContact.getValue("DisplayName") === contact.getValue("DisplayName") && 354 aContact.getValue("PrimaryEmail") === contact.getValue("PrimaryEmail") && 355 aContact.getValue("SecondEmail") === contact.getValue("SecondEmail")) { 356 return contact; 357 } 358 } 359 return null; 360 }, 361 /** 362 * Sets the preference id for this mailing list. The update method must be 363 * called in order for the change to become permanent. 364 * @param aPrefId {string} The new preference ID for this mailing list. 365 */ 366 setPrefId: function AddressBook_setPrefId(aPrefId) { 367 this.mDirectory.dirPrefId = aPrefId; 368 }, 369 /** 370 * Returns the preference ID of this directory prefixed with 371 * "extensions.gContactSync." 372 * @returns {string} The preference ID of this directory. 373 */ 374 getPrefId: function AddressBook_getPrefId() { 375 return "extensions.gContactSync." + this.mDirectory.dirPrefId + "."; 376 }, 377 /** 378 * Gets and returns the string preference, if possible, with the given name. 379 * Returns null if this list doesn't have a preference ID or if there was an 380 * error getting the preference. 381 * @param aName {string} The name of the preference to get. 382 * @param aDefaultValue {string} The value to set the preference at if it 383 * fails. Only used in Thunderbird 3. 384 * @returns {string} The value of the preference with the given name in the 385 * preference branch specified by the preference ID, if 386 * possible. Otherwise null. 387 */ 388 getStringPref: function AddressBook_getStringPref(aName, aDefaultValue) { 389 var id = this.getPrefId(); 390 if (!id) 391 return null; 392 try { 393 var branch = Components.classes["@mozilla.org/preferences-service;1"] 394 .getService(Components.interfaces.nsIPrefService) 395 .getBranch(id) 396 .QueryInterface(Components.interfaces.nsIPrefBranch2); 397 var value = branch.getCharPref(aName); 398 //com.gContactSync.LOGGER.VERBOSE_LOG("-Found the value: " + value); 399 return value; 400 } 401 // keep going if the preference doesn't exist for backward-compatibility 402 catch (e) {} 403 // now if a value was not found, use the old branch ID 404 // this is for backwards compatibility with 0.3.0a1pre2/0.2.11 and below, 405 try { 406 id = this.mDirectory.dirPrefId; 407 branch = Components.classes["@mozilla.org/preferences-service;1"] 408 .getService(Components.interfaces.nsIPrefService) 409 .getBranch(id) 410 .QueryInterface(Components.interfaces.nsIPrefBranch2); 411 value = branch.getCharPref(aName); 412 // if the value exists (if it gets here, a value exists): 413 // 1) Create the pref using the new branch/method 414 // 2) Delete the old pref 415 this.setStringPref(aName, value); 416 branch.clearUserPref(aName); 417 com.gContactSync.LOGGER.VERBOSE_LOG("Found and removed an obsolete pref: " + 418 aName + " - " + value); 419 return value; 420 } 421 // an error is expected if the value isn't present 422 catch (e) { 423 return 0; 424 } 425 return null; 426 }, 427 /** 428 * Sets the string preference, if possible, with the given name and value. 429 * @param aName {string} The name of the preference to set. 430 * @param aValue {string} The value to which the preference is set. 431 */ 432 setStringPref: function AddressBook_setStringPref(aName, aValue) { 433 var id = this.getPrefId(); 434 com.gContactSync.LOGGER.VERBOSE_LOG("Setting pref named: " + aName + " to value: " + aValue + 435 " to the branch: " + id); 436 if (!id) { 437 com.gContactSync.LOGGER.VERBOSE_LOG("Invalid ID"); 438 return; 439 } 440 if (!aName) { 441 com.gContactSync.LOGGER.VERBOSE_LOG("Invalid name"); 442 return; 443 } 444 try { 445 var branch = Components.classes["@mozilla.org/preferences-service;1"] 446 .getService(Components.interfaces.nsIPrefService) 447 .getBranch(id) 448 .QueryInterface(Components.interfaces.nsIPrefBranch2); 449 branch.setCharPref(aName, aValue); 450 } catch (e) { com.gContactSync.LOGGER.LOG_WARNING("Error while setting directory pref", e); } 451 }, 452 453 /** 454 * Returns the name of this address book. 455 * @returns {string} The name of this address book. 456 */ 457 getName: function AddressBook_getName() { 458 return this.mDirectory.dirName; 459 }, 460 /** 461 * Sets the name of this address book. Throws an error if the name is set to 462 * either the PAB or CAB's name. 463 * @param aName {string} The new name for this directory. 464 */ 465 setName: function AddressBook_setName(aName) { 466 // make sure it isn't being set to the PAB or CAB name and make sure that 467 // this isn't the PAB or CAB 468 var pab = com.gContactSync.AbManager.getAbByURI("moz-abmdbdirectory://abook.mab"); 469 var cab = com.gContactSync.AbManager.getAbByURI("moz-abmdbdirectory://history.mab"); 470 if (aName === pab.dirName || aName === cab.dirName) 471 throw "Error - cannot rename a directory to the PAB or CAB's name"; 472 if (this.getName() === pab.dirName || this.getName() === cab.dirName) 473 throw "Error - cannot rename the PAB or CAB"; 474 // in TB 3, it is as simple as changing a property of the directory 475 if (com.gContactSync.AbManager.mVersion === 3) 476 this.mDirectory.dirName = aName; 477 // in TB 2 a few extra steps are necessary... 478 else { 479 /* NOTE: this code is originally from 480 * mailnews/addrbook/resources/content/addressbook.js: 481 * http://mxr.mozilla.org/mozilla1.8/source/mailnews/addrbook/resources/content/addressbook.js#353 482 */ 483 var addressbook = Components.classes["@mozilla.org/addressbook;1"] 484 .createInstance(Components.interfaces.nsIAddressBook); 485 // the rdf service 486 var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"] 487 .getService(Components.interfaces.nsIRDFService); 488 // get the datasource for the addressdirectory 489 var datasource = RDF.GetDataSource("rdf:addressdirectory"); 490 491 // moz-abdirectory:// is the RDF root to get all types of addressbooks. 492 var parent = RDF.GetResource("moz-abdirectory://") 493 .QueryInterface(Components.interfaces.nsIAbDirectory); 494 // Copy existing dir type category id and mod time so they won't get reset. 495 var properties = this.mDirectory.directoryProperties; 496 properties.description = aName; 497 // Now do the modification. 498 addressbook.modifyAddressBook(datasource, parent, this.mDirectory, properties); 499 } 500 }, 501 /** 502 * Returns the directory type of this address book. 503 * See mailnews/addrbook/src/nsDirPrefs.h 504 * @returns {integer} The directory type of this address book. 505 * 2 means a normal Mork AB 506 * -1 means the dir type could not be found. 507 */ 508 getDirType: function AddressBook_getDirType() { 509 if ("dirType" in this.mDirectory) { 510 return this.mDirectory.dirType; 511 } 512 else if ("directoryProperties" in this.mDirectory) { 513 return this.mDirectory.directoryProperties.dirType; 514 } 515 LOGGER.LOG_WARNING("Unable to find a dirType for the AB '" + 516 this.getName() + "'"); 517 return -1; 518 }, 519 /** 520 * Creates a new mailing list in this directory and returns a MailList object 521 * representing the new list. 522 * @returns {MailList} A new MailList object. 523 */ 524 newListObj: function AddressBook_newListObj(aList, aParentDirectory, aNew) { 525 return new com.gContactSync.MailList(aList, aParentDirectory, aNew); 526 }, 527 /** 528 * Permanently deletes this address book without a confirmation dialog. 529 * This will not allow deleting the PAB or CAB and will show a popup 530 * if there is an attempt to delete one of those ABs. 531 * @returns {boolean} True if the AB was deleted. 532 */ 533 deleteAB: function AddressBook_delete() { 534 return com.gContactSync.AbManager.deleteAB(this.mURI); 535 } 536 }; 537