diff --git a/instantbird/components/logger.js b/instantbird/components/logger.js --- a/instantbird/components/logger.js +++ b/instantbird/components/logger.js @@ -349,18 +349,17 @@ Logger.prototype = { case "profile-after-change": obs.addObserver(this, "final-ui-startup", false); break; case "final-ui-startup": obs.removeObserver(this, "final-ui-startup"); ["new-conversation", "new-text", "conversation-closed", "conversation-left-chat", "account-connected", "account-disconnected", - "buddy-signed-on", "buddy-signed-off", - "buddy-away", "buddy-idle"].forEach(function(aEvent) { + "account-buddy-status-changed"].forEach(function(aEvent) { obs.addObserver(this, aEvent, false); }, this); break; case "new-text": if (!aSubject.noLog) { let log = getLogForConversation(aSubject.conversation); log.logMessage(aSubject); } @@ -376,36 +375,38 @@ Logger.prototype = { getLogForAccount(aSubject, true).logEvent("+++ " + aSubject.name + " signed on"); break; case "account-disconnected": getLogForAccount(aSubject).logEvent("+++ " + aSubject.name + " signed off"); closeLogForAccount(aSubject); break; - default: + case "account-buddy-status-changed": let status; if (!aSubject.online) status = "Offline"; else if (aSubject.mobile) status = "Mobile"; else if (aSubject.idle) status = "Idle"; else if (aSubject.available) status = "Available"; else status = "Unavailable"; - let statusText = aSubject.status; + let statusText = aSubject.statusText; if (statusText) status += " (\"" + statusText + "\")"; - let name = aSubject.buddyName; - let nameText = (aSubject.buddyAlias || name) + " (" + name + ")"; + let nameText = aSubject.displayName + " (" + aSubject.userName + ")"; getLogForAccount(aSubject.account).logEvent(nameText + " is now " + status); + break; + default: + throw "Unexpected notification " + aTopic; } }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.ibILogger]), classDescription: "Logger", classID: Components.ID("{fb0dc220-2c7a-4216-9f19-6b8f3480eae9}"), contractID: "@instantbird.org/logger;1" }; diff --git a/instantbird/content/blist.js b/instantbird/content/blist.js --- a/instantbird/content/blist.js +++ b/instantbird/content/blist.js @@ -30,18 +30,18 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -const events = ["buddy-signed-on", - "buddy-added", +const events = ["contact-availability-changed", + "contact-added", "account-disconnected", "status-changed", "purple-quit"]; const showOfflineBuddiesPref = "messenger.buddies.showOffline"; var gBuddyListContextMenu = null; @@ -112,18 +112,18 @@ buddyListContextMenu.prototype = { return; let popup = document.getElementById("context-moveto-popup"); let item; while ((item = popup.firstChild) && item.localName != "menuseparator") popup.removeChild(item); let groupId = this.target.group.groupId; - let pts = Components.classes["@instantbird.org/purple/tags;1"] - .getService(Ci.purpleITagsService); + let pts = Components.classes["@instantbird.org/purple/tags-service;1"] + .getService(Ci.imITagsService); let sortFunction = function (a, b) { let [a, b] = [a.name.toLowerCase(), b.name.toLowerCase()]; return a < b ? 1 : a > b ? -1 : 0; }; pts.getTags() .sort(sortFunction) .forEach(function (aTag) { @@ -152,18 +152,18 @@ buddyListContextMenu.prototype = { .createBundle("chrome://instantbird/locale/instantbird.properties"); let title = bundle.GetStringFromName("newGroupPromptTitle"); let message = bundle.GetStringFromName("newGroupPromptMessage"); let name = {}; if (!prompts.prompt(window, title, message, name, null, {value: false}) || !name.value) return; // the user canceled - let pts = Components.classes["@instantbird.org/purple/tags;1"] - .getService(Ci.purpleITagsService); + let pts = Components.classes["@instantbird.org/purple/tags-service;1"] + .getService(Ci.imITagsService); let tag = pts.getTagByName(name.value) || pts.createTag(name.value); this.target.moveTo(tag.id); }, showLogs: function blcm_showLogs() { if (!this.onBuddy) return; var logger = Components.classes["@instantbird.org/logger;1"] @@ -202,57 +202,60 @@ var buddyList = { let showOffline = prefBranch.getBoolPref(showOfflineBuddiesPref); this._showOffline = showOffline; let item = document.getElementById("context-show-offline-buddies"); if (showOffline) item.setAttribute("checked", "true"); else item.removeAttribute("checked"); - let pts = Components.classes["@instantbird.org/purple/tags;1"] - .getService(Ci.purpleITagsService); + let pts = Components.classes["@instantbird.org/purple/tags-service;1"] + .getService(Ci.imITagsService); let blistBox = document.getElementById("buddylistbox"); pts.getTags().forEach(function (aTag) { let elt = document.getElementById("group" + aTag.id); - if (!elt && showOffline) { + if (elt) + elt.showOffline = showOffline; + else if (showOffline) { elt = document.createElement("group"); blistBox.appendChild(elt); elt._showOffline = true; if (!elt.build(aTag)) blistBox.removeChild(elt); } - if (elt) - elt.showOffline = showOffline; }); } if (aTopic == "status-changed") { this.displayCurrentStatus(); this.showAccountManagerIfNeeded(false); return; } if (aTopic == "account-disconnected") { let account = aSubject.QueryInterface(Ci.purpleIAccount); if (account.reconnectAttempt <= 1) this.showAccountManagerIfNeeded(false); return; } - var pab = aSubject.QueryInterface(Ci.purpleIAccountBuddy); - var group = pab.tag; - var groupId = "group" + group.id; - if ((aTopic == "buddy-signed-on" || - (aTopic == "buddy-added" && (this._showOffline || pab.online))) && - !document.getElementById(groupId)) { - let groupElt = document.createElement("group"); - document.getElementById("buddylistbox").appendChild(groupElt); - if (this._showOffline) - groupElt._showOffline = true; - groupElt.build(group); + // aSubject is an imIContact + if (aSubject.online || this._showOffline) { + aSubject.getTags().forEach(function (aTag) { + if (!document.getElementById("group" + aTag.id)) { +dump("document.getElementById(\"group" + aTag.id + "\") not found!\n"); + let groupElt = document.createElement("group"); + let blistBox = document.getElementById("buddylistbox"); + blistBox.appendChild(groupElt); + if (this._showOffline) + groupElt._showOffline = true; + if (!groupElt.build(aTag)) + blistBox.removeChild(groupElt); + } + }, this); } }, displayStatusType: function bl_displayStatusType(aStatusType) { document.getElementById("statusMessage") .setAttribute("statusType", aStatusType); let bundle = @@ -502,18 +505,18 @@ var buddyList = { Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch2); buddyList._showOffline = prefBranch.getBoolPref(showOfflineBuddiesPref); if (buddyList._showOffline) { document.getElementById("context-show-offline-buddies") .setAttribute("checked", "true"); } - let pts = Components.classes["@instantbird.org/purple/tags;1"] - .getService(Ci.purpleITagsService); + let pts = Components.classes["@instantbird.org/purple/tags-service;1"] + .getService(Ci.imITagsService); let blistBox = document.getElementById("buddylistbox"); pts.getTags().forEach(function (aTag) { let groupElt = document.createElement("group"); blistBox.appendChild(groupElt); if (buddyList._showOffline) groupElt._showOffline = true; if (!groupElt.build(aTag)) blistBox.removeChild(groupElt); diff --git a/instantbird/content/buddy.xml b/instantbird/content/buddy.xml --- a/instantbird/content/buddy.xml +++ b/instantbird/content/buddy.xml @@ -49,123 +49,104 @@ - + + + + + 1000 20 - + - - - - - + + + + + + + - - - + if (aTopic == "contact-signed-on") + this.cancelRemoveNode(); + else if (aTopic == "contact-signed-off" && !this.group.showOffline) + this.removeNode(); + ]]> - - + - - + + + - delete this.accounts[id]; - --this.accountsCount; - - if (this.accountsCount) { - this.update(); - return; - } - - // No account left, this node is now useless, remove it - this.removing = true; + + + @@ -263,37 +235,35 @@ @@ -358,39 +328,25 @@ } ]]> @@ -238,19 +230,19 @@ this.buddy = null; this.elt = null; diff --git a/instantbird/content/group.xml b/instantbird/content/group.xml --- a/instantbird/content/group.xml +++ b/instantbird/content/group.xml @@ -62,178 +62,138 @@ 20 // restore the potential persisted value var source = Components.classes["@mozilla.org/rdf/datasource;1?name=local-store"] .getService(Components.interfaces.nsIRDFDataSource); var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"] .getService(Components.interfaces.nsIRDFService); var elt = RDF.GetResource(document.location + "#" + this.id); if (source.HasAssertion(elt, RDF.GetResource("closed"), - RDF.GetLiteral("true"), true)) { + RDF.GetLiteral("true"), true)) this.setAttribute("closed", "true"); - this._updateGroupLabel(); - this._updateClosedState(true); - } - let empty = true; - this.tag.getBuddies().forEach(function (aBuddy) { - aBuddy.getAccountBuddies() - .filter(function (b) (this.showOffline || b.online) && b.tag.id == this.groupId, this) - .forEach(function(b) { this.addBuddy(b); empty = false; }, this); - }, this); - - if (!empty) - this.tag.addObserver(this); - return !empty; + contacts.forEach(this.addContact, this); + this._updateGroupLabel(); + this.tag.addObserver(this); + return true; ]]> - - + + - - + + - - + // remove the contact from the array + this.contacts.splice(i, 1); + delete this.contactsById[aContact.contact.id]; - - - - @@ -253,38 +213,22 @@ delete aThis.animInterval; aThis.tag.removeObserver(aThis); aThis.parentNode.removeChild(aThis); } ]]> - - - - - - - diff --git a/purple/purplexpcom/public/Makefile.in b/purple/purplexpcom/public/Makefile.in --- a/purple/purplexpcom/public/Makefile.in +++ b/purple/purplexpcom/public/Makefile.in @@ -40,16 +40,17 @@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = purplexpcom XPIDL_MODULE = purplexpcom XPIDLSRCS = \ + imIContactsService.idl \ purpleIAccount.idl \ purpleIAccountBuddy.idl \ purpleIAccountsService.idl \ purpleIBuddy.idl \ purpleIConversation.idl \ purpleICoreService.idl \ purpleIMessage.idl \ purpleIPlugin.idl \ diff --git a/purple/purplexpcom/public/imIContactsService.idl b/purple/purplexpcom/public/imIContactsService.idl new file mode 100644 --- /dev/null +++ b/purple/purplexpcom/public/imIContactsService.idl @@ -0,0 +1,347 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Instantbird messenging client, released + * 2010. + * + * The Initial Developer of the Original Code is + * Florian QUEZE . + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" +#include "nsIObserver.idl" +#include "nsISimpleEnumerator.idl" +#include "purpleIConversation.idl" + + +interface imITag; +interface imIContact; +interface imIBuddy; +interface imIAccountBuddy; +interface purpleIProtocol; + +[scriptable, uuid(f1619b49-310b-47aa-ab1c-238aba084c62)] +interface imIContactsService: nsISupports { + void initContacts(); + void unInitContacts(); + + imIContact getContactById(in long aId); + imIBuddy getBuddyById(in long aId); + imIBuddy getBuddyByNameAndProtocol(in AUTF8String aNormalizedName, + in purpleIProtocol aPrpl); + + // These 2 functions are called by the protocol plugins when + // synchronizing the buddy list with the server stored list. + void accountBuddyAdded(in imIAccountBuddy aAccountBuddy); + void accountBuddyRemoved(in imIAccountBuddy aAccountBuddy); +}; + +[scriptable, uuid(f799a9c2-23f2-4fd1-96fb-515bad238f8c)] +interface imITagsService: nsISupports { + // Create a new tags or return the existing one if it already exists + imITag createTag(in AUTF8String aName); + // Get an existing tag by (numeric) id. Returns null if not found. + imITag getTagById(in long aId); + // Get an existing tag by name (will do an SQL query). Returns null + // if not found. + imITag getTagByName(in AUTF8String aName); + // Get an array of all existing tags. + void getTags([optional] out unsigned long tagCount, + [retval, array, size_is(tagCount)] out imITag tags); +}; + +[scriptable, uuid(c211e5e2-f0a4-4a86-9e4c-3f6b905628a5)] +interface imITag: nsISupports { + readonly attribute long id; + attribute AUTF8String name; + + // Get an array of all the contacts associated with this tag. + // Contacts can either "have the tag" (added by user action) or + // have inherited the tag because it was the server side group for + // one of the AccountBuddy of the contact. + void getContacts([optional] out unsigned long contactCount, + [retval, array, size_is(contactCount)] out imIContact contacts); + + void addObserver(in nsIObserver aObserver); + void removeObserver(in nsIObserver aObserver); + /* Observers will be notified of changes related to the contacts + * that have the tag: contact-*, buddy-*, account-buddy-* + * notifications forwarded respectively from the imIContact, + * imIBuddy and imIAccountBuddy instances. + */ + + // Exposed for add-on authors. All usage by Instantbird will come from + // the imITag implementation so it wasn't required to expose this. + // This can be used to dispatch custom notifications to the + // observers of the tag. + void notifyObservers(in nsISupports aObj, in string aEvent, + [optional] in wstring aData); +}; + +[scriptable, uuid(f13dc4fc-5334-45cb-aa58-a92851955e55)] +interface imIStatusInfo: nsISupports { + // Name suitable for display in the UI. Can either by the username, + // the server side alias, or the user set local alias of the + // contact. + readonly attribute AUTF8String displayName; + readonly attribute AUTF8String buddyIconFilename; + + const short STATUS_UNKNOWN = 0; + const short STATUS_OFFLINE = 1; + const short STATUS_INVISIBLE = 2; + const short STATUS_MOBILE = 3; + const short STATUS_IDLE = 4; + const short STATUS_AWAY = 5; + const short STATUS_UNAVAILABLE = 6; + const short STATUS_AVAILABLE = 7; + + // numerical value used to compare the availability of two buddies + // based on their current status. + // Use it only for immediate comparisons, do not store the value, + // it can change between versions for a same status of the buddy. + readonly attribute long statusType; + + readonly attribute boolean online; // (statusType > STATUS_OFFLINE) + readonly attribute boolean available; // (statusType == STATUS_AVAILABLE) + readonly attribute boolean idle; // (statusType == STATUS_IDLE) + readonly attribute boolean mobile; // (statusType == STATUS_MOBILE) + + readonly attribute AUTF8String statusText; + + // Gives more detail to compare the availability of two buddies in + // the same status. + // This is useful when 2 buddies have been idle for various times. + readonly attribute long availabilityDetails; + + // True if the buddy is online or if the account supports sending + // offline messages to the buddy. + readonly attribute boolean canSendMessage; + + // enumerator of purpleTooltipInfo components + nsISimpleEnumerator getTooltipInfo(); + + // Will select the buddy automatically based on availability, and + // the account (if needed) based on the account order in the account + // manager. + purpleIConversation createConversation(); +}; + + +[scriptable, uuid(f585b0df-f6ad-40d5-9de4-c58b14af13e4)] +interface imIContact: imIStatusInfo { + readonly attribute long id; + attribute AUTF8String alias; + attribute AUTF8String firstName; + attribute AUTF8String lastName; + + void getTags([optional] out unsigned long tagCount, + [retval, array, size_is(tagCount)] out imITag tags); + + // In some cases, contacts that have a single buddy can be special cased. + // - single object that implements both imIContact and imIBuddy (?) + // - different display in the UI: maybe display the protocol icon + // if there's only one buddy? + readonly attribute short buddyCount; + + readonly attribute imIBuddy preferredBuddy; + void getBuddies([optional] out unsigned long buddyCount, + [retval, array, size_is(buddyCount)] out imIBuddy buddies); + + void mergeContact(in imIContact aContact); + void adoptBuddy(in imIBuddy aBuddy); + + // Returns a new contact that contains only aBuddy, and has the same + // list of tags. + // Will throw if aBuddy is not a buddy of the contact. + imIContact detachBuddy(in imIBuddy aBuddy); + + // remove the contact from the buddy list. Will also remove the + // associated buddies. + void remove(); + + void addObserver(in nsIObserver aObserver); + void removeObserver(in nsIObserver aObserver); + /* Observers will be notified of changes related to the contact. + * aSubject will point to the imIContact object (except for contact-sent-message). + * Fired notifications: + * contact-availability-changed + * when either statusType or availabilityDetails has changed. + * contact-signed-on + * contact-signed-off + * contact-status-changed + * when either statusType or statusText has changed. + * contact-display-name-changed + * when the alias (or serverAlias of the most available buddy if + * no alias is set) has changed. + * The old display name is provided in aData. + * contact-icon-changed + * contact-sent-message + * aSubject points to the imIMessage object rather than the imIContact. + * + * Observers will also receive all the (forwarded) notifications + * from the linked buddies (imIBuddy instances) and their account + * buddies (imIAccountBuddy instances). + */ + + // Exposed for add-on authors. All usage by Instantbird will come from + // the imIContact implementation so it wasn't required to expose this. + // This can be used to dispatch custom notifications to the + // observers of the contact and its tags. + void notifyObservers(in nsISupports aObj, in string aEvent, + [optional] in wstring aData); +}; + + +[scriptable, uuid(fea582a0-3839-4d80-bcab-0ff82ae8f97f)] +interface imIBuddy: imIStatusInfo { + readonly attribute long id; + readonly attribute purpleIProtocol protocol; + readonly attribute AUTF8String userName; // may be formatted + readonly attribute AUTF8String normalizedName; // normalized userName + // The optional server alias is in displayName (inherited from imIStatusInfo) + // displayName = serverAlias || userName. + + readonly attribute imIContact contact; + readonly attribute imIAccountBuddy preferredAccountBuddy; + void getAccountBuddies([optional] out unsigned long accountBuddyCount, + [retval, array, size_is(accountBuddyCount)] out imIAccountBuddy accountBuddies); + + // remove the buddy from the buddy list. If the contact becomes empty, it will be removed too. + void remove(); + + void addObserver(in nsIObserver aObserver); + void removeObserver(in nsIObserver aObserver); + /* Observers will be notified of changes related to the buddy. + * aSubject will point to the imIBuddy object (except for buddy-sent-message). + * Fired notifications: + * buddy-availability-changed + * when either statusType or availabilityDetails has changed. + * buddy-signed-on + * buddy-signed-off + * buddy-status-changed + * when either statusType or statusText has changed. + * buddy-display-name-changed + * when the serverAlias has changed. + * The old display name is provided in aData. + * buddy-icon-changed + * buddy-sent-message + * aSubject points to the imIMessage object rather than the imIBuddy. + * + * Observers will also receive all the (forwarded) notifications + * from the linked account buddies (imIAccountBuddy instances). + */ + + // Exposed for add-on authors. All usage by Instantbird will come from + // the imIBuddy implementation so it wasn't required to expose this. + // This can be used to dispatch custom notifications to the + // observers of the buddy, its contact and its tags. + void notifyObservers(in nsISupports aObj, in string aEvent, + [optional] in wstring aData); + + // observe should only be called by the imIAccountBuddy + // implementations to report changes. + void observe(in nsISupports aObj, in string aEvent, + [optional] in wstring aData); +}; + +/* imIAccountBuddy implementations can send notifications to their buddy: + * + * For all of them (except account-buddy-sent-message), aSubject + * points to the imIAccountBuddy object. + * + * Supported notifications: + * account-buddy-availability-changed + * when either statusType or availabilityDetails has changed. + * account-buddy-signed-on + * account-buddy-signed-off + * account-buddy-status-changed + * when either statusType or statusText has changed. + * account-buddy-display-name-changed + * when the serverAlias has changed. + * The old display name is provided in aData. + * account-buddy-icon-changed + * account-buddy-sent-message + * aSubject points to the imIMessage object rather than the imIAccountBuddy. + * + * All notifications (even unsupported ones) will be forwarded to the contact, + * its tags and nsObserverService. + */ +[scriptable, uuid(114d24ff-56a1-4fd6-9822-4992efb6e036)] +interface imIAccountBuddy: imIStatusInfo { + readonly attribute imIBuddy buddy; + readonly attribute purpleIAccount account; + // Setting the tag will move the buddy to a different group on the + // server-stored buddy list. + attribute imITag tag; + readonly attribute AUTF8String userName; + readonly attribute AUTF8String normalizedName; + readonly attribute AUTF8String serverAlias; + + // remove the buddy from the buddy list of this account. + void remove(); +}; + +/* FIXME + * Add Buddy + * Add tag on a contact + */ + +/* FIXME missing notifications + + account-buddy + added + removed + idle-changed (useless?) + mobile-changed (Mic suggested it. Not sure it's needed) + + buddy + added + removed + deleted (?) + away + idle + alias (deprecated -> display-name-changed) + + document : buddy-preferred-account-changed + + contact + added + removed (no longer visible on the buddy list) + deleted (permanently removed from the list) + away + idle + alias (deprecated -> display-name-changed) + + merge + adopt + + tag + tag attached/detached from a contact ? +*/ \ No newline at end of file diff --git a/purple/purplexpcom/public/purpleIAccount.idl b/purple/purplexpcom/public/purpleIAccount.idl --- a/purple/purplexpcom/public/purpleIAccount.idl +++ b/purple/purplexpcom/public/purpleIAccount.idl @@ -35,18 +35,20 @@ * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" #include "purpleIProtocol.idl" #include "purpleIConversation.idl" #include "purpleIProxy.idl" -interface purpleITag; -interface purpleIBuddy; +interface purpleITag; // FIXME remove +interface imITag; +interface imIBuddy; +interface imIAccountBuddy; %{C++ #define PREF_ACCOUNT_PREFIX "messenger.account." #define PREF_OPTIONS "options." #define PREF_PRPL "prpl" #define PREF_NAME "name" #define PREF_ALIAS "alias" #define PREF_PROXY "proxy" @@ -110,19 +112,17 @@ interface purpleIAccount: nsISupports { void cancelReconnection(); purpleIConversation createConversation(in AUTF8String aName); // Used when the user wants to add a buddy to the buddy list void addBuddy(in purpleITag aTag, in AUTF8String aName); // Used while loading the buddy list at startup. - void loadBuddy(in purpleIBuddy aBuddy, in AUTF8String aName, - in AUTF8String aAlias, in AUTF8String aServerAlias, - in purpleITag aTag); + imIAccountBuddy loadBuddy(in imIBuddy aBuddy, in imITag aTag); readonly attribute boolean canJoinChat; nsISimpleEnumerator getChatRoomFields(); purpleIChatRoomFieldValues getChatRoomDefaultFieldValues([optional] in AUTF8String aDefaultChatName); /* * Create a new chat conversation if it doesn't already exist. * Returns the conversation if it already existed, null otherwise */ diff --git a/purple/purplexpcom/public/purpleIConversation.idl b/purple/purplexpcom/public/purpleIConversation.idl --- a/purple/purplexpcom/public/purpleIConversation.idl +++ b/purple/purplexpcom/public/purpleIConversation.idl @@ -35,17 +35,17 @@ * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" #include "nsISimpleEnumerator.idl" #include "nsIObserver.idl" -interface purpleIAccountBuddy; +interface imIAccountBuddy; interface purpleIAccount; interface nsIURI; interface nsIDOMDocument; /* * This is the XPCOM purple conversation component, a proxy for PurpleConversation. */ @@ -97,17 +97,17 @@ interface purpleIConversation: nsISuppor /* Observers will be all receive new-text notifications. aSubject will contain the message (purpleIMessage) */ }; [scriptable, uuid(0c072a80-103a-4992-b249-8e442b5f0d46)] interface purpleIConvIM: purpleIConversation { /* The buddy at the remote end of the conversation */ - readonly attribute purpleIAccountBuddy buddy; + readonly attribute imIAccountBuddy buddy; /* The remote buddy is not currently typing */ const short NOT_TYPING = 0; /* The remote buddy is currently typing */ const short TYPING = 1; /* The remote buddy started typing, but has stopped typing */ diff --git a/purple/purplexpcom/src/Makefile.in b/purple/purplexpcom/src/Makefile.in --- a/purple/purplexpcom/src/Makefile.in +++ b/purple/purplexpcom/src/Makefile.in @@ -60,17 +60,16 @@ ifeq (,$(filter-out WINNT WINCE,$(OS_ARC EXTRA_LIBS = $(DIST)/lib/purple.lib endif CPPSRCS = \ purpleAccount.cpp \ purpleAccountBase.cpp \ purpleAccountBuddy.cpp \ purpleAccountsService.cpp \ - purpleBuddy.cpp \ purpleConversation.cpp \ purpleConvIM.cpp \ purpleConvChat.cpp \ purpleConvChatBuddy.cpp \ purpleCoreService.cpp \ purpleDebug.cpp \ purpleDNS.cpp \ purpleInit.cpp \ @@ -87,27 +86,31 @@ CPPSRCS = \ purpleSockets.cpp \ purpleStorage.cpp \ purpleTag.cpp \ purpleTagsService.cpp \ purpleTimer.cpp \ purpleTooltipInfo.cpp \ purpleUnknownProtocol.cpp \ $(NULL) +# purpleBuddy.cpp \ ifeq ($(OS_ARCH),Darwin) CPPSRCS += nsDockTile.cpp OS_LIBS += -framework Carbon endif ifdef PURPLE_PLUGINS DEFINES += -DPURPLE_PLUGINS endif EXTRA_COMPONENTS = gtalkOverrideProtocol.js facebookOverrideProtocol.js overrideProtocols.manifest + +EXTRA_COMPONENTS += imContacts.js imContacts.manifest + ifdef MOZ_DEBUG EXTRA_COMPONENTS += jsTestProtocol.js jsTestProtocol.manifest endif EXTRA_JS_MODULES = \ jsProtoHelper.jsm \ $(NULL) diff --git a/purple/purplexpcom/src/imContacts.js b/purple/purplexpcom/src/imContacts.js new file mode 100644 --- /dev/null +++ b/purple/purplexpcom/src/imContacts.js @@ -0,0 +1,677 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Instantbird messenging client, released + * 2010. + * + * The Initial Developer of the Original Code is + * Florian QUEZE . + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +const Cc = Components.classes; +const Ci = Components.interfaces; + +XPCOMUtils.defineLazyServiceGetter(this, "obs", + "@mozilla.org/observer-service;1", + "nsIObserverService"); +XPCOMUtils.defineLazyServiceGetter(this, "prefs", + "@mozilla.org/preferences-service;1", + "nsIPrefBranch"); +XPCOMUtils.defineLazyServiceGetter(this, "pcs", + "@instantbird.org/purple/core;1", + "purpleICoreService"); +XPCOMUtils.defineLazyGetter(this, "DBConn", function() pcs.storageConnection); + + +var AccountsById = { }; +function getAccountById(aId) { + if (AccountsById.hasOwnProperty(aId)) + return AccountsById[aId]; + + let account = null; + try { + account = pcs.getAccountByNumericId(aId); + } catch (x) { /* Not found */ } + + AccountsById[aId] = account; + return account; +} + +function TagsService() { } +TagsService.prototype = { + get wrappedJSObject() this, + createTag: function(aName) { + // If the tag already exists, we don't want to create a duplicate. + let tag = this.getTagByName(aName); + if (tag) + return tag; + + let statement = DBConn.createStatement("INSERT INTO tags (name, position) VALUES(:name, 0)"); + statement.params.name = aName; + statement.executeStep(); + + tag = new Tag(DBConn.lastInsertRowID, aName); + Tags.push(tag); + return tag; + }, + // Get an existing tag by (numeric) id. Returns null if not found. + getTagById: function(aId) TagsById[aId], + // Get an existing tag by name (will do an SQL query). Returns null + // if not found. + getTagByName: function(aName) { + let statement = DBConn.createStatement("SELECT id FROM tags where name = :name"); + statement.params.name = aName; + if (!statement.executeStep()) + return null; + return this.getTagById(statement.row.id); + }, + // Get an array of all existing tags. + getTags: function(aTagCount) { + if (aTagCount) + aTagCount.value = Tags.length; + return Tags; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.imITagsService]), + classDescription: "Tags", + classID: Components.ID("{1fa92237-4303-4384-b8ac-4e65b50810a5}"), + contractID: "@instantbird.org/purple/tags-service;1" +}; + +var Tags = []; +var TagsById = { }; +function Tag(aId, aName) { + this._id = aId; + this._name = aName; + this._contacts = []; + this._observers = []; + + TagsById[this.id] = this; +} +Tag.prototype = { + get id() this._id, + get name() this._name, + set name(aNewName) { + var statement = DBConn.createStatement("UPDATE tags SET name = :name WHERE id = :id"); + statement.params.name = aNewName; + statement.params.id = this._id; + statement.execute(); + + //FIXME move the account buddies if some use this tag as their group + return aNewName; + }, + getContacts: function(aContactCount) { + let contacts = this._contacts.filter(function(c) !c._empty); + if (aContactCount) + aContactCount.value = contacts.length; + return contacts; + }, + + addObserver: function(aObserver) { +//dump("tag::addObserver\n"); + if (this._observers.indexOf(aObserver) == -1) + this._observers.push(aObserver); + }, + removeObserver: function(aObserver) { +//dump("tag::removeObserver\n"); + let index = this._observers.indexOf(aObserver); + if (index != -1) + this._observers.splice(index, 1); + }, + notifyObservers: function(aSubject, aTopic, aData) { + for each (let observer in this._observers) + observer.observe(aSubject, aTopic, aData); +// dump("obs.notifyObservers: " + aSubject.displayName + " --- " + aTopic + "\n"); + obs.notifyObservers(aSubject, aTopic, aData); + }, + + getInterfaces: function(countRef) { + var interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imITag]; + countRef.value = interfaces.length; + return interfaces; + }, + getHelperForLanguage: function(language) null, + implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, + flags: 0, + QueryInterface: XPCOMUtils.generateQI([Ci.imITag, Ci.nsIClassInfo]) +}; + +var ContactsById = { }; +var LastDummyContactId = 0; +function Contact(aId, aAlias) { + // Assign a negative id to dummy contacts that have a single buddy + this._id = aId || --LastDummyContactId; + this._alias = aAlias; + this._tags = []; + this._buddies = []; + this._observers = []; + + ContactsById[this._id] = this; +} +Contact.prototype = { + _id: 0, + get id() this._id, + get alias() this._alias, + set alias(aNewAlias) { + var statement = DBConn.createStatement("UPDATE contacts SET alias = :alias WHERE id = :id"); + statement.params.alias = aNewAlias; + statement.params.id = this._id; + statement.execute(); + //FIXME fire a notification for the change + return aNewAlias; + }, + firstName: "", // FIXME + lastName: "", // FIXME + + getTags: function(aTagCount) { + if (aTagCount) + aTagCount.value = this._tags.length; + return this._tags; + }, + getBuddies: function(aBuddyCount) { + if (aBuddyCount) + aBuddyCount.value = this._buddies.length; + return this._buddies; + }, + get _empty() this._buddies.length == 0 || + this._buddies.every(function(b) b._empty), + + mergeContact: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + adoptBuddy: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + detachBuddy: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + remove: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + // imIStatusInfo implementation + _preferredBuddy: null, + get preferredBuddy() { + if (!this._preferredBuddy) + this._updatePreferredBuddy(); + return this._preferredBuddy; + }, + set preferredBuddy(aBuddy) { + let shouldNotify = this._preferredBuddy != null; + this._preferredBuddy = aBuddy; + if (shouldNotify) + this._notifyObservers("preferred-buddy-changed"); + this._updateStatus(); + }, + // aBuddy indicate which buddy's availability has changed. + _updatePreferredBuddy: function(aBuddy) { + if (aBuddy) { + if (!this._preferredBuddy) { + this.preferredBuddy = aBuddy; + return; + } + + if (aBuddy.id == this._preferredBuddy.id) { + // The suggested buddy is already preferred, check if its + // availability has changed. + if (aBuddy.statusType > this._statusType || + (aBuddy.statusType == this._statusType && aBuddy.availabilityDetails >= this._availabilityDetails)) { + // keep the currently preferred buddy, only update the status. + this._updateStatus(); + return; + } + // We aren't sure that the currently preferred buddy should + // still be preferred. Let's go through the list! + } + else { + // The suggested buddy is not currently preferred. If it is + // more available, prefer it! + if (aBuddy.statusType > this._statusType || + (aBuddy.statusType == this._statusType && aBuddy.availabilityDetails > this._availabilityDetails)) + this._preferredBuddy = aBuddy; + return; + } + } + + let preferred; + //TODO take into account the order of the buddies in the contact. + for each (let buddy in this._buddies) { + if (!preferred || preferred.statusType < buddy.statusType || + (preferred.statusType == buddy.statusType && + preferred.availabilityDetails < buddy.availabilityDetails)) + preferred = buddy; + } + if (preferred && (!this._preferredBuddy || + preferred.id != this._preferredBuddy.id)) + this._preferredBuddy = preferred; + }, + _updateStatus: function() { + let buddy = this._preferredBuddy; // for convenience + + // Decide which notifications should be fired. + let notifications = []; + if (this._statusType != buddy.statusType || + this._availabilityDetails != buddy.availabilityDetails) + notifications.push("availability-changed"); + if (this._statusType != buddy.statusType || + this._statusText != buddy.statusText) { + notifications.push("status-changed"); + if (this.online && buddy.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE) + notifications.push("signed-off"); + if (!this.online && buddy.statusType > Ci.imIStatusInfo.STATUS_OFFLINE) + notifications.push("signed-on"); + } + + // Actually change the stored status. + [this._statusType, this._statusText, this._availabilityDetails] = + [buddy.statusType, buddy.statusText, buddy.availabilityDetails]; + + // Fire the notifications. + notifications.forEach(function(aTopic) { + this._notifyObservers(aTopic); + }, this); + }, + get displayName() this._alias || this.preferredBuddy.displayName, + get buddyIconFilename() this.preferredBuddy.buddyIconFileName, + _statusType: 0, + get statusType() this._statusType, + get online() this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE, + get available() this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE, + get idle() this.statusType == Ci.imIStatusInfo.STATUS_IDLE, + get mobile() this.statusType == Ci.imIStatusInfo.STATUS_MOBILE, + _statusText: "", + get statusText() this._statusText, + _availabilityDetails: 0, + get availabilityDetails() this._availabilityDetails, + get canSendMessage() this.preferredBuddy.canSendMessage, + //XXX should we list the buddies in the tooltip? + getTooltipInfo: function() this.preferredBuddy.getTooltipInfo(), + createConversation: function() this.preferredBuddy.createConversation(), + + addObserver: function(aObserver) { +//dump("contact::addObserver\n"); + if (this._observers.indexOf(aObserver) == -1) + this._observers.push(aObserver); + }, + removeObserver: function(aObserver) { +//dump("contact::removeObserver\n"); + let index = this._observers.indexOf(aObserver); + if (index != -1) + this._observers.splice(index, 1); + }, + // internal calls + calls from add-ons + notifyObservers: function(aSubject, aTopic, aData) { + for each (let observer in this._observers) + observer.observe(aSubject, aTopic, aData); + for each (let tag in this._tags) + tag.notifyObservers(aSubject, aTopic, aData); +//dump("contact::notifyObservers: " + aTopic + " -> done.\n"); + }, + _notifyObservers: function(aTopic, aData) { + this.notifyObservers(this, "contact-" + aTopic, aData); + }, + + // This is called by the imIBuddy implementations. + _observe: function(aSubject, aTopic, aData) { + +//dump("contact::_observe: " + aTopic + "\n"); + // Forward the notification. + this.notifyObservers(aSubject, aTopic, aData); + + let isPreferredBuddy = + aSubject instanceof Buddy && aSubject.id == this.preferredBuddy.id; + switch (aTopic) { + case "buddy-availability-changed": + this._updatePreferredBuddy(aSubject); + break; + case "buddy-status-changed": + if (isPreferredBuddy) + this._updateStatus(); + break; + case "buddy-display-name-changed": + if (isPreferredBuddy) + this._notifyObservers("display-name-changed"); + // FIXME The old display name should be provided in aData. + break; + case "buddy-icon-changed": + if (isPreferredBuddy) + this._notifyObservers("icon-changed"); + break; + case "buddy-sent-message": + this._notifyObservers("sent-message"); + } + }, + + getInterfaces: function(countRef) { + var interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imIContact]; + countRef.value = interfaces.length; + return interfaces; + }, + getHelperForLanguage: function(language) null, + implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, + flags: 0, + QueryInterface: XPCOMUtils.generateQI([Ci.imIContact, Ci.nsIClassInfo]) +}; + +var BuddiesById = { }; +function Buddy(aId, aKey, aName, aSrvAlias, aContactId) { + this._id = aId; + this._key = aKey; + this._name = aName; + if (aSrvAlias) + this._srvAlias = aSrvAlias; + this._accounts = []; + this._observers = []; + + if (aContactId) + this._contact = ContactsById[aContactId]; + else + this._contact = new Contact(null, null); + + this._contact._buddies.push(this); + + BuddiesById[this._id] = this; +} +Buddy.prototype = { + get id() this._id, + destroy: function() { + delete this._accounts; + delete this._observers; + delete this._preferredAccount; + }, + get protocol() this._accounts[0].account.protocol, + get userName() this._name, + get normalizedName() this._key, + _srvAlias: "", + get displayName() this._srvAlias || this._name, + _contact: null, + get contact() this._contact, + getAccountBuddies: function(aAccountBuddyCount) { + if (aAccountBuddyCount) + aAccountBuddyCount.value = this._accounts.length; + return this._accounts; + }, + + _addAccount: function(aAccountBuddy, aTag) { + this._accounts.push(aAccountBuddy); + let contact = this._contact; + if (this._contact._tags.indexOf(aTag) == -1) { + this._contact._tags.push(aTag); + aTag._contacts.push(contact); + // FIXME decide if this relation should be added in mozStorage? + } + + this._preferredAccount = aAccountBuddy; + }, + get _empty() this._accounts.length == 0, + + remove: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + // imIStatusInfo implementation + _preferredAccount: null, + get preferredAccountBuddy() this._preferredAccount, + set preferredAccount(aAccount) { + this._preferredAccount = aAccount; + this._notifyObservers("preferred-account-changed"); + this._updateStatus(); + }, + // aAccount indicate which account's availability has changed. + _updatePreferredAccount: function(aAccount) { + if (aAccount) { +//dump("aAccount = " + aAccount.account.numericId + ", prefered = " + this._preferredAccount.account.numericId + "\n"); + if (aAccount.account.numericId == this._preferredAccount.account.numericId) { + // The suggested account is already preferred, check if its + // availability has changed. + if (aAccount.statusType > this._statusType || + (aAccount.statusType == this._statusType && aAccount.availabilityDetails >= this._availabilityDetails)) { + // keep the currently preferred account, only update the status. + this._updateStatus(); + return; + } + // We aren't sure that the currently preferred account should + // still be preferred. Let's go through the list! + } + else { + // The suggested account is not currently preferred. If it is + // more available, prefer it! + if (aAccount.statusType > this._statusType || + (aAccount.statusType == this._statusType && aAccount.availabilityDetails > this._availabilityDetails)) + this.preferredAccount = aAccount; + return; + } + } + + let preferred; + //TODO take into account the order of the account-manager list. + for each (let account in this._accounts) { + if (!preferred || preferred.statusType < account.statusType || + (preferred.statusType == account.statusType && + preferred.availabilityDetails < account.availabilityDetails)) + preferred = account; + } + if (!this._preferredAccount) { + if (preferred) + this.preferredAccount = preferred; + return; + } + if (preferred.account.numericId != this._preferredAccount.account.numericId) + this.preferredAccount = preferred; + else + this._updateStatus(); + }, + _updateStatus: function() { + let account = this._preferredAccount; // for convenience + + // Decide which notifications should be fired. + let notifications = []; +// dump("old status type = " + this._statusType + " new status = " + account.statusType+"\n"); + if (this._statusType != account.statusType || + this._availabilityDetails != account.availabilityDetails) + notifications.push("availability-changed"); + if (this._statusType != account.statusType || + this._statusText != account.statusText) { + notifications.push("status-changed"); + if (this.online && account.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE) + notifications.push("signed-off"); + if (!this.online && account.statusType > Ci.imIStatusInfo.STATUS_OFFLINE) + notifications.push("signed-on"); + } + + // Actually change the stored status. + [this._statusType, this._statusText, this._availabilityDetails] = + [account.statusType, account.statusText, account.availabilityDetails]; + + // Fire the notifications. + notifications.forEach(function(aTopic) { + this._notifyObservers(aTopic); + }, this); + }, +// get displayName() this._preferredAccount.displayName, +// Fix the displayName. Decide if it should use srvAlias + get buddyIconFilename() this._preferredAccount.buddyIconFileName, + _statusType: 0, + get statusType() this._statusType, + get online() this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE, + get available() this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE, + get idle() this.statusType == Ci.imIStatusInfo.STATUS_IDLE, + get mobile() this.statusType == Ci.imIStatusInfo.STATUS_MOBILE, + _statusText: "", + get statusText() this._statusText, + _availabilityDetails: 0, + get availabilityDetails() this._availabilityDetails, + get canSendMessage() this._preferredAccount.canSendMessage, + //XXX should we list the accounts in the tooltip? + getTooltipInfo: function() this._preferredAccount.getTooltipInfo(), + createConversation: function() this._preferredAccount.createConversation(), + + addObserver: function(aObserver) { +//dump("buddy::addObserver\n"); + if (this._observers.indexOf(aObserver) == -1) + this._observers.push(aObserver); + }, + removeObserver: function(aObserver) { +//dump("buddy::removeObserver\n"); + let index = this._observers.indexOf(aObserver); + if (index != -1) + this._observers.splice(index, 1); + }, + // internal calls + calls from add-ons + notifyObservers: function(aSubject, aTopic, aData) { + for each (let observer in this._observers) + observer.observe(aSubject, aTopic, aData); + this._contact._observe(aSubject, aTopic, aData); + }, + _notifyObservers: function(aTopic, aData) { + this.notifyObservers(this, "buddy-" + aTopic, aData); + }, + + // This is called by the imIAccountBuddy implementations. + observe: function(aSubject, aTopic, aData) { +try{ + +//dump("buddy::_observe: " + aTopic + "\n"); + // Forward the notification. + this.notifyObservers(aSubject, aTopic, aData); + + let isPreferredAccount = + aSubject.account.numericId == this._preferredAccount.account.numericId; + switch (aTopic) { + case "account-buddy-availability-changed": + this._updatePreferredAccount(aSubject); + break; + case "account-buddy-status-changed": + if (isPreferredAccount) + this._updateStatus(); + break; + case "account-buddy-display-name-changed": + if (isPreferredAccount) + this._notifyObservers("display-name-changed"); + // FIXME The old display name should be provided in aData. + break; + case "account-buddy-icon-changed": + if (isPreferredAccount) + this._notifyObservers("icon-changed"); + break; + case "account-buddy-sent-message": + this._notifyObservers("sent-message"); + } +} catch (x) { +dump(x + "\n\n\n"); +Components.utils.reportError(x); +} + + }, + + getInterfaces: function(countRef) { + var interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imIBuddy]; + countRef.value = interfaces.length; + return interfaces; + }, + getHelperForLanguage: function(language) null, + implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, + flags: 0, + QueryInterface: XPCOMUtils.generateQI([Ci.imIBuddy, Ci.nsIClassInfo]) +}; + + +function ContactsService() { } +ContactsService.prototype = { + initContacts: function() { +try{ + var statement = DBConn.createStatement("SELECT id, name FROM tags"); + while (statement.executeStep()) + Tags.push(new Tag(statement.getInt32(0), statement.getUTF8String(1))); + + statement = DBConn.createStatement("SELECT id, alias FROM contacts"); + while (statement.executeStep()) + new Contact(statement.getInt32(0), statement.getUTF8String(1)); + + statement = DBConn.createStatement("SELECT contact_id, tag_id FROM contact_tag"); + while (statement.executeStep()) + ContactsById[statement.getInt32(0)]._tags.push(TagsById(statement.getInt32(1))); + + statement = DBConn.createStatement("SELECT id, key, name, srv_alias, contact_id FROM buddies"); + while (statement.executeStep()) + new Buddy(statement.getInt32(0), statement.getUTF8String(1), + statement.getUTF8String(2), statement.getUTF8String(3), + statement.getInt32(4)); + // FIXME is there a way to enforce that all AccountBuddies of a Buddy have the same protocol? + + statement = DBConn.createStatement("SELECT account_id, buddy_id, tag_id FROM account_buddy"); + while (statement.executeStep()) { + let account = getAccountById(statement.getInt32(0)); + let buddy = BuddiesById[statement.getInt32(1)]; + let tag = TagsById[statement.getInt32(2)]; +try{ + let ab = account.loadBuddy(buddy, tag); + if (ab) + buddy._addAccount(ab, tag); +} catch (x) { +dump(x +"\n"); +} + //FIXME ab shouldn't be NULL (once purpleAccount is finished) + // We should throw if it is! + } +} catch (x) { + dump(x +"\n"); +} + + }, + unInitContacts: function() { + AccountsById = { }; + Tags = []; + TagsById = { }; + // avoid shutdown leak caused by references to native components + // implementing imIAccountBuddy. + for (let id in BuddiesById) + BuddiesById[id].destroy(); + BuddiesById = { }; + ContactsById = { }; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.imIContactsService]), + classDescription: "Contacts", + classID: Components.ID("{8c3725dd-ee26-489d-8135-736015af8c7f}"), + contractID: "@instantbird.org/purple/contacts-service;1" +}; + +const NSGetFactory = XPCOMUtils.generateNSGetFactory([ContactsService, TagsService]); + + +//Components.classes["@instantbird.org/purple/tags-service;1"].getService(Components.interfaces.imITagsService).getTags().map(function(t)t.getContacts()).join(",") +//Components.classes["@instantbird.org/purple/tags-service;1"].getService(Components.interfaces.imITagsService).getTags().map(function(t)t.getContacts().map(function(c)c.getBuddies().map(function(b)b.id))).join(",") + +// Components.classes["@instantbird.org/purple/tags-service;1"].getService(Components.interfaces.imITagsService).getTags().map(function(t)t.getContacts().map(function(c) c.displayName)).join(",") + +// TODO move the Tags to inside the tagsService \ No newline at end of file diff --git a/purple/purplexpcom/src/imContacts.manifest b/purple/purplexpcom/src/imContacts.manifest new file mode 100644 --- /dev/null +++ b/purple/purplexpcom/src/imContacts.manifest @@ -0,0 +1,4 @@ +component {8c3725dd-ee26-489d-8135-736015af8c7f} imContacts.js +contract @instantbird.org/purple/contacts-service;1 {8c3725dd-ee26-489d-8135-736015af8c7f} +component {1fa92237-4303-4384-b8ac-4e65b50810a5} imContacts.js +contract @instantbird.org/purple/tags-service;1 {1fa92237-4303-4384-b8ac-4e65b50810a5} diff --git a/purple/purplexpcom/src/jsProtoHelper.jsm b/purple/purplexpcom/src/jsProtoHelper.jsm --- a/purple/purplexpcom/src/jsProtoHelper.jsm +++ b/purple/purplexpcom/src/jsProtoHelper.jsm @@ -128,18 +128,24 @@ const GenericAccountPrototype = { checkAutoLogin: function() this._base.checkAutoLogin(), remove: function() this._base.remove(), UnInit: function() this._base.UnInit(), connect: function() this._base.connect(), disconnect: function() this._base.disconnect(), cancelReconnection: function() this._base.cancelReconnection(), createConversation: function(aName) this._base.createConversation(aName), addBuddy: function(aTag, aName) this._base.addBuddy(aTag, aName), - loadBuddy: function(aBuddy, aName, aAlias, aServerAlias, aTag) - this._base.loadBuddy(aBuddy, aName, aAlias, aServerAlias, aTag), + loadBuddy: function(aBuddy, aTag) { + try { + return new AccountBuddy(this, aBuddy, aTag) ; + } catch (x) { + dump(x + "\n"); + return null; + } + }, getChatRoomFields: function() this._base.getChatRoomFields(), getChatRoomDefaultFieldValues: function(aDefaultChatName) this._base.getChatRoomDefaultFieldValues(aDefaultChatName), joinChat: function(aComponents) this._base.joinChat(aComponents), setBool: function(aName, aVal) this._base.setBool(aName, aVal), setInt: function(aName, aVal) this._base.setInt(aName, aVal), setString: function(aName, aVal) this._base.setString(aName, aVal), save: function() this._base.save(), @@ -191,16 +197,139 @@ const GenericAccountPrototype = { return interfaces; }, getHelperForLanguage: function(language) null, implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, flags: 0, QueryInterface: XPCOMUtils.generateQI([Ci.purpleIAccount, Ci.nsIClassInfo]) }; + +var GenericAccountBuddyPrototype = { + _init: function(aAccount, aBuddy, aTag) { + this._tag = aTag; + this._account = aAccount; + this._buddy = aBuddy; + }, + + get account() this._account, + get buddy() this._buddy, + get tag() this._tag, + set tag(aNewTag) { + //FIXME!! + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + _notifyObservers: function(aTopic, aData) { + this._buddy.observe(this, "account-buddy-" + aTopic, aData); + }, + + get userName() this._buddy.userName, // FIXME + get normalizedName() this._buddy.normalizedName, //FIXME + _serverAlias: "", + get serverAlias() this._serverAlias, + set serverAlias(aNewAlias) { + let old = this.displayName; + this._serverAlias = aNewAlias; + this._notifyObservers("display-name-changed", old); + }, + + remove: function() { + // FIXME: Add a removeAccount in imIBuddy? Or create an imPIBuddy iface? + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + // imIStatusInfo implementation + get displayName() this.serverAlias || this.userName, + _buddyIconFileName: "", + get buddyIconFilename() this._buddyIconFileName, + set buddyIconFilename(aNewFileName) { + this._buddyIconFileName = aNewFileName; + this._notifyObservers("icon-changed"); + }, + _statusType: 0, + get statusType() this._statusType, + get online() this._statusType > Ci.imIStatusInfo.STATUS_OFFLINE, + get available() this._statusType == Ci.imIStatusInfo.STATUS_AVAILABLE, + get idle() this._statusType == Ci.imIStatusInfo.STATUS_IDLE, + get mobile() this._statusType == Ci.imIStatusInfo.STATUS_MOBILE, + _statusText: "", + get statusText() this._statusText, + + // This is for use by the protocol plugin, it's not exposed in the + // imIStatusInfo interface. + // All parameters are optional and will be ignored if they are null + // or undefined. + setStatus: function(aStatusType, aStatusText, aAvailabilityDetails) { + // Ignore omitted parameters. + if (aStatusType === undefined || aStatusType === null) + aStatusType = this._statusType; + if (aStatusText === undefined || aStatusText === null) + aStatusText = this._statusText; + if (aAvailabilityDetails === undefined || aAvailabilityDetails === null) + aAvailabilityDetails = this._availabilityDetails; + + // Decide which notifications should be fired. + let notifications = []; + if (this._statusType != aStatusType || + this._availabilityDetails != aAvailabilityDetails) + notifications.push("availability-changed"); + if (this._statusType != aStatusType || + this._statusText != aStatusText) { + notifications.push("status-changed"); + if (this.online && aStatusType <= Ci.imIStatusInfo.STATUS_OFFLINE) + notifications.push("signed-off"); + if (!this.online && aStatusType > Ci.imIStatusInfo.STATUS_OFFLINE) + notifications.push("signed-on"); + } + + // Actually change the stored status. + [this._statusType, this._statusText, this._availabilityDetails] = + [aStatusType, aStatusText, aAvailabilityDetails]; + + // Fire the notifications. + notifications.forEach(function(aTopic) { + this._notifyObservers(aTopic); + }, this); + }, + + _availabilityDetails: 0, + get availabilityDetails() this._availabilityDetails, + + get canSendMessage() this.online /*|| this.account.canSendOfflineMessage(this) */, + + getTooltipInfo: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + createConversation: function() { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + getInterfaces: function(countRef) { + var interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imIAccountBuddy]; + countRef.value = interfaces.length; + return interfaces; + }, + getHelperForLanguage: function(language) null, + implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, + flags: 0, + QueryInterface: XPCOMUtils.generateQI([Ci.imIAccountBuddy, Ci.nsIClassInfo]) +}; + +function AccountBuddy(aAccount, aBuddy, aTag) { + this._init(aAccount, aBuddy, aTag); + + // TODO do the proto specific stuff here: +// dump("Linking Buddy#" + this.buddy.id + "(" + this.buddy.displayName + ") to account#" + this.account.id + "\n"); +} +AccountBuddy.prototype = GenericAccountBuddyPrototype; + + + + function Message(aWho, aMessage, aObject) { this.id = ++Message.prototype._lastId; this.time = Math.round(new Date() / 1000); this.who = aWho; this.message = aMessage; this.originalMessage = aMessage; diff --git a/purple/purplexpcom/src/purpleAccount.cpp b/purple/purplexpcom/src/purpleAccount.cpp --- a/purple/purplexpcom/src/purpleAccount.cpp +++ b/purple/purplexpcom/src/purpleAccount.cpp @@ -34,16 +34,17 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "purpleAccount.h" #include "purpleCoreService.h" #include "purpleGListEnumerator.h" #include "purpleStorage.h" +#include #include #include #include #include #include #include #include #include @@ -492,42 +493,72 @@ NS_IMETHODIMP purpleAccount::AddBuddy(pu purpleTag *tag = static_cast(aTag); PurpleBuddy *buddy = purple_buddy_new(mAccount, PromiseFlatCString(aName).get(), NULL); tag->addBuddy(buddy); purple_account_add_buddy(mAccount, buddy); return NS_OK; } -/* void LoadBuddy (in purpleIBuddy aBuddy, in AUTF8String aName, - in AUTF8String aAlias, in AUTF8String aServerAlias, - in purpleITag aTag); */ -NS_IMETHODIMP purpleAccount::LoadBuddy(purpleIBuddy *aBuddy, - const nsACString & aName, - const nsACString & aAlias, - const nsACString & aServerAlias, - purpleITag *aTag) +static inline PurpleGroup *GetPurpleGroupForTag(imITag *aTag) { + nsCString name; + nsresult rv = aTag->GetName(name); + NS_ENSURE_SUCCESS(rv, NULL); + + // creating an already existing group will return the existing group + PurpleGroup *group = purple_group_new(name.get()); + NS_ENSURE_TRUE(group, NULL); + + if (!GET_NODE_UI_DATA(group)) { + // the group is new: + // Set ui_data to the tag + purple_blist_node_set_ui_data(PURPLE_BLIST_NODE(group), aTag); + NS_ADDREF(aTag); + + purple_blist_add_group(group, NULL); + } + + return group; +} + +/* imIAccountBuddy LoadBuddy (in imIBuddy aBuddy, in imITag aTag); */ +NS_IMETHODIMP purpleAccount::LoadBuddy(imIBuddy *aBuddy, + imITag *aTag, + imIAccountBuddy **aResult) +{ + NS_ENSURE_ARG_POINTER(aBuddy); + NS_ENSURE_ARG_POINTER(aTag); PURPLE_ENSURE_INIT(mProtocol && mAccount); - purpleTag *tag = static_cast(aTag); + nsCString userName; + nsresult rv = aBuddy->GetUserName(userName); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!userName.IsEmpty(), NS_ERROR_UNEXPECTED); + + PurpleGroup *group = GetPurpleGroupForTag(aTag); + NS_ENSURE_TRUE(group, NS_ERROR_UNEXPECTED); + PurpleBuddy *buddy = - purple_buddy_new(mAccount, PromiseFlatCString(aName).get(), - aAlias.IsEmpty() ? NULL : PromiseFlatCString(aAlias).get()); + purple_buddy_new(mAccount, userName.get(), NULL); - // Ignore server aliases that are identical to the name. - // (We used to mistakenly store the name in the srv_alias column - // when there was no server alias.) - if (!aServerAlias.IsEmpty() && !aServerAlias.Equals(aName)) { - buddy->server_alias = - purple_utf8_strip_unprintables(PromiseFlatCString(aServerAlias).get()); - } - tag->addBuddy(buddy); + // Set the server alias on the blist node if the display name of the + // imIBuddy is different from the userName + nsCString displayName; + rv = aBuddy->GetDisplayName(displayName); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!displayName.IsEmpty(), NS_ERROR_UNEXPECTED); + if (!displayName.Equals(userName)) + buddy->server_alias = purple_utf8_strip_unprintables(displayName.get()); - static_cast(aBuddy)->AddAccount(this, buddy, tag); + nsCOMPtr accountBuddy = + new purpleAccountBuddy(buddy, aBuddy); + purple_blist_add_buddy(buddy, NULL, group, NULL); + + NS_ADDREF(*aResult = accountBuddy); return NS_OK; } PurplePluginProtocolInfo *purpleAccount::GetPrplInfo() { NS_ENSURE_TRUE(mAccount, NULL); PurpleConnection *gc = purple_account_get_connection(mAccount); diff --git a/purple/purplexpcom/src/purpleAccount.h b/purple/purplexpcom/src/purpleAccount.h --- a/purple/purplexpcom/src/purpleAccount.h +++ b/purple/purplexpcom/src/purpleAccount.h @@ -46,17 +46,17 @@ class purpleAccount : public purpleAccou { public: // NS_DECL_PURPLEIACCOUNT NS_SCRIPTABLE NS_IMETHOD UnInit(void); NS_SCRIPTABLE NS_IMETHOD Connect(void); NS_SCRIPTABLE NS_IMETHOD Disconnect(void); NS_SCRIPTABLE NS_IMETHOD CreateConversation(const nsACString & aName, purpleIConversation **_retval NS_OUTPARAM); NS_SCRIPTABLE NS_IMETHOD AddBuddy(purpleITag *aTag, const nsACString & aName); - NS_SCRIPTABLE NS_IMETHOD LoadBuddy(purpleIBuddy *aBuddy, const nsACString & aName, const nsACString & aAlias, const nsACString & aServerAlias, purpleITag *aTag); + NS_SCRIPTABLE NS_IMETHOD LoadBuddy(imIBuddy *aBuddy, imITag *aTag, imIAccountBuddy **aResult); NS_SCRIPTABLE NS_IMETHOD GetCanJoinChat(PRBool *aCanJoinChat); NS_SCRIPTABLE NS_IMETHOD GetChatRoomFields(nsISimpleEnumerator **_retval NS_OUTPARAM); NS_SCRIPTABLE NS_IMETHOD GetChatRoomDefaultFieldValues(const nsACString & aDefaultChatName, purpleIChatRoomFieldValues **_retval NS_OUTPARAM); NS_SCRIPTABLE NS_IMETHOD JoinChat(purpleIChatRoomFieldValues *aComponents); NS_SCRIPTABLE NS_IMETHOD GetNormalizedName(nsACString & aNormalizedName); NS_SCRIPTABLE NS_IMETHOD GetPassword(nsACString & aPassword); NS_SCRIPTABLE NS_IMETHOD SetPassword(const nsACString & aPassword); NS_SCRIPTABLE NS_IMETHOD GetRememberPassword(PRBool *aRememberPassword); diff --git a/purple/purplexpcom/src/purpleAccountBase.cpp b/purple/purplexpcom/src/purpleAccountBase.cpp --- a/purple/purplexpcom/src/purpleAccountBase.cpp +++ b/purple/purplexpcom/src/purpleAccountBase.cpp @@ -354,24 +354,20 @@ NS_IMETHODIMP purpleAccountBase::CreateC /* void addBuddy (in purpleITag aTag, in AUTF8String aName); */ NS_IMETHODIMP purpleAccountBase::AddBuddy(purpleITag *aTag, const nsACString& aName) { return NS_ERROR_NOT_IMPLEMENTED; } -/* void LoadBuddy (in purpleIBuddy aBuddy, in AUTF8String aName, - in AUTF8String aAlias, in AUTF8String aServerAlias, - in purpleITag aTag); */ -NS_IMETHODIMP purpleAccountBase::LoadBuddy(purpleIBuddy *aBuddy, - const nsACString & aName, - const nsACString & aAlias, - const nsACString & aServerAlias, - purpleITag *aTag) +/* imIAccountBuddy LoadBuddy (in imIBuddy aBuddy, in imITag aTag); */ +NS_IMETHODIMP purpleAccountBase::LoadBuddy(imIBuddy *aBuddy, + imITag *aTag, + imIAccountBuddy **aResult) { return NS_ERROR_NOT_IMPLEMENTED; } /* nsISimpleEnumerator getChatRoomFields (); */ NS_IMETHODIMP purpleAccountBase::GetChatRoomFields(nsISimpleEnumerator **aResult) { return NS_ERROR_NOT_IMPLEMENTED; diff --git a/purple/purplexpcom/src/purpleAccountBase.h b/purple/purplexpcom/src/purpleAccountBase.h --- a/purple/purplexpcom/src/purpleAccountBase.h +++ b/purple/purplexpcom/src/purpleAccountBase.h @@ -63,17 +63,17 @@ class purpleAccountBase : public purpleI public: NS_DECL_ISUPPORTS NS_DECL_PURPLEIACCOUNT NS_DECL_PURPLEIACCOUNTBASE purpleAccountBase(); protected: - ~purpleAccountBase(); + virtual ~purpleAccountBase(); nsresult GetPrefBranch(); nsresult unstoreAccount(); nsresult SetBoolPref(const char *aName, PRBool aValue); nsresult SetIntPref(const char *aName, PRInt32 aValue); nsresult SetStringPref(const char *aName, const char *aValue); inline void GetBoolPref(const char *aName); inline void GetIntPref(const char *aName); inline void GetCharPref(const char *aName); diff --git a/purple/purplexpcom/src/purpleAccountBuddy.cpp b/purple/purplexpcom/src/purpleAccountBuddy.cpp --- a/purple/purplexpcom/src/purpleAccountBuddy.cpp +++ b/purple/purplexpcom/src/purpleAccountBuddy.cpp @@ -47,59 +47,58 @@ #include #include #pragma GCC visibility push(default) #include #pragma GCC visibility pop NS_IMPL_CLASSINFO(purpleAccountBuddy, NULL, 0, PURPLE_ACCOUNTBUDDY_CID) -NS_IMPL_ISUPPORTS1_CI(purpleAccountBuddy, purpleIAccountBuddy) +NS_IMPL_ISUPPORTS1_CI(purpleAccountBuddy, imIAccountBuddy) #ifdef PR_LOGGING // // NSPR_LOG_MODULES=purpleAccountBuddy:5 // static PRLogModuleInfo *gPurpleAccountBuddyLog = nsnull; #endif #define LOG(args) PR_LOG(gPurpleAccountBuddyLog, PR_LOG_DEBUG, args) -purpleAccountBuddy::purpleAccountBuddy() +purpleAccountBuddy::purpleAccountBuddy(PurpleBuddy *aPurpleBuddy, + imIBuddy *aBuddy) { /* member initializers and constructor code */ #ifdef PR_LOGGING if (!gPurpleAccountBuddyLog) gPurpleAccountBuddyLog = PR_NewLogModule("purpleAccountBuddy"); #endif + + mBuddy = aBuddy; + mPurpleBuddy = aPurpleBuddy; + purple_blist_node_set_ui_data(PURPLE_BLIST_NODE(aPurpleBuddy), this); + PurpleAccount *account = purple_buddy_get_account(aPurpleBuddy); + mAccount = purpleAccount::fromPurpleAccount(account); + NS_ASSERTION(mAccount, "Failed to get the account from a PurpleBuddy"); } purpleAccountBuddy::~purpleAccountBuddy() { /* destructor code */ -} - -void purpleAccountBuddy::Init(purpleIAccount *aAccount, - PurpleBuddy *aBuddy, - purpleTag *aTag) -{ - mAccount = aAccount; - mBuddy = aBuddy; - purple_blist_node_set_ui_data(PURPLE_BLIST_NODE(aBuddy), this); - // aTag is unused because we don't cache the tag here any more, we - // use the ui data of the libpurple buddy list node instead. - // ToDo for 0.3: see if we should remove the 'tag' parameters in most - // 'AddAccount' methods. + if (mPurpleBuddy) + Uninit(); } void purpleAccountBuddy::Uninit() { - purple_blist_node_set_ui_data(PURPLE_BLIST_NODE(mBuddy), nsnull); + purple_blist_node_set_ui_data(PURPLE_BLIST_NODE(mPurpleBuddy), nsnull); mBuddy = NULL; + mPurpleBuddy = NULL; } +#if 0 nsresult purpleAccountBuddy::Store(PRInt32 aBuddyId) { NS_ENSURE_TRUE(aBuddyId, NS_ERROR_INVALID_ARG); PRUint32 accountId; nsresult rv = mAccount->GetNumericId(&accountId); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(accountId, NS_ERROR_UNEXPECTED); @@ -158,54 +157,60 @@ nsresult purpleAccountBuddy::UnStore(PRI rv = statement->BindInt32Parameter(2, groupId); NS_ENSURE_SUCCESS(rv, rv); LOG(("purpleAccountBuddy::UnStore(buddyId = %i, groupId = %i)", aBuddyId, groupId)); return statement->Execute(); } +#endif /* readonly attribute purpleIBuddy buddy; */ -NS_IMETHODIMP purpleAccountBuddy::GetBuddy(purpleIBuddy * *aBuddy) +NS_IMETHODIMP purpleAccountBuddy::GetBuddy(imIBuddy * *aBuddy) { NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); - purpleIBuddy *buddy = purpleBuddy::fromPurpleBuddy(mBuddy); - NS_ENSURE_TRUE(buddy, NS_ERROR_NOT_INITIALIZED); - - NS_ADDREF(*aBuddy = buddy); + NS_ADDREF(*aBuddy = mBuddy); return NS_OK; } /* readonly attribute purpleIAccount account; */ NS_IMETHODIMP purpleAccountBuddy::GetAccount(purpleIAccount * *aAccount) { NS_ENSURE_TRUE(mAccount, NS_ERROR_NOT_INITIALIZED); NS_ADDREF(*aAccount = mAccount); return NS_OK; } +static inline imITag *GetTagFromPurpleGroup(PurpleGroup *aGroup) +{ + return static_cast(GET_NODE_UI_DATA(aGroup)); +} + /* attribute purpleITag tag; */ -NS_IMETHODIMP purpleAccountBuddy::GetTag(purpleITag * *aTag) +NS_IMETHODIMP purpleAccountBuddy::GetTag(imITag * *aTag) { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - purpleITag *tag = purpleTag::fromPurpleGroup(purple_buddy_get_group(mBuddy)); + PurpleGroup *group = purple_buddy_get_group(mPurpleBuddy); + imITag *tag = GetTagFromPurpleGroup(group); NS_ENSURE_TRUE(tag, NS_ERROR_NOT_INITIALIZED); NS_ADDREF(*aTag = tag); return NS_OK; } -NS_IMETHODIMP purpleAccountBuddy::SetTag(purpleITag * aTag) +NS_IMETHODIMP purpleAccountBuddy::SetTag(imITag * aTag) { NS_ENSURE_ARG_POINTER(aTag); NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + return NS_ERROR_NOT_IMPLEMENTED; +#if 0 static_cast(aTag)->addBuddy(mBuddy); // Now collect the data we need to make the SQL UPDATE query // First, the tag id nsCOMPtr tag; nsresult rv = GetTag(getter_AddRefs(tag)); NS_ENSURE_SUCCESS(rv, rv); @@ -247,71 +252,44 @@ NS_IMETHODIMP purpleAccountBuddy::SetTag NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt32Parameter(0, tagId); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(1, accountId); NS_ENSURE_SUCCESS(rv, rv); rv = statement->BindInt64Parameter(2, buddyId); NS_ENSURE_SUCCESS(rv, rv); return statement->Execute(); +#endif } -/* readonly attribute AUTF8String buddyName; */ -NS_IMETHODIMP purpleAccountBuddy::GetBuddyName(nsACString& aBuddyName) +/* readonly attribute AUTF8String userName; */ +NS_IMETHODIMP purpleAccountBuddy::GetUserName(nsACString& aUserName) { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - aBuddyName = purple_buddy_get_name(mBuddy); + aUserName = purple_buddy_get_name(mPurpleBuddy); return NS_OK; } /* readonly attribute AUTF8String normalizedName; */ NS_IMETHODIMP purpleAccountBuddy::GetNormalizedName(nsACString& aNormalizedName) { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - aNormalizedName = purple_normalize(purple_buddy_get_account(mBuddy), - purple_buddy_get_name(mBuddy)); - return NS_OK; -} - -/* readonly attribute AUTF8String loggedIn */ -NS_IMETHODIMP purpleAccountBuddy::GetLoggedIn(nsACString& aLoggedIn) -{ - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); - - PurplePresence *presence = purple_buddy_get_presence(mBuddy); - time_t loginTime = purple_presence_get_login_time(presence); - - if ((PURPLE_BUDDY_IS_ONLINE(mBuddy)) && (loginTime > 0)) { - time_t now = time(NULL); - - if (loginTime > now) - aLoggedIn = purple_date_format_long(localtime(&loginTime)); - else { - // returns a copy - we need to free it - char *tmp = purple_str_seconds_to_string(now - loginTime); - aLoggedIn = tmp; - g_free(tmp); - } - } - else { - // didn't find a valid time - set it to NULL - aLoggedIn.SetIsVoid(true); - } - + aNormalizedName = purple_normalize(purple_buddy_get_account(mPurpleBuddy), + purple_buddy_get_name(mPurpleBuddy)); return NS_OK; } /* readonly attribute AUTF8String buddyIconFilename; */ NS_IMETHODIMP purpleAccountBuddy::GetBuddyIconFilename(nsACString& aBuddyIconFilename) { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - PurpleBuddyIcon *icon = purple_buddy_get_icon(mBuddy); + PurpleBuddyIcon *icon = purple_buddy_get_icon(mPurpleBuddy); char *fname; if (icon && (fname = purple_buddy_icon_get_full_path(icon))) { aBuddyIconFilename = NS_LITERAL_CSTRING("file://"); aBuddyIconFilename.Append(fname); g_free(fname); } else { // set it to NULL @@ -322,159 +300,209 @@ NS_IMETHODIMP purpleAccountBuddy::GetBud } void purpleAccountBuddy::CleanUserInfo(void *aData) { if (aData) purple_notify_user_info_destroy((PurpleNotifyUserInfo *)aData); } -PurplePluginProtocolInfo *purpleAccountBuddy::GetPrplInfo(PurpleBuddy *aBuddy) -{ - PurpleAccount *pAccount = purple_buddy_get_account(aBuddy); - PurpleConnection *gc = purple_account_get_connection(pAccount); - if (!gc) - return NULL; - - purpleAccount *account = purpleAccount::fromPurpleAccount(pAccount); - NS_ENSURE_TRUE(account, NULL); - - return account->GetPrplInfo(); -} - /* nsISimpleEnumerator getTooltipInfo(); */ NS_IMETHODIMP purpleAccountBuddy::GetTooltipInfo(nsISimpleEnumerator** aTooltipInfo) { NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(mAccount, NS_ERROR_NOT_INITIALIZED); #ifdef PR_LOGGING nsCString name; mAccount->GetName(name); LOG(("purpleAccountBuddy::GetTooltipInfo buddy = %s, account = %s", - mBuddy->name, name.get())); + mPurpleBuddy->name, name.get())); #endif *aTooltipInfo = nsnull; - PurplePluginProtocolInfo *prpl_info = GetPrplInfo(mBuddy); + + purpleAccount *account = + purpleAccount::fromPurpleAccount(purple_buddy_get_account(mPurpleBuddy)); + NS_ENSURE_TRUE(account, NS_ERROR_NOT_INITIALIZED); + + PurplePluginProtocolInfo *prpl_info = account->GetPrplInfo(); if (prpl_info && prpl_info->tooltip_text) { PurpleNotifyUserInfo *user_info = purple_notify_user_info_new(); /* Idle */ - PurplePresence *presence = purple_buddy_get_presence(mBuddy); + PurplePresence *presence = purple_buddy_get_presence(mPurpleBuddy); if (purple_presence_is_idle(presence)) { time_t idle_secs = purple_presence_get_idle_time(presence); if (idle_secs > 0) { char *tmp = purple_str_seconds_to_string(time(NULL) - idle_secs); purple_notify_user_info_add_pair(user_info, purpleGetText::GetText("purple", "Idle"), tmp); g_free(tmp); } } - prpl_info->tooltip_text(mBuddy, user_info, true); + prpl_info->tooltip_text(mPurpleBuddy, user_info, true); purpleGListEnumerator *enumerator = new purpleGListEnumerator(); enumerator->Init(purple_notify_user_info_get_entries(user_info), purpleTypeToInterface, CleanUserInfo, user_info); NS_ADDREF(*aTooltipInfo = enumerator); } return NS_OK; } -/* readonly attribute AUTF8String buddyAlias; */ -NS_IMETHODIMP purpleAccountBuddy::GetBuddyAlias(nsACString& aBuddyAlias) +/* readonly attribute AUTF8String displayName; */ +NS_IMETHODIMP purpleAccountBuddy::GetDisplayName(nsACString& aDisplayName) { + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); + + aDisplayName = purple_buddy_get_alias(mPurpleBuddy); + return NS_OK; +} + +/* readonly attribute AUTF8String serverAlias; */ +NS_IMETHODIMP purpleAccountBuddy::GetServerAlias(nsACString& aServerAlias) +{ + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); + + aServerAlias = purple_buddy_get_alias(mPurpleBuddy); + return NS_OK; +} + +void purpleAccountBuddy::SetServerAlias(const nsCString& aServerAlias) +{ + purple_blist_alias_buddy(mPurpleBuddy, aServerAlias.get()); + serv_alias_buddy(mPurpleBuddy); + NotifyObservers("account-buddy-display-name-changed"); // FIXME send the old alias +} + +nsresult purpleAccountBuddy::NotifyObservers(const char* aSignal) +{ + NS_ENSURE_ARG(aSignal); NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); - aBuddyAlias = purple_buddy_get_alias(mBuddy); - return NS_OK; -} -void purpleAccountBuddy::SetBuddyAlias(const nsCString& aBuddyAlias) -{ - purple_blist_alias_buddy(mBuddy, aBuddyAlias.get()); - serv_alias_buddy(mBuddy); + return mBuddy->Observe(this, aSignal, nsnull); } /* purpleIConversation createConversation (); */ NS_IMETHODIMP purpleAccountBuddy::CreateConversation(purpleIConversation **aResult) { - NS_ENSURE_TRUE(mAccount && mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mAccount && mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - nsCString buddyName(purple_buddy_get_name(mBuddy)); + nsCString buddyName(purple_buddy_get_name(mPurpleBuddy)); return mAccount->CreateConversation(buddyName, aResult); } /* readonly attribute boolean canSendMessage; */ NS_IMETHODIMP purpleAccountBuddy::GetCanSendMessage(PRBool *aCanSendMessage) { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); *aCanSendMessage = - purple_presence_is_online(purple_buddy_get_presence(mBuddy)) || - purple_account_supports_offline_message(purple_buddy_get_account(mBuddy), - mBuddy); + purple_presence_is_online(purple_buddy_get_presence(mPurpleBuddy)) || + purple_account_supports_offline_message(purple_buddy_get_account(mPurpleBuddy), + mPurpleBuddy); return NS_OK; } -#define PURPLE_PRESENCE_GET_BOOL_IMPL(aName, aFctName) \ - NS_IMETHODIMP purpleAccountBuddy::Get##aName(PRBool *a##aName) \ - { \ - NS_ENSURE_TRUE(mAccount && mBuddy, NS_ERROR_NOT_INITIALIZED); \ - \ - *a##aName = \ - purple_account_is_connected(purple_buddy_get_account(mBuddy)) && \ - purple_presence_is_##aFctName(purple_buddy_get_presence(mBuddy)); \ - return NS_OK; \ +/* readonly attribute long statusType; */ +NS_IMETHODIMP purpleAccountBuddy::GetStatusType(PRInt32 *aStatusType) +{ + NS_ENSURE_TRUE(mAccount && mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); + + if (!purple_account_is_connected(purple_buddy_get_account(mPurpleBuddy))) { + *aStatusType = STATUS_UNKNOWN; + return NS_OK; + } + + PurplePresence *presence = purple_buddy_get_presence(mPurpleBuddy); + if (!purple_presence_is_online(presence)) + *aStatusType = STATUS_OFFLINE; + else if (purple_presence_is_idle(presence)) + *aStatusType = STATUS_IDLE; + else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_MOBILE)) + *aStatusType = STATUS_MOBILE; + else if (purple_presence_is_available(presence)) + *aStatusType = STATUS_AVAILABLE; + else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY) || + purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)) + *aStatusType = STATUS_AWAY; + else + *aStatusType = STATUS_UNAVAILABLE; + return NS_OK; +} + +#define PURPLE_PRESENCE_GET_BOOL_IMPL(aName, aFctName) \ + NS_IMETHODIMP purpleAccountBuddy::Get##aName(PRBool *a##aName) \ + { \ + NS_ENSURE_TRUE(mAccount && mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); \ + \ + *a##aName = \ + purple_account_is_connected(purple_buddy_get_account(mPurpleBuddy)) && \ + purple_presence_is_##aFctName(purple_buddy_get_presence(mPurpleBuddy)); \ + return NS_OK; \ } /* readonly attribute boolean online; */ PURPLE_PRESENCE_GET_BOOL_IMPL(Online, online) /* readonly attribute boolean available; */ PURPLE_PRESENCE_GET_BOOL_IMPL(Available, available) /* readonly attribute boolean idle; */ PURPLE_PRESENCE_GET_BOOL_IMPL(Idle, idle) /* readonly attribute boolean mobile; */ NS_IMETHODIMP purpleAccountBuddy::GetMobile(PRBool *aMobile) { NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); - *aMobile = - purple_presence_is_status_primitive_active(purple_buddy_get_presence(mBuddy), - PURPLE_STATUS_MOBILE); + PurplePresence *presence = purple_buddy_get_presence(mPurpleBuddy); + *aMobile = purple_presence_is_status_primitive_active(presence, + PURPLE_STATUS_MOBILE); return NS_OK; } /* readonly attribute string status; */ -NS_IMETHODIMP purpleAccountBuddy::GetStatus(nsACString &aStatus) +NS_IMETHODIMP purpleAccountBuddy::GetStatusText(nsACString &aStatusText) { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - aStatus.Truncate(); - PurplePluginProtocolInfo *prpl_info = GetPrplInfo(mBuddy); + aStatusText.Truncate(); + + purpleAccount *account = + purpleAccount::fromPurpleAccount(purple_buddy_get_account(mPurpleBuddy)); + NS_ENSURE_TRUE(account, NS_ERROR_NOT_INITIALIZED); + + PurplePluginProtocolInfo *prpl_info = account->GetPrplInfo(); if (prpl_info && prpl_info->status_text) { - char *tmp1 = prpl_info->status_text(mBuddy); + char *tmp1 = prpl_info->status_text(mPurpleBuddy); char *tmp2 = purple_unescape_html(tmp1); - aStatus = tmp2; + aStatusText = tmp2; g_free(tmp1); g_free(tmp2); } return NS_OK; } +/* readonly attribute long availabilityDetails; */ +NS_IMETHODIMP purpleAccountBuddy::GetAvailabilityDetails(PRInt32 *aAvailabilityDetails) +{ + *aAvailabilityDetails = 0; + return NS_OK; +} + /* void remove (); */ NS_IMETHODIMP purpleAccountBuddy::Remove() { - NS_ENSURE_TRUE(mBuddy, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mPurpleBuddy, NS_ERROR_NOT_INITIALIZED); - purple_account_remove_buddy(mBuddy->account, mBuddy, - purple_buddy_get_group(mBuddy)); - purple_blist_remove_buddy(mBuddy); + purple_account_remove_buddy(mPurpleBuddy->account, mPurpleBuddy, + purple_buddy_get_group(mPurpleBuddy)); + purple_blist_remove_buddy(mPurpleBuddy); return NS_OK; } diff --git a/purple/purplexpcom/src/purpleAccountBuddy.h b/purple/purplexpcom/src/purpleAccountBuddy.h --- a/purple/purplexpcom/src/purpleAccountBuddy.h +++ b/purple/purplexpcom/src/purpleAccountBuddy.h @@ -33,54 +33,50 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef PURPLE_ACCOUNT_BUDDY_H_ # define PURPLE_ACCOUNT_BUDDY_H_ -#include "purpleIAccountBuddy.h" +#include "imIContactsService.h" #include "purpleTag.h" #include -// af152d5b-fa54-45d4-b6a7-5b4cabef9b7c #define PURPLE_ACCOUNTBUDDY_CID \ { 0xaf152d5b, 0xfa54, 0x45d4, \ { 0xb6, 0xa7, 0x5b, 0x4c, 0xab, 0xef, 0x9b, 0x7c } \ } -#define PURPLE_ACCOUNTBUDDY_CONTRACTID \ - "@instantbird.org/purple/accountbuddy;1" - #define GET_NODE_UI_DATA(aNode) \ purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(aNode)) -class purpleAccountBuddy : public purpleIAccountBuddy +class purpleAccountBuddy : public imIAccountBuddy { public: NS_DECL_ISUPPORTS - NS_DECL_PURPLEIACCOUNTBUDDY + NS_DECL_IMIACCOUNTBUDDY + NS_DECL_IMISTATUSINFO - purpleAccountBuddy(); - void Init(purpleIAccount *aAccount, PurpleBuddy *aBuddy, purpleTag *aTag); + purpleAccountBuddy(PurpleBuddy *aPurpleBuddy, imIBuddy *aBuddy); + // void Init(purpleIAccount *aAccount, PurpleBuddy *aBuddy, purpleTag *aTag); void Uninit(); - nsresult Store(PRInt32 aBuddyId); - nsresult UnStore(PRInt32 aBuddyId); - PurpleBuddy *GetBuddy() { return mBuddy; } - void SetBuddyAlias(const nsCString& aBuddyAlias); + // nsresult Store(PRInt32 aBuddyId); + // nsresult UnStore(PRInt32 aBuddyId); + void SetServerAlias(const nsCString& aBuddyAlias); static inline purpleAccountBuddy *fromPurpleBuddy(PurpleBuddy *aBuddy) { return static_cast(GET_NODE_UI_DATA(aBuddy)); } + nsresult NotifyObservers(const char *aSignal); private: ~purpleAccountBuddy(); static void CleanUserInfo(void *aData); - PurplePluginProtocolInfo *GetPrplInfo(PurpleBuddy *aBuddy); protected: /* additional members */ nsCOMPtr mAccount; - PurpleBuddy *mBuddy; - nsCOMPtr mTag; + PurpleBuddy *mPurpleBuddy; + nsCOMPtr mBuddy; }; #endif /* !PURPLE_ACCOUNT_BUDDY_H_ */ diff --git a/purple/purplexpcom/src/purpleConvIM.cpp b/purple/purplexpcom/src/purpleConvIM.cpp --- a/purple/purplexpcom/src/purpleConvIM.cpp +++ b/purple/purplexpcom/src/purpleConvIM.cpp @@ -31,17 +31,17 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "purpleConvIM.h" -#include "purpleIAccountBuddy.h" +#include "imIContactsService.h" #include "purpleCoreService.h" #include #include #include #include #include #include @@ -115,30 +115,30 @@ NS_IMETHODIMP purpleConvIM::SendMsg(cons rv = os->NotifyObservers(static_cast(this), "im-sent", NS_ConvertUTF8toUTF16(aMsg).get()); NS_ENSURE_SUCCESS(rv, rv); mSentTyping = PR_FALSE; return NS_OK; } -/* readonly attribute purpleIAccountBuddy buddy; */ -NS_IMETHODIMP purpleConvIM::GetBuddy(purpleIAccountBuddy * *aBuddy) +/* readonly attribute imIAccountBuddy buddy; */ +NS_IMETHODIMP purpleConvIM::GetBuddy(imIAccountBuddy * *aBuddy) { NS_ENSURE_TRUE(mConv, NS_ERROR_NOT_INITIALIZED); PurpleBuddy *buddy = purple_find_buddy(purple_conversation_get_account(mConv), purple_conversation_get_name(mConv)); if (!buddy) { *aBuddy = nsnull; return NS_OK; } - purpleIAccountBuddy *pab = purpleAccountBuddy::fromPurpleBuddy(buddy); + imIAccountBuddy *pab = purpleAccountBuddy::fromPurpleBuddy(buddy); NS_ENSURE_TRUE(pab, NS_ERROR_NOT_INITIALIZED); NS_ADDREF(*aBuddy = pab); return NS_OK; } /* readonly attribute short typingState; */ NS_IMETHODIMP purpleConvIM::GetTypingState(PRInt16 *aTypingState) diff --git a/purple/purplexpcom/src/purpleCoreService.cpp b/purple/purplexpcom/src/purpleCoreService.cpp --- a/purple/purplexpcom/src/purpleCoreService.cpp +++ b/purple/purplexpcom/src/purpleCoreService.cpp @@ -39,16 +39,17 @@ #include "purpleAccount.h" #include "purpleDNS.h" #include "purpleGetText.h" #include "purpleNetwork.h" #include "purpleStorage.h" #include "purpleSockets.h" #include "purpleTagsService.h" #include "purpleTimer.h" +#include "imIContactsService.h" #pragma GCC visibility push(default) #include #include #pragma GCC visibility pop #include #include @@ -87,16 +88,17 @@ static PRLogModuleInfo *gPurpleCoreServi NS_IMPL_CLASSINFO(purpleCoreService, NULL, nsIClassInfo::SINGLETON, PURPLE_CORE_SERVICE_CID) NS_IMPL_ISUPPORTS3_CI(purpleCoreService, purpleICoreService, nsIObserver, purpleIAccountsService) extern nsresult init_libpurple(); extern void connect_to_blist_signals(); +extern void disconnect_blist_signals(); purpleCoreService::purpleCoreService() :mInitialized(PR_FALSE), mQuitting(PR_FALSE), mAutoLoginStatus(AUTOLOGIN_ENABLED), mProtocols(nsnull), mProxies(nsnull), mConversations(nsnull) @@ -211,22 +213,24 @@ NS_IMETHODIMP purpleCoreService::Init() /* Load saved accounts */ mAccountsService = new purpleAccountsService(); NS_ENSURE_TRUE(mAccountsService, NS_ERROR_OUT_OF_MEMORY); rv = mAccountsService->InitAccounts(); NS_ENSURE_SUCCESS(rv, rv); +#if 0 /* Load the tags */ mTagsService = do_GetService(PURPLE_TAGS_SERVICE_CONTRACTID); NS_ENSURE_TRUE(mTagsService, NS_ERROR_OUT_OF_MEMORY); rv = mTagsService->InitTags(); NS_ENSURE_SUCCESS(rv, rv); +#endif /* Load the buddy list from mozStorage */ InitBuddyList(); /* Check if we should disable auto-login */ InitAutoLoginStatus(); /* If we are not offline, set the initial status to available */ @@ -406,19 +410,27 @@ nsresult purpleCoreService::GetLastCrash PR_Close(fd); *aLastCrashTime = strtol(data.get(), NULL, 10); return NS_OK; } #endif nsresult purpleCoreService::InitBuddyList() { + nsCOMPtr contacts = + do_GetService("@instantbird.org/purple/contacts-service;1"); + NS_ENSURE_TRUE(contacts, NS_ERROR_UNEXPECTED); + + nsresult rv = contacts->InitContacts(); + NS_ENSURE_SUCCESS(rv, rv); + /* The only effect is to set blist_loaded to TRUE */ purple_blist_load(); +#if 0 /* Now, let's read the real data from mozStorage */ purpleStorage *storageInstance = purpleStorage::GetInstance(); NS_ENSURE_TRUE(storageInstance, NS_ERROR_OUT_OF_MEMORY); mozIStorageConnection *DBConn = storageInstance->GetConnection(); nsCOMPtr statement; nsresult rv = DBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT buddies.id, name, srv_alias, alias, tag_id, account_id FROM buddies" @@ -480,16 +492,17 @@ nsresult purpleCoreService::InitBuddyLis LOG(("Found purpleBuddy with id %i in HashTable", id)); } rv = account->LoadBuddy(purpleBuddy, name, alias, serverAlias, tag); if (NS_FAILED(rv)) { NS_WARNING("Failed to Load Buddy"); } } +#endif connect_to_blist_signals(); return NS_OK; } void purpleCoreService::InitProtocols() { LOG(("Start init protocols")); @@ -583,20 +596,39 @@ NS_IMETHODIMP purpleCoreService::Quit() os->NotifyObservers(static_cast(this), "purple-quit", nsnull); for (PRInt32 i = mConversations.Count() - 1; i >= 0; --i) { mConversations[i]->UnInit(); } mConversations.Clear(); + disconnect_blist_signals(); mAccountsService->UnInitAccounts(); mAccountsService = nsnull; +#if 0 mTagsService->UnInitTags(); mTagsService = nsnull; +#endif + nsCOMPtr contacts = + do_GetService("@instantbird.org/purple/contacts-service;1"); + if (contacts) + contacts->UnInitContacts(); + + PurpleBlistNode *gnode; + for (gnode = purple_blist_get_root(); gnode; gnode = gnode->next) { + if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) + continue; + void *ui_data = purple_blist_node_get_ui_data(gnode); + if (ui_data) { + imITag *tag = static_cast(ui_data); + NS_RELEASE(tag); + } + } + mProtocols.Clear(); mProxies.Clear(); mBuddiesById.Clear(); mInitialized = PR_FALSE; purple_core_quit(); LOG(("purple_core_quit")); purpleStorage::unInit(); purpleSocketWatcher::unInit(); @@ -629,16 +661,18 @@ NS_IMETHODIMP purpleCoreService::GetBudd *aResult = buddyPtr; LOG(("purpleBuddy found in HashTable (by Id)")); return NS_OK; } /* [noscript] void purpleBuddyAdded (in PurpleNativeBuddy aBuddy); */ NS_IMETHODIMP purpleCoreService::PurpleBuddyAdded(PurpleBuddy *aBuddy) { + return NS_ERROR_NOT_IMPLEMENTED; +#if 0 /* Try to get the Id from mozStorage. If we find it, append the new * PurpleBuddy to it, and add the new PurpleBuddy pointer to the hash. */ purpleStorage *storageInstance = purpleStorage::GetInstance(); mozIStorageStatement *statement = storageInstance->mGetBuddyIdFromNameAndPrpl; mozStorageStatementScoper scoper(statement); nsCString name(purple_normalize(aBuddy->account, aBuddy->name)); nsresult rv = statement->BindUTF8StringParameter(0, name); @@ -675,24 +709,27 @@ NS_IMETHODIMP purpleCoreService::PurpleB * hash. */ nsCOMPtr buddy = do_CreateInstance(PURPLE_BUDDY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); buddy->Init(aBuddy); LOG(("purpleBuddy added in HashTable with Id %i", buddy->getId())); mBuddiesById.Put(buddy->getId(), buddy); return NS_OK; +#endif } /* [noscript] void purpleBuddyRemoved (in PurpleNativeBuddy aBuddy); */ NS_IMETHODIMP purpleCoreService::PurpleBuddyRemoved(PurpleBuddy *aBuddy) { if (mQuitting) return NS_OK; + return NS_ERROR_NOT_IMPLEMENTED; +#if 0 purpleBuddy *buddy = purpleBuddy::fromPurpleBuddy(aBuddy); NS_ENSURE_TRUE(buddy, NS_ERROR_FAILURE); nsresult rv = buddy->RemoveAccount(aBuddy); NS_ENSURE_SUCCESS(rv, rv); if (!buddy->GetAccountCount()) { // If we removed the last account, we need to remove the whole purpleBuddy @@ -700,16 +737,17 @@ NS_IMETHODIMP purpleCoreService::PurpleB NS_ENSURE_SUCCESS(rv, rv); PRUint32 id = buddy->getId(); buddy->UnInit(); mBuddiesById.Remove(id); } return NS_OK; +#endif } /* nsISimpleEnumerator getConversations (); */ NS_IMETHODIMP purpleCoreService::GetConversations(nsISimpleEnumerator **aResult) { PURPLE_ENSURE_INIT(mInitialized); return NS_NewArrayEnumerator(aResult, mConversations); diff --git a/purple/purplexpcom/src/purpleInit.cpp b/purple/purplexpcom/src/purpleInit.cpp --- a/purple/purplexpcom/src/purpleInit.cpp +++ b/purple/purplexpcom/src/purpleInit.cpp @@ -511,40 +511,46 @@ static PurpleConversationUiOps conversat static void buddy_signals(PurpleBuddy *aBuddy, const char *aSignal) { // FIXME when this was handled by purpleCoreService, signals sent // while mQuitting was true were ignored. LOG(("Attempting to send %s signal, group = %s, buddy = %s", aSignal, purple_group_get_name(purple_buddy_get_group(aBuddy)), aBuddy->name)); - nsCOMPtr pab = + nsCOMPtr pab = purpleAccountBuddy::fromPurpleBuddy(aBuddy); NS_ENSURE_TRUE(pab, ); - // FIXME: all this should be moved inside purpleAccountBuddy / purpleTag - nsCOMPtr tag; - nsresult rv = pab->GetTag(getter_AddRefs(tag)); - if (NS_SUCCEEDED(rv)) - tag->NotifyObservers(pab, aSignal, nsnull); - - nsCOMPtr os = - do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + nsresult rv = pab->NotifyObservers(aSignal); NS_ENSURE_SUCCESS(rv, ); - os->NotifyObservers(pab, aSignal, nsnull); } #define PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER(aSignal, aHandler) \ purple_signal_connect(instance, aSignal, &handle, \ PURPLE_CALLBACK(aHandler), \ (void *)aSignal) #define PURPLE_CONNECT_BUDDY_SIGNAL(aSignal) \ PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER(aSignal, buddy_signals) +static void buddy_signed_on(PurpleBuddy *aBuddy, const char *aSignal) +{ + buddy_signals(aBuddy, "account-buddy-availability-changed"); + buddy_signals(aBuddy, "account-buddy-status-changed"); + buddy_signals(aBuddy, "account-buddy-signed-on"); +} + +static void buddy_signed_off(PurpleBuddy *aBuddy, const char *aSignal) +{ + buddy_signals(aBuddy, "account-buddy-availability-changed"); + buddy_signals(aBuddy, "account-buddy-status-changed"); + buddy_signals(aBuddy, "account-buddy-signed-off"); +} + static void buddy_added(PurpleBuddy *buddy, void *null) { // This is the buddy-added purple signal. It is fired when a buddy // is added to the list or to a group. // FIXME what should we do if the buddy is moved to a new group. Is // ui_data already set in this case? @@ -568,73 +574,96 @@ static void buddy_removed(PurpleBuddy *b nsCOMPtr pcs = do_GetService(PURPLE_CORE_SERVICE_CONTRACTID); pcs->PurpleBuddyRemoved(buddy); } static void buddy_away(PurpleBuddy *aBuddy, PurpleStatus *old_status, PurpleStatus *status) { - buddy_signals(aBuddy, "buddy-away"); + buddy_signals(aBuddy, "account-buddy-availability-changed"); + buddy_signals(aBuddy, "account-buddy-status-changed"); } static void buddy_idle(PurpleBuddy *aBuddy, gboolean old_idle, gboolean idle) { - buddy_signals(aBuddy, "buddy-idle"); + buddy_signals(aBuddy, "account-buddy-availability-changed"); + buddy_signals(aBuddy, "account-buddy-status-changed"); } static void buddy_alias(PurpleBlistNode *aNode, const char *old_alias) { // We are only interested by buddy aliases if (!PURPLE_BLIST_NODE_IS_BUDDY(aNode)) return; - PurpleBuddy *buddy = (PurpleBuddy *)aNode; - buddy_signals(buddy, "buddy-alias"); + purpleAccountBuddy *pab = + purpleAccountBuddy::fromPurpleBuddy((PurpleBuddy *)aNode); + if (!pab) + return; // it's fine to ignore these notifications for unknown buddies + //XXX is this comment still valid? - purpleBuddy *buddyPtr = purpleBuddy::fromPurpleBuddy(buddy); - if (!buddyPtr) - return; // it's fine to ignore these notifications for unknown buddies - buddyPtr->UpdateServerAlias(purple_buddy_get_server_alias(buddy)); + pab->NotifyObservers("account-buddy-display-name-changed"); } +/* + * Used as handle to connect and then mass-disconnect the buddy list signals. + * The value is 1 when the signals are connected, 0 when they are not. + */ +static int blist_signals_handle = 0; + void connect_to_blist_signals() { - int handle; +#define handle blist_signals_handle void *instance = purple_blist_get_handle(); - PURPLE_CONNECT_BUDDY_SIGNAL("buddy-signed-on"); - PURPLE_CONNECT_BUDDY_SIGNAL("buddy-signed-off"); + PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("buddy-signed-on", buddy_signed_on); + PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("buddy-signed-off", buddy_signed_off); PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("buddy-added", buddy_added); PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("buddy-removed", buddy_removed); // This buddy-removed-from-group purple signal was added for // Instantbird. It is fired when a buddy is removed permanenty from // a group but not from the buddy list. (ie when the buddy has been // moved) PURPLE_CONNECT_BUDDY_SIGNAL("buddy-removed-from-group"); PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("buddy-status-changed", buddy_away); PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("buddy-idle-changed", buddy_idle); PURPLE_CONNECT_BUDDY_SIGNAL_HANDLER("blist-node-aliased", buddy_alias); - LOG(("Connecting to blist signals")); +#undef handle + blist_signals_handle = 1; + LOG(("Connected to blist signals")); +} + +void +disconnect_blist_signals() +{ + purple_signals_disconnect_by_handle(&blist_signals_handle); + blist_signals_handle = 0; } static void remove_blist_node(PurpleBuddyList *aList, PurpleBlistNode *aNode) { + // Ignore this ui ops if buddy list signals are not connected: + if (!blist_signals_handle) + return; + // This is the removed blist uiop. It is fired when a possibly // visible element of the buddy list is removed (because the account // got disconnected) // For now, we are only interested by buddy removal if (!PURPLE_BLIST_NODE_IS_BUDDY(aNode)) return; PurpleBuddy *buddy = (PurpleBuddy *) aNode; LOG(("purple uiop : remove_blist_node, name = %s", buddy->name)); - buddy_signals(buddy, "buddy-removed"); + // buddy_signals(buddy, "buddy-removed"); + buddy_signals(buddy, "account-buddy-availability-changed"); + buddy_signals(buddy, "account-buddy-status-changed"); } static PurpleBlistUiOps blist_uiops = { NULL, /* new_list */ NULL, /* new_node FIXME: should use it */ NULL, /* show */ NULL, /* update */ remove_blist_node, /* remove */ diff --git a/purple/purplexpcom/src/purpleModule.cpp b/purple/purplexpcom/src/purpleModule.cpp --- a/purple/purplexpcom/src/purpleModule.cpp +++ b/purple/purplexpcom/src/purpleModule.cpp @@ -40,65 +40,57 @@ #include "nsIClassInfoImpl.h" #include "nsServiceManagerUtils.h" #include "purpleCoreService.h" #include "purpleConvIM.h" #include "purpleConvChat.h" #include "purpleProtocol.h" #include "purpleUnknownProtocol.h" #include "purpleAccountBase.h" -#include "purpleBuddy.h" #include "purpleTag.h" #include "purpleTagsService.h" -#include "purpleAccountBuddy.h" #include "purpleMessage.h" #include "purpleProxy.h" #include "purpleProxyInfo.h" #ifdef XP_MACOSX # include "nsDockTile.h" #endif NS_GENERIC_FACTORY_CONSTRUCTOR(purpleAccountBase) -NS_GENERIC_FACTORY_CONSTRUCTOR(purpleAccountBuddy) -NS_GENERIC_FACTORY_CONSTRUCTOR(purpleBuddy) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleConvChat) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleConvIM) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleCoreService) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleMessage) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleProtocol) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleProxy) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleProxyInfo) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleTag) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleTagsService) NS_GENERIC_FACTORY_CONSTRUCTOR(purpleUnknownProtocol) #ifdef XP_MACOSX NS_GENERIC_FACTORY_CONSTRUCTOR(nsDockTile) #endif NS_DEFINE_NAMED_CID(PURPLE_ACCOUNT_CID); -NS_DEFINE_NAMED_CID(PURPLE_ACCOUNTBUDDY_CID); -NS_DEFINE_NAMED_CID(PURPLE_BUDDY_CID); NS_DEFINE_NAMED_CID(PURPLE_CONV_CHAT_CID); NS_DEFINE_NAMED_CID(PURPLE_CONV_IM_CID); NS_DEFINE_NAMED_CID(PURPLE_CORE_SERVICE_CID); NS_DEFINE_NAMED_CID(PURPLE_MESSAGE_CID); NS_DEFINE_NAMED_CID(PURPLE_PROTOCOL_CID); NS_DEFINE_NAMED_CID(PURPLE_PROXY_CID); NS_DEFINE_NAMED_CID(PURPLE_PROXY_INFO_CID); NS_DEFINE_NAMED_CID(PURPLE_TAG_CID); NS_DEFINE_NAMED_CID(PURPLE_TAGS_SERVICE_CID); NS_DEFINE_NAMED_CID(PURPLE_UNKNOWN_PROTOCOL_CID); #ifdef XP_MACOSX NS_DEFINE_NAMED_CID(NSDOCKBADGESERVICE_CID); #endif static const mozilla::Module::CIDEntry kPurpleCIDs[] = { { &kPURPLE_ACCOUNT_CID, false, NULL, purpleAccountBaseConstructor }, - { &kPURPLE_ACCOUNTBUDDY_CID, false, NULL, purpleAccountBuddyConstructor }, - { &kPURPLE_BUDDY_CID, false, NULL, purpleBuddyConstructor }, { &kPURPLE_CONV_CHAT_CID, false, NULL, purpleConvChatConstructor }, { &kPURPLE_CONV_IM_CID, false, NULL, purpleConvIMConstructor }, { &kPURPLE_CORE_SERVICE_CID, true, NULL, purpleCoreServiceConstructor }, { &kPURPLE_MESSAGE_CID, false, NULL, purpleMessageConstructor }, { &kPURPLE_PROTOCOL_CID, false, NULL, purpleProtocolConstructor }, { &kPURPLE_PROXY_CID, false, NULL, purpleProxyConstructor }, { &kPURPLE_PROXY_INFO_CID, false, NULL, purpleProxyInfoConstructor }, { &kPURPLE_TAG_CID, false, NULL, purpleTagConstructor }, @@ -108,18 +100,16 @@ static const mozilla::Module::CIDEntry k #ifdef XP_MACOSX { &kNSDOCKBADGESERVICE_CID, true, NULL, nsDockTileConstructor }, #endif { NULL } }; static const mozilla::Module::ContractIDEntry kPurpleContracts[] = { { PURPLE_ACCOUNT_CONTRACTID, &kPURPLE_ACCOUNT_CID }, - { PURPLE_ACCOUNTBUDDY_CONTRACTID, &kPURPLE_ACCOUNTBUDDY_CID }, - { PURPLE_BUDDY_CONTRACTID, &kPURPLE_BUDDY_CID }, { PURPLE_CONV_CHAT_CONTRACTID, &kPURPLE_CONV_CHAT_CID }, { PURPLE_CONV_IM_CONTRACTID, &kPURPLE_CONV_IM_CID }, { PURPLE_CORE_SERVICE_CONTRACTID, &kPURPLE_CORE_SERVICE_CID }, { PURPLE_MESSAGE_CONTRACTID, &kPURPLE_MESSAGE_CID }, { PURPLE_PROTOCOL_CONTRACTID, &kPURPLE_PROTOCOL_CID }, { PURPLE_PROXY_CONTRACTID, &kPURPLE_PROXY_CID }, { PURPLE_PROXY_INFO_CONTRACTID, &kPURPLE_PROXY_INFO_CID }, { PURPLE_TAG_CONTRACTID, &kPURPLE_TAG_CID },