videocapture.settings - Videocapture

Table of Contents

1 Namespace videocapture.settings

2 Settings format

2.1 settings.edn

2.1.1 Overview

Name   Description Generate?
:settings/smb   Access and location data for data storage on an SMB server.  
:settings/form   Data to pre-configure the GUI's input fields.  
:settings/gui   Information about which frontend to use.  
:settings/configuration   The config.edn configuration to be used.  
:settings/camera   The camera configuration.  
:settings/supervisor   Configuration settings that describe/configure the supervisor.  

2.1.2 SMB Settings

Key: :smb

Name Format Description Generate?
:settings-smb/username string? The SMB server username x
:settings-smb/password string? The SMB server password x
:settings-smb/dns string? The SMB server address x
:settings-smb/directory string? The directory on the server x

2.1.3 Configuration

Key: :configuration

(s/def :settings/configuration keyword?)

2.1.4 Form

Key: :form

Name Format Description Generate? Optional
:settings-form/room string?   x  
:settings-form/groups (s/coll-of string?)   x x

2.1.5 GUI

Key: :gui

(s/def :settings/gui string?)

2.1.6 Camera

Key: :camera

V4L2 Camera

Name Format Description Generate?
:settings-camera-device/address string? The file name of the device, e.g. /dev/video0. x

RTSP Stream

