From ff32691feafa11b3bde01212b216d87648eb64e9 Mon Sep 17 00:00:00 2001 From: Alexander Pantyuhov Date: Fri, 14 Oct 2016 17:38:02 +0300 Subject: [PATCH] Correct ordering of messages (#224) --- src/status_im/chat/handlers.cljs | 75 ++++++++++++------- .../chat/handlers/receive_message.cljs | 32 +++++--- src/status_im/chat/handlers/send_message.cljs | 37 +++++---- src/status_im/chat/screen.cljs | 4 +- src/status_im/chat/views/message_input.cljs | 7 +- .../chats_list/views/inner_item.cljs | 2 +- src/status_im/data_store/realm/messages.cljs | 2 +- .../realm/schemas/account/core.cljs | 8 +- .../realm/schemas/account/v3/chat.cljs | 32 ++++++++ .../realm/schemas/account/v3/core.cljs | 29 +++++++ .../realm/schemas/account/v3/message.cljs | 34 +++++++++ src/status_im/protocol/web3/delivery.cljs | 2 +- 12 files changed, 200 insertions(+), 64 deletions(-) create mode 100644 src/status_im/data_store/realm/schemas/account/v3/chat.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v3/core.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v3/message.cljs diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 0121132d05..0ce132db6b 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -35,7 +35,8 @@ status-im.chat.handlers.receive-message [cljs.core.async :as a] status-im.chat.handlers.webview-bridge - status-im.chat.handlers.wallet-chat)) + status-im.chat.handlers.wallet-chat + [taoensso.timbre :as log])) (register-handler :set-chat-ui-props (fn [db [_ ui-element value]] @@ -289,20 +290,22 @@ load-messages! init-chat)))) -(defn prepare-chat - [{:keys [contacts] :as db} [_ contact-id options]] - (let [name (get-in contacts [contact-id :name]) - chat (merge {:chat-id contact-id - :name (or name (generate-gfy)) - :color default-chat-color - :group-chat false - :is-active true - :timestamp (.getTime (js/Date.)) - :contacts [{:identity contact-id}] - :dapp-url nil - :dapp-hash nil} - options)] - (assoc db :new-chat chat))) +(defn prepare-chat [{:keys [contacts]} chat-id chat] + (let [name (get-in contacts [chat-id :name])] + (merge {:chat-id chat-id + :name (or name (generate-gfy)) + :color default-chat-color + :group-chat false + :is-active true + :timestamp (.getTime (js/Date.)) + :contacts [{:identity chat-id}] + :dapp-url nil + :dapp-hash nil} + chat))) + +(defn add-new-chat + [db [_ chat-id chat]] + (assoc db :new-chat (prepare-chat db chat-id chat))) (defn add-chat [{:keys [new-chat] :as db} [_ chat-id]] (-> db @@ -318,7 +321,7 @@ (dispatch [(or navigation-type :navigate-to) :chat chat-id])) (register-handler ::start-chat! - (-> prepare-chat + (-> add-new-chat ((enrich add-chat)) ((after save-new-chat!)) ((after open-chat!)))) @@ -331,10 +334,30 @@ (dispatch [::start-chat! contact-id options navigation-type]))))) (register-handler :add-chat - (-> prepare-chat + (-> add-new-chat ((enrich add-chat)) ((after save-new-chat!)))) +(defn update-chat! + [_ [_ chat]] + (chats/save chat)) + +(register-handler :update-chat! + (-> (fn [db [_ {:keys [chat-id] :as chat}]] + (if (get-in db [:chats chat-id]) + (update-in db [:chats chat-id] merge chat) + db)) + ((after update-chat!)))) + +(register-handler :upsert-chat! + (fn [db [_ {:keys [chat-id clock-value] :as opts}]] + (let [chat (if (chats/exists? chat-id) + (let [{old-clock-value :clock-value :as chat} (chats/get-by-id chat-id)] + (assoc chat :clock-value (+ (max old-clock-value clock-value)) 1)) + (prepare-chat db chat-id opts))] + (chats/save chat) + (update-in db [:chats chat-id] merge chat)))) + (register-handler :switch-command-suggestions! (u/side-effect! (fn [db] @@ -453,17 +476,6 @@ (fn [db [_ chat-id mode]] (assoc-in db [:kb-mode chat-id] mode))) -(defn update-chat! - [_ [_ chat]] - (chats/save chat)) - -(register-handler :update-chat! - (-> (fn [db [_ {:keys [chat-id] :as chat}]] - (if (get-in db [:chats chat-id]) - (update-in db [:chats chat-id] merge chat) - db)) - ((after update-chat!)))) - (register-handler :check-autorun (u/side-effect! (fn [{:keys [current-chat-id] :as db}] @@ -474,3 +486,10 @@ (a/ (chats/get-by-id chat-id) + (update :clock-value inc))] + (dispatch [:update-chat! chat]))))) diff --git a/src/status_im/chat/handlers/receive_message.cljs b/src/status_im/chat/handlers/receive_message.cljs index 8dd0c77f1d..a6b49b2120 100644 --- a/src/status_im/chat/handlers/receive_message.cljs +++ b/src/status_im/chat/handlers/receive_message.cljs @@ -7,7 +7,8 @@ [status-im.utils.random :as random] [status-im.constants :refer [content-type-command-request]] [cljs.reader :refer [read-string]] - [status-im.data-store.chats :as chats])) + [status-im.data-store.chats :as chats] + [taoensso.timbre :as log])) (defn check-preview [{:keys [content] :as message}] (if-let [preview (:preview content)] @@ -25,12 +26,17 @@ (:public-key (accounts current-account-id))) (defn receive-message - [db [_ {:keys [from group-id chat-id message-id timestamp] :as message}]] - (let [same-message (messages/get-by-id message-id) + [db [_ {:keys [from group-id chat-id message-id timestamp clock-value] :as message :or {clock-value 0}}]] + (let [same-message (messages/get-by-id message-id) current-identity (get-current-identity db) - chat-id' (or group-id chat-id from) - exists? (chats/exists? chat-id') - active? (chats/is-active? chat-id')] + chat-id' (or group-id chat-id from) + exists? (chats/exists? chat-id') + active? (chats/is-active? chat-id') + clock-value (if (= clock-value 0) + (-> (chats/get-by-id chat-id') + (get :clock-value) + (inc)) + clock-value)] (when (and (not same-message) (not= from current-identity) (or (not exists?) active?)) @@ -40,10 +46,12 @@ (cu/check-author-direction previous-message) (check-preview)) :chat-id chat-id' - :timestamp (or timestamp (random/timestamp)))] + :timestamp (or timestamp (random/timestamp)) + :clock-value clock-value)] (store-message message') - (when-not exists? - (dispatch [:add-chat chat-id' (when group-chat? {:group-chat true})])) + (dispatch [:upsert-chat! {:chat-id chat-id' + :group-chat group-chat? + :clock-value clock-value}]) (dispatch [::add-message message']) (when (= (:content-type message') content-type-command-request) (dispatch [:add-request chat-id' message'])) @@ -53,9 +61,9 @@ (u/side-effect! (fn [_ [_ {:keys [from to payload]}]] (dispatch [:received-message (merge payload - {:from from - :to to - :chat-id from})])))) + {:from from + :to to + :chat-id from})])))) (register-handler :received-message (u/side-effect! receive-message)) diff --git a/src/status_im/chat/handlers/send_message.cljs b/src/status_im/chat/handlers/send_message.cljs index d3c8fc0c61..0f1baa58b4 100644 --- a/src/status_im/chat/handlers/send_message.cljs +++ b/src/status_im/chat/handlers/send_message.cljs @@ -13,10 +13,11 @@ default-number-of-messages]] [status-im.utils.datetime :as datetime] [status-im.protocol.core :as protocol] - [taoensso.timbre :refer-macros [debug]])) + [taoensso.timbre :refer-macros [debug]] + [taoensso.timbre :as log])) (defn prepare-command - [identity chat-id + [identity chat-id clock-value {:keys [id preview preview-string params command to-message handler-data]}] (let [content {:command (command :name) :params params}] @@ -32,7 +33,8 @@ :rendered-preview preview :to-message to-message :type (:type command) - :has-handler (:has-handler command)})) + :has-handler (:has-handler command) + :clock-value (inc clock-value)})) (register-handler :send-chat-message (u/side-effect! @@ -76,8 +78,9 @@ (u/side-effect! (fn [{:keys [current-public-key] :as db} [_ {:keys [chat-id staged-command handler-data] :as params}]] - (let [command' (->> (assoc staged-command :handler-data handler-data) - (prepare-command current-public-key chat-id) + (let [{:keys [clock-value]} (get-in db [:chats chat-id]) + command' (->> (assoc staged-command :handler-data handler-data) + (prepare-command current-public-key chat-id clock-value) (cu/check-author-direction db chat-id))] (dispatch [:clear-command chat-id (:id staged-command)]) (dispatch [::send-command! (assoc params :command command')]))))) @@ -144,7 +147,7 @@ (register-handler ::prepare-message (u/side-effect! (fn [db [_ {:keys [chat-id identity message] :as params}]] - (let [{:keys [group-chat]} (get-in db [:chats chat-id]) + (let [{:keys [group-chat clock-value]} (get-in db [:chats chat-id]) message' (cu/check-author-direction db chat-id {:message-id (random/id) @@ -153,7 +156,8 @@ :from identity :content-type text-content-type :outgoing true - :timestamp (time/now-ms)}) + :timestamp (time/now-ms) + :clock-value (inc clock-value)}) message'' (if group-chat (assoc message' :group-id chat-id :message-type :group-user-message) (assoc message' :to chat-id :message-type :user-message)) @@ -179,7 +183,7 @@ chat-id :chat-id}]] (when (and message (cu/not-console? chat-id)) (let [message' (select-keys message [:from :message-id]) - payload (select-keys message [:timestamp :content :content-type]) + payload (select-keys message [:timestamp :content :content-type :clock-value]) options {:web3 web3 :message (assoc message' :payload payload)}] (if (= message-type :group-user-message) @@ -189,26 +193,29 @@ :keypair {:public public-key :private private-key}))) (protocol/send-message! (assoc-in options - [:message :to] (:to message))))))))) + [:message :to] (:to message)))) + (dispatch [:inc-clock chat-id])))))) (register-handler ::send-command-protocol! (u/side-effect! (fn [{:keys [web3 current-public-key chats] :as db} [_ {:keys [chat-id command]}]] - (let [{:keys [content message-id]} command] + (let [{:keys [content message-id clock-value]} command] (when (cu/not-console? chat-id) (let [{:keys [public-key private-key]} (chats chat-id) {:keys [group-chat]} (get-in db [:chats chat-id]) payload {:content content :content-type content-type-command - :timestamp (datetime/now-ms)} + :timestamp (datetime/now-ms) + :clock-value clock-value} options {:web3 web3 - :message {:from current-public-key - :message-id message-id - :payload payload}}] + :message {:from current-public-key + :message-id message-id + :payload payload}}] (if group-chat (protocol/send-group-message! (assoc options :group-id chat-id :keypair {:public public-key :private private-key})) (protocol/send-message! (assoc-in options - [:message :to] chat-id))))))))) + [:message :to] chat-id))) + (dispatch [:inc-clock chat-id]))))))) diff --git a/src/status_im/chat/screen.cljs b/src/status_im/chat/screen.cljs index 2274a9eef6..25d5a47838 100644 --- a/src/status_im/chat/screen.cljs +++ b/src/status_im/chat/screen.cljs @@ -31,7 +31,8 @@ [status-im.components.sync-state.offline :refer [offline-view]] [status-im.constants :refer [content-type-status]] [reagent.core :as r] - [cljs-time.core :as t])) + [cljs-time.core :as t] + [taoensso.timbre :as log])) (defn contacts-by-identity [contacts] (->> contacts @@ -120,6 +121,7 @@ (concat all-messages [status-message]) all-messages) messages (->> all-messages + (sort-by :clock-value >) (map #(assoc % :datemark (time/day-relative (:timestamp %)))) (group-by :datemark) (map (fn [[k v]] [v {:type :datemark :value k}])) diff --git a/src/status_im/chat/views/message_input.cljs b/src/status_im/chat/views/message_input.cljs index 422133b237..3ea0fb05c9 100644 --- a/src/status_im/chat/views/message_input.cljs +++ b/src/status_im/chat/views/message_input.cljs @@ -13,7 +13,8 @@ [status-im.chat.styles.plain-message :as st-message] [status-im.chat.styles.response :as st-response] [reagent.core :as r] - [clojure.string :as str])) + [clojure.string :as str] + [taoensso.timbre :as log])) (defn send-button [{:keys [on-press accessibility-label]}] [touchable-highlight {:on-press on-press @@ -57,13 +58,13 @@ :default-value (or input-message "")} input-options)]) -(defview command-input [input-options command] +(defview command-input [input-options {:keys [fullscreen] :as command}] [input-command [:get-chat-command-content] icon-width [:command-icon-width] disable? [:get :disable-input]] [text-input (merge (command-input-options command icon-width disable?) - {:auto-focus false + {:auto-focus (not fullscreen) :blur-on-submit false :accessibility-label :input :on-focus #(dispatch [:set :focused true]) diff --git a/src/status_im/chats_list/views/inner_item.cljs b/src/status_im/chats_list/views/inner_item.cljs index 89387de3f7..4fd0720562 100644 --- a/src/status_im/chats_list/views/inner_item.cljs +++ b/src/status_im/chats_list/views/inner_item.cljs @@ -70,7 +70,7 @@ (defn chat-list-item-inner-view [{:keys [chat-id name color last-message online group-chat contacts] :as chat}] - (let [last-message (or (first (:messages chat)) + (let [last-message (or (first (sort-by :clock-value > (:messages chat))) last-message) name (or name (generate-gfy))] [view st/chat-container diff --git a/src/status_im/data_store/realm/messages.cljs b/src/status_im/data_store/realm/messages.cljs index 787885ff02..62bd7fb5ae 100644 --- a/src/status_im/data_store/realm/messages.cljs +++ b/src/status_im/data_store/realm/messages.cljs @@ -30,7 +30,7 @@ (defn get-last-message [chat-id] (-> (realm/get-by-field @realm/account-realm :message :chat-id chat-id) - (realm/sorted :timestamp :desc) + (realm/sorted :clock-value :desc) (realm/single-cljs))) (defn get-unviewed diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index 98de1be71e..1707511b7e 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -1,6 +1,7 @@ (ns status-im.data-store.realm.schemas.account.core (:require [status-im.data-store.realm.schemas.account.v1.core :as v1] - [status-im.data-store.realm.schemas.account.v2.core :as v2])) + [status-im.data-store.realm.schemas.account.v2.core :as v2] + [status-im.data-store.realm.schemas.account.v3.core :as v3])) ; put schemas ordered by version (def schemas [{:schema v1/schema @@ -8,4 +9,7 @@ :migration v1/migration} {:schema v2/schema :schemaVersion 2 - :migration v2/migration}]) \ No newline at end of file + :migration v2/migration} + {:schema v3/schema + :schemaVersion 3 + :migration v3/migration}]) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/v3/chat.cljs b/src/status_im/data_store/realm/schemas/account/v3/chat.cljs new file mode 100644 index 0000000000..1aed8766c5 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v3/chat.cljs @@ -0,0 +1,32 @@ +(ns status-im.data-store.realm.schemas.account.v3.chat + (:require [taoensso.timbre :as log] + [status-im.components.styles :refer [default-chat-color]])) + +(def schema {:name :chat + :primaryKey :chat-id + :properties {:chat-id "string" + :name "string" + :color {:type "string" + :default default-chat-color} + :group-chat {:type "bool" + :indexed true} + :is-active "bool" + :timestamp "int" + :contacts {:type "list" + :objectType "chat-contact"} + :dapp-url {:type :string + :optional true} + :dapp-hash {:type :int + :optional true} + :removed-at {:type :int + :optional true} + :last-message-id "string" + :public-key {:type :string + :optional true} + :private-key {:type :string + :optional true} + :clock-value {:type :int + :default 0}}}) + +(defn migration [old-realm new-realm] + (log/debug "migrating chat schema")) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/v3/core.cljs b/src/status_im/data_store/realm/schemas/account/v3/core.cljs new file mode 100644 index 0000000000..92ae33d7a5 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v3/core.cljs @@ -0,0 +1,29 @@ +(ns status-im.data-store.realm.schemas.account.v3.core + (:require [taoensso.timbre :as log] + [status-im.data-store.realm.schemas.account.v3.chat :as chat] + [status-im.data-store.realm.schemas.account.v3.message :as message] + [status-im.data-store.realm.schemas.account.v2.contact :as contact] + [status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact] + [status-im.data-store.realm.schemas.account.v1.command :as command] + [status-im.data-store.realm.schemas.account.v1.discovery :as discovery] + [status-im.data-store.realm.schemas.account.v1.kv-store :as kv-store] + [status-im.data-store.realm.schemas.account.v1.pending-message :as pending-message] + [status-im.data-store.realm.schemas.account.v1.request :as request] + [status-im.data-store.realm.schemas.account.v1.tag :as tag] + [status-im.data-store.realm.schemas.account.v1.user-status :as user-status])) + +(def schema [chat/schema + chat-contact/schema + command/schema + contact/schema + discovery/schema + kv-store/schema + message/schema + pending-message/schema + request/schema + tag/schema + user-status/schema]) + +(defn migration [old-realm new-realm] + (log/debug "migrating v3 account database: " old-realm new-realm) + (contact/migration old-realm new-realm)) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/v3/message.cljs b/src/status_im/data_store/realm/schemas/account/v3/message.cljs new file mode 100644 index 0000000000..0ea03cc85b --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v3/message.cljs @@ -0,0 +1,34 @@ +(ns status-im.data-store.realm.schemas.account.v3.message + (:require [taoensso.timbre :as log])) + +(def schema {:name :message + :primaryKey :message-id + :properties {:message-id "string" + :from "string" + :to {:type "string" + :optional true} + :group-id {:type "string" + :optional true} + :content "string" ;; TODO make it ArrayBuffer + :content-type "string" + :timestamp "int" + :chat-id {:type "string" + :indexed true} + :outgoing "bool" + :retry-count {:type :int + :default 0} + :same-author "bool" + :same-direction "bool" + :preview {:type :string + :optional true} + :message-type {:type :string + :optional true} + :message-status {:type :string + :optional true} + :user-statuses {:type :list + :objectType "user-status"} + :clock-value {:type :int + :default 0}}}) + +(defn migration [old-realm new-realm] + (log/debug "migrating message schema")) \ No newline at end of file diff --git a/src/status_im/protocol/web3/delivery.cljs b/src/status_im/protocol/web3/delivery.cljs index d00d6e3784..36a6d2a55b 100644 --- a/src/status_im/protocol/web3/delivery.cljs +++ b/src/status_im/protocol/web3/delivery.cljs @@ -23,7 +23,7 @@ content) payload' (-> message - (select-keys [:message-id :requires-ack? :type]) + (select-keys [:message-id :requires-ack? :type :clock-value]) (merge payload) (assoc :content content') prn-str