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-2010
 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  * An object that can obtain address books by the name or URI, find the synced
 43  * address books, and edit cards.
 44  * @class
 45  */
 46 com.gContactSync.AbManager = {
 47   /** The version of Thunderbird */
 48   mVersion:   Components.classes["@mozilla.org/abmanager;1"] ? 3 : 2,
 49   /** True if the changes started in Bug 413260 have been applied */
 50   mBug413260: Components.classes["@mozilla.org/addressbook/cardproperty;1"]
 51                         .createInstance(Components.interfaces.nsIAbCard)
 52                         .getProperty !== undefined,
 53   /** attributes that can be set by getCardValue and setCardValue */
 54   mBasicAttributes: [
 55     "DisplayName", "Notes", "CellularNumber", "HomePhone", "WorkPhone",
 56     "PagerNumber", "FaxNumber", "_AimScreenName", "PrimaryEmail", "SecondEmail",
 57     "Company", "JobTitle", "HomeAddress", "WorkAddress", "NickName", "LastName",
 58     "FirstName", "HomeAddress2", "HomeCity", "HomeState", "HomeZipCode",
 59     "HomeCountry", "WorkAddress2", "WorkCity", "WorkState", "WorkZipCode",
 60     "WorkCountry", "WebPage1", "WebPage2", "Department", "Custom1", "Custom2",
 61     "Custom3", "Custom4", "WorkPhoneType", "HomePhoneType", "CellularNumberType",
 62     "FaxNumberType", "PagerNumberType"
 63   ],
 64   /**
 65    * Returns true if the given attribute is able to be set/obtained through the
 66    * setCardValue and getCardValue functions of nsIAbCard.
 67    * @param aAttribute The attribute to check.
 68    * @returns True if aAttribute is usable with set/getCardValue.
 69    */
 70   isRegularAttribute: function AbManager_isRegularAttribute(aAttribute) {
 71     return this.mBasicAttributes.indexOf(aAttribute) !== -1;
 72   },
 73   /**
 74    * Checks the validity of a directory and returns false if it is invalid.
 75    * @param aDirectory {nsIAbDirectory} The directory to check.
 76    */
 77   isDirectoryValid: function AbManager_isDirectoryValid(aDirectory) {
 78     return aDirectory &&
 79            aDirectory instanceof Components.interfaces.nsIAbDirectory &&
 80            aDirectory.dirName !== "";
 81   },
 82   /**
 83    * Checks the validity of a card and throws an error if the card is invalid.
 84    * @param aCard        {nsIAbCard} An object that should be an instance of nsIAbCard
 85    * @param aMethodName  {string} The name of the method calling checkCard (used
 86    *                              when throwing the error)
 87    */
 88   checkCard: function AbManager_checkCard(aCard, aMethodName) {
 89     var card = aCard && aCard.mCard ? aCard.mCard : aCard;
 90     if (!card || (!(card instanceof Components.interfaces.nsIAbCard) &&
 91                   !(Components.interfaces.nsIAbMDBCard && card instanceof Components.interfaces.nsIAbMDBCard))) {
 92       throw "Invalid card: " + aCard + "passed to the '" + aMethodName +
 93             "' method." + com.gContactSync.StringBundle.getStr("pleaseReport");
 94     }
 95   },
 96   /**
 97    * Returns the value of the specifiec property in the given card, or throws an
 98    * error if it is not present or blank.
 99    * @param aCard     {nsIAbCard} The card to get the value from.
100    * @param aAttrName {string}    The name of the attribute to get.
101    */
102   getCardValue: function AbManager_getCardValue(aCard, aAttrName) {
103     this.checkCard(aCard, "getCardValue");
104     if (this.mBug413260) // if the patch for Bug 413260 is applied
105       return aCard.getProperty(aAttrName, null);
106     else {
107       if (aAttrName === "LastModifiedDate")
108         return aCard.lastModifiedDate; // workaround for lastModifiedDate bug
109       var value;
110       if (this.isRegularAttribute(aAttrName))
111         try { return aCard.getCardValue(aAttrName); }
112         catch (e) { com.gContactSync.LOGGER.LOG_WARNING("Error in getCardValue: " + e); }
113       else if (Components.interfaces.nsIAbMDBCard && aCard instanceof Components.interfaces.nsIAbMDBCard)
114         return this.getMDBCardValue(aCard, aAttrName);
115       else
116         com.gContactSync.LOGGER.LOG_WARNING("Couldn't get the value " + aAttrName + " of the card "
117                            + aCard);
118     }
119     return null;
120   },
121   /**
122    * Returns an object with a property for each of the e-mail addresses of this
123    * card as recognized by gContactSync (PrimaryEmail, SecondEmail, ThirdEmail,
124    * and FourthEmail)
125    * @param aCard {nsIAbCard} The card from which the e-mail addresses are
126    *                          obtained.
127    * @returns An object with the card's e-mail addresses.
128    */
129   getCardEmailAddresses: function AbManager_getCardEmailAddresses(aCard) {
130     this.checkCard(aCard, "getCardEmailAddresses");
131     var primaryEmail = this.getCardValue(aCard, "PrimaryEmail");
132     var addresses = [];
133     if (primaryEmail)
134       addresses[primaryEmail] = true;
135     var secondEmail = this.getCardValue(aCard, "SecondEmail");
136     if (secondEmail)
137       addresses[secondEmail] = true;
138     var thirdEmail = this.getCardValue(aCard, "ThirdEmail");
139     if (thirdEmail)
140       addresses[thirdEmail] = true;
141     var fourthEmail = this.getCardValue(aCard, "FourthEmail");
142     if (fourthEmail)
143       addresses[fourthEmail] = true;
144     return addresses;
145   },
146   /**
147    * Returns true if the card has at least one e-mail address identical to one
148    * in aAddresses.
149    * @param aCard      {nsIAbCard} The card from which the e-mail addresses are
150    *                               obtained.
151    * @param aAddresses An object with the card's e-mail addresses as returned by
152    *                   AbManager.getCardEmailAddresses.
153    * @returns {boolean} True if the card has at least one e-mail address in
154    *                   common with aAddresses.
155    */
156   cardHasEmailAddress: function AbManager_cardHasEmailAddress(aCard, aAddresses) {
157     this.checkCard(aCard, "getCardEmailAddresses");
158     if (!aAddresses)
159       return false;
160     var cardAddresses = this.getCardEmailAddresses(aCard);
161     for (var i in cardAddresses) {
162       if (aAddresses[i])
163         return true;
164     }
165     return false;
166   },
167   /**
168    * Sets the value of the specifiec property in the given card but does not
169    * update the card in the database.
170    * @param aCard     {nsIAbCard} The card to get the value from.
171    * @param aAttrName {string}    The name of the attribute to set.
172    * @param aValue    {string}    The value to set for the attribute.
173    */
174   setCardValue: function AbManager_setCardValue(aCard, aAttrName, aValue) {
175     this.checkCard(aCard, "setCardValue");
176     if (!aValue)
177       aValue = "";
178     // make sure the last modified date is in milliseconds since 1/1/1970 UTC
179     // and not in microseconds
180     if (aAttrName == "LastModifiedDate" && parseInt(aValue, 10) > 2147483647) {
181       com.gContactSync.LOGGER.LOG_WARNING("Had to adjust last modified date from " + aValue);
182       aValue = aValue/1000;
183     }
184     if (this.mBug413260) { // if the patch for Bug 413260 is applied
185       if (aAttrName == "PreferMailFormat") {
186         switch (aValue) {
187           case "plaintext":
188           case "text":
189           case "1":
190             aValue = 1;
191             break;
192           case "html":
193           case "2":
194             aValue = 2;
195             break;
196           default: // if it is anything else set as unknown
197             aValue = 0;
198         }
199       }
200       aCard.setProperty(aAttrName, aValue);
201     }
202     else {
203       // workaround a last modified date bug
204       if (aAttrName == "LastModifiedDate")
205         try {
206           if (aValue == "")
207             aValue = 0;
208           aCard.lastModifiedDate = aValue;
209         } catch (e) { com.gContactSync.LOGGER.LOG_WARNING("Invalid lastModifiedDate"); }
210       else if (aAttrName == "AllowRemoteContent") {
211         // AllowRemoteContent may be 1/0 if the patch or true/false otherwise
212         var value = aValue == "1" || (aValue != "0" && aValue);
213         aCard.allowRemoteContent = value;
214       }
215       else if (aAttrName == "PreferMailFormat") {
216         // can be a 0/1/2 or unknown/plaintext/html
217         var value;
218         switch (aValue) {
219           case "plaintext":
220           case "text":
221           case "1":
222             value = 1;
223             break;
224           case "html":
225           case "2":
226             value = 2;
227             break;
228           default: // if it is anything else set as unknown
229             value = 0;
230         }
231         aCard.preferMailFormat = value;
232       }
233       else if (this.isRegularAttribute(aAttrName))
234         try { aCard.setCardValue(aAttrName, aValue); }
235         catch (e) { com.gContactSync.LOGGER.LOG_WARNING("Error in setCardValue: " + e); }
236      else if (Components.interfaces.nsIAbMDBCard && aCard instanceof Components.interfaces.nsIAbMDBCard)
237         this.setMDBCardValue(aCard, aAttrName, aValue);
238      else
239        com.gContactSync.LOGGER.LOG_WARNING("Couldn't set the value " + aAttrName + " of the card "
240                           + aCard);
241     }
242   },
243   /**
244     * Sets the requested value of an MDB card's attribute.  Performs a
245     * QueryInterface if necessary.
246     * @param aCard     {nsIAbCard} The MDB card to set the value for.
247     * @param aAttrName {string}    The name of the attribute whose value is set.
248     * @param aValue    {string}    The value to set for aAttrName.
249     *
250     * @returns {boolean} True if the attribute was set to the given value.
251     */
252   setMDBCardValue: function AbManager_setMDBCardValue(aCard, aAttrName, aValue) {
253     try {
254       aCard.setStringAttribute(aAttrName, aValue);
255       return true;
256     }
257     catch (e) {
258       com.gContactSync.LOGGER.LOG_WARNING("Error in setMDBCardValue: " + e + "\n" + aAttrName +
259                          "\n" + aValue);
260     }
261     return false;
262   },
263   /**
264    * Returns the requested value of an MDB card's attribute.  Performs a
265    * QueryInterface if necessary.
266    * @param aCard     {nsIAbCard} The MDB card to get the value from.
267    * @param aAttrName {string}    The name of the attribute whose value is returned.
268    * @returns {string} The value of aCard's attribute aAttrName.
269    */
270   getMDBCardValue: function AbManager_getMDBCardValue(aCard, aAttrName) {
271     try {
272       return aCard.getStringAttribute(aAttrName);
273     }
274     catch (e) {
275       com.gContactSync.LOGGER.LOG_WARNING("Error in getMDBCardValue: " + e + "\n" + aAttrName);
276     }
277     return null;
278   },
279   /**
280    * Returns the address book with the given URI, if found.  Does not attempt
281    * to make a new address book if not found and returns null.
282    * @returns  {nsIAbDirectory} The Address Book with the given URI
283    */
284   getAbByURI: function AbManager_getAbByURI(aURI) {
285     if (!aURI) {
286       com.gContactSync.LOGGER.LOG_WARNING("Invalid aURI supplied to the 'getAbByURI' method" +
287                          com.gContactSync.StringBundle.getStr("pleaseReport"));
288       return null;
289     }
290     try {
291       var dir;
292       if (Components.classes["@mozilla.org/abmanager;1"])
293         dir = Components.classes["@mozilla.org/abmanager;1"]
294                         .getService(Components.interfaces.nsIAbManager)
295                         .getDirectory(aURI)
296                         .QueryInterface(Components.interfaces.nsIAbDirectory);
297       else
298        dir = Components.classes["@mozilla.org/rdf/rdf-service;1"]
299               .getService(Components.interfaces.nsIRDFService)
300               .GetResource(aURI)
301               .QueryInterface(Components.interfaces.nsIAbDirectory);
302       // checks that the directory exists and is valid.  returns null if not.
303       if (!this.isDirectoryValid(dir))
304         return null;
305       return dir;
306     }
307     catch (e) { com.gContactSync.LOGGER.LOG_ERROR("Error in getAbByURI", e); }
308     return null;
309   },
310   /**
311    * Returns the Address Book if it can be found.  If it cannot be found
312    * it tries once to make it and return the newly made address book.
313    * @param aDirName    {string} The name of the address book
314    * @param aDontMakeAb {boolean} True if the address book shouldn't be created
315    *                              if not found. 
316    * @returns {nsIAbDirectory} The Address Book with the name given
317    */
318   getAbByName: function AbManager_getAbByName(aDirName, aDontMakeAb) {
319     if (!aDirName || aDirName.length == 0)
320       throw "Invalid aDirName passed to the 'getAbByName' method." +
321             com.gContactSync.StringBundle.getStr("pleaseReport");
322     var iter, data;
323     if (Components.classes["@mozilla.org/abmanager;1"]) { // TB 3
324       var abManager = Components.classes["@mozilla.org/abmanager;1"]
325                                 .getService(Components.interfaces.nsIAbManager);
326       iter = abManager.directories;
327     }
328     else { // TB 2
329       // obtain the main directory through the RDF service
330       var dir = Components.classes["@mozilla.org/rdf/rdf-service;1"]
331                           .getService(Components.interfaces.nsIRDFService)
332                           .GetResource("moz-abdirectory://")
333                           .QueryInterface(Components.interfaces.nsIAbDirectory);
334       iter = dir.childNodes;
335     }
336     while (iter.hasMoreElements()) {
337       data = iter.getNext();
338       if (data instanceof Components.interfaces.nsIAbDirectory)
339         if (data.dirName == aDirName)
340           return data;
341     }
342     iter = null;
343     if (aDontMakeAb)
344       return null;
345     // the AB doesn't exist, so make one:
346     // TODO - this should be in its own method
347     if (Components.classes["@mozilla.org/addressbook/properties;1"]) { // TB 2
348       // setup the "properties" of the new address book
349       var properties = Components.classes["@mozilla.org/addressbook/properties;1"]
350 	                             .createInstance(Components.interfaces.nsIAbDirectoryProperties);
351 	    properties.description = aDirName;
352 	    properties.dirType = 2; // address book
353       dir.createNewDirectory(properties);
354       iter = dir.childNodes;
355     }
356     else if (abManager) { // TB 3
357       abManager.newAddressBook(aDirName, "moz-abmdbdirectory://", 2);
358       iter = abManager.directories;
359     }
360     else if (Components.classes["@mozilla.org/addressbook;1"]) { // Postbox
361       var addressbook = Components.classes["@mozilla.org/addressbook;1"]
362                                    .createInstance(Components.interfaces.nsIAddressBook);
363       addressbook.newAddressBook(aDirName, "", 2);
364       iter = dir.childNodes;
365     }
366     else {
367       com.gContactSync.LOGGER.LOG_WARNING("Unable to determine how to create a directory");
368       alert("error");
369       return null;
370     }
371     if (!iter) {
372       com.gContactSync.LOGGER.LOG_WARNING("iter is invalid in getAbByName");
373       return null;
374     }
375     while (iter.hasMoreElements()) {
376       data = iter.getNext();
377       if (data instanceof Components.interfaces.nsIAbDirectory)
378         if (data.dirName == aDirName) {
379           var ab = new com.gContactSync.GAddressBook(data);
380           ab.setLastSyncDate(0);
381           return data;
382         }
383     }// end of while loop
384     return null;
385   },
386   /**
387    * Deletes the Address Book with the given URI.
388    * This does NOT provide any confirmation dialog.
389    * Note: This will not work in Thunderbird 2 with mailing lists.
390    * This will not allow deleting the PAB or CAB and will show a popup
391    * if there is an attempt to delete one of those ABs.
392    * @param aURI {string} The URI of the address book to delete.
393    */
394   deleteAB: function AbManager_deleteAB(aURI) {
395     if (!aURI) {
396       com.gContactSync.LOGGER.LOG_ERROR("Invalid URI passed to AbManager.deleteAB");
397       return false;
398     }
399     if (aURI.indexOf("abook.mab") != -1 || aURI.indexOf("history.mab") != -1) {
400       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("deletePAB"));
401       com.gContactSync.LOGGER.LOG_WARNING("Attempt made to delete the PAB or CAB.  URI: " + aURI);
402       return false;
403     }
404     com.gContactSync.LOGGER.VERBOSE_LOG("Deleting address book with the URI " + aURI);
405     // In TB 3 just use the AbManager to delete the AB
406     if (Components.classes["@mozilla.org/abmanager;1"]) {
407       var abManager = Components.classes["@mozilla.org/abmanager;1"]
408                                 .getService(Components.interfaces.nsIAbManager);
409       if (!abManager) {
410         com.gContactSync.LOGGER.LOG_ERROR("Unable to get the AB Manager service");
411         return false;
412       }
413       abManager.deleteAddressBook(aURI);
414     }
415     // TB 2 requires a bit more work
416     else {
417       // First create an array of parent resources
418       var parentArray = Components.classes["@mozilla.org/supports-array;1"]
419                                   .createInstance(Components.interfaces.nsISupportsArray);
420       if (!parentArray) {
421         com.gContactSync.LOGGER.LOG_ERROR("Unable to get an nsISupportsArray");
422         return false;
423       }
424       var parentId  = "moz-abdirectory://";
425       var parentDir = GetDirectoryFromURI(parentId);
426       parentArray.AppendElement(parentDir);
427 
428       // Next create an array of the resources to delete
429       var resourceArray = Components.classes["@mozilla.org/supports-array;1"]
430                                     .createInstance(Components.interfaces.nsISupportsArray);
431       if (!resourceArray) {
432         com.gContactSync.LOGGER.LOG_ERROR("Unable to get an nsISupportsArray");
433         return false;
434       }
435       var selectedABResource = GetDirectoryFromURI(aURI)
436                                     .QueryInterface(Components.interfaces.nsIRDFResource);
437       if (!selectedABResource) {
438         com.gContactSync.LOGGER.LOG_ERROR("Unable to get an nsISupportsArray");
439         return false;
440       }
441       resourceArray.AppendElement(selectedABResource);
442 
443       // Get the directory tree
444       var dirTree = GetDirTree();
445       if (!dirTree) {
446         com.gContactSync.LOGGER.LOG_ERROR("Unable to get the directory tree");
447         return false;
448       }
449 
450       // Finally delete the address book
451       top.addressbook.deleteAddressBooks(dirTree.database, parentArray, resourceArray);
452     }
453     return true;
454   }
455 };
456