Fetch missed range of messages

This commit is contained in:
Roman Volosovskyi 2019-04-04 16:55:35 +03:00
parent fbda69ff78
commit a4d8f57b09
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
16 changed files with 882 additions and 140 deletions

View File

@ -5,7 +5,8 @@
[status-im.chat.commands.core :as commands] [status-im.chat.commands.core :as commands]
[status-im.chat.commands.input :as commands.input] [status-im.chat.commands.input :as commands.input]
[status-im.group-chats.db :as group-chats.db] [status-im.group-chats.db :as group-chats.db]
[status-im.utils.gfycat.core :as gfycat])) [status-im.utils.gfycat.core :as gfycat]
[status-im.transport.partitioned-topic :as topic]))
(defn group-chat-name (defn group-chat-name
[{:keys [public? name]}] [{:keys [public? name]}]
@ -49,11 +50,28 @@
{} {}
chats)) chats))
(defn topic-by-current-chat
[{:keys [current-chat-id chats] :as db}]
(let [{:keys [public?]} (get chats current-chat-id)
public-key (get-in db [:account/account :public-key])]
(if public?
(get-in db [:transport/chats current-chat-id :topic])
(topic/public-key->discovery-topic-hash public-key))))
(defn messages-gap
[mailserver-topics topic]
(let [{:keys [gap-from gap-to]}
(get mailserver-topics topic)]
{:from gap-from
:to gap-to
:exists? (and gap-from gap-to
(> gap-to gap-from))}))
(defn sort-message-groups (defn sort-message-groups
"Sorts message groups according to timestamp of first message in group" "Sorts message groups according to timestamp of first message in group"
[message-groups messages] [message-groups messages]
(sort-by (sort-by
(comp unchecked-negate :timestamp (partial get messages) :message-id first second) (comp :timestamp (partial get messages) :message-id first second)
message-groups)) message-groups))
(defn quoted-message-data (defn quoted-message-data
@ -64,27 +82,93 @@
{:from from {:from from
:text (:text content)})) :text (:text content)}))
(defn add-datemark
[[datemark
message-references]]
(let [{:keys [whisper-timestamp timestamp]} (first message-references)]
(conj message-references
{:value datemark
:type :datemark
:whisper-timestamp whisper-timestamp
:timestamp timestamp})))
(defn datemark? [{:keys [type]}]
(= type :datemark))
(defn transform-message
[messages message-statuses referenced-messages]
(fn [{:keys [message-id timestamp-str] :as reference}]
(if (datemark? reference)
reference
(let [{:keys [content] :as message} (get messages message-id)
{:keys [response-to response-to-v2]} content
quote (some-> (or response-to-v2 response-to)
(quoted-message-data messages referenced-messages))]
(cond-> (-> message
(update :content dissoc :response-to :response-to-v2)
(assoc :timestamp-str timestamp-str
:user-statuses (get message-statuses message-id)))
;; quoted message reference
quote
(assoc-in [:content :response-to] quote))))))
(defn check-gap
[{:keys [exists? from]} previous-message next-message gap-added?]
(let [previous-timestamp (:whisper-timestamp previous-message)
next-whisper-timestamp (:whisper-timestamp next-message)
next-timestamp (quot (:timestamp next-message) 1000)
ignore-next-message? (> (js/Math.abs
(- next-whisper-timestamp next-timestamp))
120)]
(and (not gap-added?)
(or (and exists? previous-timestamp next-whisper-timestamp
(not ignore-next-message?)
(< previous-timestamp from next-whisper-timestamp))
(and exists? (nil? next-message))))))
(defn add-gap [messages gap]
(conj messages
{:type :gap
:value (str (:from gap))}))
(defn messages-with-datemarks-and-statuses (defn messages-with-datemarks-and-statuses
"Converts message groups into sequence of messages interspersed with datemarks, "Converts message groups into sequence of messages interspersed with datemarks,
with correct user statuses associated into message" with correct user statuses associated into message"
[message-groups messages message-statuses referenced-messages] [message-groups messages message-statuses referenced-messages messages-gap]
(mapcat (fn [[datemark message-references]] (transduce
(into (list {:value datemark (comp
:type :datemark}) (mapcat add-datemark)
(map (fn [{:keys [message-id timestamp-str]}] (map (transform-message messages message-statuses referenced-messages)))
(let [{:keys [content] :as message} (get messages message-id) (completing
{:keys [response-to response-to-v2]} content (fn [{:keys [messages datemark-reference previous-message gap-added?]}
quote (some-> (or response-to-v2 response-to) message]
(quoted-message-data messages referenced-messages))] (let [new-datemark? (datemark? message)
(cond-> (-> message add-gap? (check-gap messages-gap previous-message message gap-added?)]
(update :content dissoc :response-to :response-to-v2) {:messages (cond-> messages
(assoc :datemark datemark
:timestamp-str timestamp-str add-gap?
:user-statuses (get message-statuses message-id))) (add-gap messages-gap)
quote ;; quoted message reference
(assoc-in [:content :response-to] quote))))) :always
message-references)) (conj
message-groups)) (cond-> message
(not new-datemark?)
(assoc
:datemark
(:value datemark-reference)))))
:previous-message message
:datemark-reference (if new-datemark?
message
datemark-reference)
:gap-added? (or gap-added? add-gap?)}))
(fn [{:keys [messages previous-message gap-added?]}]
(let [add-gap? (check-gap messages-gap previous-message nil gap-added?)]
(cond-> messages
add-gap?
(add-gap messages-gap)))))
{:messages (list)
:previous-message nil}
message-groups))
(defn- set-previous-message-info [stream] (defn- set-previous-message-info [stream]
(let [{:keys [display-photo? message-type] :as previous-message} (peek stream)] (let [{:keys [display-photo? message-type] :as previous-message} (peek stream)]

View File

@ -27,9 +27,11 @@
(update-in db [:chats chat-id :message-groups datemark] (update-in db [:chats chat-id :message-groups datemark]
(fn [message-references] (fn [message-references]
(->> grouped-messages (->> grouped-messages
(map (fn [{:keys [message-id timestamp]}] (map (fn [{:keys [message-id timestamp whisper-timestamp]}]
{:message-id message-id {:message-id message-id
:timestamp-str (time/timestamp->time timestamp)})) :timestamp-str (time/timestamp->time timestamp)
:timestamp timestamp
:whisper-timestamp whisper-timestamp}))
(into (or message-references '())) (into (or message-references '()))
(sort-references (get-in db [:chats chat-id :messages])))))) (sort-references (get-in db [:chats chat-id :messages]))))))
db db

View File

@ -273,7 +273,7 @@
(get all-chats chat-id) (get all-chats chat-id)
{:keys [content content-type clock-value]} {:keys [content content-type clock-value]}
(->> (chat.db/sort-message-groups message-groups messages) (->> (chat.db/sort-message-groups message-groups messages)
first last
second second
last last
:message-id :message-id

View File

@ -166,17 +166,37 @@
(fn [{:keys [referenced-messages]}] (fn [{:keys [referenced-messages]}]
(or referenced-messages {}))) (or referenced-messages {})))
(re-frame/reg-sub
:chats/current-chat-topic
(fn [db]
(chat.db/topic-by-current-chat db)))
(re-frame/reg-sub
:chats/messages-gap
:<- [:get-in [:mailserver/topics]]
:<- [:chats/current-chat-topic]
(fn [[mailserver-topics topic]]
(chat.db/messages-gap mailserver-topics topic)))
(re-frame/reg-sub (re-frame/reg-sub
:chats/current-chat-messages-stream :chats/current-chat-messages-stream
:<- [:chats/current-chat-messages] :<- [:chats/current-chat-messages]
:<- [:chats/current-chat-message-groups] :<- [:chats/current-chat-message-groups]
:<- [:chats/current-chat-message-statuses] :<- [:chats/current-chat-message-statuses]
:<- [:chats/current-chat-referenced-messages] :<- [:chats/current-chat-referenced-messages]
(fn [[messages message-groups message-statuses referenced-messages]] :<- [:chats/messages-gap]
(fn [[messages message-groups message-statuses referenced-messages messages-gap]]
(-> (chat.db/sort-message-groups message-groups messages) (-> (chat.db/sort-message-groups message-groups messages)
(chat.db/messages-with-datemarks-and-statuses messages message-statuses referenced-messages) (chat.db/messages-with-datemarks-and-statuses messages message-statuses referenced-messages messages-gap)
chat.db/messages-stream))) chat.db/messages-stream)))
(re-frame/reg-sub
:chats/fetching-gap-in-progress?
(fn [db]
(let [chat-id (:current-chat-id db)
gaps (:mailserver/fetching-gaps-in-progress db)]
(contains? gaps chat-id))))
(re-frame/reg-sub (re-frame/reg-sub
:chats/current-chat-intro-status :chats/current-chat-intro-status
:<- [:chats/current-chat] :<- [:chats/current-chat]

View File

@ -461,6 +461,21 @@
contact-device-info/v1 contact-device-info/v1
contact-recovery/v1]) contact-recovery/v1])
(def v41 [chat/v14
transport/v8
contact/v7
message/v10
mailserver/v11
mailserver-topic/v2
user-status/v2
membership-update/v1
installation/v3
local-storage/v1
browser/v8
dapp-permissions/v9
contact-device-info/v1
contact-recovery/v1])
;; put schemas ordered by version ;; put schemas ordered by version
(def schemas [{:schema v1 (def schemas [{:schema v1
:schemaVersion 1 :schemaVersion 1
@ -581,4 +596,7 @@
:migration (constantly nil)} :migration (constantly nil)}
{:schema v40 {:schema v40
:schemaVersion 40 :schemaVersion 40
:migration migrations/v40}]) :migration migrations/v40}
{:schema v41
:schemaVersion 41
:migration (constantly nil)}])

