diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e02ed..63c0b7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,4 +5,20 @@ All notable changes to this project will be documented in this file. This change ### Changed - Add a new arity to `make-widget-async` to provide a different widget shape. -[Unreleased]: https://git.aidanis.online/aidan/whisper/compare/HEAD +## [0.1.1] - 2019-07-06 +### Changed +- Documentation on how to make the widgets. + +### Removed +- `make-widget-sync` - we're all async, all the time. + +### Fixed +- Fixed widget maker to keep working when daylight savings switches over. + +## 0.1.0 - 2019-07-06 +### Added +- Files from the new template. +- Widget maker public API - `make-widget-sync`. + +[Unreleased]: https://github.com/your-name/whisper/compare/0.1.1...HEAD +[0.1.1]: https://github.com/your-name/whisper/compare/0.1.0...0.1.1 diff --git a/README.md b/README.md index 551f72c..de9147f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,13 @@ -# Whisper +# whisper -Whisper is a client for signald. In combination, the two represent a complete replacement for the official Signal desktop client. Additionally, Whisper and Signald can be run on any mobile device supporting the Java Runtime Environment. - -**Whisper is in early stages of development.** +FIXME: description ## Installation -Currently: use lein run -TODO: package, wrap with a nice makefile +Download from http://example.com/FIXME. ## Usage -Call `lein run` in a freshly cloned repository to run Whisper. Make sure Signald is already running. - FIXME: explanation $ java -jar whisper-0.1.0-standalone.jar [args] @@ -35,7 +30,7 @@ FIXME: listing of options this app accepts. ## License -Copyright © 2019 Aidan Hahn +Copyright © 2019 FIXME This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at diff --git a/project.clj b/project.clj index cf07531..988511c 100644 --- a/project.clj +++ b/project.clj @@ -4,15 +4,9 @@ :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.0"] - [com.kohlschutter.junixsocket/junixsocket-common "2.3.2"] - [com.kohlschutter.junixsocket/junixsocket-native-common "2.3.2"] - [cljfx "1.7.10"] - [org.clojure/data.json "0.2.6"] - [org.clojure/tools.logging "1.1.0"] - [org.clojure/core.cache "1.0.207"] - [org.clojure/core.async "1.3.610"] - [clj.qrgen "0.4.0"] - [codax "1.3.1"]] + [com.kohlschutter.junixsocket/junixsocket-demo "2.2.0"] + [seesaw "1.5.0"] + [org.clojure/data.json "0.2.6"]] :main ^:skip-aot whisper.core :target-path "target/%s" :profiles {:uberjar {:aot :all}}) diff --git a/src/whisper/.#ui.clj b/src/whisper/.#ui.clj new file mode 120000 index 0000000..bb89933 --- /dev/null +++ b/src/whisper/.#ui.clj @@ -0,0 +1 @@ +mortimer@Vespucci.502:1562735217 \ No newline at end of file diff --git a/src/whisper/core.clj b/src/whisper/core.clj index d362e08..0c56039 100644 --- a/src/whisper/core.clj +++ b/src/whisper/core.clj @@ -1,137 +1,15 @@ (ns whisper.core "Whisper Namespace" - (:require [clojure.tools.logging :as log] - [clojure.data.json :as json] - [clojure.core.async :as async - :refer [ mainWindow pack! show!) + :content (reduce (fn [k v] + (apply str [k v])) + (get-next-update))))) diff --git a/src/whisper/signald.clj b/src/whisper/signald.clj index dd92e89..b8f203b 100644 --- a/src/whisper/signald.clj +++ b/src/whisper/signald.clj @@ -1,63 +1,17 @@ (ns whisper.signald - (:require [clojure.data.json :as json] - [clojure.tools.logging :as log]) + (:require [clojure.data.json :as json]) (:import (org.newsclub.net.unix AFUNIXSocket AFUNIXSocketAddress) - (java.io File))) + (java.io File BufferedReader InputStreamReader))) -(def callback-table (atom {})) +(def sigdSock (AFUNIXSocket/connectTo + (AFUNIXSocketAddress. (File. "/var/run/signald/signald.sock")))) +(def sigdRead (BufferedReader. (InputStreamReader. + (. sigdSock getInputStream)))) +(def sigdWrite (. sigdSock getOutputStream)) -(defn get-signald-sock - [filepath] - (try - (let [target (AFUNIXSocketAddress. (File. filepath))] - (log/info "Connected to signald at: " filepath) - (AFUNIXSocket/connectTo target)) - (catch Exception e - (log/error e "Caught exception connecting to socket") - nil))) +(defn get-next-update + "Retrieves and parses next line of incoming data from signald" + [] + (:data (json/read-str (.sigdRead readLine) :key-fn keyword))) -(defn assoc-callback-type - [callback-token callback] - (swap! callback-table assoc callback-token callback)) - -(defn disassoc-callback-type - [callback-token] - (swap! callback-table dissoc callback-token)) - -;; TODO wrap attempt to call callback func in try catch -(defn callback-loop - [BufferedReader] - (doall (for [line (line-seq BufferedReader)] - (let [obj (json/read-str line) - type-tok (get obj "type") - callback-func (get @callback-table type-tok)] - (log/info "Received message from signald (type: " type-tok ").") - (if (nil? callback-func) - (log/error "No callback for message type: " type-tok) - (callback-func obj)))))) - -(defn get-version-from-version-message - [version-message-obj] - (let [data (get version-message-obj "data")] - (str (get data "name") " " - (get data "version") " " - (get data "branch")))) - -(defn get-id-from-contact-or-group - [contact] - (let [c-id (get-in contact ["address" "number"]) - g-id (get-in contact ["groupId"])] - (if (nil? c-id) - (if (nil? g-id) - (log/error "Document had no contact id or group id") - g-id) - c-id))) - -;; sends data to signal socket -;; expects data to be a map -;; adds a newline -(defn make-req - [output-stream data] - (try (.write output-stream (str (json/write-str data) "\n")) (.flush output-stream) - (catch Exception e (log/error e "Error sending to signald")))) diff --git a/src/whisper/storage.clj b/src/whisper/storage.clj deleted file mode 100644 index 6aca1e1..0000000 --- a/src/whisper/storage.clj +++ /dev/null @@ -1,69 +0,0 @@ -(ns whisper.storage - "Whisper storage module" - (:require [clojure.tools.logging :as log] - [codax.core :as c])) - -(defn init-db - "opens db at filepath and makes sure schema is in place" - [filepath] - (let [db (c/open-database! filepath)] - ;; TODO: schema preperations here - db)) - -(defn init-user-schema - [db username] - (let [user-doc (c/get-at! db [username]) - user-contact-doc (get user-doc :contacts) - user-messages-doc (get user-doc :messages)] - - (if (nil? user-doc) - (c/assoc-at! db [username] {:contacts {} - :messages {}})) - (if (nil? user-contact-doc) - (c/assoc-at! db [username :contacts] {})) - - (if (nil? user-messages-doc) - (c/assoc-at! db [username :messages] {})))) - -(defn store-contact - "adds a new contact to db" - [db username contact-id contact-doc] - (let [curr-doc (c/get-at! db [username :contacts contact-id])] - (if (or (nil? curr-doc) (not= curr-doc contact-doc)) - (c/assoc-at! db [username :contacts contact-id] contact-doc))) - (if (nil? (c/get-at! db [username :messages contact-id])) - (c/assoc-at! db [username :messages contact-id] []))) - -(defn store-message - "adds a message to the db" - [db username contact-id message-doc] - (let [curr-thread (c/get-at! db [username :messages contact-id])] - ;; no contact? dont bother - (if (nil? curr-thread) - (log/info "Received message for undiscovered contact (wont store).") - (c/merge-at! db [username :messages contact-id] [message-doc])))) - -(defn get-contacts - "retrieve all contacts" - [db username] - (c/get-at! db [username :contacts])) - -(defn get-contact - "retrieve a specific contact" - [db username contact-id] - (c/get-at! db [username :contacts contact-id])) - -(defn get-thread - "get all messages to and from a specific contact/group" - [db username contact-id] - (c/get-at! db [username :messages contact-id])) - -(defn get-thread-and-contact-if-contact - "get all messages to and from a contact, as well as their contact info IF they are a stored contact" - [db username contact-id] - (let [contact-doc (c/get-at! db [username :contacts contact-id])] - (if (not (nil? contact-doc)) - ;; contact exists - {:contact contact-doc :thread (c/get-at! db [username :messages contact-id])} - ;; else return nil - nil))) diff --git a/src/whisper/storage.clj~ b/src/whisper/storage.clj~ deleted file mode 100644 index 1f1b157..0000000 --- a/src/whisper/storage.clj~ +++ /dev/null @@ -1,3 +0,0 @@ -(ns whisper.storage - "Whisper storage module - (:require [codax])) diff --git a/src/whisper/ui.clj b/src/whisper/ui.clj deleted file mode 100644 index 2336125..0000000 --- a/src/whisper/ui.clj +++ /dev/null @@ -1,156 +0,0 @@ -(ns whisper.ui - "Whisper UI Module" - (:require [cljfx.api :as fx] - [clojure.data.json :as json] - [clojure.tools.logging :as log] - [clojure.core.cache :as cache] - [clj.qrgen :as qr] - [clojure.java.io :as io]) - - (:import [net.glxn.qrgen.core.image ImageType] - [java.io FileInputStream] - [javafx.scene.image Image WritableImage])) - -;; simple permutation of json into label -(defn paint-message - [message] - {:fx/type :label - :text (json/write-str message)}) - -(defn paint-contact - [contact] - {:fx/type :v-box - :children [{:fx/type :label - :scale-x 1.1 - :text (get contact "name")} - {:fx/type :label - :scale-x 0.9 - :text (get contact "id")}]}) - -(defn paint-flag - [flag] - {:fx/type :label - :v-box/margin 10 - :text (json/write-str flag)}) - -;; atomic global state for the UI -(def main-context (atom (fx/create-context - {:contacts [] - :messages [] - :flags []} - cache/lru-cache-factory))) - -(def qrcode-context (atom (fx/create-context - {:message "In Progress..." } - cache/lru-cache-factory))) - -(defn add-json-message - [obj] - (swap! main-context fx/swap-context update :messages conj (paint-message obj))) - -(defn add-json-contact - [obj] - (swap! main-context fx/swap-context update :contacts conj (paint-contact obj))) - -(defn add-str-flag - [str] - (swap! main-context fx/swap-context update :flags conj - (paint-flag str))) - -;; TODO: maybe an actual UI lol -(defn main-ui - [{:keys [fx/context]}] - {:fx/type :stage - :showing true - :width 500 - :height 500 - :scene {:fx/type :scene - :root {:fx/type :v-box - :children (conj (fx/sub-val context :flags) - {:fx/type :h-box - :children [{:fx/type :v-box - :children (fx/sub-val context :contacts)} - ;; TODO: scrollable view (list view?) - {:fx/type :v-box - :children (fx/sub-val context :messages)} - ;; TODO: compose message view - ]})}}}) - -(defn qrcode-ui - [{:keys [showing image]}] - {:fx/type :stage - :showing showing - :title "link device" - :width 500 - :height 500 - :scene {:fx/type :scene - :root {:fx/type :v-box - :children [{:fx/type :image-view - :fit-height 480 - :fit-width 480 - :image image}]}}}) - -(defn user-select-ui - [{:keys [users callback showing]}] - {:fx/type :stage - :showing showing - :title "select a user" - :width 200 - :height 500 - :scene {:fx/type :scene - :root {:fx/type :v-box - :children [{:fx/type :label - :v-box/margin 5 - :text "Pick a user to continue"} - {:fx/type fx/ext-let-refs - :refs {::toggle-group {:fx/type :toggle-group}} - :desc {:fx/type :v-box - :padding 20 - :spacing 10 - :children (for [user users] - {:fx/type :radio-button - :toggle-group {:fx/type fx/ext-get-ref - :ref ::toggle-group} - :selected false - :text (get user "username") - :on-action #(callback user %)})}}]}}}) - -(def main-renderer (fx/create-renderer - :middleware (comp - fx/wrap-context-desc - (fx/wrap-map-desc (fn [_] {:fx/type main-ui}))) - :opts {:fx.opt/type->lifecycle #(or (fx/keyword->lifecycle %) - (fx/fn->lifecycle-with-context %))})) - -(def aux-renderer (fx/create-renderer)) - -(defn paint-main - [] - (fx/mount-renderer main-context main-renderer)) - -(defn paint-linking - [obj] - (if (nil? obj) - ;; if no obj, turn off window - (aux-renderer {:fx/type qrcode-ui - :showing false - :image (WritableImage. 1 1) - :message "lol"}) - - ;; else gen qr - (let [uri (str (get-in obj ["data" "uri"])) - image (io/file (qr/from uri))] - (aux-renderer {:fx/type qrcode-ui - :showing true - :image (Image. (FileInputStream. image))})))) - -;; TODO: modify callback into event handler -(defn paint-user-select - [users showing callback] - (aux-renderer - {:fx/type user-select-ui - :showing showing - ;; "Make New User" is a magic token. see handle-users in core - ;; TODO: dont - :users (conj users {"username" "Link New User"}) - :callback callback}))