Refactor png generation

next
Marcin Kulik 8 years ago
parent 0b6ef2492a
commit 137ee108c5

@ -4,13 +4,14 @@
:ragtime #var duct.component.ragtime/ragtime
:file-store #var asciinema.component.s3-file-store/s3-file-store
:exp-set #var asciinema.component.redis-client/redis-client
:png-gen #var asciinema.component.a2png/a2png
:executor #var asciinema.component.fixed-thread-executor/fixed-thread-executor}
:endpoints
{:asciicasts #var asciinema.endpoint.asciicasts/asciicasts-endpoint}
:dependencies
{:http {:app :asciicasts}
:ragtime [:db]
:asciicasts [:db :file-store :exp-set :executor]}
:asciicasts [:db :file-store :exp-set :executor :png-gen]}
:config
{:http
{:port http-port}
@ -26,6 +27,8 @@
:exp-set
{:host redis-host
:port redis-port}
:png-gen
{:bin-path "a2png/a2png.sh"}
:executor
{:threads 1
:queue-length 1}}}

@ -0,0 +1,4 @@
(ns asciinema.boundary.png-generator)
(defprotocol PngGenerator
(generate [this json-is png-params]))

@ -0,0 +1,30 @@
(ns asciinema.component.a2png
(:require [asciinema.boundary.png-generator :as png-generator]
[asciinema.util.io :refer [cleanup-input-stream create-tmp-dir]]
[clojure.java
[io :as io]
[shell :as shell]]))
(defn- exec-a2png [bin-path in-url out-path {:keys [snapshot-at theme scale]}]
(let [{:keys [exit] :as result} (shell/sh bin-path
"-t" theme
"-s" (str scale)
in-url
out-path
(str snapshot-at))]
(when-not (zero? exit)
(throw (ex-info "a2png error" result)))))
(defrecord A2png [bin-path]
png-generator/PngGenerator
(generate [this json-is png-params]
(let [dir (create-tmp-dir "a2png-")
cleanup #(shell/sh "rm" "-rf" (.getPath dir))
json-local-path (str dir "/asciicast.json")
png-local-path (str dir "/asciicast.png")]
(io/copy json-is (io/file json-local-path))
(exec-a2png bin-path json-local-path png-local-path png-params)
(cleanup-input-stream (io/input-stream png-local-path) cleanup))))
(defn a2png [{:keys [bin-path]}]
(->A2png bin-path))

