penpot/exporter/src/app/handlers/export_frames.cljs
Elena Torró 66b4a99ac3
🎉 Implement export jobs to process export requests (#11296)
* ✨ Add export job model, store and scheduler to exporter

* ✨ Render wasm exports on pooled worker threads

* ✨ Add export job REST API to exporter

* ✨ Use export job API and allow cancelling wasm exports

* 🔧 Show export jobs in the internal debug panel

* 🔧 Pass flags and export job settings to the exporter container

* 📚 Document the exporter job API and its redis layout
2026-08-31 14:42:51 +02:00

94 lines
3.1 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns app.handlers.export-frames
(:require
[app.common.spec :as us]
[app.handlers.export-shapes :refer [count-objects headless-exports? prepare-exports]]
[app.handlers.resources :as rsc]
[app.jobs :as jobs]
[app.jobs.utils :as job.utils]
[app.renderer :as rd]
[app.util.shell :as sh]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[promesa.core :as p]))
(declare ^:private join-pdf)
(declare ^:private move-file)
(s/def ::name ::us/string)
(s/def ::file-id ::us/uuid)
(s/def ::page-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::is-wasm ::us/boolean)
(s/def ::export
(s/keys :req-un [::file-id ::page-id ::object-id ::name]))
(s/def ::exports
(s/every ::export :kind vector? :min-count 1))
(s/def ::params
(s/keys :req-un [::exports]
:opt-un [::name ::is-wasm]))
(defn- run-export
[job auth-token resource {:keys [exports is-wasm file-id]}]
(let [rendered (atom [])
on-object
(fn [{:keys [path] :as _object}]
(job.utils/track! (:id job) path)
(jobs/progress! job (count (swap! rendered conj path))))
exports
(map #(assoc % :is-wasm is-wasm :job-id (:id job)) exports)]
(job.utils/track! (:id job) (:path resource))
(->> (rd/with-scope exports
(fn [render]
(jobs/check-cancelled! job)
(->> exports
(map (fn [export] (render export on-object)))
(p/all))))
(p/fmap (fn [_] @rendered))
(p/mcat (partial join-pdf job file-id))
(p/mcat (partial move-file resource))
(p/fmap (constantly resource))
(p/mcat (partial rsc/upload-resource auth-token))
(p/mcat (fn [resource]
(->> (sh/stat (:path resource))
(p/fmap #(merge resource %)))))
(p/fmap (fn [resource] (dissoc resource :path))))))
(defn prepare
[auth-token {:keys [exports name is-wasm] :as _params}]
(let [exports (-> (map #(assoc % :type :pdf :scale 1 :suffix "") exports)
(prepare-exports auth-token is-wasm))
resource (rsc/create :pdf (or name (-> exports first :name)))
file-id (-> exports first :file-id)]
{:resource resource
:total (count-objects exports)
:headless (headless-exports? exports is-wasm)
:run (fn [job] (run-export job auth-token resource
{:exports exports
:is-wasm is-wasm
:file-id file-id}))}))
(defn- join-pdf
[job file-id paths]
(p/let [prefix (str/concat "penpot.pdfunite." file-id ".")
path (job.utils/track! (:id job) (sh/tempfile :prefix prefix :suffix ".pdf"))]
(apply sh/run-cmd! "pdfunite" (conj (vec paths) path))
path))
(defn- move-file
[{:keys [path] :as resource} output-path]
(p/do
(sh/move! output-path path)
resource))