From 6910d3c122c4dc5117ee348b717d2f65afcc5c9f Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Wed, 14 Nov 2012 11:49:56 -0800 Subject: [PATCH] Bug 809661 - Need a speedy way to construct a thread list for SMS messages. r=sicking, a=blocking-basecamp --- dom/sms/Makefile.in | 2 +- dom/sms/interfaces/nsIDOMSmsManager.idl | 4 +- dom/sms/interfaces/nsISmsDatabaseService.idl | 4 +- dom/sms/interfaces/nsISmsRequest.idl | 14 +- dom/sms/src/SmsManager.cpp | 14 + dom/sms/src/SmsRequest.cpp | 151 ++++++++++- dom/sms/src/SmsRequest.h | 21 +- dom/sms/src/android/SmsDatabaseService.cpp | 7 + dom/sms/src/fallback/SmsDatabaseService.cpp | 7 + dom/sms/src/ipc/PSms.ipdl | 5 + dom/sms/src/ipc/PSmsRequest.ipdl | 20 ++ dom/sms/src/ipc/SmsChild.cpp | 8 + dom/sms/src/ipc/SmsIPCService.cpp | 7 + dom/sms/src/ipc/SmsParent.cpp | 17 ++ dom/sms/src/ipc/SmsParent.h | 3 + dom/sms/src/ril/SmsDatabaseService.js | 262 +++++++++++++++---- js/xpconnect/src/dictionary_helper_gen.conf | 3 +- 17 files changed, 492 insertions(+), 57 deletions(-) diff --git a/dom/sms/Makefile.in b/dom/sms/Makefile.in index 8196e39412c6..14338d78479a 100644 --- a/dom/sms/Makefile.in +++ b/dom/sms/Makefile.in @@ -11,7 +11,7 @@ relativesrcdir = @relativesrcdir@ include $(DEPTH)/config/autoconf.mk -PARALLEL_DIRS = interfaces src +DIRS = interfaces src TEST_DIRS += tests ifdef ENABLE_TESTS diff --git a/dom/sms/interfaces/nsIDOMSmsManager.idl b/dom/sms/interfaces/nsIDOMSmsManager.idl index a6ce0e457df5..36eca7954a43 100644 --- a/dom/sms/interfaces/nsIDOMSmsManager.idl +++ b/dom/sms/interfaces/nsIDOMSmsManager.idl @@ -8,7 +8,7 @@ interface nsIDOMEventListener; interface nsIDOMMozSmsRequest; interface nsIDOMMozSmsFilter; -[scriptable, builtinclass, uuid(1bee1224-56a2-4935-af7b-0011746306cb)] +[scriptable, builtinclass, uuid(caaf5c38-a730-4dbb-a0f0-12384bfac8e3)] interface nsIDOMMozSmsManager : nsIDOMEventTarget { unsigned short getNumberOfMessagesForText(in DOMString text); @@ -29,6 +29,8 @@ interface nsIDOMMozSmsManager : nsIDOMEventTarget nsIDOMMozSmsRequest markMessageRead(in long id, in boolean aValue); + nsIDOMMozSmsRequest getThreadList(); + [implicit_jscontext] attribute jsval onreceived; [implicit_jscontext] attribute jsval onsent; [implicit_jscontext] attribute jsval ondeliverysuccess; diff --git a/dom/sms/interfaces/nsISmsDatabaseService.idl b/dom/sms/interfaces/nsISmsDatabaseService.idl index 157f0462c13a..cff1a6d8550d 100644 --- a/dom/sms/interfaces/nsISmsDatabaseService.idl +++ b/dom/sms/interfaces/nsISmsDatabaseService.idl @@ -14,7 +14,7 @@ interface nsIDOMMozSmsFilter; interface nsISmsRequest; -[scriptable, uuid(6ad55465-6937-4491-93ca-29dad9775d46)] +[scriptable, uuid(c2cb2af7-6b96-4915-bcc8-54ad705d6110)] interface nsISmsDatabaseService : nsISupports { // Takes some information required to save the message and returns its id. @@ -30,4 +30,6 @@ interface nsISmsDatabaseService : nsISupports void getNextMessageInList(in long listId, in nsISmsRequest request); void clearMessageList(in long listId); void markMessageRead(in long messageId, in boolean value, in nsISmsRequest request); + + void getThreadList(in nsISmsRequest request); }; diff --git a/dom/sms/interfaces/nsISmsRequest.idl b/dom/sms/interfaces/nsISmsRequest.idl index 8ce490fcf6a9..993cb43ae2a1 100644 --- a/dom/sms/interfaces/nsISmsRequest.idl +++ b/dom/sms/interfaces/nsISmsRequest.idl @@ -6,7 +6,15 @@ interface nsIDOMMozSmsMessage; -[scriptable, builtinclass, uuid(97067327-64b9-4e26-848b-59e443c55db9)] +dictionary SmsThreadListItem +{ + DOMString senderOrReceiver; + unsigned long long timestamp; + DOMString body; + unsigned long long unreadCount; +}; + +[scriptable, builtinclass, uuid(82a6d16d-cf33-4745-8662-8b5d441f512f)] interface nsISmsRequest : nsISupports { /** @@ -37,4 +45,8 @@ interface nsISmsRequest : nsISupports void notifyMessageMarkedRead(in boolean read); void notifyMarkMessageReadFailed(in long error); + + [implicit_jscontext] + void notifyThreadList(in jsval threadList); + void notifyThreadListFailed(in long error); }; diff --git a/dom/sms/src/SmsManager.cpp b/dom/sms/src/SmsManager.cpp index 40493d00aebf..d022cc680fb2 100644 --- a/dom/sms/src/SmsManager.cpp +++ b/dom/sms/src/SmsManager.cpp @@ -303,6 +303,20 @@ SmsManager::MarkMessageRead(int32_t aId, bool aValue, return NS_OK; } +NS_IMETHODIMP +SmsManager::GetThreadList(nsIDOMMozSmsRequest** aRequest) +{ + nsCOMPtr req = SmsRequest::Create(this); + nsCOMPtr smsDBService = + do_GetService(SMS_DATABASE_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(smsDBService, NS_ERROR_FAILURE); + nsCOMPtr forwarder = + new SmsRequestForwarder(static_cast(req.get())); + smsDBService->GetThreadList(forwarder); + req.forget(aRequest); + return NS_OK; +} + nsresult SmsManager::DispatchTrustedSmsEventToSelf(const nsAString& aEventName, nsIDOMMozSmsMessage* aMessage) { diff --git a/dom/sms/src/SmsRequest.cpp b/dom/sms/src/SmsRequest.cpp index 2630e499c65c..6ca0f6de3a70 100644 --- a/dom/sms/src/SmsRequest.cpp +++ b/dom/sms/src/SmsRequest.cpp @@ -9,11 +9,16 @@ #include "nsDOMString.h" #include "nsContentUtils.h" #include "nsIDOMSmsMessage.h" +#include "nsIScriptGlobalObject.h" +#include "nsPIDOMWindow.h" #include "SmsCursor.h" #include "SmsMessage.h" #include "SmsManager.h" #include "mozilla/dom/DOMError.h" #include "SmsParent.h" +#include "jsapi.h" +#include "DictionaryHelpers.h" +#include "xpcpublic.h" #define SUCCESS_EVENT_NAME NS_LITERAL_STRING("success") #define ERROR_EVENT_NAME NS_LITERAL_STRING("error") @@ -145,12 +150,7 @@ SmsRequest::SetSuccess(nsIDOMMozSmsMessage* aMessage) void SmsRequest::SetSuccess(bool aResult) { - NS_PRECONDITION(!mDone, "mDone shouldn't have been set to true already!"); - NS_PRECONDITION(!mError, "mError shouldn't have been set!"); - NS_PRECONDITION(mResult == JSVAL_NULL, "mResult shouldn't have been set!"); - - mResult.setBoolean(aResult); - mDone = true; + SetSuccess(aResult ? JSVAL_TRUE : JSVAL_FALSE); } void @@ -168,6 +168,17 @@ SmsRequest::SetSuccess(nsIDOMMozSmsCursor* aCursor) } } +void +SmsRequest::SetSuccess(const jsval& aResult) +{ + NS_PRECONDITION(!mDone, "mDone shouldn't have been set to true already!"); + NS_PRECONDITION(!mError, "mError shouldn't have been set!"); + NS_PRECONDITION(JSVAL_IS_VOID(mResult), "mResult shouldn't have been set!"); + + mResult = aResult; + mDone = true; +} + bool SmsRequest::SetSuccessInternal(nsISupports* aObject) { @@ -445,6 +456,134 @@ SmsRequest::NotifyMarkMessageReadFailed(int32_t aError) return NotifyError(aError); } +NS_IMETHODIMP +SmsRequest::NotifyThreadList(const jsval& aThreadList, JSContext* aCx) +{ + MOZ_ASSERT(aThreadList.isObject()); + + if (mParent) { + JSObject* array = const_cast(&aThreadList.toObject()); + + uint32_t length; + bool ok = JS_GetArrayLength(aCx, array, &length); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + ReplyThreadList reply; + InfallibleTArray& ipcItems = reply.items(); + + if (length) { + ipcItems.SetCapacity(length); + + for (uint32_t i = 0; i < length; i++) { + jsval arrayEntry; + ok = JS_GetElement(aCx, array, i, &arrayEntry); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + MOZ_ASSERT(arrayEntry.isObject()); + + SmsThreadListItem item; + nsresult rv = item.Init(aCx, &arrayEntry); + NS_ENSURE_SUCCESS(rv, rv); + + ThreadListItem* ipcItem = ipcItems.AppendElement(); + ipcItem->senderOrReceiver() = item.senderOrReceiver; + ipcItem->timestamp() = item.timestamp; + ipcItem->body() = item.body; + ipcItem->unreadCount() = item.unreadCount; + } + } + + return SendMessageReply(reply); + } + + return NotifySuccess(aThreadList); +} + +NS_IMETHODIMP +SmsRequest::NotifyThreadListFailed(int32_t aError) +{ + if (mParent) { + return SendMessageReply(MessageReply(ReplyThreadListFail(aError))); + } + return NotifyError(aError); +} + +void +SmsRequest::NotifyThreadList(const InfallibleTArray& aItems) +{ + MOZ_ASSERT(!mParent); + MOZ_ASSERT(GetOwner()); + + nsresult rv; + nsIScriptContext* sc = GetContextForEventHandlers(&rv); + NS_ENSURE_SUCCESS_VOID(rv); + NS_ENSURE_TRUE_VOID(sc); + + JSContext* cx = sc->GetNativeContext(); + MOZ_ASSERT(cx); + + nsCOMPtr sgo = do_QueryInterface(GetOwner()); + + JSObject* ownerObj = sgo->GetGlobalJSObject(); + NS_ENSURE_TRUE_VOID(ownerObj); + + nsCxPusher pusher; + NS_ENSURE_TRUE_VOID(pusher.Push(cx, false)); + + JSAutoRequest ar(cx); + JSAutoCompartment ac(cx, ownerObj); + + JSObject* array = JS_NewArrayObject(cx, aItems.Length(), nullptr); + NS_ENSURE_TRUE_VOID(array); + + bool ok; + + for (uint32_t i = 0; i < aItems.Length(); i++) { + const ThreadListItem& source = aItems[i]; + + nsString temp = source.senderOrReceiver(); + + jsval senderOrReceiver; + ok = xpc::StringToJsval(cx, temp, &senderOrReceiver); + NS_ENSURE_TRUE_VOID(ok); + + JSObject* timestampObj = JS_NewDateObjectMsec(cx, source.timestamp()); + NS_ENSURE_TRUE_VOID(timestampObj); + + jsval timestamp = OBJECT_TO_JSVAL(timestampObj); + + temp = source.body(); + + jsval body; + ok = xpc::StringToJsval(cx, temp, &body); + NS_ENSURE_TRUE_VOID(ok); + + jsval unreadCount = JS_NumberValue(double(source.unreadCount())); + + JSObject* elementObj = JS_NewObject(cx, nullptr, nullptr, nullptr); + NS_ENSURE_TRUE_VOID(elementObj); + + ok = JS_SetProperty(cx, elementObj, "senderOrReceiver", &senderOrReceiver); + NS_ENSURE_TRUE_VOID(ok); + + ok = JS_SetProperty(cx, elementObj, "timestamp", ×tamp); + NS_ENSURE_TRUE_VOID(ok); + + ok = JS_SetProperty(cx, elementObj, "body", &body); + NS_ENSURE_TRUE_VOID(ok); + + ok = JS_SetProperty(cx, elementObj, "unreadCount", &unreadCount); + NS_ENSURE_TRUE_VOID(ok); + + jsval element = OBJECT_TO_JSVAL(elementObj); + + ok = JS_SetElement(cx, array, i, &element); + NS_ENSURE_TRUE_VOID(ok); + } + + NotifyThreadList(OBJECT_TO_JSVAL(array), cx); +} + } // namespace sms } // namespace dom } // namespace mozilla diff --git a/dom/sms/src/SmsRequest.h b/dom/sms/src/SmsRequest.h index e27cf80b005a..7abe4b1b9809 100644 --- a/dom/sms/src/SmsRequest.h +++ b/dom/sms/src/SmsRequest.h @@ -17,24 +17,33 @@ namespace mozilla { namespace dom { namespace sms { +class SmsRequestChild; class SmsRequestParent; class MessageReply; +class ThreadListItem; // We need this forwarder to avoid a QI to nsIClassInfo. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=775997#c51 class SmsRequestForwarder : public nsISmsRequest { - NS_FORWARD_NSISMSREQUEST(mRealRequest->) + friend class SmsRequestChild; +public: NS_DECL_ISUPPORTS + NS_FORWARD_NSISMSREQUEST(mRealRequest->) SmsRequestForwarder(nsISmsRequest* aRealRequest) { mRealRequest = aRealRequest; } + +private: virtual ~SmsRequestForwarder() {} -private: + nsISmsRequest* GetRealRequest() { + return mRealRequest; + } + nsCOMPtr mRealRequest; }; @@ -66,6 +75,9 @@ public: mParentAlive = false; } + void + NotifyThreadList(const InfallibleTArray& aItems); + private: SmsRequest() MOZ_DELETE; @@ -100,6 +112,11 @@ private: */ void SetSuccess(nsIDOMMozSmsCursor* aCursor); + /** + * Set the object in a success state with the result being the given jsval. + */ + void SetSuccess(const jsval& aVal); + /** * Set the object in an error state with the error type being aError. */ diff --git a/dom/sms/src/android/SmsDatabaseService.cpp b/dom/sms/src/android/SmsDatabaseService.cpp index b68ad3233c77..bcf527f1e868 100644 --- a/dom/sms/src/android/SmsDatabaseService.cpp +++ b/dom/sms/src/android/SmsDatabaseService.cpp @@ -112,6 +112,13 @@ SmsDatabaseService::MarkMessageRead(int32_t aMessageId, bool aValue, return NS_OK; } +NS_IMETHODIMP +SmsDatabaseService::GetThreadList(nsISmsRequest* aRequest) +{ + NS_NOTYETIMPLEMENTED("Implement me!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + } // namespace sms } // namespace dom } // namespace mozilla diff --git a/dom/sms/src/fallback/SmsDatabaseService.cpp b/dom/sms/src/fallback/SmsDatabaseService.cpp index cb66dbf36432..35b0e69095e4 100644 --- a/dom/sms/src/fallback/SmsDatabaseService.cpp +++ b/dom/sms/src/fallback/SmsDatabaseService.cpp @@ -90,6 +90,13 @@ SmsDatabaseService::MarkMessageRead(int32_t aMessageId, return NS_OK; } +NS_IMETHODIMP +SmsDatabaseService::GetThreadList(nsISmsRequest* aRequest) +{ + NS_ERROR("We should not be here!"); + return NS_OK; +} + } // namespace sms } // namespace dom } // namespace mozilla diff --git a/dom/sms/src/ipc/PSms.ipdl b/dom/sms/src/ipc/PSms.ipdl index 9059832425c1..139d8b75d3c3 100644 --- a/dom/sms/src/ipc/PSms.ipdl +++ b/dom/sms/src/ipc/PSms.ipdl @@ -53,6 +53,10 @@ struct MarkMessageReadRequest bool value; }; +struct GetThreadListRequest +{ +}; + union IPCSmsRequest { SendMessageRequest; @@ -61,6 +65,7 @@ union IPCSmsRequest CreateMessageListRequest; GetNextMessageInListRequest; MarkMessageReadRequest; + GetThreadListRequest; }; sync protocol PSms { diff --git a/dom/sms/src/ipc/PSmsRequest.ipdl b/dom/sms/src/ipc/PSmsRequest.ipdl index ef4b9b40d83e..6949eea9b473 100644 --- a/dom/sms/src/ipc/PSmsRequest.ipdl +++ b/dom/sms/src/ipc/PSmsRequest.ipdl @@ -85,6 +85,24 @@ struct ReplyNoMessageInList { }; +struct ThreadListItem +{ + nsString senderOrReceiver; + uint64_t timestamp; + nsString body; + uint64_t unreadCount; +}; + +struct ReplyThreadList +{ + ThreadListItem[] items; +}; + +struct ReplyThreadListFail +{ + int32_t error; +}; + union MessageReply { ReplyMessageSend; @@ -99,6 +117,8 @@ union MessageReply ReplyGetNextMessage; ReplyMarkeMessageRead; ReplyMarkeMessageReadFail; + ReplyThreadList; + ReplyThreadListFail; }; } // namespace sms diff --git a/dom/sms/src/ipc/SmsChild.cpp b/dom/sms/src/ipc/SmsChild.cpp index 1314267001c0..ada82e852e86 100644 --- a/dom/sms/src/ipc/SmsChild.cpp +++ b/dom/sms/src/ipc/SmsChild.cpp @@ -161,6 +161,14 @@ SmsRequestChild::Recv__delete__(const MessageReply& aReply) case MessageReply::TReplyMarkeMessageReadFail: mReplyRequest->NotifyMarkMessageReadFailed(aReply.get_ReplyMarkeMessageReadFail().error()); break; + case MessageReply::TReplyThreadList: { + SmsRequestForwarder* forwarder = static_cast(mReplyRequest.get()); + SmsRequest* request = static_cast(forwarder->GetRealRequest()); + request->NotifyThreadList(aReply.get_ReplyThreadList().items()); + } break; + case MessageReply::TReplyThreadListFail: + mReplyRequest->NotifyThreadListFailed(aReply.get_ReplyThreadListFail().error()); + break; default: MOZ_NOT_REACHED("Received invalid response parameters!"); return false; diff --git a/dom/sms/src/ipc/SmsIPCService.cpp b/dom/sms/src/ipc/SmsIPCService.cpp index a700669acc42..b1b2cde7691a 100644 --- a/dom/sms/src/ipc/SmsIPCService.cpp +++ b/dom/sms/src/ipc/SmsIPCService.cpp @@ -179,6 +179,13 @@ SmsIPCService::MarkMessageRead(int32_t aMessageId, return NS_OK; } +NS_IMETHODIMP +SmsIPCService::GetThreadList(nsISmsRequest* aRequest) +{ + SendRequest(GetThreadListRequest(), aRequest); + return NS_OK; +} + } // namespace sms } // namespace dom } // namespace mozilla diff --git a/dom/sms/src/ipc/SmsParent.cpp b/dom/sms/src/ipc/SmsParent.cpp index aabf3ebdd4e3..caf1d17e8361 100644 --- a/dom/sms/src/ipc/SmsParent.cpp +++ b/dom/sms/src/ipc/SmsParent.cpp @@ -203,6 +203,8 @@ SmsParent::RecvPSmsRequestConstructor(PSmsRequestParent* aActor, return actor->DoRequest(aRequest.get_GetNextMessageInListRequest()); case IPCSmsRequest::TMarkMessageReadRequest: return actor->DoRequest(aRequest.get_MarkMessageReadRequest()); + case IPCSmsRequest::TGetThreadListRequest: + return actor->DoRequest(aRequest.get_GetThreadListRequest()); default: MOZ_NOT_REACHED("Unknown type!"); return false; @@ -347,6 +349,21 @@ SmsRequestParent::DoRequest(const MarkMessageReadRequest& aRequest) return true; } +bool +SmsRequestParent::DoRequest(const GetThreadListRequest& aRequest) +{ + nsCOMPtr smsDBService = + do_GetService(SMS_DATABASE_SERVICE_CONTRACTID); + + NS_ENSURE_TRUE(smsDBService, true); + mSmsRequest = SmsRequest::Create(this); + nsCOMPtr forwarder = new SmsRequestForwarder(mSmsRequest); + nsresult rv = smsDBService->GetThreadList(forwarder); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + } // namespace sms } // namespace dom } // namespace mozilla diff --git a/dom/sms/src/ipc/SmsParent.h b/dom/sms/src/ipc/SmsParent.h index d791c55560ee..82ebae78856b 100644 --- a/dom/sms/src/ipc/SmsParent.h +++ b/dom/sms/src/ipc/SmsParent.h @@ -103,6 +103,9 @@ protected: bool DoRequest(const MarkMessageReadRequest& aRequest); + + bool + DoRequest(const GetThreadListRequest& aRequest); }; } // namespace sms diff --git a/dom/sms/src/ril/SmsDatabaseService.js b/dom/sms/src/ril/SmsDatabaseService.js index fd8ad723225a..a56d31338009 100644 --- a/dom/sms/src/ril/SmsDatabaseService.js +++ b/dom/sms/src/ril/SmsDatabaseService.js @@ -14,8 +14,9 @@ const RIL_SMSDATABASESERVICE_CID = Components.ID("{a1fa610c-eb6c-4ac2-878f-b005d const DEBUG = false; const DB_NAME = "sms"; -const DB_VERSION = 3; +const DB_VERSION = 4; const STORE_NAME = "sms"; +const MOST_RECENT_STORE_NAME = "most-recent"; const DELIVERY_SENT = "sent"; const DELIVERY_RECEIVED = "received"; @@ -52,6 +53,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gIDBManager", const GLOBAL_SCOPE = this; +function numberFromMessage(message) { + return message.delivery == DELIVERY_SENT ? message.receiver : message.sender; +} + /** * SmsDatabaseService */ @@ -175,6 +180,10 @@ SmsDatabaseService.prototype = { objectStore = event.target.transaction.objectStore(STORE_NAME); self.upgradeSchema2(objectStore); break; + case 3: + if (DEBUG) debug("Upgrade to version 4. Add quick threads view.") + self.upgradeSchema3(event.target.transaction); + break; default: event.target.transaction.abort(); callback("Old database version: " + event.oldVersion, null); @@ -182,7 +191,7 @@ SmsDatabaseService.prototype = { } currentVersion++; } - }; + } request.onerror = function (event) { //TODO look at event.target.Code and change error constant accordingly callback("Error opening database!", null); @@ -200,15 +209,22 @@ SmsDatabaseService.prototype = { * @param callback * Function to call when the transaction is available. It will * be invoked with the transaction and the 'sms' object store. + * @param objectStores + * Function to call when the transaction is available. It will + * be invoked with the transaction and the 'sms' object store. */ - newTxn: function newTxn(txn_type, callback) { + newTxn: function newTxn(txn_type, callback, objectStores) { + if (!objectStores) { + objectStores = [STORE_NAME]; + } + if (DEBUG) debug("Opening transaction for objectStores: " + objectStores); this.ensureDB(function (error, db) { if (error) { if (DEBUG) debug("Could not open database: " + error); callback(error); return; } - let txn = db.transaction([STORE_NAME], txn_type); + let txn = db.transaction(objectStores, txn_type); if (DEBUG) debug("Started transaction " + txn + " of type " + txn_type); if (DEBUG) { txn.oncomplete = function oncomplete(event) { @@ -220,9 +236,18 @@ SmsDatabaseService.prototype = { debug("Error occurred during transaction: " + event.target.errorCode); }; } - if (DEBUG) debug("Retrieving object store", STORE_NAME); - let store = txn.objectStore(STORE_NAME); - callback(null, txn, store); + let stores; + if (objectStores.length == 1) { + if (DEBUG) debug("Retrieving object store " + objectStores[0]); + stores = txn.objectStore(objectStores[0]); + } else { + stores = []; + for each (let storeName in objectStores) { + if (DEBUG) debug("Retrieving object store " + storeName); + stores.push(txn.objectStore(storeName)); + } + } + callback(null, txn, stores); }); }, @@ -233,8 +258,8 @@ SmsDatabaseService.prototype = { * TODO full text search on body??? */ createSchema: function createSchema(db) { + // This objectStore holds the main SMS data. let objectStore = db.createObjectStore(STORE_NAME, { keyPath: "id" }); - objectStore.createIndex("id", "id", { unique: true }); objectStore.createIndex("delivery", "delivery", { unique: false }); objectStore.createIndex("sender", "sender", { unique: false }); objectStore.createIndex("receiver", "receiver", { unique: false }); @@ -246,7 +271,6 @@ SmsDatabaseService.prototype = { * Upgrade to the corresponding database schema version. */ upgradeSchema: function upgradeSchema(objectStore) { - // For now, the only possible upgrade is to version 2. objectStore.createIndex("read", "read", { unique: false }); }, @@ -265,6 +289,29 @@ SmsDatabaseService.prototype = { } }, + upgradeSchema3: function upgradeSchema2(transaction) { + // Delete redundant "id" index. + let objectStore = transaction.objectStore(STORE_NAME); + if (objectStore.indexNames.contains("id")) { + objectStore.deleteIndex("id"); + } + + /** + * This objectStore can be used to quickly construct a thread view of the + * SMS database. Each entry looks like this: + * + * { senderOrReceiver: (primary key), + * id: , + * timestamp: , + * body: , + * unreadCount: } + * + */ + objectStore = db.createObjectStore(MOST_RECENT_STORE_NAME, + { keyPath: "senderOrReceiver" }); + objectStore.createIndex("timestamp", "timestamp"); + }, + /** * Helper function to make the intersection of the partial result arrays * obtained within createMessageList. @@ -354,12 +401,43 @@ SmsDatabaseService.prototype = { this.lastKey += 1; message.id = this.lastKey; if (DEBUG) debug("Going to store " + JSON.stringify(message)); - this.newTxn(READ_WRITE, function(error, txn, store) { + this.newTxn(READ_WRITE, function(error, txn, stores) { if (error) { return; } - let request = store.put(message); - }); + // First add to main objectStore. + stores[0].put(message); + + let number = numberFromMessage(message); + + // Next update the other objectStore. + stores[1].get(number).onsuccess = function(event) { + let mostRecentEntry = event.target.result; + if (mostRecentEntry) { + let needsUpdate = false; + + if (mostRecentEntry.timestamp <= message.timestamp) { + mostRecentEntry.timestamp = message.timestamp; + mostRecentEntry.body = message.body; + needsUpdate = true; + } + + if (!message.read) { + mostRecentEntry.unreadCount++; + needsUpdate = true; + } + + if (needsUpdate) { + event.target.source.put(mostRecentEntry); + } + } else { + event.target.source.add({ senderOrReceiver: number, + timestamp: message.timestamp, + body: message.body, + unreadCount: message.read ? 0 : 1 }); + } + } + }, [STORE_NAME, MOST_RECENT_STORE_NAME]); // We return the key that we expect to store in the db return message.id; }, @@ -499,33 +577,103 @@ SmsDatabaseService.prototype = { deleteMessage: function deleteMessage(messageId, aRequest) { let deleted = false; let self = this; - this.newTxn(READ_WRITE, function (error, txn, store) { + this.newTxn(READ_WRITE, function (error, txn, stores) { if (error) { aRequest.notifyDeleteMessageFailed(Ci.nsISmsRequest.INTERNAL_ERROR); return; } - let request = store.count(messageId); - - request.onsuccess = function onsuccess(event) { - let count = event.target.result; - if (DEBUG) debug("Count for messageId " + messageId + ": " + count); - deleted = (count == 1); - if (deleted) { - store.delete(messageId); - } + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction", event.target.errorCode); + //TODO look at event.target.errorCode, pick appropriate error constant + aRequest.notifyDeleteMessageFailed(Ci.nsISmsRequest.INTERNAL_ERROR); }; + const smsStore = stores[0]; + const mruStore = stores[1]; + + let deleted = false; + txn.oncomplete = function oncomplete(event) { if (DEBUG) debug("Transaction " + txn + " completed."); aRequest.notifyMessageDeleted(deleted); }; - txn.onerror = function onerror(event) { - if (DEBUG) debug("Caught error on transaction", event.target.errorCode); - //TODO look at event.target.errorCode, pick appropriate error constant - aRequest.notifyDeleteMessageFailed(Ci.nsISmsRequest.INTERNAL_ERROR); + smsStore.get(messageId).onsuccess = function(event) { + let message = event.target.result; + if (message) { + if (DEBUG) debug("Deleting message id " + messageId); + + // First actually delete the message. + event.target.source.delete(messageId).onsuccess = function(event) { + deleted = true; + + // Then update unread count and most recent message. + let number = numberFromMessage(message); + + mruStore.get(number).onsuccess = function(event) { + // This must exist. + let mostRecentEntry = event.target.result; + + if (!message.read) { + mostRecentEntry.unreadCount--; + } + + if (mostRecentEntry.id == messageId) { + // This sucks, we have to find a new most-recent message. + message = null; + + // Check most recent sender. + smsStore.index("sender").openCursor(number, "prev").onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + message = cursor.value; + } + }; + + // Check most recent receiver. + smsStore.index("receiver").openCursor(number, "prev").onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (!message || cursor.value.timeStamp > message.timestamp) { + message = cursor.value; + } + } + + // If we found a new message then we need to update the data + // in the most-recent store. Otherwise we can delete it. + if (message) { + mostRecentEntry.id = message.id; + mostRecentEntry.timestamp = message.timestamp; + mostRecentEntry.body = message.body; + if (DEBUG) { + debug("Updating mru entry: " + + JSON.stringify(mostRecentEntry)); + } + mruStore.put(mostRecentEntry); + } + else { + if (DEBUG) { + debug("Deleting mru entry for number '" + number + "'"); + } + mruStore.delete(number); + } + }; + } else if (!message.read) { + // Shortcut, just update the unread count. + if (DEBUG) { + debug("Updating unread count for number '" + number + "': " + + (mostRecentEntry.unreadCount + 1) + " -> " + + mostRecentEntry.unreadCount); + } + mruStore.put(mostRecentEntry); + } + }; + }; + } else if (DEBUG) { + debug("Message id " + messageId + " does not exist"); + } }; - }); + }, [STORE_NAME, MOST_RECENT_STORE_NAME]); }, createMessageList: function createMessageList(filter, reverse, aRequest) { @@ -556,7 +704,7 @@ SmsDatabaseService.prototype = { if (DEBUG) { debug("These messages match the " + filter + " filter: " + filteredKeys[filter]); - } + } return; } // The cursor primaryKey is stored in its corresponding partial array @@ -726,18 +874,20 @@ SmsDatabaseService.prototype = { markMessageRead: function markMessageRead(messageId, value, aRequest) { if (DEBUG) debug("Setting message " + messageId + " read to " + value); - this.newTxn(READ_WRITE, function (error, txn, store) { + this.newTxn(READ_WRITE, function (error, txn, stores) { if (error) { if (DEBUG) debug(error); aRequest.notifyMarkMessageReadFailed(Ci.nsISmsRequest.INTERNAL_ERROR); return; } - let getRequest = store.get(messageId); - - getRequest.onsuccess = function onsuccess(event) { + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction ", event.target.errorCode); + aRequest.notifyMarkMessageReadFailed(Ci.nsISmsRequest.INTERNAL_ERROR); + }; + stores[0].get(messageId).onsuccess = function onsuccess(event) { let message = event.target.result; - if (DEBUG) debug("Message ID " + messageId + " not found"); if (!message) { + if (DEBUG) debug("Message ID " + messageId + " not found"); aRequest.notifyMarkMessageReadFailed(Ci.nsISmsRequest.NOT_FOUND_ERROR); return; } @@ -758,26 +908,50 @@ SmsDatabaseService.prototype = { } message.read = value ? FILTER_READ_READ : FILTER_READ_UNREAD; if (DEBUG) debug("Message.read set to: " + value); - let putRequest = store.put(message); - putRequest.onsuccess = function onsuccess(event) { + event.target.source.put(message).onsuccess = function onsuccess(event) { if (DEBUG) { debug("Update successfully completed. Message: " + JSON.stringify(event.target.result)); } - let checkRequest = store.get(message.id); - checkRequest.onsuccess = function onsuccess(event) { - aRequest.notifyMessageMarkedRead(event.target.result.read); - }; - } - }; + // Now update the unread count. + let number = numberFromMessage(message); + + stores[1].get(number).onsuccess = function(event) { + let mostRecentEntry = event.target.result; + mostRecentEntry.unreadCount += value ? -1 : 1; + if (DEBUG) { + debug("Updating unreadCount for '" + number + "': " + + (value ? + mostRecentEntry.unreadCount + 1 : + mostRecentEntry.unreadCount - 1) + + " -> " + mostRecentEntry.unreadCount); + } + event.target.source.put(mostRecentEntry).onsuccess = function(event) { + aRequest.notifyMessageMarkedRead(message.read); + }; + }; + }; + }; + }, [STORE_NAME, MOST_RECENT_STORE_NAME]); + }, + getThreadList: function getThreadList(aRequest) { + if (DEBUG) debug("Getting thread list"); + this.newTxn(READ_ONLY, function (error, txn, store) { + if (error) { + if (DEBUG) debug(error); + aRequest.notifyThreadListFailed(Ci.nsISmsRequest.INTERNAL_ERROR); + return; + } txn.onerror = function onerror(event) { if (DEBUG) debug("Caught error on transaction ", event.target.errorCode); - aRequest.notifyMarkMessageReadFailed(Ci.nsISmsRequest.INTERNAL_ERROR); + aRequest.notifyThreadListFailed(Ci.nsISmsRequest.INTERNAL_ERROR); }; - }); + store.index("timestamp").mozGetAll().onsuccess = function(event) { + aRequest.notifyThreadList(event.target.result); + }; + }, [MOST_RECENT_STORE_NAME]); } - }; XPCOMUtils.defineLazyGetter(SmsDatabaseService.prototype, "mRIL", function () { diff --git a/js/xpconnect/src/dictionary_helper_gen.conf b/js/xpconnect/src/dictionary_helper_gen.conf index 46b87df4e5de..3202abf94569 100644 --- a/js/xpconnect/src/dictionary_helper_gen.conf +++ b/js/xpconnect/src/dictionary_helper_gen.conf @@ -22,7 +22,8 @@ dictionaries = [ [ 'CameraPosition', 'nsIDOMCameraManager.idl' ], [ 'CameraSelector', 'nsIDOMCameraManager.idl' ], [ 'CameraPictureOptions', 'nsIDOMCameraManager.idl' ], - [ 'CameraRecordingOptions', 'nsIDOMCameraManager.idl' ] + [ 'CameraRecordingOptions', 'nsIDOMCameraManager.idl' ], + [ 'SmsThreadListItem', 'nsISmsRequest.idl' ] ] # include file names