Correct ordering of messages (#224)

This commit is contained in:
Alexander Pantyuhov 2016-10-14 17:38:02 +03:00
parent 2c72274025
commit ff32691fea
12 changed files with 200 additions and 64 deletions

View File

@ -35,7 +35,8 @@
status-im.chat.handlers.receive-message status-im.chat.handlers.receive-message
[cljs.core.async :as a] [cljs.core.async :as a]
status-im.chat.handlers.webview-bridge 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 (register-handler :set-chat-ui-props
(fn [db [_ ui-element value]] (fn [db [_ ui-element value]]
@ -289,20 +290,22 @@
load-messages! load-messages!
init-chat)))) init-chat))))
(defn prepare-chat (defn prepare-chat [{:keys [contacts]} chat-id chat]
[{:keys [contacts] :as db} [_ contact-id options]] (let [name (get-in contacts [chat-id :name])]
(let [name (get-in contacts [contact-id :name]) (merge {:chat-id chat-id
chat (merge {:chat-id contact-id :name (or name (generate-gfy))
:name (or name (generate-gfy)) :color default-chat-color
:color default-chat-color :group-chat false
:group-chat false :is-active true
:is-active true :timestamp (.getTime (js/Date.))
:timestamp (.getTime (js/Date.)) :contacts [{:identity chat-id}]
:contacts [{:identity contact-id}] :dapp-url nil
:dapp-url nil :dapp-hash nil}
:dapp-hash nil} chat)))
options)]
(assoc db :new-chat 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]] (defn add-chat [{:keys [new-chat] :as db} [_ chat-id]]
(-> db (-> db
@ -318,7 +321,7 @@
(dispatch [(or navigation-type :navigate-to) :chat chat-id])) (dispatch [(or navigation-type :navigate-to) :chat chat-id]))
(register-handler ::start-chat! (register-handler ::start-chat!
(-> prepare-chat (-> add-new-chat
((enrich add-chat)) ((enrich add-chat))
((after save-new-chat!)) ((after save-new-chat!))
((after open-chat!)))) ((after open-chat!))))
@ -331,10 +334,30 @@
(dispatch [::start-chat! contact-id options navigation-type]))))) (dispatch [::start-chat! contact-id options navigation-type])))))
(register-handler :add-chat (register-handler :add-chat
(-> prepare-chat (-> add-new-chat
((enrich add-chat)) ((enrich add-chat))
((after save-new-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! (register-handler :switch-command-suggestions!
(u/side-effect! (u/side-effect!
(fn [db] (fn [db]
@ -453,17 +476,6 @@
(fn [db [_ chat-id mode]] (fn [db [_ chat-id mode]]
(assoc-in db [:kb-mode 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 (register-handler :check-autorun
(u/side-effect! (u/side-effect!
(fn [{:keys [current-chat-id] :as db}] (fn [{:keys [current-chat-id] :as db}]
@ -474,3 +486,10 @@
(a/<! (a/timeout 100)) (a/<! (a/timeout 100))
(dispatch [:set-chat-command (keyword autorun)]) (dispatch [:set-chat-command (keyword autorun)])
(dispatch [:animate-command-suggestions]))))))) (dispatch [:animate-command-suggestions])))))))
(register-handler :inc-clock
(u/side-effect!
(fn [_ [_ chat-id]]
(let [chat (-> (chats/get-by-id chat-id)
(update :clock-value inc))]
(dispatch [:update-chat! chat])))))

View File

@ -7,7 +7,8 @@
[status-im.utils.random :as random] [status-im.utils.random :as random]
[status-im.constants :refer [content-type-command-request]] [status-im.constants :refer [content-type-command-request]]
[cljs.reader :refer [read-string]] [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}] (defn check-preview [{:keys [content] :as message}]
(if-let [preview (:preview content)] (if-let [preview (:preview content)]
@ -25,12 +26,17 @@
(:public-key (accounts current-account-id))) (:public-key (accounts current-account-id)))
(defn receive-message (defn receive-message
[db [_ {:keys [from group-id chat-id message-id timestamp] :as message}]] [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) (let [same-message (messages/get-by-id message-id)
current-identity (get-current-identity db) current-identity (get-current-identity db)
chat-id' (or group-id chat-id from) chat-id' (or group-id chat-id from)
exists? (chats/exists? chat-id') exists? (chats/exists? chat-id')
active? (chats/is-active? 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) (when (and (not same-message)
(not= from current-identity) (not= from current-identity)
(or (not exists?) active?)) (or (not exists?) active?))
@ -40,10 +46,12 @@
(cu/check-author-direction previous-message) (cu/check-author-direction previous-message)
(check-preview)) (check-preview))
:chat-id chat-id' :chat-id chat-id'
:timestamp (or timestamp (random/timestamp)))] :timestamp (or timestamp (random/timestamp))
:clock-value clock-value)]
(store-message message') (store-message message')
(when-not exists? (dispatch [:upsert-chat! {:chat-id chat-id'
(dispatch [:add-chat chat-id' (when group-chat? {:group-chat true})])) :group-chat group-chat?
:clock-value clock-value}])
(dispatch [::add-message message']) (dispatch [::add-message message'])
(when (= (:content-type message') content-type-command-request) (when (= (:content-type message') content-type-command-request)
(dispatch [:add-request chat-id' message'])) (dispatch [:add-request chat-id' message']))
@ -53,9 +61,9 @@
(u/side-effect! (u/side-effect!
(fn [_ [_ {:keys [from to payload]}]] (fn [_ [_ {:keys [from to payload]}]]
(dispatch [:received-message (merge payload (dispatch [:received-message (merge payload
{:from from {:from from
:to to :to to
:chat-id from})])))) :chat-id from})]))))
(register-handler :received-message (register-handler :received-message
(u/side-effect! receive-message)) (u/side-effect! receive-message))

View File

@ -13,10 +13,11 @@
default-number-of-messages]] default-number-of-messages]]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[status-im.protocol.core :as protocol] [status-im.protocol.core :as protocol]
[taoensso.timbre :refer-macros [debug]])) [taoensso.timbre :refer-macros [debug]]
[taoensso.timbre :as log]))
(defn prepare-command (defn prepare-command
[identity chat-id [identity chat-id clock-value
{:keys [id preview preview-string params command to-message handler-data]}] {:keys [id preview preview-string params command to-message handler-data]}]
(let [content {:command (command :name) (let [content {:command (command :name)
:params params}] :params params}]
@ -32,7 +33,8 @@
:rendered-preview preview :rendered-preview preview
:to-message to-message :to-message to-message
:type (:type command) :type (:type command)
:has-handler (:has-handler command)})) :has-handler (:has-handler command)
:clock-value (inc clock-value)}))
(register-handler :send-chat-message (register-handler :send-chat-message
(u/side-effect! (u/side-effect!
@ -76,8 +78,9 @@
(u/side-effect! (u/side-effect!
(fn [{:keys [current-public-key] :as db} (fn [{:keys [current-public-key] :as db}
[_ {:keys [chat-id staged-command handler-data] :as params}]] [_ {:keys [chat-id staged-command handler-data] :as params}]]
(let [command' (->> (assoc staged-command :handler-data handler-data) (let [{:keys [clock-value]} (get-in db [:chats chat-id])
(prepare-command current-public-key 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))] (cu/check-author-direction db chat-id))]
(dispatch [:clear-command chat-id (:id staged-command)]) (dispatch [:clear-command chat-id (:id staged-command)])
(dispatch [::send-command! (assoc params :command command')]))))) (dispatch [::send-command! (assoc params :command command')])))))
@ -144,7 +147,7 @@
(register-handler ::prepare-message (register-handler ::prepare-message
(u/side-effect! (u/side-effect!
(fn [db [_ {:keys [chat-id identity message] :as params}]] (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 message' (cu/check-author-direction
db chat-id db chat-id
{:message-id (random/id) {:message-id (random/id)
@ -153,7 +156,8 @@
:from identity :from identity
:content-type text-content-type :content-type text-content-type
:outgoing true :outgoing true
:timestamp (time/now-ms)}) :timestamp (time/now-ms)
:clock-value (inc clock-value)})
message'' (if group-chat message'' (if group-chat
(assoc message' :group-id chat-id :message-type :group-user-message) (assoc message' :group-id chat-id :message-type :group-user-message)
(assoc message' :to chat-id :message-type :user-message)) (assoc message' :to chat-id :message-type :user-message))
@ -179,7 +183,7 @@
chat-id :chat-id}]] chat-id :chat-id}]]
(when (and message (cu/not-console? chat-id)) (when (and message (cu/not-console? chat-id))
(let [message' (select-keys message [:from :message-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 options {:web3 web3
:message (assoc message' :payload payload)}] :message (assoc message' :payload payload)}]
(if (= message-type :group-user-message) (if (= message-type :group-user-message)
@ -189,26 +193,29 @@
:keypair {:public public-key :keypair {:public public-key
:private private-key}))) :private private-key})))
(protocol/send-message! (assoc-in options (protocol/send-message! (assoc-in options
[:message :to] (:to message))))))))) [:message :to] (:to message))))
(dispatch [:inc-clock chat-id]))))))
(register-handler ::send-command-protocol! (register-handler ::send-command-protocol!
(u/side-effect! (u/side-effect!
(fn [{:keys [web3 current-public-key chats] :as db} [_ {:keys [chat-id command]}]] (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) (when (cu/not-console? chat-id)
(let [{:keys [public-key private-key]} (chats chat-id) (let [{:keys [public-key private-key]} (chats chat-id)
{:keys [group-chat]} (get-in db [:chats chat-id]) {:keys [group-chat]} (get-in db [:chats chat-id])
payload {:content content payload {:content content
:content-type content-type-command :content-type content-type-command
:timestamp (datetime/now-ms)} :timestamp (datetime/now-ms)
:clock-value clock-value}
options {:web3 web3 options {:web3 web3
:message {:from current-public-key :message {:from current-public-key
:message-id message-id :message-id message-id
:payload payload}}] :payload payload}}]
(if group-chat (if group-chat
(protocol/send-group-message! (assoc options (protocol/send-group-message! (assoc options
:group-id chat-id :group-id chat-id
:keypair {:public public-key :keypair {:public public-key
:private private-key})) :private private-key}))
(protocol/send-message! (assoc-in options (protocol/send-message! (assoc-in options
[:message :to] chat-id))))))))) [:message :to] chat-id)))
(dispatch [:inc-clock chat-id])))))))

