1 /*
2 * Copyright 2013 MongoDB Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18 #undef MONGOC_LOG_DOMAIN
19 #define MONGOC_LOG_DOMAIN "gridfs"
20
21 #include "mongoc-bulk-operation.h"
22 #include "mongoc-client-private.h"
23 #include "mongoc-collection.h"
24 #include "mongoc-collection-private.h"
25 #include "mongoc-error.h"
26 #include "mongoc-index.h"
27 #include "mongoc-gridfs.h"
28 #include "mongoc-gridfs-private.h"
29 #include "mongoc-gridfs-file.h"
30 #include "mongoc-gridfs-file-private.h"
31 #include "mongoc-gridfs-file-list.h"
32 #include "mongoc-gridfs-file-list-private.h"
33 #include "mongoc-client.h"
34 #include "mongoc-trace-private.h"
35 #include "mongoc-cursor-private.h"
36 #include "mongoc-util-private.h"
37
38 #define MONGOC_GRIDFS_STREAM_CHUNK 4096
39
40
41 /**
42 * _mongoc_gridfs_ensure_index:
43 *
44 * ensure gridfs indexes
45 *
46 * Ensure fast searches for chunks via [ files_id, n ]
47 * Ensure fast searches for files via [ filename ]
48 */
49 static bool
_mongoc_gridfs_ensure_index(mongoc_gridfs_t * gridfs,bson_error_t * error)50 _mongoc_gridfs_ensure_index (mongoc_gridfs_t *gridfs, bson_error_t *error)
51 {
52 bson_t keys;
53 bson_t opts;
54 bool r;
55
56 ENTRY;
57
58 bson_init (&opts);
59 BSON_APPEND_BOOL (&opts, "unique", true);
60
61 bson_init (&keys);
62
63 BSON_APPEND_INT32 (&keys, "files_id", 1);
64 BSON_APPEND_INT32 (&keys, "n", 1);
65
66 r = _mongoc_collection_create_index_if_not_exists (
67 gridfs->chunks, &keys, &opts, error);
68
69 bson_destroy (&opts);
70 bson_destroy (&keys);
71
72 if (!r) {
73 RETURN (r);
74 }
75
76 bson_init (&keys);
77
78 BSON_APPEND_INT32 (&keys, "filename", 1);
79 BSON_APPEND_INT32 (&keys, "uploadDate", 1);
80
81 r = _mongoc_collection_create_index_if_not_exists (
82 gridfs->files, &keys, NULL, error);
83
84 bson_destroy (&keys);
85
86 if (!r) {
87 RETURN (r);
88 }
89
90 RETURN (1);
91 }
92
93
94 mongoc_gridfs_t *
_mongoc_gridfs_new(mongoc_client_t * client,const char * db,const char * prefix,bson_error_t * error)95 _mongoc_gridfs_new (mongoc_client_t *client,
96 const char *db,
97 const char *prefix,
98 bson_error_t *error)
99 {
100 mongoc_gridfs_t *gridfs;
101 char buf[128];
102 bool r;
103 uint32_t prefix_len;
104
105 ENTRY;
106
107 BSON_ASSERT (client);
108 BSON_ASSERT (db);
109
110 if (!prefix) {
111 prefix = "fs";
112 }
113
114 /* make sure prefix is short enough to bucket the chunks and files
115 * collections
116 */
117 prefix_len = (uint32_t) strlen (prefix);
118 BSON_ASSERT (prefix_len + sizeof (".chunks") < sizeof (buf));
119
120 gridfs = (mongoc_gridfs_t *) bson_malloc0 (sizeof *gridfs);
121
122 gridfs->client = client;
123
124 bson_snprintf (buf, sizeof (buf), "%s.chunks", prefix);
125 gridfs->chunks = mongoc_client_get_collection (client, db, buf);
126
127 bson_snprintf (buf, sizeof (buf), "%s.files", prefix);
128 gridfs->files = mongoc_client_get_collection (client, db, buf);
129
130 r = _mongoc_gridfs_ensure_index (gridfs, error);
131
132 if (!r) {
133 mongoc_gridfs_destroy (gridfs);
134 RETURN (NULL);
135 }
136
137 RETURN (gridfs);
138 }
139
140
141 bool
mongoc_gridfs_drop(mongoc_gridfs_t * gridfs,bson_error_t * error)142 mongoc_gridfs_drop (mongoc_gridfs_t *gridfs, bson_error_t *error)
143 {
144 bool r;
145
146 ENTRY;
147
148 r = mongoc_collection_drop (gridfs->files, error);
149 if (!r) {
150 RETURN (0);
151 }
152
153 r = mongoc_collection_drop (gridfs->chunks, error);
154 if (!r) {
155 RETURN (0);
156 }
157
158 RETURN (1);
159 }
160
161
162 void
mongoc_gridfs_destroy(mongoc_gridfs_t * gridfs)163 mongoc_gridfs_destroy (mongoc_gridfs_t *gridfs)
164 {
165 ENTRY;
166
167 if (!gridfs) {
168 EXIT;
169 }
170
171 mongoc_collection_destroy (gridfs->files);
172 mongoc_collection_destroy (gridfs->chunks);
173
174 bson_free (gridfs);
175
176 EXIT;
177 }
178
179
180 /** find all matching gridfs files */
181 mongoc_gridfs_file_list_t *
mongoc_gridfs_find(mongoc_gridfs_t * gridfs,const bson_t * query)182 mongoc_gridfs_find (mongoc_gridfs_t *gridfs, const bson_t *query)
183 {
184 return _mongoc_gridfs_file_list_new (gridfs, query, 0);
185 }
186
187
188 /** find a single gridfs file */
189 mongoc_gridfs_file_t *
mongoc_gridfs_find_one(mongoc_gridfs_t * gridfs,const bson_t * query,bson_error_t * error)190 mongoc_gridfs_find_one (mongoc_gridfs_t *gridfs,
191 const bson_t *query,
192 bson_error_t *error)
193 {
194 mongoc_gridfs_file_list_t *list;
195 mongoc_gridfs_file_t *file;
196
197 ENTRY;
198
199 list = _mongoc_gridfs_file_list_new (gridfs, query, 1);
200
201 file = mongoc_gridfs_file_list_next (list);
202 if (!mongoc_gridfs_file_list_error (list, error) && error) {
203 /* no error, but an error out-pointer was provided - clear it */
204 memset (error, 0, sizeof (*error));
205 }
206
207 mongoc_gridfs_file_list_destroy (list);
208
209 RETURN (file);
210 }
211
212
213 /** find all matching gridfs files */
214 mongoc_gridfs_file_list_t *
mongoc_gridfs_find_with_opts(mongoc_gridfs_t * gridfs,const bson_t * filter,const bson_t * opts)215 mongoc_gridfs_find_with_opts (mongoc_gridfs_t *gridfs,
216 const bson_t *filter,
217 const bson_t *opts)
218 {
219 return _mongoc_gridfs_file_list_new_with_opts (gridfs, filter, opts);
220 }
221
222
223 /** find a single gridfs file */
224 mongoc_gridfs_file_t *
mongoc_gridfs_find_one_with_opts(mongoc_gridfs_t * gridfs,const bson_t * filter,const bson_t * opts,bson_error_t * error)225 mongoc_gridfs_find_one_with_opts (mongoc_gridfs_t *gridfs,
226 const bson_t *filter,
227 const bson_t *opts,
228 bson_error_t *error)
229 {
230 mongoc_gridfs_file_list_t *list;
231 mongoc_gridfs_file_t *file;
232 bson_t new_opts;
233
234 ENTRY;
235
236 bson_init (&new_opts);
237
238 if (opts) {
239 bson_copy_to_excluding_noinit (opts, &new_opts, "limit", (char *) NULL);
240 }
241
242 BSON_APPEND_INT32 (&new_opts, "limit", 1);
243
244 list = _mongoc_gridfs_file_list_new_with_opts (gridfs, filter, &new_opts);
245 file = mongoc_gridfs_file_list_next (list);
246
247 if (!mongoc_gridfs_file_list_error (list, error) && error) {
248 /* no error, but an error out-pointer was provided - clear it */
249 memset (error, 0, sizeof (*error));
250 }
251
252 mongoc_gridfs_file_list_destroy (list);
253 bson_destroy (&new_opts);
254
255 RETURN (file);
256 }
257
258
259 /** find a single gridfs file by filename */
260 mongoc_gridfs_file_t *
mongoc_gridfs_find_one_by_filename(mongoc_gridfs_t * gridfs,const char * filename,bson_error_t * error)261 mongoc_gridfs_find_one_by_filename (mongoc_gridfs_t *gridfs,
262 const char *filename,
263 bson_error_t *error)
264 {
265 mongoc_gridfs_file_t *file;
266
267 bson_t filter;
268
269 bson_init (&filter);
270
271 bson_append_utf8 (&filter, "filename", -1, filename, -1);
272
273 file = mongoc_gridfs_find_one_with_opts (gridfs, &filter, NULL, error);
274
275 bson_destroy (&filter);
276
277 return file;
278 }
279
280
281 /** create a gridfs file from a stream
282 *
283 * The stream is fully consumed in creating the file
284 */
285 mongoc_gridfs_file_t *
mongoc_gridfs_create_file_from_stream(mongoc_gridfs_t * gridfs,mongoc_stream_t * stream,mongoc_gridfs_file_opt_t * opt)286 mongoc_gridfs_create_file_from_stream (mongoc_gridfs_t *gridfs,
287 mongoc_stream_t *stream,
288 mongoc_gridfs_file_opt_t *opt)
289 {
290 mongoc_gridfs_file_t *file;
291 ssize_t r;
292 uint8_t buf[MONGOC_GRIDFS_STREAM_CHUNK];
293 mongoc_iovec_t iov;
294 int timeout;
295
296 ENTRY;
297
298 BSON_ASSERT (gridfs);
299 BSON_ASSERT (stream);
300
301 iov.iov_base = (void *) buf;
302 iov.iov_len = 0;
303
304 file = _mongoc_gridfs_file_new (gridfs, opt);
305 timeout = gridfs->client->cluster.sockettimeoutms;
306
307 for (;;) {
308 r = mongoc_stream_read (
309 stream, iov.iov_base, MONGOC_GRIDFS_STREAM_CHUNK, 0, timeout);
310
311 if (r > 0) {
312 iov.iov_len = r;
313 if (mongoc_gridfs_file_writev (file, &iov, 1, timeout) < 0) {
314 MONGOC_ERROR ("%s", file->error.message);
315 mongoc_gridfs_file_destroy (file);
316 RETURN (NULL);
317 }
318 } else if (r == 0) {
319 break;
320 } else {
321 MONGOC_ERROR ("Error reading from GridFS file source stream");
322 mongoc_gridfs_file_destroy (file);
323 RETURN (NULL);
324 }
325 }
326
327 mongoc_stream_failed (stream);
328
329 if (-1 == mongoc_gridfs_file_seek (file, 0, SEEK_SET)) {
330 MONGOC_ERROR ("%s", file->error.message);
331 mongoc_gridfs_file_destroy (file);
332 RETURN (NULL);
333 }
334
335 RETURN (file);
336 }
337
338
339 /** create an empty gridfs file */
340 mongoc_gridfs_file_t *
mongoc_gridfs_create_file(mongoc_gridfs_t * gridfs,mongoc_gridfs_file_opt_t * opt)341 mongoc_gridfs_create_file (mongoc_gridfs_t *gridfs,
342 mongoc_gridfs_file_opt_t *opt)
343 {
344 mongoc_gridfs_file_t *file;
345
346 ENTRY;
347
348 BSON_ASSERT (gridfs);
349
350 file = _mongoc_gridfs_file_new (gridfs, opt);
351
352 RETURN (file);
353 }
354
355 /** accessor functions for collections */
356 mongoc_collection_t *
mongoc_gridfs_get_files(mongoc_gridfs_t * gridfs)357 mongoc_gridfs_get_files (mongoc_gridfs_t *gridfs)
358 {
359 BSON_ASSERT (gridfs);
360
361 return gridfs->files;
362 }
363
364 mongoc_collection_t *
mongoc_gridfs_get_chunks(mongoc_gridfs_t * gridfs)365 mongoc_gridfs_get_chunks (mongoc_gridfs_t *gridfs)
366 {
367 BSON_ASSERT (gridfs);
368
369 return gridfs->chunks;
370 }
371
372
373 bool
mongoc_gridfs_remove_by_filename(mongoc_gridfs_t * gridfs,const char * filename,bson_error_t * error)374 mongoc_gridfs_remove_by_filename (mongoc_gridfs_t *gridfs,
375 const char *filename,
376 bson_error_t *error)
377 {
378 mongoc_bulk_operation_t *bulk_files = NULL;
379 mongoc_bulk_operation_t *bulk_chunks = NULL;
380 mongoc_cursor_t *cursor = NULL;
381 bson_error_t files_error;
382 bson_error_t chunks_error;
383 const bson_t *doc;
384 const char *key;
385 char keybuf[16];
386 int count = 0;
387 bool chunks_ret;
388 bool files_ret;
389 bool ret = false;
390 bson_iter_t iter;
391 bson_t *files_q = NULL;
392 bson_t *chunks_q = NULL;
393 bson_t find_filter = BSON_INITIALIZER;
394 bson_t find_opts = BSON_INITIALIZER;
395 bson_t find_opts_project;
396 bson_t ar = BSON_INITIALIZER;
397 bson_t opts = BSON_INITIALIZER;
398
399 BSON_ASSERT (gridfs);
400
401 if (!filename) {
402 bson_set_error (error,
403 MONGOC_ERROR_GRIDFS,
404 MONGOC_ERROR_GRIDFS_INVALID_FILENAME,
405 "A non-NULL filename must be specified.");
406 return false;
407 }
408
409 /*
410 * Find all files matching this filename. Hopefully just one, but not
411 * strictly required!
412 */
413
414 BSON_APPEND_UTF8 (&find_filter, "filename", filename);
415 BSON_APPEND_DOCUMENT_BEGIN (&find_opts, "projection", &find_opts_project);
416 BSON_APPEND_INT32 (&find_opts_project, "_id", 1);
417 bson_append_document_end (&find_opts, &find_opts_project);
418
419 cursor = _mongoc_cursor_find_new (gridfs->client,
420 gridfs->files->ns,
421 &find_filter,
422 &find_opts,
423 NULL /* user_prefs */,
424 NULL /* default_prefs */,
425 NULL /* read_concern */);
426
427 BSON_ASSERT (cursor);
428
429 while (mongoc_cursor_next (cursor, &doc)) {
430 if (bson_iter_init_find (&iter, doc, "_id")) {
431 const bson_value_t *value = bson_iter_value (&iter);
432
433 bson_uint32_to_string (count, &key, keybuf, sizeof keybuf);
434 BSON_APPEND_VALUE (&ar, key, value);
435 }
436 }
437
438 if (mongoc_cursor_error (cursor, error)) {
439 goto failure;
440 }
441
442 bson_append_bool (&opts, "ordered", 7, false);
443 bulk_files =
444 mongoc_collection_create_bulk_operation_with_opts (gridfs->files, &opts);
445 bulk_chunks =
446 mongoc_collection_create_bulk_operation_with_opts (gridfs->chunks, &opts);
447
448 bson_destroy (&opts);
449
450 files_q = BCON_NEW ("_id", "{", "$in", BCON_ARRAY (&ar), "}");
451 chunks_q = BCON_NEW ("files_id", "{", "$in", BCON_ARRAY (&ar), "}");
452
453 mongoc_bulk_operation_remove (bulk_files, files_q);
454 mongoc_bulk_operation_remove (bulk_chunks, chunks_q);
455
456 files_ret = mongoc_bulk_operation_execute (bulk_files, NULL, &files_error);
457 chunks_ret =
458 mongoc_bulk_operation_execute (bulk_chunks, NULL, &chunks_error);
459
460 if (error) {
461 if (!files_ret) {
462 memcpy (error, &files_error, sizeof *error);
463 } else if (!chunks_ret) {
464 memcpy (error, &chunks_error, sizeof *error);
465 }
466 }
467
468 ret = (files_ret && chunks_ret);
469
470 failure:
471 if (cursor) {
472 mongoc_cursor_destroy (cursor);
473 }
474 if (bulk_files) {
475 mongoc_bulk_operation_destroy (bulk_files);
476 }
477 if (bulk_chunks) {
478 mongoc_bulk_operation_destroy (bulk_chunks);
479 }
480 bson_destroy (&find_filter);
481 bson_destroy (&find_opts);
482 bson_destroy (&ar);
483 if (files_q) {
484 bson_destroy (files_q);
485 }
486 if (chunks_q) {
487 bson_destroy (chunks_q);
488 }
489
490 return ret;
491 }
492