View File

@ -5,3 +5,14 @@
:properties {:topic :string :properties {:topic :string
:chat-ids :string :chat-ids :string
:last-request {:type :int :default 1}}}) :last-request {:type :int :default 1}}})
(def v2
(-> v1
(assoc-in
[:properties :gap-from]
{:type :int
:optional true})
(assoc-in
[:properties :gap-to]
{:type :int
:optional true})))

View File

@ -62,3 +62,9 @@
(-> v8 (-> v8
(assoc-in [:properties :raw-payload-hash] (assoc-in [:properties :raw-payload-hash]
{:type :string}))) {:type :string})))
(def v10
(-> v9
(assoc-in [:properties :whisper-timestamp]
{:type :int
:optional true})))

View File

@ -1,3 +1,4 @@
; This entity is not used in the newer version of schema
(ns status-im.data-store.realm.schemas.account.transport-inbox-topic) (ns status-im.data-store.realm.schemas.account.transport-inbox-topic)
(def v1 {:name :transport-inbox-topic (def v1 {:name :transport-inbox-topic

View File

@ -54,7 +54,8 @@
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet] [status-im.ui.components.bottom-sheet.core :as bottom-sheet]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.utils.build :as build])) [status-im.utils.build :as build]
[status-im.chat.db :as chat.db]))
;; init module ;; init module
@ -445,7 +446,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:mailserver/fetch-history :mailserver/fetch-history
(fn [cofx [_ chat-id from-timestamp]] (fn [cofx [_ chat-id from-timestamp]]
(mailserver/fetch-history cofx chat-id from-timestamp))) (mailserver/fetch-history cofx chat-id {:from from-timestamp})))
(handlers/register-handler-fx (handlers/register-handler-fx
:mailserver.callback/generate-mailserver-symkey-success :mailserver.callback/generate-mailserver-symkey-success
@ -721,8 +722,22 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/fetch-history-pressed :chat.ui/fetch-history-pressed
(fn [cofx [_ chat-id]] (fn [{:keys [now] :as cofx} [_ chat-id]]
(mailserver/fetch-history cofx chat-id 1))) (mailserver/fetch-history cofx chat-id
{:from (- (quot now 1000) (* 24 3600))})))
(handlers/register-handler-fx
:chat.ui/fill-the-gap
(fn [{:keys [db] :as cofx}]
(let [mailserver-topics (:mailserver/topics db)
chat-id (:current-chat-id db)
topic (chat.db/topic-by-current-chat db)
gap (chat.db/messages-gap mailserver-topics topic)]
(mailserver/fill-the-gap
cofx
(assoc gap
:topic topic
:chat-id chat-id)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/remove-chat-pressed :chat.ui/remove-chat-pressed

View File

@ -2,13 +2,11 @@
status-im.mailserver.core status-im.mailserver.core
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.accounts.db :as accounts.db] [status-im.accounts.db :as accounts.db]
[status-im.data-store.core :as data-store]
[status-im.fleet.core :as fleet] [status-im.fleet.core :as fleet]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.transport.utils :as transport.utils] [status-im.transport.utils :as transport.utils]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.constants :as constants]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.transport.db :as transport.db] [status-im.transport.db :as transport.db]
@ -37,6 +35,7 @@
(def one-day (* 24 3600)) (def one-day (* 24 3600))
(def seven-days (* 7 one-day)) (def seven-days (* 7 one-day))
(def max-request-range one-day)
(def maximum-number-of-attempts 2) (def maximum-number-of-attempts 2)
(def request-timeout 30) (def request-timeout 30)
(def min-limit 100) (def min-limit 100)
@ -298,7 +297,7 @@
(assoc-in [:mailserver/current-request :request-id] request-id))} (assoc-in [:mailserver/current-request :request-id] request-id))}
{:db (assoc-in db [:mailserver/current-request :request-id] request-id)})))) {:db (assoc-in db [:mailserver/current-request :request-id] request-id)}))))
(defn request-messages! [web3 {:keys [sym-key-id address]} {:keys [topics cursor to from] :as request}] (defn request-messages! [web3 {:keys [sym-key-id address]} {:keys [topics cursor to from force-to?] :as request}]
;; Add some room to from, unless we break day boundaries so that messages that have ;; Add some room to from, unless we break day boundaries so that messages that have
;; been received after the last request are also fetched ;; been received after the last request are also fetched
(let [actual-from (adjust-request-for-transit-time from) (let [actual-from (adjust-request-for-transit-time from)
@ -307,16 +306,20 @@
(log/info "mailserver: request-messages for: " (log/info "mailserver: request-messages for: "
" topics " topics " topics " topics
" from " actual-from " from " actual-from
" force-to? " force-to?
" to " to
" cursor " cursor " cursor " cursor
" limit " actual-limit) " limit " actual-limit)
(.requestMessages (transport.utils/shh web3) (.requestMessages (transport.utils/shh web3)
(clj->js {:topics topics (clj->js (cond-> {:topics topics
:mailServerPeer address :mailServerPeer address
:symKeyID sym-key-id :symKeyID sym-key-id
:timeout request-timeout :timeout request-timeout
:limit actual-limit :limit actual-limit
:cursor cursor :cursor cursor
:from actual-from}) :from actual-from}
force-to?
(assoc :to to)))
(fn [error request-id] (fn [error request-id]
(if-not error (if-not error
(do (do
@ -341,25 +344,43 @@
sym-key-id) sym-key-id)
mailserver))) mailserver)))
(defn ->request (defn topic->request
[now-in-s [last-request topics]] [default-request-to requests-from requests-to]
(when (< last-request now-in-s) (fn [[topic {:keys [last-request]}]]
{:topics topics (let [force-request-from (get requests-from topic)
;; To is currently not sent to the mailserver, but we use to calculate force-request-to (get requests-to topic)]
;; when the last-request was sent. (when (or force-request-from
:to now-in-s (> default-request-to last-request))
:from (max last-request (let [from (or force-request-from
(- now-in-s one-day))})) (max last-request
(- default-request-to max-request-range)))
to (or force-request-to default-request-to)]
{:topic topic
:from from
:to to
:force-to? (not (nil? force-request-to))})))))
(defn aggregate-requests
[acc {:keys [topic from to force-to?]}]
(update acc [from to force-to?]
(fn [{:keys [topics]}]
{:topics ((fnil conj #{}) topics topic)
:from from
:to to
;; To is sent to the mailserver only when force-to? is true,
;; also we use to calculate when the last-request was sent.
:force-to? force-to?})))
(defn prepare-messages-requests (defn prepare-messages-requests
[{:keys [db now] :as cofx} request-to] [{{:keys [:mailserver/requests-from
(let [web3 (:web3 db)] :mailserver/requests-to
(->> :mailserver/topics]} :db}
(:mailserver/topics db) default-request-to]
(reduce (fn [acc [topic {:keys [last-request]}]] (transduce
(update acc last-request conj topic)) (keep (topic->request default-request-to requests-from requests-to))
{}) (completing aggregate-requests vals)
(keep (partial ->request request-to))))) {}
topics))
(fx/defn process-next-messages-request (fx/defn process-next-messages-request
[{:keys [db now] :as cofx}] [{:keys [db now] :as cofx}]
@ -372,6 +393,7 @@
(quot now 1000)) (quot now 1000))
requests (prepare-messages-requests cofx request-to) requests (prepare-messages-requests cofx request-to)
web3 (:web3 db)] web3 (:web3 db)]
(log/debug "Mailserver: planned requests " requests)
(if-let [request (first requests)] (if-let [request (first requests)]
{:db (assoc db {:db (assoc db
:mailserver/pending-requests (count requests) :mailserver/pending-requests (count requests)
@ -382,7 +404,9 @@
:request request}} :request request}}
{:db (dissoc db {:db (dissoc db
:mailserver/pending-requests :mailserver/pending-requests
:mailserver/request-to)}))))) :mailserver/request-to
:mailserver/requests-from
:mailserver/requests-to)})))))
(fx/defn add-mailserver-trusted (fx/defn add-mailserver-trusted
"the current mailserver has been trusted "the current mailserver has been trusted
@ -485,14 +509,100 @@
{:topic topic {:topic topic
:mailserver-topic mailserver-topic})]}))) :mailserver-topic mailserver-topic})]})))
(defn get-updated-mailserver-topics [db topics last-request] (defn calculate-gap
(reduce (fn [acc topic] [{:keys [gap-from
(if-let [mailserver-topic (some-> (get-in db [:mailserver/topics topic]) gap-to
(assoc :last-request last-request))] last-request] :as config}
(assoc acc topic mailserver-topic) {:keys [request-from
acc)) request-to]}]
{} (merge config
topics)) (cond
(nil? gap-from)
{:gap-from request-to
:gap-to request-to
:last-request request-to}
;;------GF GT--------LRT F---T
(> request-from last-request)
{:gap-from last-request
:gap-to request-from
:last-request request-to}
;;------GF GT--------LRT
;; F----------T
(and (>= last-request request-from gap-to)
(> request-to last-request))
{:last-request request-to}
;;------GF GT--------LRT
;; F----T
(and (>= last-request request-from gap-to)
(>= last-request request-to gap-to))
config
;;------GF GT--------LRT
;; F-------T
(and (> gap-to request-from gap-from)
(>= last-request request-to gap-to))
{:gap-to request-from}
;;------GF GT--------LRT
;; F-T
(and (> gap-to request-from gap-from)
(> gap-to request-to gap-from))
config
;;------GF GT--------LRT
;; F------T
(and (>= gap-from request-from)
(> gap-to request-to gap-from))
{:gap-from request-to}
;;---------GF=GT=LRT
;; F---T
(and (>= gap-from request-from)
(>= gap-from request-to)
(= gap-from last-request))
{:gap-from request-to}
;;------GF GT--------LRT
;; F---T
(and (>= gap-from request-from)
(>= gap-from request-to))
config
;;------GF GT--------LRT
;; F-------------T
(and (>= gap-from request-from)
(>= last-request request-to gap-to))
{:gap-from last-request
:gap-to last-request
:last-request last-request}
;;------GF GT--------LRT
;; F------------------------T
(and (>= gap-from request-from)
(>= request-to last-request))
{:gap-from request-to
:gap-to request-to
:last-request request-to}
;;------GF GT--------LRT
;; F-----------------T
(and (> gap-to request-from gap-from)
(>= request-to last-request))
{:gap-to request-from
:last-request request-to})))
(defn get-updated-mailserver-topics [db requested-topics from to]
(into
{}
(keep (fn [topic]
(when-let [config (get-in db [:mailserver/topics topic])]
[topic (calculate-gap config
{:request-from from
:request-to to})])))
requested-topics))
(fx/defn update-mailserver-topics (fx/defn update-mailserver-topics
"TODO: add support for cursors "TODO: add support for cursors
@ -500,7 +610,7 @@
[{:keys [db now] :as cofx} {:keys [request-id cursor]}] [{:keys [db now] :as cofx} {:keys [request-id cursor]}]
(when-let [request (get db :mailserver/current-request)] (when-let [request (get db :mailserver/current-request)]
(let [{:keys [from to topics]} request (let [{:keys [from to topics]} request
mailserver-topics (get-updated-mailserver-topics db topics to)] mailserver-topics (get-updated-mailserver-topics db topics from to)]
(log/info "mailserver: message request " request-id (log/info "mailserver: message request " request-id
"completed for mailserver topics" topics "from" from "to" to) "completed for mailserver topics" topics "from" from "to" to)
(if (empty? mailserver-topics) (if (empty? mailserver-topics)
@ -516,16 +626,31 @@
:mailserver/request-messages {:web3 (:web3 db) :mailserver/request-messages {:web3 (:web3 db)
:mailserver mailserver :mailserver mailserver
:request request-with-cursor}})) :request request-with-cursor}}))
(fx/merge cofx (let [{:keys [chat-id] :as current-request} (db :mailserver/current-request)
{:db (-> db gaps (db :mailserver/fetching-gaps-in-progress)
(dissoc :mailserver/current-request) fetching-gap-completed? (= (get gaps chat-id)
(update :mailserver/topics merge mailserver-topics)) (select-keys
:data-store/tx (mapv (fn [[topic mailserver-topic]] current-request
(data-store.mailservers/save-mailserver-topic-tx [:from :to :force-to? :topics :chat-id]))]
{:topic topic (fx/merge cofx
:mailserver-topic mailserver-topic})) {:db (-> db
mailserver-topics)} (dissoc :mailserver/current-request)
(process-next-messages-request))))))) (update :mailserver/requests-from
#(apply dissoc % topics))
(update :mailserver/requests-to
#(apply dissoc % topics))
(update :mailserver/topics merge mailserver-topics)
(update :mailserver/fetching-gaps-in-progress
(fn [gaps]
(if fetching-gap-completed?
(dissoc gaps chat-id)
gaps))))
:data-store/tx (mapv (fn [[topic mailserver-topic]]
(data-store.mailservers/save-mailserver-topic-tx
{:topic topic
:mailserver-topic mailserver-topic}))
mailserver-topics)}
(process-next-messages-request))))))))
(fx/defn retry-next-messages-request (fx/defn retry-next-messages-request
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
@ -543,8 +668,8 @@
:mailserver/pending-requests))}) :mailserver/pending-requests))})
(fx/defn handle-request-completed (fx/defn handle-request-completed
[{{:keys [chats] :as db} :db :as cofx} [{{:keys [chats]} :db :as cofx}
{:keys [requestID lastEnvelopeHash cursor errorMessage] :as event}] {:keys [requestID lastEnvelopeHash cursor errorMessage]}]
(when (accounts.db/logged-in? cofx) (when (accounts.db/logged-in? cofx)
(if (empty? errorMessage) (if (empty? errorMessage)
(let [never-synced-chats-in-request (let [never-synced-chats-in-request
@ -596,12 +721,13 @@
add the chat-id to the topic and reset last-request add the chat-id to the topic and reset last-request
there was no filter for the chat and messages for that there was no filter for the chat and messages for that
so the whole history for that topic needs to be re-fetched" so the whole history for that topic needs to be re-fetched"
[{:keys [db] :as cofx} {:keys [topic chat-id]}] [{:keys [db now] :as cofx} {:keys [topic chat-id]}]
(let [{:keys [chat-ids last-request] :as current-mailserver-topic} (let [{:keys [chat-ids last-request] :as current-mailserver-topic}
(get-in db [:mailserver/topics topic] {:chat-ids #{}})] (get-in db [:mailserver/topics topic] {:chat-ids #{}})]
(when-let [mailserver-topic (when-not (chat-ids chat-id) (when-let [mailserver-topic (when-not (chat-ids chat-id)
(-> current-mailserver-topic (-> current-mailserver-topic
(assoc :last-request 1) (assoc :last-request (- (quot now 1000)
(* 24 60 60)))
(update :chat-ids conj chat-id)))] (update :chat-ids conj chat-id)))]
(fx/merge cofx (fx/merge cofx
{:db (assoc-in db [:mailserver/topics topic] mailserver-topic) {:db (assoc-in db [:mailserver/topics topic] mailserver-topic)
@ -610,21 +736,42 @@
:mailserver-topic mailserver-topic})]})))) :mailserver-topic mailserver-topic})]}))))
(fx/defn fetch-history (fx/defn fetch-history
[{:keys [db] :as cofx} chat-id from-timestamp] [{:keys [db] :as cofx} chat-id {:keys [from to]}]
(log/debug "fetch-history" "chat-id:" chat-id "from-timestamp:" from-timestamp)
(log/debug "fetch-history" "chat-id:" chat-id "from-timestamp:" from)
(let [public-key (accounts.db/current-public-key cofx) (let [public-key (accounts.db/current-public-key cofx)
topic (or (get-in db [:transport/chats chat-id :topic]) topic (or (get-in db [:transport/chats chat-id :topic])
(transport.topic/public-key->discovery-topic-hash public-key)) (transport.topic/public-key->discovery-topic-hash public-key))]
{:keys [chat-ids last-request] :as current-mailserver-topic} (fx/merge cofx
(get-in db [:mailserver/topics topic] {:chat-ids #{}})] {:db (cond-> (assoc-in db [:mailserver/requests-from topic] from)
(let [mailserver-topic (-> current-mailserver-topic
(assoc :last-request (min from-timestamp last-request)))] to
(fx/merge cofx (assoc-in [:mailserver/requests-to topic] to))}
{:db (assoc-in db [:mailserver/topics topic] mailserver-topic) (process-next-messages-request))))
:data-store/tx [(data-store.mailservers/save-mailserver-topic-tx
{:topic topic (fx/defn fill-the-gap
:mailserver-topic mailserver-topic})]} [{:keys [db] :as cofx} {:keys [exists? from to topic chat-id]}]
(process-next-messages-request))))) (let [mailserver (get-mailserver-when-ready cofx)
request {:from from
:to to
:force-to? true
:topics [topic]
:chat-id chat-id}]
(when exists?
{:db
(-> db
(assoc
:mailserver/pending-requests 1
:mailserver/current-request request
:mailserver/request-to to)
(update :mailserver/fetching-gaps-in-progress
assoc chat-id request))
:mailserver/request-messages
{:web3 (:web3 db)
:mailserver mailserver
:request request}})))
(fx/defn resend-request (fx/defn resend-request
[{:keys [db] :as cofx} {:keys [request-id]}] [{:keys [db] :as cofx} {:keys [request-id]}]

View File

@ -43,6 +43,13 @@
(pos-int? pending-requests) (pos-int? pending-requests)
(not (or connecting? connection-error? request-error?))))) (not (or connecting? connection-error? request-error?)))))
(re-frame/reg-sub
:mailserver/connected?
(fn [db]
(let [connected? (= :connected (:mailserver/state db))
online? (= :online (:network-status db))]
(and connected? online?))))
(re-frame/reg-sub (re-frame/reg-sub
:mailserver/current-id :mailserver/current-id
(fn [db] (fn [db]

View File

@ -113,7 +113,7 @@
(fx/merge cofx (fx/merge cofx
(send-direct-message current-public-key nil this) (send-direct-message current-public-key nil this)
(send-with-pubkey params))))) (send-with-pubkey params)))))
(receive [this chat-id signature _ cofx] (receive [this chat-id signature timestamp cofx]
{:chat-received-message/add-fx {:chat-received-message/add-fx
[(assoc (into {} this) [(assoc (into {} this)
:old-message-id (transport.utils/old-message-id this) :old-message-id (transport.utils/old-message-id this)
@ -121,6 +121,7 @@
signature signature
(.-payload (:js-obj cofx))) (.-payload (:js-obj cofx)))
:chat-id chat-id :chat-id chat-id
:whisper-timestamp timestamp
:raw-payload-hash (transport.utils/sha3 :raw-payload-hash (transport.utils/sha3
(.-payload (:js-obj cofx))) (.-payload (:js-obj cofx)))
:from signature :from signature

View File

@ -70,6 +70,37 @@
[{{:keys [value]} :row}] [{{:keys [value]} :row}]
[message-datemark/chat-datemark-mobile value]) [message-datemark/chat-datemark-mobile value])
(defview gap []
(letsubs [in-progress? [:chats/fetching-gap-in-progress?]
connected? [:mailserver/connected?]]
[react/view {:align-self :stretch
:margin-top 24
:margin-bottom 24
:height 48
:align-items :center
:justify-content :center
:border-color colors/gray-light
:border-top-width 1
:border-bottom-width 1
:background-color :white}
[react/touchable-highlight
{:on-press (when (and connected? (not in-progress?))
#(re-frame/dispatch [:chat.ui/fill-the-gap]))}
[react/view {:flex 1
:align-items :center
:justify-content :center}
(if in-progress?
[react/activity-indicator]
[react/text
{:style {:color (if connected?
colors/blue
colors/gray)}}
(i18n/label :t/fetch-messages)])]]]))
(defmethod message-row :gap
[_]
[gap])
(defmethod message-row :default (defmethod message-row :default
[{:keys [group-chat current-public-key modal? row]}] [{:keys [group-chat current-public-key modal? row]}]
[message/chat-message (assoc row [message/chat-message (assoc row

View File

@ -1,35 +1,35 @@
(ns status-im.test.chat.db (ns status-im.test.chat.db
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.db :as s])) [status-im.chat.db :as db]))
(deftest group-chat-name (deftest group-chat-name
(testing "it prepends # if it's a public chat" (testing "it prepends # if it's a public chat"
(is (= "#withhash" (s/group-chat-name {:group-chat true (is (= "#withhash" (db/group-chat-name {:group-chat true
:chat-id "1" :chat-id "1"
:public? true :public? true
:name "withhash"})))) :name "withhash"}))))
(testing "it leaves the name unchanged if it's a group chat" (testing "it leaves the name unchanged if it's a group chat"
(is (= "unchanged" (s/group-chat-name {:group-chat true (is (= "unchanged" (db/group-chat-name {:group-chat true
:chat-id "1" :chat-id "1"
:name "unchanged"}))))) :name "unchanged"})))))
(deftest message-stream-tests (deftest message-stream-tests
(testing "messages with no interspersed datemarks" (testing "messages with no interspersed datemarks"
(let [m1 {:from "1" (let [m1 {:from "1"
:datemark "a" :datemark "a"
:outgoing false} :outgoing false}
m2 {:from "2" m2 {:from "2"
:datemark "a" :datemark "a"
:outgoing true} :outgoing true}
m3 {:from "2" m3 {:from "2"
:datemark "a" :datemark "a"
:outgoing true} :outgoing true}
dm1 {:type :datemark dm1 {:type :datemark
:value "a"} :value "a"}
messages [m1 m2 m3 dm1] messages [m1 m2 m3 dm1]
[actual-m1 [actual-m1
actual-m2 actual-m2
actual-m3] (s/messages-stream messages)] actual-m3] (db/messages-stream messages)]
(testing "it marks only the first message as :last?" (testing "it marks only the first message as :last?"
(is (:last? actual-m1)) (is (:last? actual-m1))
(is (not (:last? actual-m2))) (is (not (:last? actual-m2)))
@ -59,29 +59,29 @@
(is (:last-in-group? actual-m2)) (is (:last-in-group? actual-m2))
(is (not (:last-in-group? actual-m3)))))) (is (not (:last-in-group? actual-m3))))))
(testing "messages with interspersed datemarks" (testing "messages with interspersed datemarks"
(let [m1 {:from "2" ; first & last in group (let [m1 {:from "2" ; first & last in group
:timestamp 63000 :timestamp 63000
:outgoing true} :outgoing true}
dm1 {:type :datemark dm1 {:type :datemark
:value "a"} :value "a"}
m2 {:from "2" ; first & last in group as more than 1 minute after previous message m2 {:from "2" ; first & last in group as more than 1 minute after previous message
:timestamp 62000 :timestamp 62000
:outgoing false} :outgoing false}
m3 {:from "2" ; last in group m3 {:from "2" ; last in group
:timestamp 1 :timestamp 1
:outgoing false} :outgoing false}
m4 {:from "2" ; first in group m4 {:from "2" ; first in group
:timestamp 0 :timestamp 0
:outgoing false} :outgoing false}
dm2 {:type :datemark dm2 {:type :datemark
:value "b"} :value "b"}
messages [m1 dm1 m2 m3 m4 dm2] messages [m1 dm1 m2 m3 m4 dm2]
[actual-m1 [actual-m1
_ _
actual-m2 actual-m2
actual-m3 actual-m3
actual-m4 actual-m4
_] (s/messages-stream messages)] _] (db/messages-stream messages)]
(testing "it marks the first outgoing message as :last-outgoing?" (testing "it marks the first outgoing message as :last-outgoing?"
(is (:last-outgoing? actual-m1)) (is (:last-outgoing? actual-m1))
(is (not (:last-outgoing? actual-m2))) (is (not (:last-outgoing? actual-m2)))
@ -103,12 +103,12 @@
(is (:last-in-group? actual-m3)) (is (:last-in-group? actual-m3))
(is (not (:last-in-group? actual-m4)))))) (is (not (:last-in-group? actual-m4))))))
(testing "system-messages" (testing "system-messages"
(let [m1 {:from "system" (let [m1 {:from "system"
:message-type :system-message :message-type :system-message
:datemark "a" :datemark "a"
:outgoing false} :outgoing false}
messages [m1] messages [m1]
[actual-m1] (s/messages-stream messages)] [actual-m1] (db/messages-stream messages)]
(testing "it does display the photo" (testing "it does display the photo"
(is (:display-photo? actual-m1)) (is (:display-photo? actual-m1))
(testing "it does not display the username" (testing "it does not display the username"
@ -122,4 +122,218 @@
"3" {:is-active false :chat-id "3"}}] "3" {:is-active false :chat-id "3"}}]
(testing "it returns only chats with is-active" (testing "it returns only chats with is-active"
(is (= #{"1" "2"} (is (= #{"1" "2"}
(set (keys (s/active-chats {} chats {})))))))) (set (keys (db/active-chats {} chats {}))))))))
(deftest messages-with-datemarks-and-statuses
(testing "empty state"
(is (empty?
(db/messages-with-datemarks-and-statuses
nil
nil
nil
nil
nil))))
(testing "simple case"
(is (=
'({:whisper-timestamp 40
:timestamp 40
:content nil
:timestamp-str "14:00"
:user-statuses nil
:datemark "today"}
{:whisper-timestamp 30
:timestamp 30
:content nil
:timestamp-str "13:00"
:user-statuses nil
:datemark "today"}
{:value "today"
:type :datemark
:whisper-timestamp 30
:timestamp 30}
{:whisper-timestamp 20
:timestamp 20
:content nil
:timestamp-str "12:00"
:user-statuses nil
:datemark "yesterday"}
{:whisper-timestamp 10
:timestamp 10
:content nil
:timestamp-str "11:00"
:user-statuses nil
:datemark "yesterday"}
{:value "yesterday"
:type :datemark
:whisper-timestamp 10
:timestamp 10})
(db/messages-with-datemarks-and-statuses
{"yesterday"
(list
{:message-id :m1
:timestamp-str "11:00"
:whisper-timestamp 10
:timestamp 10}
{:message-id :m2
:timestamp-str "12:00"
:whisper-timestamp 20
:timestamp 20})
"today"
(list
{:message-id :m3
:timestamp-str "13:00"
:whisper-timestamp 30
:timestamp 30}
{:message-id :m4
:timestamp-str "14:00"
:whisper-timestamp 40
:timestamp 40})}
{:m1 {:whisper-timestamp 10
:timestamp 10}
:m2 {:whisper-timestamp 20
:timestamp 20}
:m3 {:whisper-timestamp 30
:timestamp 30}
:m4 {:whisper-timestamp 40
:timestamp 40}}
nil
nil
nil))))
(testing "simple case with gap"
(is (=
'({:whisper-timestamp 40
:timestamp 40
:content nil
:timestamp-str "14:00"
:user-statuses nil
:datemark "today"}
{:whisper-timestamp 30
:timestamp 30
:content nil
:timestamp-str "13:00"
:user-statuses nil
:datemark "today"}
{:value "today"
:type :datemark
:whisper-timestamp 30
:timestamp 30}
{:type :gap
:value "25"}
{:whisper-timestamp 20
:timestamp 20
:content nil
:timestamp-str "12:00"
:user-statuses nil
:datemark "yesterday"}
{:whisper-timestamp 10
:timestamp 10
:content nil
:timestamp-str "11:00"
:user-statuses nil
:datemark "yesterday"}
{:value "yesterday"
:type :datemark
:whisper-timestamp 10
:timestamp 10})
(db/messages-with-datemarks-and-statuses
{"yesterday"
(list
{:message-id :m1
:timestamp-str "11:00"
:whisper-timestamp 10
:timestamp 10}
{:message-id :m2
:timestamp-str "12:00"
:whisper-timestamp 20
:timestamp 20})
"today"
(list
{:message-id :m3
:timestamp-str "13:00"
:whisper-timestamp 30
:timestamp 30}
{:message-id :m4
:timestamp-str "14:00"
:whisper-timestamp 40
:timestamp 40})}
{:m1 {:whisper-timestamp 10
:timestamp 10}
:m2 {:whisper-timestamp 20
:timestamp 20}
:m3 {:whisper-timestamp 30
:timestamp 30}
:m4 {:whisper-timestamp 40
:timestamp 40}}
nil
nil
{:from 25
:exists? true}))))
(testing "simple case with gap after all messages"
(is (=
'({:type :gap
:value "100"}
{:whisper-timestamp 40
:timestamp 40
:content nil
:timestamp-str "14:00"
:user-statuses nil
:datemark "today"}
{:whisper-timestamp 30
:timestamp 30
:content nil
:timestamp-str "13:00"
:user-statuses nil
:datemark "today"}
{:value "today"
:type :datemark
:whisper-timestamp 30
:timestamp 30}
{:whisper-timestamp 20
:timestamp 20
:content nil
:timestamp-str "12:00"
:user-statuses nil
:datemark "yesterday"}
{:whisper-timestamp 10
:timestamp 10
:content nil
:timestamp-str "11:00"
:user-statuses nil
:datemark "yesterday"}
{:value "yesterday"
:type :datemark
:whisper-timestamp 10
:timestamp 10})
(db/messages-with-datemarks-and-statuses
{"yesterday"
(list
{:message-id :m1
:timestamp-str "11:00"
:whisper-timestamp 10
:timestamp 10}
{:message-id :m2
:timestamp-str "12:00"
:whisper-timestamp 20
:timestamp 20})
"today"
(list
{:message-id :m3
:timestamp-str "13:00"
:whisper-timestamp 30
:timestamp 30}
{:message-id :m4
:timestamp-str "14:00"
:whisper-timestamp 40
:timestamp 40})}
{:m1 {:whisper-timestamp 10
:timestamp 10}
:m2 {:whisper-timestamp 20
:timestamp 20}
:m3 {:whisper-timestamp 30
:timestamp 30}
:m4 {:whisper-timestamp 40
:timestamp 40}}
nil
nil
{:from 100
:exists? true})))))

View File

@ -627,3 +627,187 @@
(is (not (-> (mailserver/connect-to-mailserver {:db mailserver-with-sym-key-db}) (is (not (-> (mailserver/connect-to-mailserver {:db mailserver-with-sym-key-db})
:shh/generate-sym-key-from-password :shh/generate-sym-key-from-password
first))))))) first)))))))
(deftest calculate-gap
(testing "new topic"
(is (= {:gap-from 10
:gap-to 10
:last-request 10}
(mailserver/calculate-gap
{:gap-from nil
:gap-to nil
:last-request nil}
{:request-from 5
:request-to 10}))))
(testing "calculate-gap#1"
(is (= {:gap-from 3
:gap-to 4
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 3}
{:request-from 4
:request-to 5}))))
(testing "calculate-gap#2"
(is (= {:gap-from 1
:gap-to 2
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 4}
{:request-from 3
:request-to 5}))))
(testing "calculate-gap#2-1"
(is (= {:gap-from 1
:gap-to 2
:last-request 4}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 3}
{:request-from 3
:request-to 4}))))
(testing "calculate-gap#3"
(is (= {:gap-from 1
:gap-to 2
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 5}
{:request-from 3
:request-to 4}))))
(testing "calculate-gap#3-1"
(is (= {:gap-from 1
:gap-to 2
:last-request 3}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 3}
{:request-from 2
:request-to 3}))))
(testing "calculate-gap#4"
(is (= {:gap-from 1
:gap-to 2
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 5}
{:request-from 3
:request-to 4}))))
(testing "calculate-gap#5"
(is (= {:gap-from 1
:gap-to 4
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 4
:last-request 5}
{:request-from 2
:request-to 3}))))
(testing "calculate-gap#6"
(is (= {:gap-from 2
:gap-to 3
:last-request 4}
(mailserver/calculate-gap
{:gap-from 2
:gap-to 3
:last-request 4}
{:request-from 1
:request-to 2}))))
(testing "calculate-gap#6-1"
(is (= {:gap-from 1
:gap-to 4
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 4
:last-request 5}
{:request-from 2
:request-to 3}))))
(testing "calculate-gap#0"
(is (= {:gap-from 2
:gap-to 3
:last-request 3}
(mailserver/calculate-gap
{:gap-from 3
:gap-to 3
:last-request 3}
{:request-from 1
:request-to 2}))))
(testing "calculate-gap#7"
(is (= {:gap-from 3
:gap-to 4
:last-request 5}
(mailserver/calculate-gap
{:gap-from 3
:gap-to 4
:last-request 5}
{:request-from 1
:request-to 2}))))
(testing "calculate-gap#8"
(is (= {:gap-from 5
:gap-to 5
:last-request 5}
(mailserver/calculate-gap
{:gap-from 2
:gap-to 3
:last-request 5}
{:request-from 1
:request-to 4}))))
(testing "calculate-gap#8-1"
(is (= {:gap-from 3
:gap-to 3
:last-request 3}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 3}
{:request-from 1
:request-to 2}))))
(testing "calculate-gap#9"
(is (= {:gap-from 5
:gap-to 5
:last-request 5}
(mailserver/calculate-gap
{:gap-from 2
:gap-to 3
:last-request 4}
{:request-from 1
:request-to 5}))))
(testing "calculate-gap#9-1"
(is (= {:gap-from 3
:gap-to 3
:last-request 3}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 3}
{:request-from 1
:request-to 3}))))
(testing "calculate-gap#10"
(is (= {:gap-from 1
:gap-to 2
:last-request 5}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 3
:last-request 4}
{:request-from 2
:request-to 5}))))
(testing "calculate-gap#10-1"
(is (= {:gap-from 1
:gap-to 2
:last-request 3}
(mailserver/calculate-gap
{:gap-from 1
:gap-to 2
:last-request 3}
{:request-from 2
:request-to 3})))))

View File

@ -997,5 +997,6 @@
"chaos-unicorn-day-details": "\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83D\uDE80!", "chaos-unicorn-day-details": "\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83E\uDD84\uD83D\uDE80!",
"invalid-format": "Invalid format\nMust be {{format}}", "invalid-format": "Invalid format\nMust be {{format}}",
"mailserver-format": "enode://{enode-id}:{password}@{ip-address}:{port}", "mailserver-format": "enode://{enode-id}:{password}@{ip-address}:{port}",
"bootnode-format": "enode://{enode-id}@{ip-address}:{port}" "bootnode-format": "enode://{enode-id}@{ip-address}:{port}",
"fetch-messages": "↓ Fetch messages"
} }