summaryrefslogtreecommitdiff
path: root/src/mulk/benki/db.clj
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2011-11-14 21:37:55 +0100
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2011-11-14 21:37:55 +0100
commit6ec65ec2d276c359fab11d079536e47f31052647 (patch)
tree2544d3be30495bfa8ec7fce6b88c9271eb5ae72f /src/mulk/benki/db.clj
parent9d556a9a1ae3172584fb12eb9932cb08c3ab44a0 (diff)
Implement a Berkeley-DB-based persistence layer.
Diffstat (limited to 'src/mulk/benki/db.clj')
-rw-r--r--src/mulk/benki/db.clj90
1 files changed, 90 insertions, 0 deletions
diff --git a/src/mulk/benki/db.clj b/src/mulk/benki/db.clj
new file mode 100644
index 0000000..d0f922a
--- /dev/null
+++ b/src/mulk/benki/db.clj
@@ -0,0 +1,90 @@
+(ns mulk.benki.db
+ (:refer-clojure)
+ (:use mulk.benki.util
+ clojure.java.io)
+ (:import [com.sleepycat.je
+ Database DatabaseConfig DatabaseEntry Environment EnvironmentConfig
+ OperationStatus]))
+
+(def ^{:dynamic true :private true} *txn*)
+(def ^{:dynamic true :private true} *db*)
+
+(defn- db-directory []
+ ;; FIXME: This is a hack.
+ ;;(file (.getParent (first (clojure.java.classpath/classpath))) "db")
+ (file "db"))
+
+(defn- make-db-env []
+ (let [envconfig (doto (EnvironmentConfig.)
+ (.setTransactional true)
+ (.setAllowCreate true)
+ (.setTxnSerializableIsolation true))]
+ (Environment. (db-directory) envconfig)))
+
+(defonce dbenv (make-db-env))
+
+(defn call-with-transaction [thunk]
+ (binding [*txn* (.beginTransaction dbenv nil nil)]
+ (try (let [result (thunk)]
+ (.commit *txn*)
+ result)
+ (finally (.abort *txn*)))))
+
+(defn call-with-db [thunk]
+ (let [dbconfig (doto (DatabaseConfig.)
+ (.setTransactional true)
+ (.setAllowCreate true)
+ (.setSortedDuplicates false))]
+ (call-with-transaction
+ (fn []
+ (binding [*db* (.openDatabase dbenv *txn* "benki" dbconfig)]
+ (thunk))))))
+
+(defmacro with-db [& body]
+ `(call-with-db (fn [] ~@body)))
+
+(defn- dump-str-for-db [x]
+ (binding [*print-dup* true
+ *print-meta* true]
+ (pr-str x)))
+
+(defn- getkey [key default]
+ (let [entry (DatabaseEntry.)]
+ (if (= (.get *db* *txn* (DatabaseEntry. (.getBytes key)) entry nil)
+ OperationStatus/SUCCESS)
+ (-> entry
+ (.getData)
+ (String.)
+ (read-string))
+ default)))
+
+(defn- putkey [key val]
+ (let [bdbkey key]
+ (.put *db*
+ *txn*
+ (DatabaseEntry. (.getBytes bdbkey))
+ (DatabaseEntry. (.getBytes (dump-str-for-db val))))))
+
+
+(defonce state-vars (atom {}))
+
+
+(defmacro defstate [sym default]
+ (let [dbkey (str *ns* "/" (name sym))]
+ `(defonce ~sym
+ (let [r# (ref (with-db (getkey ~dbkey ~default)))]
+ (swap! state-vars #(assoc % ~dbkey r#))
+ r#))))
+
+
+(defn save-all-global-state! []
+ (with-db
+ (dosync
+ (doseq [[key r] @state-vars]
+ (putkey key (ensure r))))))
+
+(defn reload-all-global-state! []
+ (with-db
+ (dosync
+ (doseq [[key r] @state-vars]
+ (alter r #(getkey key %))))))