Skip to content

Instantly share code, notes, and snippets.

@joshrotenberg
Last active January 15, 2016 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshrotenberg/4a8b699c42419b78659a to your computer and use it in GitHub Desktop.
Save joshrotenberg/4a8b699c42419b78659a to your computer and use it in GitHub Desktop.
(ns stuff.file-cache
(:require [clj-time.core :as t])
(:require [clojure.java.io :as io])
(:require [taoensso.nippy :as nippy])
(:import [java.io DataInputStream DataOutputStream]))
;; This was just a quick experiment. I wanted to cache some lookup tables from a database. Rather than memoize each one individually,
;; though, I just wanted the whole chunk at once, the use case being a big table in Oracle that only changed periodically. I would use this
;; to cache on disk, and then also cache each one in memory, so even if the app restarted it wouldn't have to hit the database more than
;; once per 24 hour period. Worked ok in development, never quite made it to production.
(defn freeze
"Freeze data to a file at path and tag it good until expires. Returns the data stored."
[path data expires]
(locking data
(with-open [w (io/output-stream path)]
(nippy/freeze-to-out! (DataOutputStream. w) {:stored-at (t/now) :expires-at expires :data data})
data)))
(defn thaw
"Returns the data in path, or nil if expired. Throws an exception if the file doesn't exist."
[path]
(locking path
(if-not (.exists (io/as-file path))
nil
(with-open [r (io/input-stream path)]
(let [t (nippy/thaw-from-in! (DataInputStream. r))
n (t/now)]
(when (t/before? n (:expires-at t))
(:data t)))))))
(defn cached
"Takes a path, an expiration time (Joda), and a function that returns the data to store. Returns a function
that returns the data from the right source."
[f path expires-at]
(fn []
(if-let [d (thaw path)]
d
(freeze path (f) expires-at))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment