Fork me on GitHub
#beginners
<
2022-07-04
>
Konrad Claesson04:07:05

Does anyone know how to generate a LazySeq of date time's using the new Java time API? Below is how I did it with clj-time:

(defn time-range
  "Returns a lazy sequence of DateTimes in the half-open interval [start, end), incremented by 'step' units of time."
  [start end step]
  (let [inf-range (time-period/periodic-seq start step)
        before-end? (fn [t] (time/within? (time/interval start end)

Ben Sless04:07:58

A naive approach would be map Instant/ofMillis on a range. Wdyt?

Konrad Claesson05:07:38

This seems to do the trick

(map #(-> (java.time.LocalDate/parse "2015-02-13") (.plusDays %) str) (range 10 15 1))
What does "wdyt" mean?

Ben Sless05:07:30

"What do you think"

Ben Sless05:07:45

Nice and concise solution. Maybe just bind the parsed date in a let outside the sequence

👍 1
emccue14:07:00

I would say also maybe try with cljc-java-time

Yosevu Kilonzo07:07:51

How do I write a jpg image blob from a mysql database to a file? The image is a byte array that looks like #object[[B 0x5f79a914 [[email protected]] and I converted it to an input stream with java.io.ByteArrayInputStream so now it looks like #object[java.io.ByteArrayInputStream 0x575af21a [email protected]]. I'm trying to follow https://stackoverflow.com/a/50427686, which looks like what I need, but I'm struggling to translate to clojure. Is this the write approach to use javax.imageio.ImageIO to write the image to a file?

Martin Půda08:07:53

(-> (ImageIO/read your-ByteArrayInputStream-here)
    (ImageIO/write "jpg" (File. "output.jpg")))

Yosevu Kilonzo08:07:47

Hmm, why would (ImageIO/read image-input-stream) return nil?

Yosevu Kilonzo12:07:27

I was able to get it to work with this:

(defn base64-to-file [base64-string]
  (with-open [out (output-stream "kitten.jpg")]
    (.write out (.decode (java.util.Base64/getDecoder) base64-string)))))

Ivan Koz08:07:28

In general, how would you synchronize authorization attempts so there is no two concurrent requests are being made? I'm writing a simple rest client which has a session lifetime, and would appreciate some guidelines in that area.

Ben Sless08:07:35

I guess this is per client?

Ivan Koz09:07:12

Each client is given a session token in response to auth request. I want to avoid creating two sessions//sending two auth requests, so there is a need to sync login attempts somehow. 1. Two threads are attempting to make a request. 2. Both found out that session token is missing or expired 3. Both trying to login by sending an auth request I'm missing an insight on how to make second thread wait with it's login attempt and use session token acquired by the first thread instead. In Java I would synchronize and check on a status variable which indicates that session is ok and second thread can skip the login stage.

Ivan Koz09:07:52

If to do it the Java way, I could use locking

Ben Sless10:07:16

What do you want to happen to the second request? Do you want to reject it? do you want to return to it the same result?

Ivan Koz10:07:01

I want to reuse the session token acquired by the first request

Ben Sless10:07:22

have some key value mapping, both requests take it, check if they already have a token for their key. If so, done, if not, generate a token, assoc to the map, and try to set it relative to the old value. This will succeed only for one request

Ben Sless10:07:52

The other will end up reading the result set by the first

Ben Sless10:07:07

There could be some errors here, but this is the gist of it:

(defn acquire-token
  [request tokens]
  (let [k (request-key request)]
    (if-let [v (get @tokens k)]
      v
      (let [token (create-token request)]
        (loop [old @tokens]
          (if-let [v (get old k)]
            v
            (let [newv (assoc old k token)]
              (if (compare-and-set! tokens old newv)
                v
                (recur @tokens)))))))))

Ben Sless10:07:44

Another solution is to have a single writer - both requests come in, "enqueue" their requests for a token and get back a promise. The writer gets the first request, keeps an association for it, the result is returned to the promise. Second request comes in, belongs to same association, so it gets the same result

Ben Sless10:07:11

(loop [state {}]
  (let [req (<! in)
        k (request-key req)]
    (if-let [v (get state k)]
      (do (>! (:promise req) v)
          (recur state))
      (let [token (create-token req)]
        (>! (:promise req) token)
        (recur (assoc state k token))))))

Ivan Koz10:07:04

I like the queue idea, i'll think about it, ty.

Ben Sless10:07:06

The first solution pretty much has a queue, too, but it's inside the CPU 🙃

Ivan Koz05:07:09

@UK0810AQ2 i've done a prototype using simple lock, could you review it?

(def state (atom {:login :bad
                  :token nil}))

(defn authorized? []
  (let [{:keys [login token]} @state]
    (and (= login :good) (when token true))))

(defn reset-login []
  (reset! state {:login :bad
                 :token nil}))

(def login
  (let [lck (Object.)]
    (fn []
      (locking lck
        (if (authorized?)
          [:reusing-token (@state :token)]
          (do (let [token (rand-int 1000)]
                (Thread/sleep (rand-int 2000))
                (reset! state (-> @state
                                  (assoc :login :good)
                                  (assoc :token token))))
              [:new-token (@state :token)]))))))

(comment
  (authorized?)
  (login)
  (reset-login))

(defn test-1 []
  (-> (group-by #(first @%) (doall (take 1000 (repeatedly #(future (login))))))
      (update :new-token count)
      (update :reusing-token count)))

(comment
  (reset-login)
  (test-1))

Ben Sless05:07:57

I'm terrible with locks, but I'll try

Ivan Koz05:07:53

Extracted test to a function. And just to make it clear, authorized? and login both are called from an async requests in my client.

emccue14:07:54

or just use locking

emccue14:07:45

(defn make-request
  [session-token]
  (locking (.intern session-token)
    ...))

emccue14:07:56

perf cost to interning the string every time but :fart noises:

Benjamin17:07:08

When I pull in

ch.qos.logback/logback-classic {:mvn/version "1.2.8"}
I get SLF4J: Class path contains multiple SLF4J bindings. I think I can exclude slf4j-nop how do I do that ?

Alex Miller (Clojure team)17:07:59

:exclusions [whatever/slf4j-nop]

Alex Miller (Clojure team)17:07:28

With whatever being the correct groupId (don't remember what that is)

Alex Miller (Clojure team)17:07:49

That goes in the coord map

Benjamin17:07:32

SLF4J: Found binding in [jar:file:/home/benj/.m2/repository/org/slf4j/slf4j-nop/1.7.36/slf4j-nop-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
deps:
ch.qos.logback/logback-classic
        {:mvn/version "1.2.8"
         :exclusions [org.slf4j/slf4j-api]}
when I do clj -X:deps tree | grep slf4j there is nothing left Tried excluding org.slf4j/slf4j-nop

Alex Miller (Clojure team)18:07:15

Well you can't exclude the api, presumably that's what's being used for logging

Alex Miller (Clojure team)18:07:35

What do you see with the grep before the change

Benjamin18:07:23

[[email protected] my-oauth-example] 0 $ clj -X:deps tree | grep slf --before-context=3
    . com.fasterxml.jackson.core/jackson-core 2.13.2
ch.qos.logback/logback-classic 1.2.8
  . ch.qos.logback/logback-core 1.2.8
  . org.slf4j/slf4j-api 1.7.32

Alex Miller (Clojure team)18:07:57

That looks like there is no impl

Benjamin07:07:54

I guess then some library must be pulling it in dynamically or something? Full deps:

{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        metosin/reitit {:mvn/version "0.5.18"}
        metosin/muuntaja {:mvn/version "0.6.8"}
        metosin/ring-http-response {:mvn/version "0.9.3"}
        ring/ring-core {:mvn/version "1.9.5"}
        ring/ring-defaults {:mvn/version "0.3.3"}
        luminus/ring-undertow-adapter {:mvn/version "1.2.6"}
        org.clojure/data.json {:mvn/version "2.4.0"}
        ring-oauth2/ring-oauth2 {:mvn/version "0.2.0"}
        radicalzephyr/ring.middleware.logger {:mvn/version "0.6.0"}
        ch.qos.logback/logback-classic
        {:mvn/version "1.2.8"}}}

Alex Miller (Clojure team)12:07:10

The other possibility is that something is (wrongly) including slf4j inside it's own jar

Alex Miller (Clojure team)12:07:14

But even then I'd assume that wouldn't happen multiple times. I'm not really sure what to suggest. I guess you could pull in these top level deps one by one until you see the issue

Benjamin13:07:35

ah yea I see. That already helps thanks

Alex Miller (Clojure team)13:07:43

Or the opposite - remove them one by one