file.cljs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. (ns frontend.worker.file
  2. "Save pages to files for file-based graphs"
  3. (:require [clojure.core.async :as async]
  4. [clojure.string :as string]
  5. [clojure.set :as set]
  6. [frontend.worker.file.core :as file]
  7. [logseq.outliner.tree :as otree]
  8. [lambdaisland.glogi :as log]
  9. [cljs-time.core :as t]
  10. [cljs-time.coerce :as tc]
  11. [frontend.worker.util :as worker-util]
  12. [frontend.common.async-util :as async-util]
  13. [datascript.core :as d]
  14. [logseq.db :as ldb]
  15. [malli.core :as m]
  16. [frontend.worker.state :as worker-state]
  17. [goog.object :as gobj]
  18. [logseq.common.util :as common-util]))
  19. (def *writes file/*writes)
  20. (def dissoc-request! file/dissoc-request!)
  21. (def conj-page-write! file/conj-page-write!)
  22. (defonce file-writes-chan
  23. (let [coercer (m/coercer [:catn
  24. [:repo :string]
  25. [:page-id :any]
  26. [:outliner-op :any]
  27. [:epoch :int]
  28. [:request-id :int]])]
  29. (async/chan 10000 (map coercer))))
  30. (def batch-write-interval 1000)
  31. (def whiteboard-blocks-pull-keys-with-persisted-ids
  32. '[:block/properties
  33. :block/uuid
  34. :block/order
  35. :block/title
  36. :block/format
  37. :block/created-at
  38. :block/updated-at
  39. :block/collapsed?
  40. {:block/page [:block/uuid]}
  41. {:block/parent [:block/uuid]}])
  42. (defn- cleanup-whiteboard-block
  43. [block]
  44. (if (get-in block [:block/properties :ls-type] false)
  45. (dissoc block
  46. :db/id
  47. :block/uuid ;; shape block uuid is read from properties
  48. :block/collapsed?
  49. :block/title
  50. :block/format
  51. :block/order
  52. :block/page
  53. :block/parent) ;; these are auto-generated for whiteboard shapes
  54. (dissoc block :db/id :block/page)))
  55. (defn do-write-file!
  56. [repo conn page-db-id outliner-op context request-id]
  57. (let [page-block (d/pull @conn '[*] page-db-id)
  58. page-db-id (:db/id page-block)
  59. whiteboard? (ldb/whiteboard? page-block)
  60. blocks-count (ldb/get-page-blocks-count @conn page-db-id)
  61. blocks-just-deleted? (and (zero? blocks-count)
  62. (contains? #{:delete-blocks :move-blocks} outliner-op))]
  63. (if (or (>= blocks-count 1) blocks-just-deleted?)
  64. (if (and (or (> blocks-count 500) whiteboard?)
  65. (not (worker-state/tx-idle? repo {:diff 3000})))
  66. (async/put! file-writes-chan [repo page-db-id outliner-op (tc/to-long (t/now)) request-id])
  67. (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*])
  68. blocks (ldb/get-page-blocks @conn (:db/id page-block) {:pull-keys pull-keys})
  69. blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)]
  70. (if (and (= 1 (count blocks))
  71. (string/blank? (:block/title (first blocks)))
  72. (nil? (:block/file page-block))
  73. (not whiteboard?))
  74. (dissoc-request! request-id)
  75. (let [tree-or-blocks (if whiteboard? blocks
  76. (otree/blocks->vec-tree repo @conn blocks (:db/id page-block)))]
  77. (if page-block
  78. (file/save-tree! repo conn page-block tree-or-blocks blocks-just-deleted? context request-id)
  79. (do
  80. (js/console.error (str "can't find page id: " page-db-id))
  81. (dissoc-request! request-id)))))))
  82. (dissoc-request! request-id))))
  83. (defn write-files!
  84. [conn pages context]
  85. (when (seq pages)
  86. (let [all-request-ids (set (map last pages))
  87. distincted-pages (common-util/distinct-by #(take 3 %) pages)
  88. repeated-ids (set/difference all-request-ids (set (map last distincted-pages)))]
  89. (doseq [id repeated-ids]
  90. (dissoc-request! id))
  91. (doseq [[repo page-id outliner-op _time request-id] distincted-pages]
  92. (try (do-write-file! repo conn page-id outliner-op context request-id)
  93. (catch :default e
  94. (worker-util/post-message :notification
  95. [[:div
  96. [:p "Write file failed, please copy the changes to other editors in case of losing data."]
  97. "Error: " (str (gobj/get e "stack"))]
  98. :error])
  99. (log/error :file/write-file-error {:error e})
  100. (dissoc-request! request-id)))))))
  101. (defn sync-to-file
  102. [repo page-id tx-meta]
  103. (when (and page-id
  104. (not (:created-from-journal-template? tx-meta))
  105. (not (:delete-files? tx-meta)))
  106. (let [request-id (conj-page-write! page-id)]
  107. (async/put! file-writes-chan [repo page-id (:outliner-op tx-meta) (tc/to-long (t/now)) request-id]))))
  108. (defn <ratelimit-file-writes!
  109. []
  110. (async-util/<ratelimit file-writes-chan batch-write-interval
  111. :filter-fn (fn [_] true)
  112. :flush-fn
  113. (fn [col]
  114. (when (seq col)
  115. (let [repo (ffirst col)
  116. conn (worker-state/get-datascript-conn repo)]
  117. (if conn
  118. (when-not (ldb/db-based-graph? @conn)
  119. (write-files! conn col (worker-state/get-context)))
  120. (js/console.error (str "DB is not found for ") repo)))))))