 
 
 (defn from-file
-  "Creates a lazy block from a local file. The file is read once to calculate
-  the identifier."
+  "Create a lazy block from a local file. Returns the block, or nil if the file
+  does not exist or is empty.
+
+  The file is read once to calculate the identifier."
   ([file]
    (from-file file default-algorithm))
   ([file algorithm]
    (let [file (io/file file)
-         hash-fn (data/checked-hasher algorithm)
-         reader (fn file-reader [] (FileInputStream. file))
-         id (hash-fn (reader))]
-     (data/lazy-block id (.length file) reader))))
+         hash-fn (data/hasher algorithm)]
+     (when (and (.exists file) (pos? (.length file)))
+       (data/create-block
+         (hash-fn (FileInputStream. file))
+         (.length file)
+         (Instant/ofEpochMilli (.lastModified file))
+         (fn reader [] (FileInputStream. file)))))))
 
 
 (defn open
-  "Opens an input stream to read the contents of the block.
-
-  If `start` and `end` are given, the input stream will only return content
+  "Open an input stream to read the contents of the block.
+
+  If `:start` and `:end` are given, the input stream will only return content
   from the starting index byte to the byte before the end index. For example,
-  opening a block with size _n_ with `(open block 0 n)` would return the full
-  block contents."
+  opening a block with size _n_ with `(open block {:start 0, :end n})` would
+  return the full block contents."
   (^java.io.InputStream
    [block]
-   (data/content-stream block nil nil))
+   (open block nil))
   (^java.io.InputStream
-   [block start end]
-   (when-not (and (integer? start)
-                  (integer? end)
-                  (not= start end)
-                  (<= 0 start end (:size block)))
-     (throw (IllegalArgumentException.
-              (format "Range bounds must be distinct integers within block bounds: [0, %d]"
-                      (:size block)))))
-   (data/content-stream block start end)))
+   [block opts]
+   (let [{:keys [start end]} opts]
+     (when (and start (or (not (nat-int? start))
+                          (<= (:size block) start)))
+       (throw (IllegalArgumentException.
+                (format "Range start must be an integer within block size %d: %s"
+                        (:size block) start))))
+     (when (and end (or (not (pos-int? end))
+                        (<= (:size block) end)))
+       (throw (IllegalArgumentException.
+                (format "Range end must be an integer within block size %d: %s"
+                        (:size block) end))))
+     (when (and start end (not (< start end)))
+       (throw (IllegalArgumentException.
+                (format "Range start %d must be less than range end %d"
+                        start end))))
+     (data/content-stream block start end))))
 
 
 (defn read!
-  "Reads data into memory from the given source and hashes it to identify the
+  "Read data into memory from the given source and hash it to identify the
   block."
   ([source]
    (read! source default-algorithm))

    (java.io
      File
      FileInputStream)
    multiformats.hash.Multihash
    org.apache.commons.io.input.CountingInputStream))


(def default-algorithm
  "The hashing algorithm used if not specified in functions which create blocks."
  :sha2-256)



;; ## Stat Metadata

(defn with-stats
  "Returns the given block with updated stat metadata."
  [block stats]
  (vary-meta block assoc :block/stats stats))


(defn meta-stats
  "Returns stat information from a block's metadata, if present."
  [block]
  (:block/stats (meta block)))




(ns blocks.core
  "Core block storage API.

  Functions which may cause side effects or IO are marked with bangs - for
  example `(read! \"foo\")` doesn't have side-effects, but
  `(read!  some-input-stream)` will consume bytes from the stream."
  (:refer-clojure :exclude [get list])
  (:require
    [blocks.data :as data]
