            (org.apache.commons.codec.digest DigestUtils)
            (java.io ByteArrayInputStream)
            (java.util.zip GZIPInputStream)))
+
+(defmacro catch-s3-errors [& body]
+  `(try
+    ~@body
+    (catch org.jets3t.service.S3ServiceException e#
+      (throw (Exception. (str "S3 Error " (. e# getS3ErrorCode)
+                              " " (. e# getS3ErrorMessage)) e#)))))
 
 (def #^{:private true} get-s3
      (memoize (fn []                 

  "Returns a list of symbols.  Each symbol has a name matching a
  bucket name, and metadata matching that bucket's S3 metadata."
  []
  (map make-bucket-symbol
       (.listAllBuckets (get-s3))))

(defn list-objects
  ([bucket-name]

     (.listObjects (get-s3) (get-bucket bucket-name) nil)))

(defn delete-object [bucket-name object-key]
  (catch-s3-errors (.deleteObject (get-s3) bucket-name object-key)))

(defn get-object [bucket-name object-key]
  (.getObject (get-s3) (get-bucket bucket-name) object-key))

(defn get-object-meta [bucket-name object-key]
  (catch-s3-errors
   (into {} (.getMetadataMap
             (.getObjectDetails (get-s3) (get-bucket bucket-name)
                                object-key)))))

(defn get-object-stream [bucket-name object-key]
  (catch-s3-errors
   (let [object (get-object bucket-name object-key)
         input (.getDataInputStream object)]
     (if (= (.getContentEncoding object) "gzip")
       (GZIPInputStream. input)
       input))))

(defn get-object-string [bucket-name object-key]
  (catch-s3-errors
   (IOUtils/toString (get-object-stream bucket-name object-key)
                     "UTF-8")))

(defn put-object-stream [bucket-name object-key stream metadata]
  (catch-s3-errors
   (let [bucket (get-bucket bucket-name)
         object (S3Object. bucket object-key)]
     (.addAllMetadata object metadata)
     (.setDataInputStream object stream)
     (.putObject (get-s3) bucket object))))

(defn put-object-string [bucket-name object-key data-string metadata]
  (catch-s3-errors
   (let [bucket (get-bucket bucket-name)
         object (S3Object. bucket object-key data-string)]
     (.addAllMetadata object metadata)
     (.putObject (get-s3) bucket object))))

(defn put-object-gzip-string
  [bucket-name object-key data-string metadata]
  (catch-s3-errors
   (let [bucket (get-bucket bucket-name)
         object (S3Object. bucket object-key)
         bytes (zip/gzip-utf8-string data-string)]
     (.addAllMetadata object metadata)
     (.setContentEncoding object "gzip")
     (.setContentLength object (count bytes))
     (.setDataInputStream object (ByteArrayInputStream. bytes))
     (.putObject (get-s3) bucket object))))

(defn put-object-gzip-bytes
  [bucket-name object-key bytes metadata]
  (catch-s3-errors
   (let [bucket (get-bucket bucket-name)
         object (S3Object. bucket object-key)
         gzipped-bytes (zip/gzip-bytes bytes)]
     (.addAllMetadata object metadata)
     (.setContentEncoding object "gzip")
     (.setContentLength object (count gzipped-bytes))
     (.setMd5Hash object (DigestUtils/md5 gzipped-bytes))
     (.setDataInputStream object (ByteArrayInputStream. gzipped-bytes))
     (.putObject (get-s3) bucket object))))
