file.cljs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. (ns frontend.handler.common.file
  2. "Common file related fns for handlers"
  3. (:require [frontend.config :as config]
  4. [frontend.state :as state]
  5. [frontend.db :as db]
  6. [logseq.graph-parser :as graph-parser]
  7. [logseq.graph-parser.util :as gp-util]
  8. [logseq.graph-parser.config :as gp-config]
  9. [frontend.fs.diff-merge :as diff-merge]
  10. [frontend.fs :as fs]
  11. [frontend.context.i18n :refer [t]]
  12. [promesa.core :as p]
  13. [clojure.string :as string]
  14. [cljs-bean.core :as bean]))
  15. (defn- page-exists-in-another-file
  16. "Conflict of files towards same page"
  17. [repo-url page file]
  18. (when-let [page-name (:block/name page)]
  19. (let [current-file (:file/path (db/get-page-file repo-url page-name))]
  20. (when (not= file current-file)
  21. current-file))))
  22. (defn- validate-existing-file
  23. "Handle the case when the file is already exists in db
  24. Likely caused by renaming between caps and non-caps, then cause file system
  25. bugs on some OS
  26. e.g. on macOS, it doesn't fire the file change event when renaming between
  27. caps and non-caps"
  28. [repo-url file-page file-path]
  29. (when-let [current-file (page-exists-in-another-file repo-url file-page file-path)]
  30. (when (not= file-path current-file)
  31. (cond
  32. ;; TODO: handle case sensitive file system
  33. (= (gp-util/path-normalize (string/lower-case current-file))
  34. (gp-util/path-normalize (string/lower-case file-path)))
  35. ;; case renamed
  36. (when-let [file (db/pull [:file/path current-file])]
  37. (p/let [disk-content (fs/read-file "" current-file)]
  38. (fs/backup-db-file! repo-url current-file (:file/content file) disk-content))
  39. (db/transact! repo-url [{:db/id (:db/id file)
  40. :file/path file-path}]))
  41. :else
  42. (let [error (t :file/validate-existing-file-error current-file file-path)]
  43. (state/pub-event! [:notification/show
  44. {:content error
  45. :status :error
  46. :clear? false}]))))))
  47. (defn- validate-and-get-blocks-to-delete
  48. "An implementation for the delete-blocks-fn in graph-parser/parse-file"
  49. [repo-url db file-page file-path retain-uuid-blocks]
  50. (validate-existing-file repo-url file-page file-path)
  51. (graph-parser/get-blocks-to-delete db file-page file-path retain-uuid-blocks))
  52. (defn- diff-merge-uuids
  53. "Infer new uuids from existing DB data and diff with the new AST
  54. Return a list of uuids for the new blocks"
  55. [format ast content {:keys [page-name] :as options}]
  56. (let [base-diffblocks (diff-merge/db->diff-blocks page-name)
  57. income-diffblocks (diff-merge/ast->diff-blocks ast content format options)
  58. diff-ops (diff-merge/diff base-diffblocks income-diffblocks)
  59. new-uuids (diff-merge/attachUUID diff-ops (map :uuid base-diffblocks))]
  60. (bean/->clj new-uuids)))
  61. (defn- reset-file!-impl
  62. "Parse file considering diff-merge with local or remote file
  63. Decide how to treat the parsed file based on the file's triggering event
  64. options -
  65. :fs/reset-event - the event that triggered the file update
  66. :fs/local-file-change - file changed on local disk
  67. :fs/remote-file-change - file changed on remote"
  68. [repo-url file content {:fs/keys [event] :as options}]
  69. (let [db-conn (db/get-db repo-url false)]
  70. (case event
  71. ;; the file is already in db, so we can use the existing file's blocks
  72. ;; to do the diff-merge
  73. :fs/local-file-change
  74. (graph-parser/parse-file db-conn file content (assoc-in options [:extract-options :resolve-uuid-fn] diff-merge-uuids))
  75. ;; TODO Junyi: 3 ways to handle remote file change
  76. ;; The file is on remote, so we should have
  77. ;; 1. a "common ancestor" file locally
  78. ;; the worst case is that the file is not in db, so we should use the
  79. ;; empty file as the common ancestor
  80. ;; 2. a "remote version" just fetched from remote
  81. ;; default to parse the file
  82. (graph-parser/parse-file db-conn file content options))))
  83. (defn reset-file!
  84. "Main fn for updating a db with the results of a parsed file"
  85. ([repo-url file-path content]
  86. (reset-file! repo-url file-path content {}))
  87. ([repo-url file-path content {:keys [verbose extracted-block-ids] :as options}]
  88. (let [new? (nil? (db/entity [:file/path file-path]))
  89. options (merge (dissoc options :verbose :extracted-block-ids)
  90. {:new? new?
  91. :delete-blocks-fn (partial validate-and-get-blocks-to-delete repo-url)
  92. ;; Options here should also be present in gp-cli/parse-graph
  93. :extract-options (merge
  94. {:user-config (state/get-config)
  95. :date-formatter (state/get-date-formatter)
  96. :block-pattern (config/get-block-pattern (gp-util/get-format file-path))
  97. :supported-formats (gp-config/supported-formats)
  98. :filename-format (state/get-filename-format repo-url)}
  99. ;; To avoid skipping the `:or` bounds for keyword destructuring
  100. (when (some? extracted-block-ids) {:extracted-block-ids extracted-block-ids})
  101. (when (some? verbose) {:verbose verbose}))})]
  102. (:tx (reset-file!-impl repo-url file-path content options)))))