Name Format Description Generate?
:settings-camera-rtsp/address string?   x
:settings-camera-rtsp/username string?   x
:settings-camera-rtsp/password string?   x
(s/def :settings-camera/type #{:device :rtsp})

<<type-from-table(table=settings-camera-v4l2-type)>>

<<type-from-table(table=settings-camera-rtsp-type)>>

(defmulti camera-type :type)
(defmethod camera-type :device [_]
  (s/keys :req-un [:settings-camera-device/address]))
(defmethod camera-type :rtsp [_]
  (s/keys :req-un [:settings-camera-rtsp/address
		   :settings-camera-rtsp/username
		   :settings-camera-rtsp/password]))

(s/def :settings/camera (s/coll-of (s/multi-spec camera-type :settings-camera/type)))

2.1.7 Supervisor

Key: :supervisor

Name Format Description Generate?
:settings-supervisor/ip string? A valid IPv4 addressing the supervisor. x
:settings-supervisor/port int? The network port to be used. x
:settings-supervisor/activated boolean? Value true means: this instance is the supervisor. x

2.1.8 An example settings.edn

{:smb {:username "wisc"
       :password "secret"
       :dns "wiscserver.unibe.ch"
       :directory "videoupload"}
 :configuration :wisc
 :form {:room "A001"
	:groups ["A" "B" "C" "D" "E" "F" "G" "H"]}
 :gui "wisc-mainwindow.fxml"
 :camera [{:type :device :address "/dev/video0"}]
 :supervisor {:ip ""
	      :port 9898
	      :activated false}}

3 Settingskeeping

(defn get-config-key [settings]
  {:post [(keyword? %)]}
  (get-in @settings [:configuration]))

(defn get-setting
  "Fetches a setting from the combined settings structure. The keyword `:C' gets replaced by the value of the `:configuration' variable in settings.edn."
  [top & levels]
  {:pre [(keyword? top)]}
  (let [fst (if (= top :C) [:configs (get-config-key (:settings @settings))] [top])
	{:keys [settings config]} @settings
	result (or (get-in @settings (into fst levels))
		   (get-in @config (into fst levels)))]
    (trace fst levels " => " result)
    result))

(defn update-setting "Replaces a setting with a temporary value."
  [keys value]
  (debug keys " => " value)
  (swap! (:settings @settings) update-in keys (fn [_] value)))

(defn init-settings []
  (let [confdir (if (str/blank? (System/getenv "XDG_CONFIG_HOME")) "" (str (System/getenv "XDG_CONFIG_HOME") "/"))
	settings (atom (s/conform ::settings (read-string (Files/readString (.toPath (io/file (str confdir "settings.edn")))))))
	config (read-string (Files/readString (.toPath (io/file (str confdir "config.edn")))))]
    (swap! settings assoc :xdg {:config confdir :home (str (System/getenv "HOME") "/videocapture/")})
    (debug "Checking settings validity:" (s/valid? ::settings @settings))
    (debug "Checking settings validity, smb:" (s/valid? :settings/smb (:smb @settings)))
    (debug "Checking settings validity, configuration:" (s/valid? :settings/configuration (:configuration @settings)))
    (debug "Checking settings validity, form:" (s/valid? :settings/form (:form @settings)))
    (debug "Checking settings validity, gui:" (s/valid? :settings/gui (:gui @settings)))
    (debug "Checking settings validity, camera:" (s/valid? :settings/camera (:camera @settings)))
    (debug "Checking settings validity, supervisor:" (s/valid? :settings/supervisor (:supervisor @settings)))
    (when (= @settings :clojure.spec.alpha/invalid)
      (fatal "The settings file does not conform to the spec; terminating.")
      (gui-util/alert :error "Inkorrekte Einstellungsdatei"
		      "Die Einstellungsdatei \"settings.edn\" entspricht nicht der Spezifikation. Details entnehmen Sie der Logdatei. Das Programm wird jetzt beendet.")
      (mount/stop)
      (System/exit 0))
    {:settings settings
     :config (atom config)}))

(defn destruct-settings [settings]
  nil)

(mount/defstate settings
  :start (init-settings)
  :stop (destruct-settings settings))

4 Settings window

4.1 Initialization

(defn init-config-list [instance]
  (let [config-box ^ChoiceBox (.-configBox instance)
	config-choices (keys (get-setting :configs))]
    (debug config-choices)
    (.addAll (.getItems config-box) (into-array String (map name config-choices)))
    (.setValue config-box (name (get-setting :configuration)))))

(defn init-group-list [instance]
  (.setText (.-roomNumber instance) (get-setting :form :room))
  (let [group-list ^ListView (.-groupList instance)]
    (.setSelectionMode (.getSelectionModel group-list) SelectionMode/SINGLE)
    (.addAll (.getItems group-list) (into-array String (get-setting :form :groups)))
    (.setOnMouseClicked group-list (fx/fi javafx.event.EventHandler [event]
					  (.setText ^TextField (.-groupInput instance)
						    (-> group-list .getSelectionModel .getSelectedItem))))))

(defn init-camera-pane [instance]
  (.setText (.-camType instance) (name (get-setting :camera :type)))
  (.setText (.-camAddress instance) (get-setting :camera :address))
  (.setText (.-camUsername instance) (get-setting :camera :username))
  (.setText (.-camPassword instance) (get-setting :camera :password)))

(defn init-server-pane [instance]
  (.setText (.-smbServer instance) (get-setting :smb :dns))
  (.setText (.-smbDir instance) (get-setting :smb :directory))
  (.setText (.-smbUser instance) (get-setting :smb :username))
  (.setText (.-smbPassword instance) (get-setting :smb :password)))

(defn init-supervisor-pane [instance]
  (if (get-setting :clients :activated)
    (do (.setText (.-supervisorState instance) "Aktiviert")
	(.setSelected (.-supervisorState instance) true))
    (do (.setText (.-supervisorState instance) "Deaktiviert")
	(.setSelected (.-supervisorState instance) false)))
  (.setOnAction (.-supervisorState instance) (fx/fi javafx.event.EventHandler [event]
						    (if (= (.getText (.-supervisorState instance)) "Aktiviert")
						      (.setText (.-supervisorState instance) "Deaktiviert")
						      (.setText (.-supervisorState instance) "Aktiviert"))))

  (let [source-list ^ListView (.-sourceList instance)]
    (.setSelectionMode (.getSelectionModel source-list) SelectionMode/SINGLE)
    (.addAll (.getItems source-list) (into-array String (get-setting :supervisor :videosources)))
    (.setOnMouseClicked source-list (fx/fi javafx.event.EventHandler [event]
					   (.setText ^TextField (.-sourceInput instance)
						     (-> source-list .getSelectionModel .getSelectedItem))))))

(defn stage-init [instance]

  (init-config-list instance)
  (init-group-list instance)
  (init-camera-pane instance)
  (init-supervisor-pane instance)
  (init-server-pane instance)
  )

(defn init "This initializes the settings window." [^Window parent]
  (let [stage (new Stage)
	content (fxml/load-fxml-with-controller (io/resource "fxml/settings.fxml") "videocapture.settings/stage-init")
	scene (new javafx.scene.Scene content 1024 768)]
    (.add (.getStylesheets scene) (.toExternalForm (io/resource "css/gui.css")))
    (.initOwner stage parent)
    (.initModality stage Modality/WINDOW_MODAL)
    (.setTitle stage "Einstellungen")
    (.setScene stage scene)
    (.show stage)
    stage))

4.2 Manipulation

(defn move-listview-item [listview direction]
  (let [items (.getItems listview)
	index (-> listview .getSelectionModel .getSelectedIndex)
	item (-> listview .getSelectionModel .getSelectedItem)
	new-index (if (= direction :up) (dec index) (inc index))]
    (when-not (or (= -1 new-index) (= (count items) new-index))
      (.remove items index (inc index))
      (.add items new-index item)
      (.selectIndices (.getSelectionModel listview) new-index (int-array [])))))

(defn source-add [instance event]
  (let [source-input ^TextField (.-sourceInput instance)]
    (.add (.getItems ^ListView (.-sourceList instance)) (.getText source-input))
    (.setText source-input "")))

(defn add-group [instance event]
  (let [group-input ^TextField (.-groupInput instance)]
    (.add (.getItems ^ListView (.-groupList instance)) (.getText group-input))
    (.setText group-input "")))

(defn rm-group [instance event]
  (let [group-input ^TextField (.-groupInput instance)
	item (-> instance .-groupList .getSelectionModel .getSelectedItem)]
    (.setText group-input "")
    (.remove ^List (.getItems (.-groupList instance)) ^String item)))

(defn source-del [instance event]
  (let [source-input ^TextField (.-sourceInput instance)
      item (-> instance .sourceList .getSelectionModel .getSelectedItem)]
    (.setText source-input "")
    (.remove ^List (.getItems (.-sourceList instance)) ^String item)))

(defn source-mv-up [instance event]
  (move-listview-item (.-sourceList instance) :up))

(defn source-mv-down [instance event]
  (move-listview-item (.-sourceList instance) :down))

(defn mv-up-group [instance event]
  (move-listview-item (.-groupList instance) :up))

(defn mv-down-group [instance event]
  (move-listview-item (.-groupList instance) :down))

4.3 Storage

(defn save-supervisor-pane [settings instance]
  (let [activated (.isSelected (.-supervisorState instance))
	items (.getItems (.-sourceList instance))]
    (-> settings
	(assoc-in [:supervisor :activated] activated)
	(assoc-in [:supervisor :videosources] (into [] items)))))

(defn save [instance event]
  (reset! (:settings @settings)
	  (-> @(:settings @settings)
	      (assoc-in [:form :room] (.getText (.-roomNumber instance)))
	      (assoc-in [:form :groups] (into [] (.getItems (.-groupList instance))))
	      (assoc :camera {:type (keyword (.getText (.-camType instance)))
			      :address (.getText (.-camAddress instance))
			      :username (.getText (.-camUsername instance))
			      :password (.getText (.-camPassword instance))})
	      (assoc :configuration (keyword (.getValue (.-configBox instance))))
	      (assoc :smb {:username (.getText (.-smbUser instance))
			   :password (.getText (.-smbPassword instance))
			   :dns (.getText (.-smbServer instance))
			   :directory (.getText (.-smbDir instance))})
	      (save-supervisor-pane instance)))
  (pprint/pprint @(:settings @settings) (io/writer "settings.edn"))
  (-> instance .-toplevel .getScene .getWindow .hide))

(defn cancel [instance event]
  (-> instance .-toplevel .getScene .getWindow .hide))

5 Complete namespace definition

(ns videocapture.settings
  (:require [clojure.edn :as edn]
	    [clojure.string :as str]
	    [clojure.spec.alpha :as s]
	    [clojure.java.io :as io]
	    [clojurefx.fxml :as fxml]
	    [clojure.pprint :as pprint]
	    [mount.core :as mount]
	    [clojurefx.clojurefx :as fx]
	    [taoensso.timbre :as timbre
	     :refer [log trace debug info warn error fatal report
		     logf tracef debugf infof warnf errorf fatalf reportf
		     spy get-env]]
	    [videocapture.version :as version]
	    [videocapture.gui-util :as gui-util]
	    )
  (:import (javafx.stage Stage Window Modality)
	   (javafx.scene.control ListView SelectionMode TextField ChoiceBox)
	   (java.util List)
	   (java.nio.file Files)))

(declare settings)

<<types>>

<<settings>>

<<gui-init>>
<<gui-manipulate>>
<<gui-save>>

Author: Daniel Ziltener

Created: 2019-06-21 Fr 16:22

Validate