allow insecure? with x509

allow insecure? with x509

--- src/clj_http/conn_mgr.clj
+++ src/clj_http/conn_mgr.clj
@@ -32,10 +32,12 @@
         keystore))))

 (defn get-keystore-scheme-registry
-  [{:keys [keystore keystore-pass trust-store trust-store-pass]}]
+  [{:keys [keystore keystore-pass trust-store trust-store-pass insecure?]}]
   (let [ks (get-keystore keystore keystore-pass)
         ts (get-keystore trust-store trust-store-pass)
         factory (SSLSocketFactory. ks keystore-pass ts)]
+    (if insecure?
+      (.setHostnameVerifier factory SSLSocketFactory/ALLOW_ALL_HOSTNAME_VERIFIER))
     (doto (SchemeRegistryFactory/createDefault)
       (.register (Scheme. "https" 443 factory)))))

@@ -43,10 +45,10 @@
 (defn ^SingleClientConnManager make-regular-conn-manager
   [{:keys [insecure? keystore trust-store] :as req}]
   (cond
-    insecure? (SingleClientConnManager. insecure-scheme-registry)
-
     (or keystore trust-store)
     (SingleClientConnManager. (get-keystore-scheme-registry req))
+
+    insecure? (SingleClientConnManager. insecure-scheme-registry)

     :else (SingleClientConnManager.)))


.toUpperCase -> string/upper-case

.toUpperCase -> string/upper-case

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/Yleisradio/rabid.git/'


Add c.t.n.find-test

Add c.t.n.find-test

Tests 'platform' argument to find/find-sources-in-dir

error: pathspec 'src/test/clojure/clojure/tools/namespace/find_test.clj' did not match any file(s) known to git

