Initial code checkin, untested

This commit is contained in:
niten 2023-04-11 08:01:31 -07:00
parent 00861c6d6c
commit 5b62a20bba
1 changed files with 209 additions and 1 deletions

View File

@ -1,2 +1,210 @@
(ns suanni.syno-eyes (ns suanni.syno-eyes
(:require [suanni.eyes :refer [SuanniEyes]])) (:require [suanni.eyes :refer [SuanniEyes]]
[fudo-clojure.http.client :as client]
[fudo-clojure.http.request :as req]
[fudo-clojure.logging :as log]
[fudo-clojure.result :as result]
[clojure.string :as str])
(:import java.net.InetAddress))
;; ### BaseSynoClient
;;
;; In order to fully initialize the SynoClient, we need to be able to query the
;; Synology host to get path/version information and to authenticate. This base
;; client will have enough functionality to do that. Calling `authenticate!` on
;; the base client will actually perform the queries necessary to initialize
;; full functionality.
(defprotocol IBaseSynoClient
(get! [_ req])
(initialize! [_ username passwd]))
;; ### SynoClient
;;
;; The SynoClient is the client that is actually able to do things like list
;; cameras and take snapshots.
(defprotocol ISynoClient
(disconnect! [_])
(camera-snapshot! [_ camera-id])
(list-cameras! [_])
(get-camera-by-location! [_ loc]))
(defprotocol ICamera
(id [_])
(location [_])
(vendor [_])
(model [_])
(host [_])
(port [_])
(take-snapshot! [_]))
(defrecord Camera [conn data]
ICamera
(id [_] (:id data))
(location [_] (-> data :newName keyword))
(vendor [_] (-> data :vendor))
(model [_] (-> data :model))
(host [_] (-> data :ip))
(port [_] (-> data :port))
(take-snapshot! [self] (camera-snapshot! conn (id self))))
(defn- perform-request! [http-client req]
(result/bind (client/execute-request! http-client req)
(fn [resp]
(if (:error resp)
(throw (ex-info "error performing request"
{:request req
:error (:error resp)
:response resp}))
(cond (:data resp) (:data resp)
(:body resp) (:body resp))))))
(defn- get-hostname []
(-> (InetAddress/getLocalHost)
(.getHostName)))
(defn- make-api-info-request
[{api :api}]
(-> (req/base-request)
(req/with-path "/webapi/query.cgi")
(req/withQueryParams
{
:api :SYNO.API.Info
:method :Query
:version 1
:query api
})))
(defn- make-auth-request
[{max-version :maxVersion
path :path
account :account
passwd :passwd}]
(-> (req/base-request)
(req/with-path (format "/webapi/%s" path))
(req/with-query-params
{
:version max-version
:session :SurveillanceStation
:api :SYNO.API.Auth
:method :login
:account account
:passwd passwd
:format :sid
:enable_device_token true
:device_name (get-hostname)
})))
(defn- make-logout-request
[{max-version :maxVersion path :path}]
(-> (req/base-request)
(req/with-path (format "/webapi/%s" path))
(req/withQueryParams
{
:version max-version
:session :SurveillanceStation
:api :SYNO.API.Auth
:method :Logout
})))
(defn- make-list-cameras-request [{max-version :maxVersion path :path}]
(-> (req/base-request)
(req/with-path (format "webapi/%s" path))
(req/with-query-params
{
:version max-version
:session :SurveillanceStation
:api :SYNO.SurveillanceStation.Camera
:method :List
})))
(defn- make-snapshot-request
[{max-version :maxVersion path :path} camera-id]
(-> (req/base-request)
(req/with-path (format "/webapi/%s" path))
(req/with-response-format :binary)
(req/with-option :as :byte-array)
(req/withQueryParams
{
:version max-version
:session :SurveillanceStation
:api :SYNO.SurveillanceStation.Camera
:method :GetSnapshot
:id camera-id
})))
(defn- get-api-info! [conn api]
(some-> conn
(get! (make-api-info-request {:api api}))
api))
(defn- authenticate! [conn username passwd]
(->> (get-api-info! conn :SYNO.API.Auth)
(merge {:username username :passwd passwd})
(make-auth-request)
(get! conn)))
(defn- find-first [pred lst]
(loop [els lst]
(if (pred (first els))
(first els)
(recur (rest els)))))
(defrecord SynoClient [conn auth-info api-info logger]
IBaseSynoClient
(get! [_ req]
(get! conn
(-> req
(req/with-query-params
{:device_id (:device_id auth-info)
:_sid (:sid auth-info)}))))
(initialize! [_ _ _]
(throw (ex-info "client already initialized!" {})))
ISynoClient
(disconnect! [self]
(->> (:SYNO.API.Auth api-info)
(make-logout-request)
(get! self)))
(camera-snapshot! [self camera-id]
(log/info! logger (format "fetching snapshot from camera %s" camera-id))
(get! self (make-snapshot-request (:SYNO.SurveillanceStation.Camera api-info) camera-id)))
(list-cameras! [self]
(log/info! logger "fetching camera list")
(let [cams (into []
(map (partial ->Camera self))
(-> self
(get! (make-list-cameras-request
(:SYNO.SurveillanceStation.Camera api-info)))
:cameras))]
(log/info! logger (format "fetched %s cameras" (count cams)))
cams))
(get-camera-by-location! [self loc]
(->> (list-cameras! self)
(find-first (fn [cam] (= loc (location cam)))))))
(defn- initialize-connection!
[conn & {username :username passwd :passwd logger :logger}]
(let [api-info (into {} (map (fn [api] [api (get-api-info! conn api)]))
[:SYNO.SurveillanceStation.Camera
:SYNO.API.Auth])
auth-info (authenticate! conn username passwd)]
(->SynoClient conn auth-info api-info logger)))
(defn create [& {host :host port :port logger :logger}]
(let [http-client (client/json-client)]
(reify
IBaseSynoClient
(get! [_ req]
(perform-request! http-client
(-> req
(req/with-host host)
(req/with-port port)
(req/with-option :insecure? true)
(req/as-get))))
(initialize! [self username passwd]
(initialize-connection! self
:username username
:passwd passwd
:logger logger)))))