Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions components/config/src/config/core.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(ns config.core
(:require [clojure.java.io :as io]
[clojure.edn :as edn]))
(:require [clojure.edn :as edn]
[clojure.java.io :as io]))

;;; Private vars

Expand Down Expand Up @@ -33,6 +33,11 @@
(reset! config-file new-config-file)
(reset! config-cache (read-config @config-file))))

(defn merge-config!
"Deep-merges `overrides` into the cached config."
[overrides]
(swap! config-cache (fn [c] (merge-with merge (or c (read-config @config-file)) overrides))))

(defn get-config
"Retrieves the key `k` from the config file.
Can also be called with the keys leading to a config.
Expand Down
13 changes: 8 additions & 5 deletions components/config/src/config/interface.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
(:require [config.core :as core]))

(def ^{:argslist '([] [config-file])
:doc "Re/loads a configuration file. Defaults to the last loaded file,
or config.edn."}
:doc
"Re/loads a configuration file. Defaults to the last loaded file, or config.edn."}
load-config core/load-config)

(def ^{:argslist '([& ks])
(def ^{:argslist '([overrides])
:doc "Deep-merges `overrides` into the cached config."}
merge-config! core/merge-config!)

(def ^{:argslist '([& ks])
:doc
"Retrieves the key `k` from the config file. Can also be called with the
multiple keys leading to a config.
Expand All @@ -21,6 +25,5 @@
```clojure
(get-config :mail) ; => {:host \"google.com\" :port 543}
(get-config :mail :host) ; => \"google.com\"
```"
}
```"}
get-config core/get-config)
21 changes: 13 additions & 8 deletions components/jcef/src/jcef/setup.clj
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@

(defn jcef-builder
"Produces a `CEFAppBuilder` with the installation
directory set according to the System OS."
directory set according to the System OS.

Skips installation (i.e. no Chrome/CEF download) whenever an
install directory is already present on disk. In packaged
Conveyor mode (`app.dir` is set) installation is always skipped
because the bundle ships with the app."
[]
(let [app-dir (System/getProperty "app.dir")
jcef-dir (get-jcef-dir)]
(if (nil? app-dir)
(doto (CefAppBuilder.)
(.setInstallDir jcef-dir))
(doto (CefAppBuilder.)
(.setInstallDir jcef-dir)
(.setSkipInstallation true)))))
jcef-dir (get-jcef-dir)
builder (doto (CefAppBuilder.)
(.setInstallDir jcef-dir))]
(when (or (some? app-dir)
(.exists jcef-dir))
(.setSkipInstallation builder true))
builder))
112 changes: 71 additions & 41 deletions projects/behave/src/clj/behave/core.clj
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
(ns behave.core
(:gen-class)
(:import [javax.swing JFrame SwingUtilities UIManager]
[javax.imageio ImageIO])
(:require [clojure.java.io :as io]
[behave.handlers :refer [create-cef-handler-stack]]
[behave.server :refer [init-config! init-db!]]
[file-utils.interface :refer [os-type app-data-dir]]
(:import [javax.imageio ImageIO]
[javax.swing JFrame SwingUtilities UIManager])
(:require [behave.handlers :refer [create-cef-handler-stack]]
[behave.server :as server]
[clojure.java.io :as io]
[config.interface :refer [get-config]]
[jcef.core :refer [show-dev-tools!]]
[jcef.interface :refer [create-cef-app! custom-request-handler show-loader!]]
[file-utils.interface :refer [os-type app-data-dir]]
[logging.interface :as l :refer [log-str]]))

;;; Logging
Expand Down Expand Up @@ -60,49 +58,81 @@

(defonce ^:private the-app (atom nil))

(defn -main
"CEF client start method."
[& _args]
(init-config!)
(let [loader (show-loader! "Behave7" (io/resource "public/images/android-chrome-512x512.png"))
mode (get-config :server :mode)
http-port (or (get-config :server :http-port) 8080)
org-name (get-config :site :org-name)
app-name (get-config :site :app-name)
my-app-data-dir (app-data-dir org-name app-name)
log-config (if (= "prod" mode)
(assoc (get-config :logging) :log-dir (str (io/file my-app-data-dir "logs")))
(get-config :logging))
db-config (if (= "prod" mode)
(assoc-in (get-config :database :config)
[:store :path]
(str (io/file my-app-data-dir "db.sqlite")))
(get-config :database :config))
cache-path (str (io/file my-app-data-dir ".cache"))
request-handler (custom-request-handler
{:protocol "http"
:authority (format "localhost:%s" http-port)
:resource-dir "public"
:ring-handler (create-cef-handler-stack)})]
;;; Runtime Detection

(defn- conveyor?
"True when running inside a Conveyor-packaged app (app.dir is set)."
[]
(some? (System/getProperty "app.dir")))

;;; Entry Points

(defn- start-cef!
"Start the app in JCEF desktop mode."
[]
;; Lazy-require jcef so server mode never loads jcef namespaces or
;; org.cef.* classes. Only this code path pulls in the native bundle.
(let [show-loader! (requiring-resolve 'jcef.interface/show-loader!)
create-cef-app! (requiring-resolve 'jcef.interface/create-cef-app!)
custom-request-handler (requiring-resolve 'jcef.interface/custom-request-handler)
loader (show-loader! "Behave7" (io/resource "public/images/android-chrome-512x512.png"))
mode (get-config :server :mode)
http-port (or (get-config :server :http-port) 8080)
org-name (get-config :site :org-name)
app-name (get-config :site :app-name)
my-app-data-dir (app-data-dir org-name app-name)
log-config (if (= "prod" mode)
(assoc (get-config :logging) :log-dir (str (io/file my-app-data-dir "logs")))
(get-config :logging))
db-config (if (= "prod" mode)
(assoc-in (get-config :database :config)
[:store :path]
(str (io/file my-app-data-dir "db.sqlite")))
(get-config :database :config))
cache-path (str (io/file my-app-data-dir ".cache"))
request-handler (custom-request-handler
{:protocol "http"
:authority (format "localhost:%s" http-port)
:resource-dir "public"
:ring-handler (create-cef-handler-stack)})]

(start-logging! log-config)
(init-db! db-config)
(server/init-db! db-config)

(create-cef-app!
{:title (get-config :site :title)
:url (str "http://localhost:" http-port)
:cache-path cache-path
:fullscreen? true
:on-shown (fn [app & _]
(reset! the-app app)
(.dispose (:frame loader)))
:request-handler request-handler
{:title (get-config :site :title)
:url (str "http://localhost:" http-port)
:cache-path cache-path
:fullscreen? true
:on-shown (fn [app & _]
(reset! the-app app)
(.dispose (:frame loader)))
:request-handler request-handler
:on-before-launch
(fn [{:keys [frame]}]
(on-before-launch frame (get-config :site :title)))})))

(defn -main
"Unified entry point. Detects runtime environment and starts
in CEF desktop mode or HTTP server mode."
[& _args]
(if (conveyor?)
(do
(server/init-config!)
(server/enrich-config!)
(start-cef!))
(do
(server/start-server!)
;; `server.core/start-server!` runs Jetty with `:join? false`, and
;; neither `vms-sync!` nor `watch-kill-signal!` blocks — so without
;; parking here the main thread would return and the JVM would exit
;; before any request could be served. Block until the process is
;; killed (Ctrl-C / SIGTERM).
@(promise))))

(comment
(-main)
(start-cef!)
;; Dev Tools
@the-app
(require '[jcef.core :as jc])
Expand Down
53 changes: 30 additions & 23 deletions projects/behave/src/clj/behave/handlers.clj
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
(ns behave.handlers
(:require [behave-routing.main :refer [routes]]
[behave.download-vms :refer [export-from-vms export-images-from-vms]]
[behave.init :refer [init-handler]]
[behave.init :refer [active-clients init-handler]]
[behave.open :refer [open-handler]]
[behave.save :refer [save-handler]]
[behave.sync :refer [sync-handler]]
[behave.views :refer [render-page render-tests-page]]
[bidi.bidi :refer [match-route]]
[clojure.core.async :refer [<! alts! chan go-loop put! timeout]]
[clojure.edn :as edn]
[clojure.stacktrace :as st]
[clojure.string :as str]
[config.interface :refer [get-config]]
[logging.interface :as l :refer [log-str]]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
[ring.middleware.reload :refer [wrap-reload]]
[ring.middleware.resource :refer [wrap-resource]]
[ring.util.codec :refer [url-decode]]
Expand All @@ -40,7 +39,7 @@
(inst-ms (java.util.Date.)))

(defn- kill-app!
"Use Runtime exit to kill entire JVM Process"
"Use Runtime exit to kill entire JVM Process."
[]
(.exit (Runtime/getRuntime) 0))

Expand Down Expand Up @@ -77,13 +76,18 @@
(if (= (get-config :server :mode) "prod")
(let [{:keys [cancel]} params]
(cond
(nil? cancel)
cancel
(do
(reset! close-time (now-in-ms))
(put! @kill-channel true))
(swap! active-clients inc)
(log-str [:CLIENTS :cancel-close @active-clients])
(put! @cancel-channel true))

:else
(put! @cancel-channel true))
(let [n (swap! active-clients dec)]
(log-str [:CLIENTS :close n])
(reset! close-time (now-in-ms))
(when (<= n 0)
(put! @kill-channel true))))
{:status 200 :body "OK"})
{:status 404 :body "Not Found"}))

Expand Down Expand Up @@ -113,8 +117,8 @@
(str/split #"&"))
params (reduce (fn [params keyval]
(let [[k v] (str/split keyval #"=")]
(assoc params (keyword k) (edn/read-string v))))
params keyvals)]
(assoc params (keyword k) (edn/read-string v))))
params keyvals)]
(handler (assoc req :params params))))))

(defn- wrap-params [handler]
Expand Down Expand Up @@ -167,30 +171,33 @@
(fn [request]
(handler (assoc request :figwheel? figwheel?))))

(defn server-handler-stack
"Server handler stack."
[{:keys [reload? figwheel?]}]
(defn handler-stack
"Unified handler stack. Options:
- `:cef?` Skip resource serving and multipart (CEF handles these)
- `:reload?` Enable hot-reload middleware
- `:figwheel?` Attach figwheel context to requests"
[{:keys [cef? reload? figwheel?]}]
(-> routing-handler
(wrap-figwheel figwheel?)
wrap-params
wrap-keyword-params
wrap-query-params
wrap-req-content-type+accept
(wrap-resource "public" {:allow-symlinks? true})
(wrap-content-type {:mime-types {"wasm" "application/wasm"}})
wrap-multipart-params
(optional-middleware #(wrap-resource % "public" {:allow-symlinks? true}) (not cef?))
(optional-middleware #(wrap-content-type % {:mime-types {"wasm" "application/wasm"}}) (not cef?))
(optional-middleware wrap-multipart-params (not cef?))
wrap-exceptions
(optional-middleware #(wrap-reload % {:dirs (reloadable-clj-files)}) reload?)))

(defn server-handler-stack
"Server handler stack."
[{:keys [reload? figwheel?]}]
(handler-stack {:reload? reload? :figwheel? figwheel?}))

(defn create-cef-handler-stack
"Custom handler stack for Chrome Embedded Framework."
"Handler stack for Chrome Embedded Framework."
[]
(-> routing-handler
wrap-params
wrap-keyword-params
wrap-query-params
wrap-req-content-type+accept
wrap-exceptions))
(handler-stack {:cef? true}))

;; This is for Figwheel
(def ^{:doc "Figwheel handler."}
Expand Down
17 changes: 16 additions & 1 deletion projects/behave/src/clj/behave/init.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
[transport.interface :refer [clj-> mime->type]])
(:import (java.io ByteArrayInputStream)))

;;; Client Tracking

(def active-clients
"Number of connected browser windows/tabs."
(atom 0))

(defn register-client!
"Increments the active client count. Call on each /api/init."
[]
(let [n (swap! active-clients inc)]
(log-str [:CLIENTS :register n])))

;;; Helpers

(defn- resource [s]
(.getResource (ClassLoader/getSystemClassLoader) s))

Expand All @@ -25,7 +39,8 @@
(defn init-handler [{:keys [request-method accept] :as req}]
(log-str "Request Received:" (select-keys req [:uri :request-method :params]))
(let [res-type (or (mime->type accept) :edn)]
(when (and (= request-method :get))
(when (= request-method :get)
(register-client!)
(s/release-conn!)
(reset! current-worksheet-atom nil)
(init!)
Expand Down
Loading
Loading