View File

@ -31,7 +31,8 @@
[status-im.components.sync-state.offline :refer [offline-view]] [status-im.components.sync-state.offline :refer [offline-view]]
[status-im.constants :refer [content-type-status]] [status-im.constants :refer [content-type-status]]
[reagent.core :as r] [reagent.core :as r]
[cljs-time.core :as t])) [cljs-time.core :as t]
[taoensso.timbre :as log]))
(defn contacts-by-identity [contacts] (defn contacts-by-identity [contacts]
(->> contacts (->> contacts
@ -120,6 +121,7 @@
(concat all-messages [status-message]) (concat all-messages [status-message])
all-messages) all-messages)
messages (->> all-messages messages (->> all-messages
(sort-by :clock-value >)
(map #(assoc % :datemark (time/day-relative (:timestamp %)))) (map #(assoc % :datemark (time/day-relative (:timestamp %))))
(group-by :datemark) (group-by :datemark)
(map (fn [[k v]] [v {:type :datemark :value k}])) (map (fn [[k v]] [v {:type :datemark :value k}]))

View File

@ -13,7 +13,8 @@
[status-im.chat.styles.plain-message :as st-message] [status-im.chat.styles.plain-message :as st-message]
[status-im.chat.styles.response :as st-response] [status-im.chat.styles.response :as st-response]
[reagent.core :as r] [reagent.core :as r]
[clojure.string :as str])) [clojure.string :as str]
[taoensso.timbre :as log]))
(defn send-button [{:keys [on-press accessibility-label]}] (defn send-button [{:keys [on-press accessibility-label]}]
[touchable-highlight {:on-press on-press [touchable-highlight {:on-press on-press
@ -57,13 +58,13 @@
:default-value (or input-message "")} :default-value (or input-message "")}
input-options)]) input-options)])
(defview command-input [input-options command] (defview command-input [input-options {:keys [fullscreen] :as command}]
[input-command [:get-chat-command-content] [input-command [:get-chat-command-content]
icon-width [:command-icon-width] icon-width [:command-icon-width]
disable? [:get :disable-input]] disable? [:get :disable-input]]
[text-input (merge [text-input (merge
(command-input-options command icon-width disable?) (command-input-options command icon-width disable?)
{:auto-focus false {:auto-focus (not fullscreen)
:blur-on-submit false :blur-on-submit false
:accessibility-label :input :accessibility-label :input
:on-focus #(dispatch [:set :focused true]) :on-focus #(dispatch [:set :focused true])

View File

@ -70,7 +70,7 @@
(defn chat-list-item-inner-view [{:keys [chat-id name color last-message (defn chat-list-item-inner-view [{:keys [chat-id name color last-message
online group-chat contacts] :as chat}] 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) last-message)
name (or name (generate-gfy))] name (or name (generate-gfy))]
[view st/chat-container [view st/chat-container

View File

@ -30,7 +30,7 @@
(defn get-last-message (defn get-last-message
[chat-id] [chat-id]
(-> (realm/get-by-field @realm/account-realm :message :chat-id 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))) (realm/single-cljs)))
(defn get-unviewed (defn get-unviewed

View File

@ -1,6 +1,7 @@
(ns status-im.data-store.realm.schemas.account.core (ns status-im.data-store.realm.schemas.account.core
(:require [status-im.data-store.realm.schemas.account.v1.core :as v1] (: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 ; put schemas ordered by version
(def schemas [{:schema v1/schema (def schemas [{:schema v1/schema
@ -8,4 +9,7 @@
:migration v1/migration} :migration v1/migration}
{:schema v2/schema {:schema v2/schema
:schemaVersion 2 :schemaVersion 2
:migration v2/migration}]) :migration v2/migration}
{:schema v3/schema
:schemaVersion 3
:migration v3/migration}])

View File

@ -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"))

View File

@ -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))

View File

@ -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"))

View File

@ -23,7 +23,7 @@
content) content)
payload' (-> message payload' (-> message
(select-keys [:message-id :requires-ack? :type]) (select-keys [:message-id :requires-ack? :type :clock-value])
(merge payload) (merge payload)
(assoc :content content') (assoc :content content')
prn-str prn-str