core.cljs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. (ns frontend.db.rtc.core
  2. "Main ns for rtc related fns"
  3. (:require-macros
  4. [frontend.db.rtc.macro :refer [with-sub-data-from-ws get-req-id get-result-ch]])
  5. (:require [cljs-time.coerce :as tc]
  6. [cljs-time.core :as t]
  7. [cljs.core.async :as async :refer [<! >! chan go go-loop]]
  8. [clojure.set :as set]
  9. [cognitect.transit :as transit]
  10. [frontend.async-util :include-macros true :refer [<?]]
  11. [frontend.db :as db]
  12. [frontend.db.react :as react]
  13. [frontend.db.rtc.const :as rtc-const]
  14. [frontend.db.rtc.op-mem-layer :as op-mem-layer]
  15. [frontend.db.rtc.ws :as ws]
  16. [frontend.handler.page :as page-handler]
  17. [frontend.handler.property.util :as pu]
  18. [frontend.handler.user :as user]
  19. [frontend.handler.whiteboard :as whiteboard-handler]
  20. [frontend.modules.outliner.core :as outliner-core]
  21. [frontend.modules.outliner.transaction :as outliner-tx]
  22. [frontend.state :as state]
  23. [frontend.util :as util]
  24. [malli.core :as m]
  25. [malli.util :as mu]))
  26. ;; +-------------+
  27. ;; | |
  28. ;; | server |
  29. ;; | |
  30. ;; +----^----+---+
  31. ;; | |
  32. ;; | |
  33. ;; | rtc-const/data-from-ws-schema
  34. ;; | |
  35. ;; rtc-const/data-to-ws-schema |
  36. ;; | |
  37. ;; | |
  38. ;; | |
  39. ;; +----+----v---+ +------------+
  40. ;; | +---------------------> |
  41. ;; | client | | indexeddb |
  42. ;; | |<--------------------+ |
  43. ;; +-------------+ +------------+
  44. ;; frontend.db.rtc.op/op-schema
  45. (def state-schema
  46. "
  47. | :*graph-uuid | atom of graph-uuid syncing now |
  48. | :*repo | atom of repo name syncing now |
  49. | :data-from-ws-chan | channel for receive messages from server websocket |
  50. | :data-from-ws-pub | pub of :data-from-ws-chan, dispatch by :req-id |
  51. | :*stop-rtc-loop-chan | atom of chan to stop <loop-for-rtc |
  52. | :*ws | atom of websocket |
  53. | :*rtc-state | atom of state of current rtc progress |
  54. | :toggle-auto-push-client-ops-chan | channel to toggle pushing client ops automatically |
  55. | :*auto-push-client-ops? | atom to show if it's push client-ops automatically |
  56. | :force-push-client-ops-chan | chan used to force push client-ops |
  57. "
  58. [:map {:closed true}
  59. [:*graph-uuid :any]
  60. [:*repo :any]
  61. [:data-from-ws-chan :any]
  62. [:data-from-ws-pub :any]
  63. [:*stop-rtc-loop-chan :any]
  64. [:*ws :any]
  65. [:*rtc-state :any]
  66. [:toggle-auto-push-client-ops-chan :any]
  67. [:*auto-push-client-ops? :any]
  68. [:force-push-client-ops-chan :any]])
  69. (def state-validator
  70. (let [validator (m/validator state-schema)]
  71. (fn [data]
  72. (if (validator data)
  73. true
  74. (prn (mu/explain-data state-schema data))))))
  75. (def rtc-state-schema
  76. [:enum :open :closed])
  77. (def rtc-state-validator (m/validator rtc-state-schema))
  78. (def transit-w (transit/writer :json))
  79. (def transit-r (transit/reader :json))
  80. (defmulti transact-db! (fn [action & _args] action))
  81. (defmethod transact-db! :delete-blocks [_ & args]
  82. (outliner-tx/transact!
  83. {:persist-op? false}
  84. (apply outliner-core/delete-blocks! (state/get-current-repo) (db/get-db false)
  85. (state/get-date-formatter)
  86. args)))
  87. (defmethod transact-db! :move-blocks [_ & args]
  88. (outliner-tx/transact!
  89. {:persist-op? false}
  90. (apply outliner-core/move-blocks! (state/get-current-repo) (db/get-db false) args)))
  91. (defmethod transact-db! :insert-blocks [_ & args]
  92. (outliner-tx/transact!
  93. {:persist-op? false}
  94. (apply outliner-core/insert-blocks! (state/get-current-repo) (db/get-db false) args)))
  95. (defmethod transact-db! :save-block [_ & args]
  96. (outliner-tx/transact!
  97. {:persist-op? false}
  98. (apply outliner-core/save-block! (state/get-current-repo) (db/get-db false) (state/get-date-formatter) args)))
  99. (defmethod transact-db! :delete-whiteboard-blocks [_ repo block-uuids]
  100. (db/transact! repo
  101. (mapv (fn [block-uuid] [:db/retractEntity [:block/uuid block-uuid]]) block-uuids)
  102. {:persist-op? false}))
  103. (defmethod transact-db! :upsert-whiteboard-block [_ repo blocks]
  104. (db/transact! repo blocks {:persist-op? false}))
  105. (defmethod transact-db! :raw [_ & args]
  106. (apply db/transact! args))
  107. (defn- whiteboard-page-block?
  108. [block]
  109. (contains? (set (:block/type block)) "whiteboard"))
  110. (defn- group-remote-remove-ops-by-whiteboard-block
  111. "return {true [<whiteboard-block-ops>], false [<other-ops>]}"
  112. [repo remote-remove-ops]
  113. (group-by (fn [{:keys [block-uuid]}]
  114. (boolean
  115. (when-let [block (db/pull repo [{:block/parent [:block/type]}] [:block/uuid block-uuid])]
  116. (whiteboard-page-block? (:block/parent block)))))
  117. remote-remove-ops))
  118. (defn apply-remote-remove-ops
  119. [repo remove-ops]
  120. (prn :remove-ops remove-ops)
  121. (let [{whiteboard-block-ops true other-ops false} (group-remote-remove-ops-by-whiteboard-block repo remove-ops)]
  122. (transact-db! :delete-whiteboard-blocks (map :block-uuid whiteboard-block-ops))
  123. (doseq [op other-ops]
  124. (when-let [block (db/pull repo '[*] [:block/uuid (:block-uuid op)])]
  125. (transact-db! :delete-blocks [block] {:children? false})
  126. (prn :apply-remote-remove-ops (:block-uuid op))))))
  127. (defn- insert-or-move-block
  128. [repo block-uuid remote-parents remote-left-uuid move? op-value]
  129. (when (and (seq remote-parents) remote-left-uuid)
  130. (let [first-remote-parent (first remote-parents)
  131. local-parent (db/pull repo '[*] [:block/uuid first-remote-parent])
  132. whiteboard-page-block? (whiteboard-page-block? local-parent)
  133. ;; when insert blocks in whiteboard, local-left is ignored
  134. local-left (when-not whiteboard-page-block? (db/pull repo '[*] [:block/uuid remote-left-uuid]))
  135. b {:block/uuid block-uuid}
  136. ;; b-ent (db/entity repo [:block/uuid (uuid block-uuid-str)])
  137. ]
  138. (case [whiteboard-page-block? (some? local-parent) (some? local-left)]
  139. [false false true]
  140. (if move?
  141. (transact-db! :move-blocks [b] local-left true)
  142. (transact-db! :insert-blocks
  143. [{:block/uuid block-uuid
  144. :block/content ""
  145. :block/format :markdown}]
  146. local-left {:sibling? true :keep-uuid? true}))
  147. [false true true]
  148. (let [sibling? (not= (:block/uuid local-parent) (:block/uuid local-left))]
  149. (if move?
  150. (transact-db! :move-blocks [b] local-left sibling?)
  151. (transact-db! :insert-blocks
  152. [{:block/uuid block-uuid :block/content ""
  153. :block/format :markdown}]
  154. local-left {:sibling? sibling? :keep-uuid? true})))
  155. [false true false]
  156. (if move?
  157. (transact-db! :move-blocks [b] local-parent false)
  158. (transact-db! :insert-blocks
  159. [{:block/uuid block-uuid :block/content ""
  160. :block/format :markdown}]
  161. local-parent {:sibling? false :keep-uuid? true}))
  162. ;; Don't need to insert-whiteboard-block here,
  163. ;; will do :upsert-whiteboard-block in `update-block-attrs`
  164. [true true false]
  165. (when (nil? (:properties op-value))
  166. ;; when :properties is nil, this block should be treat as normal block
  167. (if move?
  168. (transact-db! :move-blocks [b] local-parent false)
  169. (transact-db! :insert-blocks [{:block/uuid block-uuid :block/content "" :block/format :markdown}]
  170. local-parent {:sibling? false :keep-uuid? true})))
  171. [true true true]
  172. (when (nil? (:properties op-value))
  173. (let [sibling? (not= (:block/uuid local-parent) (:block/uuid local-left))]
  174. (if move?
  175. (transact-db! :move-blocks [b] local-left sibling?)
  176. (transact-db! :insert-blocks [{:block/uuid block-uuid :block/content "" :block/format :markdown}]
  177. local-left {:sibling? sibling? :keep-uuid? true}))))
  178. (throw (ex-info "Don't know where to insert" {:block-uuid block-uuid :remote-parents remote-parents
  179. :remote-left remote-left-uuid}))))))
  180. (defn- move-ops-map->sorted-move-ops
  181. [move-ops-map]
  182. (let [uuid->dep-uuids (into {} (map (fn [[uuid env]] [uuid (set (conj (:parents env) (:left env)))]) move-ops-map))
  183. all-uuids (set (keys move-ops-map))
  184. sorted-uuids
  185. (loop [r []
  186. rest-uuids all-uuids
  187. uuid (first rest-uuids)]
  188. (if-not uuid
  189. r
  190. (let [dep-uuids (uuid->dep-uuids uuid)]
  191. (if-let [next-uuid (first (set/intersection dep-uuids rest-uuids))]
  192. (recur r rest-uuids next-uuid)
  193. (let [rest-uuids* (disj rest-uuids uuid)]
  194. (recur (conj r uuid) rest-uuids* (first rest-uuids*)))))))]
  195. (mapv move-ops-map sorted-uuids)))
  196. (comment
  197. (def move-ops-map {"2" {:parents ["1"] :left "1" :x "2"}
  198. "1" {:parents ["3"] :left nil :x "1"}
  199. "3" {:parents [] :left nil :x "3"}})
  200. (move-ops-map->sorted-move-ops move-ops-map))
  201. (defn- check-block-pos
  202. "NOTE: some blocks don't have :block/left (e.g. whiteboard blocks)"
  203. [repo block-uuid remote-parents remote-left-uuid]
  204. (let [local-b (db/pull repo '[{:block/left [:block/uuid]}
  205. {:block/parent [:block/uuid]}
  206. *]
  207. [:block/uuid block-uuid])
  208. remote-parent-uuid (first remote-parents)]
  209. (cond
  210. (nil? local-b)
  211. :not-exist
  212. (and (nil? (:block/left local-b))
  213. (not= (:block/uuid (:block/parent local-b)) remote-parent-uuid))
  214. ;; blocks don't have :block/left
  215. :wrong-pos
  216. (and (:block/left local-b)
  217. (or (not= (:block/uuid (:block/parent local-b)) remote-parent-uuid)
  218. (not= (:block/uuid (:block/left local-b)) remote-left-uuid)))
  219. :wrong-pos
  220. :else nil)))
  221. (defn- upsert-whiteboard-block
  222. [repo {:keys [parents properties] :as _op-value}]
  223. (let [first-remote-parent (first parents)]
  224. (when-let [local-parent (db/pull repo '[*] [:block/uuid first-remote-parent])]
  225. (let [page-name (:block/name local-parent)
  226. properties* (transit/read transit-r properties)
  227. shape-property-id (pu/get-pid :logseq.tldraw.shape)
  228. shape (and (map? properties*)
  229. (get properties* shape-property-id))]
  230. (assert (some? page-name) local-parent)
  231. (assert (some? shape) properties*)
  232. (transact-db! :upsert-whiteboard-block repo [(whiteboard-handler/shape->block shape page-name)])))))
  233. (defn- update-block-attrs
  234. [repo block-uuid {:keys [parents properties _content] :as op-value}]
  235. (let [key-set (set/intersection
  236. (conj rtc-const/general-attr-set :content)
  237. (set (keys op-value)))]
  238. (when (seq key-set)
  239. (let [first-remote-parent (first parents)
  240. local-parent (db/pull repo '[*] [:block/uuid first-remote-parent])
  241. whiteboard-page-block? (whiteboard-page-block? local-parent)]
  242. (cond
  243. (and whiteboard-page-block? properties)
  244. (upsert-whiteboard-block repo op-value)
  245. :else
  246. (let [b-ent (db/pull repo '[*] [:block/uuid block-uuid])
  247. new-block
  248. (cond-> (db/pull repo '[*] (:db/id b-ent))
  249. (and (contains? key-set :content)
  250. (not= (:content op-value)
  251. (:block/content b-ent))) (assoc :block/content (:content op-value))
  252. (contains? key-set :updated-at) (assoc :block/updated-at (:updated-at op-value))
  253. (contains? key-set :created-at) (assoc :block/created-at (:created-at op-value))
  254. (contains? key-set :alias) (assoc :block/alias (some->> (seq (:alias op-value))
  255. (map (partial vector :block/uuid))
  256. (db/pull-many repo [:db/id])
  257. (keep :db/id)))
  258. (contains? key-set :type) (assoc :block/type (:type op-value))
  259. (contains? key-set :schema) (assoc :block/schema (transit/read transit-r (:schema op-value)))
  260. (contains? key-set :tags) (assoc :block/tags (some->> (seq (:tags op-value))
  261. (map (partial vector :block/uuid))
  262. (db/pull-many repo [:db/id])
  263. (filter :db/id)))
  264. ;; FIXME: it looks save-block won't save :block/properties??
  265. ;; so I need to transact properties myself
  266. ;; (contains? key-set :properties) (assoc :block/properties
  267. ;; (transit/read transit-r (:properties op-value)))
  268. )]
  269. (transact-db! :save-block new-block)
  270. (let [properties (transit/read transit-r (:properties op-value))]
  271. (transact-db! :raw
  272. repo
  273. [{:block/uuid block-uuid
  274. :block/properties properties}]
  275. {:outliner-op :save-block}))))))))
  276. (defn apply-remote-move-ops
  277. [repo sorted-move-ops]
  278. (prn :sorted-move-ops sorted-move-ops)
  279. (doseq [{:keys [parents left self] :as op-value} sorted-move-ops]
  280. (let [r (check-block-pos repo self parents left)]
  281. (case r
  282. :not-exist
  283. (insert-or-move-block repo self parents left false op-value)
  284. :wrong-pos
  285. (insert-or-move-block repo self parents left true op-value)
  286. nil ; do nothing
  287. nil)
  288. (update-block-attrs repo self op-value)
  289. (prn :apply-remote-move-ops self r parents left))))
  290. (defn apply-remote-update-ops
  291. [repo update-ops]
  292. (prn :update-ops update-ops)
  293. (doseq [{:keys [parents left self] :as op-value} update-ops]
  294. (when (and parents left)
  295. (let [r (check-block-pos repo self parents left)]
  296. (case r
  297. :not-exist
  298. (insert-or-move-block repo self parents left false op-value)
  299. :wrong-pos
  300. (insert-or-move-block repo self parents left true op-value)
  301. nil)))
  302. (update-block-attrs repo self op-value)
  303. (prn :apply-remote-update-ops self)))
  304. (defn apply-remote-update-page-ops
  305. [repo update-page-ops]
  306. (doseq [{:keys [self page-name original-name] :as op-value} update-page-ops]
  307. (let [old-page-original-name (:block/original-name
  308. (db/pull repo [:block/original-name] [:block/uuid self]))
  309. exist-page (db/pull repo [:block/uuid] [:block/name page-name])]
  310. (cond
  311. ;; same name but different uuid
  312. ;; remote page has same block/name as local's, but they don't have same block/uuid.
  313. ;; 1. rename local page's name to '<origin-name>-<ms-epoch>-Conflict'
  314. ;; 2. create page, name=<origin-name>, uuid=remote-uuid
  315. (and exist-page (not= (:block/uuid exist-page) self))
  316. (do (page-handler/rename! original-name (util/format "%s-%s-CONFLICT" original-name (tc/to-long (t/now))))
  317. (page-handler/create! original-name {:redirect? false :create-first-block? false
  318. :uuid self :persist-op? false}))
  319. ;; a client-page has same uuid as remote but different page-names,
  320. ;; then we need to rename the client-page to remote-page-name
  321. (and old-page-original-name (not= old-page-original-name original-name))
  322. (page-handler/rename! old-page-original-name original-name false false)
  323. ;; no such page, name=remote-page-name, OR, uuid=remote-block-uuid
  324. ;; just create-page
  325. :else
  326. (page-handler/create! original-name {:redirect? false :create-first-block? false
  327. :uuid self :persist-op? false}))
  328. (update-block-attrs repo self op-value))))
  329. (defn apply-remote-remove-page-ops
  330. [repo remove-page-ops]
  331. (doseq [op remove-page-ops]
  332. (when-let [page-name (:block/name
  333. (db/pull repo [:block/name] [:block/uuid (:block-uuid op)]))]
  334. (page-handler/delete! page-name nil {:redirect-to-home? false :persist-op? false}))))
  335. (defn filter-remote-data-by-local-unpushed-ops
  336. "when remote-data request client to move/update/remove/... blocks,
  337. these updates maybe not needed, because this client just updated some of these blocks,
  338. so we need to filter these just-updated blocks out, according to the unpushed-local-ops"
  339. [affected-blocks-map local-unpushed-ops]
  340. ;; (assert (op-mem-layer/ops-coercer local-unpushed-ops) local-unpushed-ops)
  341. (reduce
  342. (fn [affected-blocks-map local-op]
  343. (case (first local-op)
  344. "move"
  345. (let [block-uuid (:block-uuid (second local-op))
  346. remote-op (get affected-blocks-map block-uuid)]
  347. (case (:op remote-op)
  348. :remove (dissoc affected-blocks-map (:block-uuid remote-op))
  349. :move (dissoc affected-blocks-map (:self remote-op))
  350. ;; default
  351. affected-blocks-map))
  352. "update"
  353. (let [block-uuid (:block-uuid (second local-op))
  354. local-updated-attr-set (set (keys (:updated-attrs (second local-op))))]
  355. (if-let [remote-op (get affected-blocks-map block-uuid)]
  356. (assoc affected-blocks-map block-uuid
  357. (if (#{:update-attrs :move} (:op remote-op))
  358. (apply dissoc remote-op local-updated-attr-set)
  359. remote-op))
  360. affected-blocks-map))
  361. ;;else
  362. affected-blocks-map))
  363. affected-blocks-map local-unpushed-ops))
  364. (defn <apply-remote-data
  365. [repo data-from-ws]
  366. (assert (rtc-const/data-from-ws-validator data-from-ws) data-from-ws)
  367. (go
  368. (let [remote-t (:t data-from-ws)
  369. remote-t-before (:t-before data-from-ws)
  370. local-tx (op-mem-layer/get-local-tx repo)]
  371. (cond
  372. (not (and (pos? remote-t)
  373. (pos? remote-t-before)))
  374. (throw (ex-info "invalid remote-data" {:data data-from-ws}))
  375. (<= remote-t local-tx)
  376. (prn ::skip :remote-t remote-t :remote-t remote-t-before :local-t local-tx)
  377. (< local-tx remote-t-before)
  378. (do (prn ::need-pull-remote-data :remote-t remote-t :remote-t-before remote-t-before :local-t local-tx)
  379. ::need-pull-remote-data)
  380. (<= remote-t-before local-tx remote-t)
  381. (let [affected-blocks-map (:affected-blocks data-from-ws)
  382. unpushed-ops (op-mem-layer/get-all-ops repo)
  383. affected-blocks-map* (if unpushed-ops
  384. (filter-remote-data-by-local-unpushed-ops
  385. affected-blocks-map unpushed-ops)
  386. affected-blocks-map)
  387. {remove-ops-map :remove move-ops-map :move update-ops-map :update-attrs
  388. update-page-ops-map :update-page remove-page-ops-map :remove-page}
  389. (update-vals
  390. (group-by (fn [[_ env]] (get env :op)) affected-blocks-map*)
  391. (partial into {}))
  392. remove-ops (vals remove-ops-map)
  393. sorted-move-ops (move-ops-map->sorted-move-ops move-ops-map)
  394. update-ops (vals update-ops-map)
  395. update-page-ops (vals update-page-ops-map)
  396. remove-page-ops (vals remove-page-ops-map)]
  397. (state/set-state! [:rtc/remote-batch-tx-state repo]
  398. {:in-transaction? true
  399. :txs []})
  400. (util/profile :apply-remote-update-page-ops (apply-remote-update-page-ops repo update-page-ops))
  401. (util/profile :apply-remote-remove-ops (apply-remote-remove-ops repo remove-ops))
  402. (util/profile :apply-remote-move-ops (apply-remote-move-ops repo sorted-move-ops))
  403. (util/profile :apply-remote-update-ops (apply-remote-update-ops repo update-ops))
  404. (util/profile :apply-remote-remove-page-ops (apply-remote-remove-page-ops repo remove-page-ops))
  405. (let [txs (get-in @state/state [:rtc/remote-batch-tx-state repo :txs])]
  406. (util/profile
  407. :batch-refresh
  408. (react/batch-refresh! repo txs)))
  409. (op-mem-layer/update-local-tx! repo remote-t))
  410. :else (throw (ex-info "unreachable" {:remote-t remote-t
  411. :remote-t-before remote-t-before
  412. :local-t local-tx}))))))
  413. (defn- <push-data-from-ws-handler
  414. [repo push-data-from-ws]
  415. (prn :push-data-from-ws push-data-from-ws)
  416. (go
  417. (let [r (<! (<apply-remote-data repo push-data-from-ws))]
  418. (when (= r ::need-pull-remote-data)
  419. r))))
  420. (defn- remove-non-exist-block-uuids-in-add-retract-map
  421. [repo add-retract-map]
  422. (let [{:keys [add retract]} add-retract-map
  423. add* (->> add
  424. (map (fn [x] [:block/uuid x]))
  425. (db/pull-many repo [:block/uuid])
  426. (keep :block/uuid))]
  427. (cond-> {}
  428. (seq add*) (assoc :add add*)
  429. (seq retract) (assoc :retract retract))))
  430. (defmulti local-block-ops->remote-ops-aux (fn [tp & _] tp))
  431. (defmethod local-block-ops->remote-ops-aux :move-op
  432. [_ & {:keys [parent-uuid left-uuid block-uuid *remote-ops *depend-on-block-uuid-set]}]
  433. (when parent-uuid
  434. (let [target-uuid (or left-uuid parent-uuid)
  435. sibling? (not= left-uuid parent-uuid)]
  436. (swap! *remote-ops conj [:move {:block-uuid block-uuid :target-uuid target-uuid :sibling? sibling?}])
  437. (swap! *depend-on-block-uuid-set conj target-uuid))))
  438. (defmethod local-block-ops->remote-ops-aux :update-op
  439. [_ & {:keys [repo block update-op left-uuid parent-uuid *remote-ops]}]
  440. (let [block-uuid (:block/uuid block)
  441. attr-map (:updated-attrs (second update-op))
  442. attr-alias-map (when (contains? attr-map :alias)
  443. (remove-non-exist-block-uuids-in-add-retract-map repo (:alias attr-map)))
  444. attr-tags-map (when (contains? attr-map :tags)
  445. (remove-non-exist-block-uuids-in-add-retract-map repo (:tags attr-map)))
  446. attr-type-map (when (contains? attr-map :type)
  447. (let [{:keys [add retract]} (:type attr-map)
  448. current-type-value (set (:block/type block))
  449. add (set/intersection add current-type-value)
  450. retract (set/difference retract current-type-value)]
  451. (cond-> {}
  452. (seq add) (assoc :add add)
  453. (seq retract) (assoc :retract retract))))
  454. attr-properties-map (when (contains? attr-map :properties)
  455. (let [{:keys [add retract]} (:properties attr-map)
  456. properties (:block/properties block)
  457. add* (into []
  458. (update-vals (select-keys properties add)
  459. (partial transit/write transit-w)))]
  460. (cond-> {}
  461. (seq add*) (assoc :add add*)
  462. (seq retract) (assoc :retract retract))))
  463. target-uuid (or left-uuid parent-uuid)
  464. sibling? (not= left-uuid parent-uuid)]
  465. (swap! *remote-ops conj
  466. [:update
  467. (cond-> {:block-uuid block-uuid}
  468. (:block/updated-at block) (assoc :updated-at (:block/updated-at block))
  469. (:block/created-at block) (assoc :created-at (:block/created-at block))
  470. (contains? attr-map :schema) (assoc :schema
  471. (transit/write transit-w (:block/schema block)))
  472. attr-alias-map (assoc :alias attr-alias-map)
  473. attr-type-map (assoc :type attr-type-map)
  474. attr-tags-map (assoc :tags attr-tags-map)
  475. attr-properties-map (assoc :properties attr-properties-map)
  476. (and (contains? attr-map :content)
  477. (:block/content block))
  478. (assoc :content (:block/content block))
  479. (and (contains? attr-map :link)
  480. (:block/uuid (:block/link block)))
  481. (assoc :link (:block/uuid (:block/link block)))
  482. target-uuid (assoc :target-uuid target-uuid :sibling? sibling?))])))
  483. (defmethod local-block-ops->remote-ops-aux :update-page-op
  484. [_ & {:keys [repo block-uuid *remote-ops]}]
  485. (when-let [{page-name :block/name original-name :block/original-name}
  486. (db/pull repo [:block/name :block/original-name] [:block/uuid block-uuid])]
  487. (swap! *remote-ops conj
  488. [:update-page {:block-uuid block-uuid
  489. :page-name page-name
  490. :original-name (or original-name page-name)}])))
  491. (defmethod local-block-ops->remote-ops-aux :remove-op
  492. [_ & {:keys [repo remove-op *remote-ops]}]
  493. (when-let [block-uuid (:block-uuid (second remove-op))]
  494. (when (nil? (db/pull repo [:block/uuid] [:block/uuid block-uuid]))
  495. (swap! *remote-ops conj [:remove {:block-uuids [block-uuid]}]))))
  496. (defmethod local-block-ops->remote-ops-aux :remove-page-op
  497. [_ & {:keys [repo remove-page-op *remote-ops]}]
  498. (when-let [block-uuid (:block-uuid (second remove-page-op))]
  499. (when (nil? (db/pull repo [:block/uuid] [:block/uuid block-uuid]))
  500. (swap! *remote-ops conj [:remove-page {:block-uuid block-uuid}]))))
  501. (defn- local-block-ops->remote-ops
  502. [repo block-ops]
  503. (let [*depend-on-block-uuid-set (atom #{})
  504. *remote-ops (atom [])
  505. {move-op :move remove-op :remove update-op :update update-page-op :update-page remove-page-op :remove-page}
  506. block-ops]
  507. (when-let [block-uuid
  508. (some (comp :block-uuid second) [move-op update-op update-page-op])]
  509. (when-let [block (db/pull repo
  510. '[{:block/left [:block/uuid]}
  511. {:block/parent [:block/uuid]}
  512. {:block/link [:block/uuid]}
  513. *]
  514. [:block/uuid block-uuid])]
  515. (let [left-uuid (some-> block :block/left :block/uuid)
  516. parent-uuid (some-> block :block/parent :block/uuid)]
  517. (when parent-uuid ; whiteboard blocks don't have :block/left
  518. ;; remote-move-op
  519. (when move-op
  520. (local-block-ops->remote-ops-aux :move-op
  521. :parent-uuid parent-uuid
  522. :left-uuid left-uuid
  523. :block-uuid block-uuid
  524. :*remote-ops *remote-ops
  525. :*depend-on-block-uuid-set *depend-on-block-uuid-set)))
  526. ;; remote-update-op
  527. (when update-op
  528. (local-block-ops->remote-ops-aux :update-op
  529. :repo repo
  530. :block block
  531. :update-op update-op
  532. :parent-uuid parent-uuid
  533. :left-uuid left-uuid
  534. :*remote-ops *remote-ops)))
  535. ;; remote-update-page-op
  536. (when update-page-op
  537. (local-block-ops->remote-ops-aux :update-page-op
  538. :repo repo
  539. :block-uuid block-uuid
  540. :*remote-ops *remote-ops))))
  541. ;; remote-remove-op
  542. (when remove-op
  543. (local-block-ops->remote-ops-aux :remove-op
  544. :repo repo
  545. :remove-op remove-op
  546. :*remote-ops *remote-ops))
  547. ;; remote-remove-page-op
  548. (when remove-page-op
  549. (local-block-ops->remote-ops-aux :remove-page-op
  550. :repo repo
  551. :remove-page-op remove-page-op
  552. :*remote-ops *remote-ops))
  553. {:remote-ops @*remote-ops
  554. :depend-on-block-uuids @*depend-on-block-uuid-set}))
  555. (defn gen-block-uuid->remote-ops
  556. [repo & {:keys [n] :or {n 50}}]
  557. (loop [current-handling-block-ops nil
  558. current-handling-block-uuid nil
  559. depend-on-block-uuid-coll nil
  560. r {}]
  561. (cond
  562. (and (empty? current-handling-block-ops)
  563. (empty? depend-on-block-uuid-coll)
  564. (>= (count r) n))
  565. r
  566. (and (empty? current-handling-block-ops)
  567. (empty? depend-on-block-uuid-coll))
  568. (if-let [{min-epoch-block-ops :ops block-uuid :block-uuid} (op-mem-layer/get-min-epoch-block-ops repo)]
  569. (do (assert (not (contains? r block-uuid)) {:r r :block-uuid block-uuid})
  570. (op-mem-layer/remove-block-ops! repo block-uuid)
  571. (recur min-epoch-block-ops block-uuid depend-on-block-uuid-coll r))
  572. ;; finish
  573. r)
  574. (and (empty? current-handling-block-ops)
  575. (seq depend-on-block-uuid-coll))
  576. (let [[block-uuid & other-block-uuids] depend-on-block-uuid-coll
  577. block-ops (op-mem-layer/get-block-ops repo block-uuid)]
  578. (op-mem-layer/remove-block-ops! repo block-uuid)
  579. (recur block-ops block-uuid other-block-uuids r))
  580. (seq current-handling-block-ops)
  581. (let [{:keys [remote-ops depend-on-block-uuids]}
  582. (local-block-ops->remote-ops repo current-handling-block-ops)]
  583. (recur nil nil
  584. (set/union (set depend-on-block-uuid-coll)
  585. (op-mem-layer/intersection-block-uuids repo depend-on-block-uuids))
  586. (assoc r current-handling-block-uuid (into {} remote-ops)))))))
  587. (defn sort-remote-ops
  588. [block-uuid->remote-ops]
  589. (let [block-uuid->dep-uuid
  590. (into {}
  591. (keep (fn [[block-uuid remote-ops]]
  592. (when-let [move-op (get remote-ops :move)]
  593. [block-uuid (:target-uuid move-op)])))
  594. block-uuid->remote-ops)
  595. all-move-uuids (set (keys block-uuid->dep-uuid))
  596. sorted-uuids
  597. (loop [r []
  598. rest-uuids all-move-uuids
  599. uuid (first rest-uuids)]
  600. (if-not uuid
  601. r
  602. (let [dep-uuid (block-uuid->dep-uuid uuid)]
  603. (if-let [next-uuid (get rest-uuids dep-uuid)]
  604. (recur r rest-uuids next-uuid)
  605. (let [rest-uuids* (disj rest-uuids uuid)]
  606. (recur (conj r uuid) rest-uuids* (first rest-uuids*)))))))
  607. sorted-move-ops (keep
  608. (fn [block-uuid]
  609. (some->> (get-in block-uuid->remote-ops [block-uuid :move])
  610. (vector :move)))
  611. sorted-uuids)
  612. remove-ops (keep
  613. (fn [[_ remote-ops]]
  614. (some->> (:remove remote-ops) (vector :remove)))
  615. block-uuid->remote-ops)
  616. update-ops (keep
  617. (fn [[_ remote-ops]]
  618. (some->> (:update remote-ops) (vector :update)))
  619. block-uuid->remote-ops)
  620. update-page-ops (keep
  621. (fn [[_ remote-ops]]
  622. (some->> (:update-page remote-ops) (vector :update-page)))
  623. block-uuid->remote-ops)
  624. remove-page-ops (keep
  625. (fn [[_ remote-ops]]
  626. (some->> (:remove-page remote-ops) (vector :remove-page)))
  627. block-uuid->remote-ops)]
  628. (concat update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops)))
  629. (defn- <client-op-update-handler
  630. [state]
  631. {:pre [(some? @(:*graph-uuid state))
  632. (some? @(:*repo state))]}
  633. (go
  634. (let [repo @(:*repo state)]
  635. (op-mem-layer/new-branch! repo)
  636. (try
  637. (let [ops-for-remote (sort-remote-ops (gen-block-uuid->remote-ops repo))
  638. local-tx (op-mem-layer/get-local-tx repo)
  639. r (<? (ws/<send&receive state {:action "apply-ops" :graph-uuid @(:*graph-uuid state)
  640. :ops ops-for-remote :t-before (or local-tx 1)}))]
  641. (if-let [remote-ex (:ex-data r)]
  642. (case (:type remote-ex)
  643. ;; conflict-update remote-graph, keep these local-pending-ops
  644. ;; and try to send ops later
  645. :graph-lock-failed
  646. (do (prn :graph-lock-failed)
  647. (op-mem-layer/rollback! repo)
  648. nil)
  649. ;; this case means something wrong in remote-graph data,
  650. ;; nothing to do at client-side
  651. :graph-lock-missing
  652. (do (prn :graph-lock-missing)
  653. (op-mem-layer/rollback! repo)
  654. nil)
  655. :get-s3-object-failed
  656. (do (prn ::get-s3-object-failed r)
  657. (op-mem-layer/rollback! repo)
  658. nil)
  659. ;; else
  660. (do (op-mem-layer/rollback! repo)
  661. (throw (ex-info "Unavailable" {:remote-ex remote-ex}))))
  662. (do (assert (pos? (:t r)) r)
  663. (op-mem-layer/commit! repo)
  664. (<! (<apply-remote-data repo r))
  665. (prn :<client-op-update-handler :t (:t r)))))
  666. (catch :default e
  667. (prn ::unknown-ex e)
  668. (op-mem-layer/rollback! repo)
  669. nil)))))
  670. (defn- make-push-client-ops-timeout-ch
  671. [repo never-timeout?]
  672. (if never-timeout?
  673. (chan)
  674. (go
  675. (<! (async/timeout 2000))
  676. (pos? (op-mem-layer/get-unpushed-block-update-count repo)))))
  677. (defn <loop-for-rtc
  678. [state graph-uuid repo & {:keys [loop-started-ch]}]
  679. {:pre [(state-validator state)
  680. (some? graph-uuid)
  681. (some? repo)]}
  682. (go
  683. (reset! (:*repo state) repo)
  684. (reset! (:*rtc-state state) :open)
  685. (let [{:keys [data-from-ws-pub _client-op-update-chan]} state
  686. push-data-from-ws-ch (chan (async/sliding-buffer 100) (map rtc-const/data-from-ws-coercer))
  687. stop-rtc-loop-chan (chan)
  688. *auto-push-client-ops? (:*auto-push-client-ops? state)
  689. force-push-client-ops-ch (:force-push-client-ops-chan state)
  690. toggle-auto-push-client-ops-ch (:toggle-auto-push-client-ops-chan state)]
  691. (reset! (:*stop-rtc-loop-chan state) stop-rtc-loop-chan)
  692. (<! (ws/<ensure-ws-open! state))
  693. (reset! (:*graph-uuid state) graph-uuid)
  694. (with-sub-data-from-ws state
  695. (<! (ws/<send! state {:action "register-graph-updates" :req-id (get-req-id) :graph-uuid graph-uuid}))
  696. (<! (get-result-ch)))
  697. (async/sub data-from-ws-pub "push-updates" push-data-from-ws-ch)
  698. (when loop-started-ch (async/close! loop-started-ch))
  699. (<! (go-loop [push-client-ops-ch
  700. (make-push-client-ops-timeout-ch repo (not @*auto-push-client-ops?))]
  701. (let [{:keys [push-data-from-ws client-op-update stop continue]}
  702. (async/alt!
  703. toggle-auto-push-client-ops-ch {:continue true}
  704. force-push-client-ops-ch {:client-op-update true}
  705. push-client-ops-ch ([v] (if (and @*auto-push-client-ops? (true? v))
  706. {:client-op-update true}
  707. {:continue true}))
  708. push-data-from-ws-ch ([v] {:push-data-from-ws v})
  709. stop-rtc-loop-chan {:stop true}
  710. :priority true)]
  711. (cond
  712. continue
  713. (recur (make-push-client-ops-timeout-ch repo (not @*auto-push-client-ops?)))
  714. push-data-from-ws
  715. (let [r (<! (<push-data-from-ws-handler repo push-data-from-ws))]
  716. (when (= r ::need-pull-remote-data)
  717. ;; trigger a force push, which can pull remote-diff-data from local-t to remote-t
  718. (async/put! force-push-client-ops-ch true))
  719. (recur (make-push-client-ops-timeout-ch repo (not @*auto-push-client-ops?))))
  720. client-op-update
  721. (let [maybe-exp (<! (user/<wrap-ensure-id&access-token
  722. (<! (<client-op-update-handler state))))]
  723. (if (= :expired-token (:anom (ex-data maybe-exp)))
  724. (prn ::<loop-for-rtc "quitting loop" maybe-exp)
  725. (recur (make-push-client-ops-timeout-ch repo (not @*auto-push-client-ops?)))))
  726. stop
  727. (do (ws/stop @(:*ws state))
  728. (reset! (:*rtc-state state) :closed))
  729. :else
  730. nil))))
  731. (async/unsub data-from-ws-pub "push-updates" push-data-from-ws-ch))))
  732. (defn <grant-graph-access-to-others
  733. [state graph-uuid & {:keys [target-user-uuids target-user-emails]}]
  734. (go
  735. (let [r (with-sub-data-from-ws state
  736. (<! (ws/<send! state (cond-> {:req-id (get-req-id)
  737. :action "grant-access"
  738. :graph-uuid graph-uuid}
  739. target-user-uuids (assoc :target-user-uuids target-user-uuids)
  740. target-user-emails (assoc :target-user-emails target-user-emails))))
  741. (<! (get-result-ch)))]
  742. (if-let [ex-message (:ex-message r)]
  743. (prn ::<grant-graph-access-to-others ex-message (:ex-data r))
  744. (prn ::<grant-graph-access-to-others :succ)))))
  745. (defn <toggle-auto-push-client-ops
  746. [state]
  747. (go
  748. (swap! (:*auto-push-client-ops? state) not)
  749. (>! (:toggle-auto-push-client-ops-chan state) true)))
  750. (defn <get-block-content-versions
  751. [state block-uuid]
  752. (go
  753. (when (some-> state :*graph-uuid deref)
  754. (with-sub-data-from-ws state
  755. (<! (ws/<send! state {:req-id (get-req-id)
  756. :action "query-block-content-versions"
  757. :block-uuids [block-uuid]
  758. :graph-uuid @(:*graph-uuid state)}))
  759. (let [{:keys [ex-message ex-data versions]} (<! (get-result-ch))]
  760. (if ex-message
  761. (prn ::<get-block-content-versions :ex-message ex-message :ex-data ex-data)
  762. versions))))))
  763. (defn init-state
  764. [ws data-from-ws-chan]
  765. ;; {:post [(m/validate state-schema %)]}
  766. {:*rtc-state (atom :closed :validator rtc-state-validator)
  767. :*graph-uuid (atom nil)
  768. :*repo (atom nil)
  769. :data-from-ws-chan data-from-ws-chan
  770. :data-from-ws-pub (async/pub data-from-ws-chan :req-id)
  771. :toggle-auto-push-client-ops-chan (chan (async/sliding-buffer 1))
  772. :*auto-push-client-ops? (atom true :validator boolean?)
  773. :*stop-rtc-loop-chan (atom nil)
  774. :force-push-client-ops-chan (chan (async/sliding-buffer 1))
  775. :*ws (atom ws)})
  776. (defn <init-state
  777. []
  778. (go
  779. (let [data-from-ws-chan (chan (async/sliding-buffer 100))
  780. ws-opened-ch (chan)]
  781. (<! (user/<wrap-ensure-id&access-token
  782. (let [token (state/get-auth-id-token)
  783. ws (ws/ws-listen token data-from-ws-chan ws-opened-ch)]
  784. (<! ws-opened-ch)
  785. (init-state ws data-from-ws-chan)))))))