summaryrefslogtreecommitdiff
path: root/src/app/portmaster.clj
blob: 3fb57e2bf0ce7ab25af56d4093dac152f45a16a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
(ns app.portmaster
  (:import java.io.File
           (java.util.concurrent Executors TimeUnit)
           net.sf.expectit.ExpectBuilder
           net.sf.expectit.matcher.Matchers))

(def prompt (Matchers/contains "pm>"))
(def the-unexpected (Matchers/anyString)) ; I couldn't help myself

(def pm (agent nil))

(defn parse-status
  [s]
  (map rest (re-seq #" ([0-9]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)" s)))

(defn parse-current
  [s]
  (map rest (re-seq #"#([0-9]+).* ([0-9.]+)A.* ([0-9.]+)A" s)))

(defn login-maybe
  [expect username password]
  ; flush input
  (try
    (.. expect
      (withTimeout 1 TimeUnit/MILLISECONDS)
      (expect the-unexpected))
    (catch Exception e))
  (let [matchers (into-array [(Matchers/contains "Username:")
                              (Matchers/contains "Enter choice (1 or 2):")
                              prompt])
        results (.getResults (.. expect
                               (withTimeout 100 TimeUnit/MILLISECONDS)
                               (sendLine)
                               (expect (Matchers/anyOf matchers))))]
    (when (.isSuccessful (first results))
      (.. expect
        (sendLine username)
        (expect (Matchers/contains "Password:")))
      (.. expect
        (sendLine password)
        (expect prompt)))
    (when (.isSuccessful (second results))
      (.. expect
        (withTimeout 5000 TimeUnit/MILLISECONDS)
        (sendLine "2")
        (expect (Matchers/contains "Username:")))
      (login-maybe expect username password))))

(defn do-command
  ([expect command]
   (.. expect (sendLine command) (expect prompt)))
  ([command]
   (send pm (fn [{:keys [expect] :as state}]
              (do-command expect command)
              state))))

(defn poll
  [{:keys [expect] :as state} username password]
  (try
    (login-maybe expect username password)
    (let [result (do-command expect "status all")
          status (parse-status (.getBefore result))
          state (if (nil? status)
                  state
                  (assoc state :status status))
          result (do-command expect "current")
          current (parse-current (.getBefore result))
          state (if (nil? current)
                  state
                  (assoc state :current current))]
      state)
    (catch Exception e
      (println "poll failed")
      (println (.getMessage e))
      state)))

(defn turn-on
  [port]
  ; TODO: updated the status
  (do-command (str "on " port)))

(defn turn-off
  [port]
  (do-command (str "off " port)))

(defn init
  [port-name username password]
  (let [socat (.. (ProcessBuilder.
                    (into-array ["socat"
                                 "-v"
                                 (str "OPEN:" port-name ",b9600,raw")
                                 "-"]))
                (redirectError (File. "/tmp/portmaster.log"))
                (start))
        expect (.. (new ExpectBuilder)
                 (withInputs (into-array [(.getInputStream socat)]))
                 (withOutput (.getOutputStream socat))
                 (withTimeout 1000 TimeUnit/MILLISECONDS)
                 (withExceptionOnFailure)
                 (build))
        executor (Executors/newSingleThreadScheduledExecutor)]
    (send pm assoc :socat socat :expect expect)
    (.scheduleAtFixedRate executor
                          #(send pm poll username password)
                          2
                          10
                          TimeUnit/SECONDS)))