|
|
@@ -3,38 +3,149 @@
|
|
|
(:require
|
|
|
[datascript.core :as d]
|
|
|
[frontend.common.crypt :as crypt]
|
|
|
+ [frontend.worker.platform :as platform]
|
|
|
+ [frontend.worker.shared-service :as shared-service]
|
|
|
[frontend.worker.state :as worker-state]
|
|
|
[frontend.worker.sync.auth :as sync-auth]
|
|
|
[frontend.worker.sync.client-op :as client-op]
|
|
|
[frontend.worker.sync.crypt :as sync-crypt]
|
|
|
[frontend.worker.sync.large-title :as sync-large-title]
|
|
|
[lambdaisland.glogi :as log]
|
|
|
+ [logseq.common.util :as common-util]
|
|
|
[logseq.db :as ldb]
|
|
|
[promesa.core :as p]))
|
|
|
|
|
|
(def max-asset-size (* 100 1024 1024))
|
|
|
|
|
|
-(defn exported-graph-aes-key
|
|
|
+(defn graph-aes-key
|
|
|
[repo graph-id fail-fast-f]
|
|
|
(if (sync-crypt/graph-e2ee? repo)
|
|
|
(p/let [aes-key (sync-crypt/<ensure-graph-aes-key repo graph-id)
|
|
|
_ (when (nil? aes-key)
|
|
|
(fail-fast-f :db-sync/missing-field {:repo repo :field :aes-key}))]
|
|
|
- (crypt/<export-aes-key aes-key))
|
|
|
+ aes-key)
|
|
|
(p/resolved nil)))
|
|
|
|
|
|
+(defn- asset-file-name
|
|
|
+ [asset-uuid asset-type]
|
|
|
+ (str asset-uuid "." asset-type))
|
|
|
+
|
|
|
+(defn- ->uint8
|
|
|
+ [payload]
|
|
|
+ (cond
|
|
|
+ (instance? js/Uint8Array payload)
|
|
|
+ payload
|
|
|
+
|
|
|
+ (instance? js/ArrayBuffer payload)
|
|
|
+ (js/Uint8Array. payload)
|
|
|
+
|
|
|
+ (and (exists? js/ArrayBuffer)
|
|
|
+ (.isView js/ArrayBuffer payload))
|
|
|
+ (js/Uint8Array. (.-buffer payload) (.-byteOffset payload) (.-byteLength payload))
|
|
|
+
|
|
|
+ (array? payload)
|
|
|
+ (js/Uint8Array. payload)
|
|
|
+
|
|
|
+ (sequential? payload)
|
|
|
+ (js/Uint8Array. (clj->js payload))
|
|
|
+
|
|
|
+ (and (object? payload)
|
|
|
+ (= "Buffer" (aget payload "type"))
|
|
|
+ (array? (aget payload "data")))
|
|
|
+ (js/Uint8Array. (aget payload "data"))
|
|
|
+
|
|
|
+ :else
|
|
|
+ (throw (ex-info "unsupported binary payload"
|
|
|
+ {:payload-type (str (type payload))}))))
|
|
|
+
|
|
|
+(defn- payload-size
|
|
|
+ [payload]
|
|
|
+ (cond
|
|
|
+ (string? payload) (count payload)
|
|
|
+ (some? (.-byteLength payload)) (.-byteLength payload)
|
|
|
+ (some? (.-length payload)) (.-length payload)
|
|
|
+ :else 0))
|
|
|
+
|
|
|
+(defn- notify-asset-progress!
|
|
|
+ [repo asset-id direction loaded total]
|
|
|
+ (shared-service/broadcast-to-clients!
|
|
|
+ :rtc-asset-upload-download-progress
|
|
|
+ {:repo repo
|
|
|
+ :asset-id asset-id
|
|
|
+ :progress {:direction direction
|
|
|
+ :loaded loaded
|
|
|
+ :total total}}))
|
|
|
+
|
|
|
+(defn- mark-asset-write-finish!
|
|
|
+ [repo asset-id]
|
|
|
+ (shared-service/broadcast-to-clients!
|
|
|
+ :asset-file-write-finish
|
|
|
+ {:repo repo
|
|
|
+ :asset-id asset-id
|
|
|
+ :ts (common-util/time-ms)}))
|
|
|
+
|
|
|
+(defn- <read-asset-bytes
|
|
|
+ [repo asset-id asset-type]
|
|
|
+ (platform/asset-read-bytes! (platform/current)
|
|
|
+ repo
|
|
|
+ (asset-file-name asset-id asset-type)))
|
|
|
+
|
|
|
+(defn- <write-asset-bytes!
|
|
|
+ [repo asset-id asset-type payload]
|
|
|
+ (p/let [_ (platform/asset-write-bytes! (platform/current)
|
|
|
+ repo
|
|
|
+ (asset-file-name asset-id asset-type)
|
|
|
+ payload)]
|
|
|
+ (mark-asset-write-finish! repo asset-id)
|
|
|
+ nil))
|
|
|
+
|
|
|
(defn upload-remote-asset!
|
|
|
[repo graph-id asset-uuid asset-type checksum]
|
|
|
(let [base (sync-auth/http-base-url @worker-state/*db-sync-config)]
|
|
|
(if (and (seq base) (seq graph-id) (seq asset-type) (seq checksum))
|
|
|
- (p/let [exported-aes-key (exported-graph-aes-key
|
|
|
- repo graph-id
|
|
|
- (fn [tag data]
|
|
|
- (throw (ex-info (name tag) data))))]
|
|
|
- (worker-state/<invoke-main-thread :thread-api/rtc-upload-asset
|
|
|
- repo exported-aes-key (str asset-uuid) asset-type checksum
|
|
|
- (sync-large-title/asset-url base graph-id (str asset-uuid) asset-type)
|
|
|
- {:extra-headers (sync-auth/auth-headers (worker-state/get-id-token))}))
|
|
|
+ (-> (p/let [aes-key (graph-aes-key
|
|
|
+ repo graph-id
|
|
|
+ (fn [tag data]
|
|
|
+ (throw (ex-info (name tag) data))) )
|
|
|
+ asset-id (str asset-uuid)
|
|
|
+ put-url (sync-large-title/asset-url base graph-id asset-id asset-type)
|
|
|
+ asset-file (try
|
|
|
+ (<read-asset-bytes repo asset-id asset-type)
|
|
|
+ (catch :default e
|
|
|
+ (log/info :read-asset e)
|
|
|
+ (throw (ex-info "read-asset failed"
|
|
|
+ {:type :rtc.exception/read-asset-failed}
|
|
|
+ e))))
|
|
|
+ asset-file (if aes-key (->uint8 asset-file) asset-file)
|
|
|
+ payload (if (not aes-key)
|
|
|
+ asset-file
|
|
|
+ (ldb/write-transit-str
|
|
|
+ (crypt/<encrypt-uint8array aes-key asset-file)))
|
|
|
+ total (payload-size payload)
|
|
|
+ _ (notify-asset-progress! repo asset-id :upload 0 total)
|
|
|
+ headers (merge (sync-auth/auth-headers (worker-state/get-id-token))
|
|
|
+ {"x-amz-meta-checksum" checksum
|
|
|
+ "x-amz-meta-type" asset-type})
|
|
|
+ ^js resp (js/fetch put-url
|
|
|
+ (clj->js {:method "PUT"
|
|
|
+ :headers headers
|
|
|
+ :body payload}))
|
|
|
+ status (.-status resp)
|
|
|
+ _ (notify-asset-progress! repo asset-id :upload total total)]
|
|
|
+ (when-not (.-ok resp)
|
|
|
+ (throw (ex-info "upload-asset failed"
|
|
|
+ {:type :rtc.exception/upload-asset-failed
|
|
|
+ :data {:status status}})))
|
|
|
+ nil)
|
|
|
+ (p/catch
|
|
|
+ (fn [e]
|
|
|
+ (if (contains? #{:rtc.exception/read-asset-failed
|
|
|
+ :rtc.exception/upload-asset-failed}
|
|
|
+ (:type (ex-data e)))
|
|
|
+ (p/rejected e)
|
|
|
+ (p/rejected (ex-info "upload-asset failed"
|
|
|
+ {:type :rtc.exception/upload-asset-failed}
|
|
|
+ e))))))
|
|
|
(p/rejected (ex-info "missing asset upload info"
|
|
|
{:repo repo
|
|
|
:asset-uuid asset-uuid
|
|
|
@@ -166,18 +277,60 @@
|
|
|
:broadcast-rtc-state!-f broadcast-rtc-state!-f
|
|
|
:fail-fast-f fail-fast-f})))
|
|
|
|
|
|
+(defn- parse-content-length
|
|
|
+ [^js resp]
|
|
|
+ (when-let [content-length (some-> (.-headers resp) (.get "content-length"))]
|
|
|
+ (let [length (js/parseInt content-length 10)]
|
|
|
+ (when (not (js/isNaN length))
|
|
|
+ length))))
|
|
|
+
|
|
|
(defn download-remote-asset!
|
|
|
[repo graph-id asset-uuid asset-type]
|
|
|
(let [base (sync-auth/http-base-url @worker-state/*db-sync-config)]
|
|
|
(if (and (seq base) (seq graph-id) (seq asset-type))
|
|
|
- (p/let [exported-aes-key (exported-graph-aes-key
|
|
|
- repo graph-id
|
|
|
- (fn [tag data]
|
|
|
- (throw (ex-info (name tag) data))))]
|
|
|
- (worker-state/<invoke-main-thread :thread-api/rtc-download-asset
|
|
|
- repo exported-aes-key (str asset-uuid) asset-type
|
|
|
- (sync-large-title/asset-url base graph-id (str asset-uuid) asset-type)
|
|
|
- {:extra-headers (sync-auth/auth-headers (worker-state/get-id-token))}))
|
|
|
+ (-> (p/let [aes-key (graph-aes-key
|
|
|
+ repo graph-id
|
|
|
+ (fn [tag data]
|
|
|
+ (throw (ex-info (name tag) data))))
|
|
|
+ asset-id (str asset-uuid)
|
|
|
+ get-url (sync-large-title/asset-url base graph-id asset-id asset-type)
|
|
|
+ headers (sync-auth/auth-headers (worker-state/get-id-token))
|
|
|
+ request-opts (cond-> {:method "GET"}
|
|
|
+ (seq headers) (assoc :headers headers))
|
|
|
+ ^js resp (js/fetch get-url
|
|
|
+ (clj->js request-opts))
|
|
|
+ status (.-status resp)
|
|
|
+ _ (when-not (.-ok resp)
|
|
|
+ (throw (ex-info "download asset failed"
|
|
|
+ {:type :rtc.exception/download-asset-failed
|
|
|
+ :data {:status status}})))
|
|
|
+ total (or (parse-content-length resp) 0)
|
|
|
+ _ (notify-asset-progress! repo asset-id :download 0 total)
|
|
|
+ body (.arrayBuffer resp)
|
|
|
+ body-size (.-byteLength body)
|
|
|
+ total' (if (pos? total) total body-size)
|
|
|
+ _ (notify-asset-progress! repo asset-id :download body-size total')
|
|
|
+ asset-file
|
|
|
+ (if (not aes-key)
|
|
|
+ body
|
|
|
+ (try
|
|
|
+ (let [asset-file-untransited (ldb/read-transit-str (.decode (js/TextDecoder.) body))]
|
|
|
+ (crypt/<decrypt-uint8array aes-key asset-file-untransited))
|
|
|
+ (catch js/SyntaxError _
|
|
|
+ body)
|
|
|
+ (catch :default e
|
|
|
+ ;; if decrypt failed, write origin-body
|
|
|
+ (if (= "decrypt-uint8array" (ex-message e))
|
|
|
+ body
|
|
|
+ (throw e)))))]
|
|
|
+ (<write-asset-bytes! repo asset-id asset-type asset-file))
|
|
|
+ (p/catch
|
|
|
+ (fn [e]
|
|
|
+ (if (= :rtc.exception/download-asset-failed (:type (ex-data e)))
|
|
|
+ (p/rejected e)
|
|
|
+ (p/rejected (ex-info "download asset failed"
|
|
|
+ {:type :rtc.exception/download-asset-failed}
|
|
|
+ e))))))
|
|
|
(p/rejected (ex-info "missing asset download info"
|
|
|
{:repo repo
|
|
|
:asset-uuid asset-uuid
|
|
|
@@ -193,22 +346,28 @@
|
|
|
client
|
|
|
#(when-let [conn (worker-state/get-datascript-conn repo)]
|
|
|
(when-let [ent (d/entity @conn [:block/uuid asset-uuid])]
|
|
|
- (let [asset-type (:logseq.property.asset/type ent)]
|
|
|
- (-> (p/let [meta (when (seq asset-type)
|
|
|
- (worker-state/<invoke-main-thread
|
|
|
- :thread-api/get-asset-file-metadata
|
|
|
- repo (str asset-uuid) asset-type))]
|
|
|
- (when (and (seq asset-type)
|
|
|
- (:logseq.property.asset/remote-metadata ent)
|
|
|
- (nil? meta))
|
|
|
- (p/let [_ (download-remote-asset! repo graph-id asset-uuid asset-type)]
|
|
|
- (when (d/entity @conn [:block/uuid asset-uuid])
|
|
|
- (ldb/transact!
|
|
|
- conn
|
|
|
- [{:block/uuid asset-uuid
|
|
|
- :logseq.property.asset/remote-metadata nil}]
|
|
|
- {:persist-op? true}))
|
|
|
- (client-op/remove-asset-op repo asset-uuid)
|
|
|
- (broadcast-rtc-state!-f client))))
|
|
|
+ (let [asset-type (:logseq.property.asset/type ent)
|
|
|
+ asset-id (str asset-uuid)
|
|
|
+ should-download? (and (seq asset-type)
|
|
|
+ (:logseq.property.asset/remote-metadata ent))]
|
|
|
+ (-> (p/let [meta (when should-download?
|
|
|
+ (platform/asset-stat (platform/current)
|
|
|
+ repo
|
|
|
+ (asset-file-name asset-id asset-type)))
|
|
|
+ missing-local? (and should-download? (nil? meta))
|
|
|
+ _ (when missing-local?
|
|
|
+ (download-remote-asset! repo graph-id asset-uuid asset-type))
|
|
|
+ _ (when missing-local?
|
|
|
+ (when-let [target-ent (d/entity @conn [:block/uuid asset-uuid])]
|
|
|
+ (ldb/transact!
|
|
|
+ conn
|
|
|
+ [[:db/retract (:db/id target-ent)
|
|
|
+ :logseq.property.asset/remote-metadata]]
|
|
|
+ {:persist-op? true})))
|
|
|
+ _ (when missing-local?
|
|
|
+ (client-op/remove-asset-op repo asset-uuid))
|
|
|
+ _ (when missing-local?
|
|
|
+ (broadcast-rtc-state!-f client))]
|
|
|
+ nil)
|
|
|
(p/catch (fn [e]
|
|
|
(js/console.error e)))))))))))
|