This repository has been archived by the owner on Jul 25, 2020. It is now read-only.
/
blobstore.clj
292 lines (247 loc) · 9.49 KB
/
blobstore.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
;;
;; Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
;;
;; ====================================================================
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
;; ====================================================================
;;
(ns org.jclouds.blobstore
"A clojure binding for the jclouds BlobStore.
Current supported services are:
[s3, azureblob, atmos, cloudfiles]
Here's a quick example of how to view blob resources in rackspace
(use 'org.jclouds.blobstore)
(use 'clojure.contrib.pprint)
(def user \"rackspace_username\")
(def password \"rackspace_password\")
(def blobstore-name \"cloudfiles\")
(with-blobstore [blobstore-name user password]
(pprint (containers))
(pprint (blobs blobstore your_container_name)))
See http://code.google.com/p/jclouds for details."
(:use [org.jclouds.core])
(:import [java.io File FileOutputStream OutputStream]
[org.jclouds.blobstore
AsyncBlobStore BlobStore BlobStoreContext BlobStoreContextFactory
domain.BlobMetadata domain.StorageMetadata domain.Blob
options.ListContainerOptions]
[org.jclouds.encryption.internal JCEEncryptionService]
[java.util Arrays]
[com.google.common.collect ImmutableSet]))
(try
(require '[clojure.contrib.io :as io])
(catch Exception e
(require '[clojure.contrib.duck-streams :as io])))
(defn blobstore
"Create a logged in context.
Options for communication style
:sync and :async.
Options can also be specified for extension modules
:log4j :enterprise :httpnio :apachehc :bouncycastle :joda :gae"
[#^String service #^String account #^String key & options]
(let [context
(.createContext
(BlobStoreContextFactory.) service account key
(apply modules (filter #(not (#{:sync :async} %)) options)))]
(if (some #(= :async %) options)
(.getAsyncBlobStore context)
(.getBlobStore context))))
(defn blobstore-context
"Returns a blobstore context from a blobstore."
[blobstore]
(.getContext blobstore))
(defn blobstore?
[object]
(or (instance? BlobStore object)
(instance? AsyncBlobStore object)))
(defn blobstore-context?
[object]
(instance? BlobStoreContext object))
(defn as-blobstore
"Tries hard to produce a blobstore from its input arguments"
[& args]
(cond
(blobstore? (first args)) (first args)
(blobstore-context? (first args)) (.getBlobStore (first args))
:else (apply blobstore args)))
(def *blobstore*)
(def *encryption-service* (JCEEncryptionService.)) ;; TODO: use guice
(def *max-retries* 3)
(defmacro with-blobstore [[& blobstore-or-args] & body]
`(binding [*blobstore* (as-blobstore ~@blobstore-or-args)]
~@body))
(defn containers
"List all containers in a blobstore."
([] (containers *blobstore*))
([blobstore] (.list blobstore)))
(def #^{:private true} list-option-map
{:after-marker #(.afterMarker %1 %2)
:in-directory #(.inDirectory %1 %2)
:max-results #(.maxResults %1 %2)
:with-details #(when %2 (.withDetails %1))
:recursive #(when %2 (.recursive %1))})
(defn list-container
"List a container. Options are:
:after-marker string
:in-direcory path
:max-results n
:recursive true"
[blobstore & args]
(if (blobstore? blobstore)
(let [[container-name & args] args
options (apply hash-map args)
list-options (reduce
(fn [lco [k v]]
((list-option-map k) lco v)
lco)
(ListContainerOptions.)
options)]
(.list blobstore container-name list-options))
(apply list-container *blobstore* blobstore args)))
(defn create-container
"Create a container."
([container-name]
(create-container container-name "default" *blobstore*))
([container-name location-name]
(create-container container-name location-name *blobstore*))
([container-name location-name blobstore]
(.createContainerInLocation blobstore location-name container-name)))
(defn clear-container
"Clear a container."
([container-name]
(clear-container container-name *blobstore*))
([container-name blobstore]
(.clearContainer blobstore container-name)))
(defn delete-container
"Delete a container."
([container-name]
(delete-container container-name *blobstore*))
([container-name blobstore]
(.deleteContainer blobstore container-name)))
(defn container-exists?
"Predicate to check presence of a container"
([container-name]
(container-exists? container-name *blobstore*))
([container-name blobstore]
(.containerExists blobstore container-name)))
(defn directory-exists?
"Predicate to check presence of a directory"
([container-name path]
(directory-exists? container-name path *blobstore*))
([container-name path blobstore]
(.directoryExists blobstore container-name path)))
(defn create-directory
"Create a directory path."
([container-name path]
(create-directory container-name path *blobstore*))
([container-name path blobstore]
(.createDirectory blobstore container-name path)))
(defn delete-directory
"Delete a directory path."
([container-name path]
(delete-directory container-name path *blobstore*))
([container-name path blobstore]
(.deleteDirectory blobstore container-name path)))
(defn blob-exists?
"Predicate to check presence of a blob"
([container-name path]
(blob-exists? container-name path *blobstore*))
([container-name path blobstore]
(.blobExists blobstore container-name path)))
(defn put-blob
"Put a blob. Metadata in the blob determines location."
([container-name blob]
(put-blob container-name blob *blobstore*))
([container-name blob blobstore]
(.putBlob blobstore container-name blob)))
(defn blob-metadata
"Get blob metadata from given path"
([container-name path]
(blob-metadata container-name path *blobstore*))
([container-name path blobstore]
(.blobMetadata blobstore container-name path)))
(defn get-blob
"Get blob from given path"
([container-name path]
(get-blob container-name path *blobstore*))
([container-name path blobstore]
(.getBlob blobstore container-name path)))
(defn remove-blob
"Remove blob from given path"
([container-name path]
(remove-blob container-name path *blobstore*))
([container-name path blobstore]
(.removeBlob blobstore container-name path)))
(defn count-blobs
"Count blobs"
([container-name]
(count-blobs container-name *blobstore*))
([container-name blobstore]
(.countBlob blobstore container-name)))
(defn blobs
"List the blobs in a container:
blobstore container -> blobs
list the blobs in a container under a path:
blobstore container dir -> blobs
example:
(pprint
(blobs
(blobstore-context flightcaster-creds)
\"somecontainer\" \"some-dir\"))"
([blobstore container-name]
(.list (as-blobstore blobstore) container-name))
([blobstore container-name dir]
(.list (as-blobstore blobstore) container-name
(.inDirectory (new ListContainerOptions) dir))))
(defn upload-blob
"Create an blob representing text data:
container, name, string -> etag"
([container-name name data] ;; TODO: allow payload to be a stream
(upload-blob container-name name data *blobstore*))
([container-name name data blobstore]
(put-blob container-name
(doto (.newBlob blobstore name)
(.setPayload data)
(.generateMD5))
blobstore)))
(defmulti #^{:arglists '[[container-name name target]
[container-name name target blobstore]]}
download-blob (fn [& args]
(if (= (count args) 3)
::short-form
(class (last (butlast args))))))
(defmethod download-blob ::short-form
[container-name name target]
(download-blob container-name name target *blobstore*))
(defmethod download-blob OutputStream [container-name name target blobstore
& [retries]]
(let [blob (get-blob container-name name blobstore)
digest-stream (.md5OutputStream ;; TODO: not all clouds use MD5
*encryption-service* target)]
(io/copy (.getContent blob) digest-stream)
(let [digest (.getMD5 digest-stream)
metadata-digest (.getContentMD5 (.getMetadata blob))]
(when-not (Arrays/equals digest metadata-digest)
(if (<= (or retries 0) *max-retries*)
(recur container-name name target blobstore [(inc (or retries 1))])
(throw (Exception. (format "Download failed for %s/%s"
container-name name))))))))
(defmethod download-blob File [container-name name target blobstore]
(download-blob container-name name (FileOutputStream. target) blobstore))
(define-accessors StorageMetadata "blob" type id name
location-id uri last-modfied)
(define-accessors BlobMetadata "blob" content-type)
(defn blob-etag [blob]
(.getETag blob))
(defn blob-md5 [blob]
(.getContentMD5 blob))