Initial code checkin, untested
This commit is contained in:
parent
00861c6d6c
commit
5b62a20bba
|
@ -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)))))
|
||||||
|
|
Loading…
Reference in New Issue