--- src/test/clojure/clojure/tools/namespace/find_test.clj
+++ src/test/clojure/clojure/tools/namespace/find_test.clj
@@ -0,0 +1,29 @@
+(ns clojure.tools.namespace.find-test
+  (:require [clojure.test :refer [deftest is]]
+            [clojure.tools.namespace.test-helpers :as help]
+            [clojure.tools.namespace.find :as find])
+  (:import (java.io File)))
+
+(deftest t-find-clj-and-cljc-files
+  "main.clj depends on one.cljc which depends on two.clj.
+  two.cljs also exists but should not be returned"
+  (let [dir (help/create-temp-dir "t-find-clj-and-cljc-files")
+        main-clj (help/create-source dir 'example.main :clj '[example.one])
+        one-cljc (help/create-source dir 'example.one :cljc '[example.two])
+        two-clj (help/create-source dir 'example.two :clj)
+        two-cljs (help/create-source dir 'example.two :cljs)]
+    (is (help/same-files?
+         [main-clj one-cljc two-clj]
+         (find/find-sources-in-dir dir)))))
+
+(deftest t-find-cljs-and-cljc-files
+  "main.cljs depends on one.cljc which depends on two.cljs.
+  two.clj also exists but should not be returned"
+  (let [dir (help/create-temp-dir "t-find-cljs-and-cljc-files")
+        main-cljs (help/create-source dir 'example.main :cljs '[example.one])
+        one-cljc (help/create-source dir 'example.one :cljc '[example.two])
+        two-clj (help/create-source dir 'example.two :clj)
+        two-cljs (help/create-source dir 'example.two :cljs)]
+    (is (help/same-files?
+         [main-cljs one-cljc two-cljs]
+         (find/find-sources-in-dir dir find/cljs)))))


added some logging to migrations

added some logging to migrations

--- src/lobos/migrations.clj
+++ src/lobos/migrations.clj
@@ -3,7 +3,7 @@
                             bigint boolean char double float time complement])
   (:use (lobos [migration :only [defmigration]] connectivity core schema))
   (:use lobos.helpers)
-
+  (:use [clojure.tools.logging :only (debug info warn error)])
   (:use ruuvi-server.standalone.config)
   (:use ruuvi-server.database)
   )
@@ -73,8 +73,12 @@
   (println "Execute" (name direction))
   (open-global (create-connection-pool *database-config*))
   (if (= :rollback direction)
-    (rollback :all)
-    (migrate)
+    (do
+      (info "rollbacking")
+      (rollback :all))
+    (do
+      (info "migrating forward")
+      (migrate))
     )
   (println "Done")
   )


Delete compiler.cljs

Delete compiler.cljs

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/parenode/clojure-cup-2015.git/'


Add a test for sqlite/column-names

Add a test for sqlite/column-names

The upcoming korma update broke the original version of this fn, so I added a test first.

error: pathspec 'test/vip/data_processor/db/sqlite_test.clj' did not match any file(s) known to git

--- test/vip/data_processor/db/sqlite_test.clj
+++ test/vip/data_processor/db/sqlite_test.clj
@@ -0,0 +1,9 @@
+(ns vip.data-processor.db.sqlite-test
+  (:require [vip.data-processor.db.sqlite :as sqlite]
+            [clojure.test :refer :all]
+            [vip.data-processor.validation.data-spec.v3-0 :as v3-0]))
+
+(deftest column-names-test
+  (let [db (sqlite/temp-db "column-names-test" "3.0")]
+    (is (= ["state_id" "early_vote_site_id"]
+           (sqlite/column-names (-> db :tables :state-early-vote-sites))))))


create immutable object with map

create immutable object with map

error: pathspec 'src/sandbox/builder/map.clj' did not match any file(s) known to git

--- src/sandbox/builder/map.clj
+++ src/sandbox/builder/map.clj
@@ -0,0 +1,8 @@
+(ns sandbox.builder.map)
+
+(def p
+  {:first-name "John"
+   :middle-name "Quincy"
+   :last-name "Adams"})
+
+(into {} (for [[k, v] p] [k (.toUpperCase v)]))


Solve Banknotes and Coins in clojure

Solve Banknotes and Coins in clojure

error: pathspec 'solutions/uri/1021/1021.clj' did not match any file(s) known to git

--- solutions/uri/1021/1021.clj
+++ solutions/uri/1021/1021.clj
@@ -0,0 +1,31 @@
+(ns main
+  (:require [clojure.string :as string]))
+
+(def notas [10000.0 5000.0 2000.0 1000.0 500.0 200.0])
+(def coins [100.0 50.0 25.0 10.0 5.0 1.0])
+
+(defn print-result [value currencies currency-type]
+  (println (str (string/upper-case currency-type) "S:"))
+  (loop [value value
+         [currency & remaining-currencies] currencies]
+    (let [quantity (quot value currency)]
+      (printf
+       "%.0f %s(s) de R$ %.2f%n"
+       quantity
+       currency-type
+       (with-precision 2 (/ currency 100.0))))
+    (if (seq remaining-currencies)
+      (recur (mod value currency) remaining-currencies)
+      (mod value currency))))
+
+(defn main []
+  (loop [line (read-line)]
+    (when line
+      (let [value (-> line
+                      (Double/parseDouble))]
+            (-> (* value 100)
+                (print-result notas "nota")
+                (print-result coins "moeda"))
+        (recur (read-line))))))
+
+(main)


Disabling PhantomJS for now to placate Travis

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/xcthulhu/clj-bitauth.git/'


Add the actual .clj

Add the actual .clj

error: pathspec '.lein/profiles.clj' did not match any file(s) known to git

--- .lein/profiles.clj
+++ .lein/profiles.clj
@@ -0,0 +1,2 @@
+{:user {:plugins [[cider/cider-nrepl "0.11.0-SNAPSHOT"]]
+        :dependencies [[org.clojure/tools.nrepl "0.2.12"]]}}


CoreAllocate type renamed to Core

CoreAllocate type renamed to Core

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/michaelsbradleyjr/transwarp.git/'


test for examples/ring-spec-swagger

test for examples/ring-spec-swagger

error: pathspec 'examples/ring-spec-swagger/test/example/server_test.clj' did not match any file(s) known to git

--- examples/ring-spec-swagger/test/example/server_test.clj
+++ examples/ring-spec-swagger/test/example/server_test.clj
@@ -0,0 +1,31 @@
+(ns example.server-test
+  (:require [clojure.test :refer :all]
+            [example.server :refer [app]]))
+
+(deftest example-server
+
+  (testing "GET"
+    (is (= (-> {:request-method :get :uri "/math/plus" :query-params {:x 20 :y 3}}
+               app :body slurp)
+           "{\"total\":23}")))
+
+  (testing "POST"
+    (is (= (-> {:request-method :post :uri "/math/plus" :body-params {:x 40 :y 2}}
+               app :body slurp)
+           "{\"total\":42}")))
+
+  (testing "Download"
+    (is (= (-> {:request-method :get :uri "/files/download"}
+               app :body (#(slurp % :encoding "ascii")) count)  ;; binary
+           (.length (clojure.java.io/file "resources/reitit.png"))
+           506325)))
+
+  (testing "Upload"
+    (let [file (clojure.java.io/file "resources/reitit.png")
+          multipart-temp-file-part {:tempfile file
+                                    :size (.length file)
+                                    :filename (.getName file)
+                                    :content-type "image/png;"}]
+         (is (= (-> {:request-method :post :uri "/files/upload" :multipart-params {:file multipart-temp-file-part}}
+                    app :body slurp)
+                "{\"name\":\"reitit.png\",\"size\":506325}")))))


missed dependency

missed dependency

error: pathspec 'src-cljs/frontend/db/trans.cljs' did not match any file(s) known to git

--- src-cljs/frontend/db/trans.cljs
+++ src-cljs/frontend/db/trans.cljs
@@ -0,0 +1,17 @@
+(ns frontend.db.trans
+  (:require [datascript :as d]))
+
+;; Need a better way to accomplish this--perhaps a separate transient db?
+;; lets us use the first 5000 ids for things like bot chats and bot drawings
+(defonce transient-ids (atom {}))
+
+(defn get-next-transient-id [conn]
+  (loop [id (get @transient-ids conn 1)]
+    (let [ids @transient-ids]
+      (if (or (first (d/datoms @conn :eavt id))
+              (not (compare-and-set! transient-ids ids (assoc-in ids [conn] (inc id)))))
+        (recur (inc id))
+        id))))
+
+(defn reset-id [conn]
+  (swap! transient-ids assoc conn 1))


First cut of comments

First cut of comments

error: pathspec 'src/langnostic/comments.clj' did not match any file(s) known to git

--- src/langnostic/comments.clj
+++ src/langnostic/comments.clj
@@ -0,0 +1,33 @@
+(ns langnostic.comments
+  (:require [codax.core :as db]
+
+            [langnostic.conf :as conf]))
+
+(def DB (db/open-database! conf/DB-PATH))
+
+(defn now! []
+  (quot (System/currentTimeMillis) 1000))
+
+(defn get-comments-for [post-id]
+  (db/get-at! DB [:posts post-id :comments]))
+
+(defn post-comment!
+  ([user post-id content]
+   (db/with-write-transaction [DB tx]
+     (let [path [(count (db/get-at tx [:posts post-id :comments]))]]
+       (db/update-at
+        tx [:posts post-id :comments]
+        #(assoc % (first path) {:user user :path path :content content :replies []})))))
+  ([user post-id comment-path content]
+   (db/with-write-transaction [DB tx]
+     (let [target (conj comment-path :replies)
+           new-ix (count (db/get-at tx (concat [:posts post-id :comments] target)))
+           path (conj target new-ix)]
+       (-> tx
+           (db/update-at
+            (concat [:posts post-id :comments] target)
+            #(conj % {:user user :path path :content content :replies []})))))))
+
+(defn edit-comment!
+  [user comment-path new-content]
+   nil)


butlasto relation should use defna instead of defne

--- src/metabase/util/logic.clj
+++ src/metabase/util/logic.clj
@@ -3,7 +3,7 @@
   (:refer-clojure :exclude [==])
   (:require [clojure.core.logic :refer :all]))

-(defne butlast°
+(defna butlast°
   "A relation such that BUSTLASTV is all items but the last item LASTV of list L."
   [butlastv lastv l]
   ([[]   ?x [?x]])


Minor reorganization.

Minor reorganization.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/couragelabs/freefrog.git/'


Refactoring.

Refactoring.


Simplification.

Simplification.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/couragelabs/coachbot.git/'


concurency

concurency

error: pathspec 'src/mark-volkmann-tutorial/concurrency.clj' did not match any file(s) known to git

--- src/mark-volkmann-tutorial/concurrency.clj
+++ src/mark-volkmann-tutorial/concurrency.clj
@@ -0,0 +1,47 @@
+;; Concurrency
+;; http://java.ociweb.com/mark/clojure/article.html#Concurrency
+
+; primary concurrency challange - manage access to shared, mutable state
+; lock - hard: whichh & when to lock
+; problems: deadlocks, race conditions
+
+; Clojure - try to solve that problems:
+; all data is immutable
+; mutable are reference types: Var, Ref, Atom, Agent -> provide safe ways to
+; easy to run function in new thread
+; Java Interop -> http://jcip.net/ Java Concurrency in practice book
+
+; future - macro, runs body in different thread using thread pool, result arent neede immediately
+(defn- polynomial
+  "computes the value of a polynomial
+   with the given coefficients for a given value x"
+  [coefs x]
+  ; For example, if coefs contains 3 values then exponents is (2 1 0).
+  (let [exponents (reverse (range (count coefs)))]
+    ; Multiply each coefficient by x raised to the corresponding exponent
+    ; and sum those results.
+    ; coefs go into %1 and exponents go into %2.
+    (apply + (map #(* %1 (Math/pow x %2)) coefs exponents))))
+
+(defn- derivative
+  "computes the value of the derivative of a polynomial
+   with the given coefficients for a given value x"
+  [coefs x]
+  ; The coefficients of the derivative function are obtained by
+  ; multiplying all but the last coefficient by its corresponding exponent.
+  ; The extra exponent will be ignored.
+  (let [exponents (reverse (range (count coefs)))
+        derivative-coefs (map #(* %1 %2) (butlast coefs) exponents)]
+    (polynomial derivative-coefs x)))
+
+(def f (partial polynomial [2 1 3])) ; 2x^2 + x + 3
+(def f-prime (partial derivative [2 1 3])) ; 4x + 1
+
+(println "creating future")
+(def my-future (future (f-prime 2)))   ; f-prime is called in another thread
+(println "created future")
+(println "result is" @my-future)
+(shutdown-agents)
+
+; pmap - parallel map
+; clojure.parallel: par, pdistinct, pfilter-dupes, pfilter-nils, pmax, pmin, preduce, psort, psummary, pvec


add some type mappings that were missing in the native QP

--- src/metabase/driver/generic_sql/native.clj
+++ src/metabase/driver/generic_sql/native.clj
@@ -11,19 +11,23 @@

 (def class->base-type
   "Map of classes returned from DB call to metabase.models.field/base-types"
-  {java.lang.Boolean :BooleanField
-   java.lang.Double :FloatField
-   java.lang.Integer :IntegerField
-   java.lang.Long :IntegerField
-   java.lang.String :TextField
-   java.sql.Timestamp :DateTimeField})
+  {java.lang.Boolean    :BooleanField
+   java.lang.Double     :FloatField
+   java.lang.Float      :FloatField
+   java.lang.Integer    :IntegerField
+   java.lang.Long       :IntegerField
+   java.lang.String     :TextField
+   java.math.BigDecimal :DecimalField
+   java.math.BigInteger :BigIntegerField
+   java.sql.Date        :DateField
+   java.sql.Timestamp   :DateTimeField})

 (defn- value->base-type
   "Attempt to match a value we get back from the DB with the corresponding base-type`."
   [v]
   (if-not v :UnknownField
           (or (class->base-type (type v))
-              (throw (ApiException. (int 500) (format "Missing base type mapping for %s in metabase.driver.native/class->base-type. Please add an entry."
+              (throw (ApiException. (int 500) (format "Missing base type mapping for %s in metabase.driver.generic-sql.native/class->base-type. Please add an entry."
                                                       (str (type v))))))))
 (defn process-and-run
   "Process and run a native (raw SQL) QUERY."


dox for `/api/user`

dox for `/api/user`

--- src/metabase/api/user.clj
+++ src/metabase/api/user.clj
@@ -15,22 +15,30 @@
   (check-403 (or (= user-id *current-user-id*)
                  (:is_superuser @*current-user*))))

-(defendpoint GET "/" []
-  (check-superuser) ; user must be a superuser to proceed
+(defendpoint GET "/"
+  "Fetch a list of all `Users`. You must be a superuser to do this."
+  []
+  (check-superuser)
   (sel :many User))

-(defendpoint GET "/current" []
+(defendpoint GET "/current"
+  "Fetch the current user, their `OrgPerms`, and associated `Orgs`."
+  []
   (->404 @*current-user*
          (hydrate [:org_perms :organization])))

-(defendpoint GET "/:id" [id]
-  (check-self-or-superuser id)        ; user must be getting their own details OR they must be a superuser to proceed
+(defendpoint GET "/:id"
+  "Fetch a `User`. You must be fetching yourself *or* be a superuser."
+  [id]
+  (check-self-or-superuser id)
   (check-404 (sel :one User :id id)))

-(defendpoint PUT "/:id" [id :as {{:keys [email first_name last_name] :as body} :body}]
+(defendpoint PUT "/:id"
+  "Update a `User`."
+  [id :as {{:keys [email first_name last_name] :as body} :body}]
   {email      [Required Email]
    first_name NonEmptyString
    last_name  NonEmptyString}
@@ -43,7 +51,9 @@
   (sel :one User :id id))

-(defendpoint PUT "/:id/password" [id :as {{:keys [password old_password]} :body}]
+(defendpoint PUT "/:id/password"
+  "Update a user's password."
+  [id :as {{:keys [password old_password]} :body}]
   {password     [Required ComplexPassword]
    old_password Required}
   (check-self-or-superuser id)


add nrepl debug proxy util

add nrepl debug proxy util

error: pathspec 'src/main/shadow/nrepl_debug.clj' did not match any file(s) known to git

--- src/main/shadow/nrepl_debug.clj
+++ src/main/shadow/nrepl_debug.clj
@@ -0,0 +1,77 @@
+(ns shadow.nrepl-debug
+  (:require [nrepl.transport :as transport]
+            [clojure.pprint :refer (pprint)]
+            [clojure.walk :as walk]
+            [clojure.java.io :as io])
+  (:import [java.net ServerSocket Socket]
+           [java.io FileOutputStream OutputStreamWriter]))
+
+(defonce print-lock (Object.))
+
+(defn log [out from msg]
+  (let [text (with-out-str (pprint msg))]
+    (prn [:msg from (count text)])
+    (locking print-lock
+      (binding [*out* out]
+        (println ";; ----------------------------------------")
+        (println ";; -- FROM " from)
+        (println ";; ----------------------------------------")
+        (println text))
+      )))
+
+(defn client-loop [id client to-port]
+  (let [target (Socket. "localhost" to-port)
+
+        target-transport
+        (transport/bencode target)
+
+        client-transport
+        (transport/bencode client)
+
+        log-file
+        (io/file "target" "nrepl-debug" (str "nrepl-" id ".log.edn"))]
+
+    (io/make-parents log-file)
+
+    (with-open [log-out (-> log-file (FileOutputStream.) (OutputStreamWriter.))]
+
+      ;; target -> client
+      (doto (Thread.
+              #(do (try
+                     (loop []
+                       (when-some [target-msg (transport/recv target-transport)]
+                         (log log-out :target target-msg)
+                         (transport/send client-transport target-msg)
+                         (recur)))
+                     (catch Exception e
+                       (prn [:target-ex e])))
+                   (.close client)))
+        (.start))
+
+      (try
+        ;; client -> target
+        (loop []
+          (when-some [client-msg (transport/recv client-transport)]
+            (log log-out :client client-msg)
+            (transport/send target-transport client-msg)
+            (recur)))
+        (catch Exception e
+          (prn [:client-ex e]))))
+
+    (.close target)
+    (.close client)))
+
+(defn -main [from to]
+  (let [from-port (Long/parseLong from)
+        to-port (Long/parseLong to)
+
+        server-socket
+        (ServerSocket. from-port)]
+
+    (prn [:server-ready from-port to-port])
+
+    (loop [id 0]
+      (let [client (.accept server-socket)]
+        (prn [:client-accepted client])
+        (client-loop id client to-port)
+        (recur (inc id))))))


Added code listings from Clojure day 2

Added code listings from Clojure day 2

error: pathspec 'clojure/compass.clj' did not match any file(s) known to git

--- clojure/compass.clj
+++ clojure/compass.clj
@@ -0,0 +1,17 @@
+(defprotocol Compass
+  (direction [c])
+  (left [c])
+  (right [c]))
+
+(def directions [:north :east :south :west])
+
+(defn turn [base amount]
+  (rem (+ base amount) (count directions)))
+
+(defrecord SimpleCompass [bearing]
+  Compass
+  (direction [_] (directions bearing))
+  (left [_] (SimpleCompass. (turn bearing 3)))
+  (right [_] (SimpleCompass. (turn bearing 1)))
+  Object
+  (toString [this] (str "[" (direction this) "]")))


added p12.clj

added p12.clj

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/drcabana/euler-fp.git/'


Add c.d.postal.stress for load-testing.

Add c.d.postal.stress for load-testing.

error: pathspec 'src/clj/com/draines/postal/stress.clj' did not match any file(s) known to git

--- src/clj/com/draines/postal/stress.clj
+++ src/clj/com/draines/postal/stress.clj
@@ -0,0 +1,76 @@
+(ns com.draines.postal.stress
+  (:import [java.util Date]
+           [java.text SimpleDateFormat]
+           [java.util.concurrent CountDownLatch])
+  (:use [clojure.test]
+        [com.draines.postal.smtp :only [smtp-send]]
+        [com.draines.postal.message :only [make-fixture]]))
+
+(def DATEFORMAT (SimpleDateFormat. "yyyy-MM-dd.HH:mm:ss"))
+
+(def *from* "foo@lolz.dom")
+(def *to* "bar@lolz.dom")
+
+(defonce *counter* (atom 0))
+
+(defn reset-counter! []
+  (reset! *counter* 0))
+
+(def *logger* (agent nil))
+
+(defn log* [x s]
+  (let [s* (format "%s %s"
+                   (.format DATEFORMAT (Date.))
+                   (apply str (interpose " " s)))]
+    (println s*)
+    (flush)
+    s*))
+
+(defn log [& s]
+  (send-off *logger* log* s))
+
+(defn partition-work
+  "Break up total number of messages evenly over desired number of threads."
+  [n ts]
+  (if (> ts n)
+    [n]
+    (let [m (int (Math/floor (float (/ n ts))))]
+      (loop [acc [] n n]
+        (if (>= n m)
+          (recur (conj acc m) (- n m))
+          (if (pos? n)
+            (conj acc n)
+            acc))))))
+
+(defn spam
+  ([host port from to n]
+     (spam host from to port n 0))
+  ([host port from to n delay]
+     (log (format "(thread: %s) %s msgs -> %s"
+                  (-> (Thread/currentThread) .getId)
+                  n host))
+     (let [date (.format DATEFORMAT (Date.))]
+       (dotimes [x n]
+         (smtp-send host port (make-fixture from to))
+         (swap! *counter* inc)
+         (Thread/sleep delay))
+       n))
+  ([host port from to n delay threads]
+     (let [latch (CountDownLatch. threads)
+           res (doall
+                (map #(future
+                       (let [ct (spam host port from to % delay)]
+                         (.countDown latch)
+                         ct))
+                     (partition-work n threads)))]
+       (.await latch)
+       (reduce #(+ %1 (deref %2)) 0 res))))
+
+
+(deftest test-partition-work
+  (is (= [5 5] (partition-work 10 2)))
+  (is (= [3 3 3 1] (partition-work 10 3)))
+  (is (= [111 111 111 111 111 111 111 111 111 111 3]
+           (partition-work 1113 10))))
+
+