@ -4,46 +4,16 @@
[executor :as executor]
[expiring-set :as exp-set]
[file-store :as fstore]
[png-generator :as png]
[user-database :as udb]]
[asciinema.model.asciicast :as asciicast]
[asciinema.util.io :refer [with-tmp-dir]]
[asciinema.yada :refer [resource not-found-model]]
[asciinema.yada :refer [not-found-model resource]]
[clj-time.core :as t]
[clojure.java
[io :as io]
[shell :as shell]]
[environ.core :refer [env]]
[schema.core :as s]
[yada.yada :as yada]))
(def Theme (apply s/enum asciicast/themes))
(def png-ttl-days 7)
(defn- a2png [in-url out-path {:keys [snapshot-at theme scale]}]
(let [a2png-bin (:a2png-bin env "a2png/a2png.sh")
{:keys [exit] :as result} (shell/sh a2png-bin
"-t" theme
"-s" (str scale)
in-url
out-path
(str snapshot-at))]
(when-not (zero? exit)
(throw (ex-info "a2png error" result)))))
(defn- generate-png [file-store exp-set asciicast png-params png-store-path]
(with-tmp-dir [dir "asciinema-png-"]
(let [json-store-path (asciicast/json-store-path asciicast)
json-local-path (str dir "/asciicast.json")
png-local-path (str dir "/asciicast.png")
expires (-> png-ttl-days t/days t/from-now)]
(with-open [in (fstore/input-stream file-store json-store-path)]
(let [out (io/file json-local-path)]
(io/copy in out)))
(a2png json-local-path png-local-path png-params)
(fstore/put-file file-store (io/file png-local-path) png-store-path)
(exp-set/conj! exp-set png-store-path expires))))
(defn- service-unavailable-response [ctx]
(-> (:response ctx)
(assoc :status 503)
@ -69,36 +39,52 @@
filename (str "asciicast-" (:id asciicast) ".json")]
(fstore/serve-file file-store ctx path (when dl {:filename filename}))))}))
(defn asciicast-png-resource [db file-store exp-set executor]
(def png-ttl-days 7)
(defn asciicast-png-resource [db file-store exp-set executor png-gen]
(resource
{:produces "image/png"
:parameters {:path {:token String}
:query {(s/optional-key :time) s/Num
(s/optional-key :theme) Theme
(s/optional-key :scale) (s/enum "1" "2")}}
:properties (fn [ctx]
(if-let [asciicast (adb/get-asciicast-by-token db (-> ctx :parameters :path :token))]
(let [user (udb/get-user-by-id db (:user_id asciicast))
{:keys [time theme scale]} (-> ctx :parameters :query)
png-params (cond-> (asciicast/png-params asciicast user)
time (assoc :snapshot-at time)
theme (assoc :theme theme)
scale (assoc :scale (Integer/parseInt scale)))]
{:version (asciicast/png-version asciicast png-params)
::asciicast asciicast
::png-params png-params})
{:exists? false}))
:response (fn [ctx]
(let [asciicast (-> ctx :properties ::asciicast)
png-params (-> ctx :properties ::png-params)
png-store-path (asciicast/png-store-path asciicast png-params)]
(if (exp-set/contains? exp-set png-store-path)
(fstore/serve-file file-store ctx png-store-path {})
(async-response ctx executor (fn []
(generate-png file-store exp-set asciicast png-params png-store-path)
(fstore/serve-file file-store ctx png-store-path {}))))))}))
{:produces
"image/png"
:parameters
{:path {:token String}
:query {(s/optional-key :time) s/Num
(s/optional-key :theme) Theme
(s/optional-key :scale) (s/enum "1" "2")}}
:properties
(fn [ctx]
(if-let [asciicast (adb/get-asciicast-by-token db (-> ctx :parameters :path :token))]
(let [user (udb/get-user-by-id db (:user_id asciicast))
{:keys [time theme scale]} (-> ctx :parameters :query)
png-params (cond-> (asciicast/png-params asciicast user)
time (assoc :snapshot-at time)
theme (assoc :theme theme)
scale (assoc :scale (Integer/parseInt scale)))]
{:version (asciicast/png-version asciicast png-params)
::asciicast asciicast
::png-params png-params})
{:exists? false}))
:response
(fn [ctx]
(let [asciicast (-> ctx :properties ::asciicast)
png-params (-> ctx :properties ::png-params)
png-store-path (asciicast/png-store-path asciicast png-params)
expires (-> png-ttl-days t/days t/from-now)]
(if (exp-set/contains? exp-set png-store-path)
(fstore/serve-file file-store ctx png-store-path {})
(async-response ctx
executor
(fn []
(let [json-store-path (asciicast/json-store-path asciicast)]
(with-open [json-is (fstore/input-stream file-store json-store-path)
png-is (png/generate png-gen json-is png-params)]
(fstore/put-file file-store png-is png-store-path)))
(exp-set/conj! exp-set png-store-path expires)
(fstore/serve-file file-store ctx png-store-path {}))))))}))
(defn asciicasts-endpoint [{:keys [db file-store exp-set executor]}]
(defn asciicasts-endpoint [{:keys [db file-store exp-set executor png-gen]}]
["" [["/a/" [[[:token ".json"] (asciicast-json-resource db file-store)]
[[:token ".png"] (asciicast-png-resource db file-store exp-set executor)]]]
[[:token ".png"] (asciicast-png-resource db file-store exp-set executor png-gen)]]]
[true (yada/resource not-found-model)]]])

@ -1,7 +1,8 @@
(ns asciinema.util.io
(:require [clojure.java.shell :as shell])
(:import java.nio.file.Files
java.nio.file.attribute.FileAttribute))
(:import java.io.FilterInputStream
java.nio.file.attribute.FileAttribute
java.nio.file.Files))
(defn create-tmp-dir [prefix]
(let [dir (Files/createTempDirectory prefix (into-array FileAttribute []))]
@ -13,3 +14,9 @@
~@body
(finally
(shell/sh "rm" "-rf" (.getPath ~sym))))))
(defn cleanup-input-stream [is cleanup]
(proxy [FilterInputStream] [is]
(close []
(proxy-super close)
(cleanup))))

Loading…
Cancel
Save