/* gridfs.c */ /* Copyright 2009-2012 10gen Inc. * * 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. */ #include "gridfs.h" #include #include #include #include MONGO_EXPORT gridfs* gridfs_create() { return (gridfs*)bson_malloc(sizeof(gridfs)); } MONGO_EXPORT void gridfs_dispose(gridfs* gfs) { free(gfs); } MONGO_EXPORT gridfile* gridfile_create() { return (gridfile*)bson_malloc(sizeof(gridfile)); } MONGO_EXPORT void gridfile_dispose(gridfile* gf) { free(gf); } MONGO_EXPORT void gridfile_get_descriptor(gridfile* gf, bson* out) { *out = *gf->meta; } static bson *chunk_new( bson_oid_t id, int chunkNumber, const char *data, int len ) { bson *b = bson_malloc( sizeof( bson ) ); bson_init( b ); bson_append_oid( b, "files_id", &id ); bson_append_int( b, "n", chunkNumber ); bson_append_binary( b, "data", BSON_BIN_BINARY, data, len ); bson_finish( b ); return b; } static void chunk_free( bson *oChunk ) { bson_destroy( oChunk ); bson_free( oChunk ); } int gridfs_init( mongo *client, const char *dbname, const char *prefix, gridfs *gfs ) { int options; bson b; bson_bool_t success; gfs->client = client; /* Allocate space to own the dbname */ gfs->dbname = ( const char * )bson_malloc( strlen( dbname )+1 ); strcpy( ( char * )gfs->dbname, dbname ); /* Allocate space to own the prefix */ if ( prefix == NULL ) prefix = "fs"; gfs->prefix = ( const char * )bson_malloc( strlen( prefix )+1 ); strcpy( ( char * )gfs->prefix, prefix ); /* Allocate space to own files_ns */ gfs->files_ns = ( const char * ) bson_malloc ( strlen( prefix )+strlen( dbname )+strlen( ".files" )+2 ); strcpy( ( char * )gfs->files_ns, dbname ); strcat( ( char * )gfs->files_ns, "." ); strcat( ( char * )gfs->files_ns, prefix ); strcat( ( char * )gfs->files_ns, ".files" ); /* Allocate space to own chunks_ns */ gfs->chunks_ns = ( const char * ) bson_malloc( strlen( prefix ) + strlen( dbname ) + strlen( ".chunks" ) + 2 ); strcpy( ( char * )gfs->chunks_ns, dbname ); strcat( ( char * )gfs->chunks_ns, "." ); strcat( ( char * )gfs->chunks_ns, prefix ); strcat( ( char * )gfs->chunks_ns, ".chunks" ); bson_init( &b ); bson_append_int( &b, "filename", 1 ); bson_finish( &b ); options = 0; success = ( mongo_create_index( gfs->client, gfs->files_ns, &b, options, NULL ) == MONGO_OK ); bson_destroy( &b ); if ( !success ) { bson_free( ( char * )gfs->dbname ); bson_free( ( char * )gfs->prefix ); bson_free( ( char * )gfs->files_ns ); bson_free( ( char * )gfs->chunks_ns ); return MONGO_ERROR; } bson_init( &b ); bson_append_int( &b, "files_id", 1 ); bson_append_int( &b, "n", 1 ); bson_finish( &b ); options = MONGO_INDEX_UNIQUE; success = ( mongo_create_index( gfs->client, gfs->chunks_ns, &b, options, NULL ) == MONGO_OK ); bson_destroy( &b ); if ( !success ) { bson_free( ( char * )gfs->dbname ); bson_free( ( char * )gfs->prefix ); bson_free( ( char * )gfs->files_ns ); bson_free( ( char * )gfs->chunks_ns ); return MONGO_ERROR; } return MONGO_OK; } MONGO_EXPORT void gridfs_destroy( gridfs *gfs ) { if ( gfs == NULL ) return; if ( gfs->dbname ) bson_free( ( char * )gfs->dbname ); if ( gfs->prefix ) bson_free( ( char * )gfs->prefix ); if ( gfs->files_ns ) bson_free( ( char * )gfs->files_ns ); if ( gfs->chunks_ns ) bson_free( ( char * )gfs->chunks_ns ); } static int gridfs_insert_file( gridfs *gfs, const char *name, const bson_oid_t id, gridfs_offset length, const char *contenttype ) { bson command; bson ret; bson res; bson_iterator it; int result; int64_t d; /* Check run md5 */ bson_init( &command ); bson_append_oid( &command, "filemd5", &id ); bson_append_string( &command, "root", gfs->prefix ); bson_finish( &command ); result = mongo_run_command( gfs->client, gfs->dbname, &command, &res ); bson_destroy( &command ); if (result != MONGO_OK) return result; /* Create and insert BSON for file metadata */ bson_init( &ret ); bson_append_oid( &ret, "_id", &id ); if ( name != NULL && *name != '\0' ) { bson_append_string( &ret, "filename", name ); } bson_append_long( &ret, "length", length ); bson_append_int( &ret, "chunkSize", DEFAULT_CHUNK_SIZE ); d = ( bson_date_t )1000*time( NULL ); bson_append_date( &ret, "uploadDate", d); bson_find( &it, &res, "md5" ); bson_append_string( &ret, "md5", bson_iterator_string( &it ) ); bson_destroy( &res ); if ( contenttype != NULL && *contenttype != '\0' ) { bson_append_string( &ret, "contentType", contenttype ); } bson_finish( &ret ); result = mongo_insert( gfs->client, gfs->files_ns, &ret, NULL ); bson_destroy( &ret ); return result; } MONGO_EXPORT int gridfs_store_buffer( gridfs *gfs, const char *data, gridfs_offset length, const char *remotename, const char *contenttype ) { char const *end = data + length; const char *data_ptr = data; bson_oid_t id; int chunkNumber = 0; int chunkLen; bson *oChunk; /* Large files Assertion */ /* assert( length <= 0xffffffff ); */ /* Generate and append an oid*/ bson_oid_gen( &id ); /* Insert the file's data chunk by chunk */ while ( data_ptr < end ) { chunkLen = DEFAULT_CHUNK_SIZE < ( unsigned int )( end - data_ptr ) ? DEFAULT_CHUNK_SIZE : ( unsigned int )( end - data_ptr ); oChunk = chunk_new( id, chunkNumber, data_ptr, chunkLen ); mongo_insert( gfs->client, gfs->chunks_ns, oChunk, NULL ); chunk_free( oChunk ); chunkNumber++; data_ptr += chunkLen; } /* Inserts file's metadata */ return gridfs_insert_file( gfs, remotename, id, length, contenttype ); } MONGO_EXPORT void gridfile_writer_init( gridfile *gfile, gridfs *gfs, const char *remote_name, const char *content_type ) { gfile->gfs = gfs; bson_oid_gen( &( gfile->id ) ); gfile->chunk_num = 0; gfile->length = 0; gfile->pending_len = 0; gfile->pending_data = NULL; gfile->remote_name = ( char * )bson_malloc( strlen( remote_name ) + 1 ); strcpy( ( char * )gfile->remote_name, remote_name ); gfile->content_type = ( char * )bson_malloc( strlen( content_type ) + 1 ); strcpy( ( char * )gfile->content_type, content_type ); } MONGO_EXPORT void gridfile_write_buffer( gridfile *gfile, const char *data, gridfs_offset length ) { int bytes_left = 0; int data_partial_len = 0; int chunks_to_write = 0; char *buffer; bson *oChunk; gridfs_offset to_write = length + gfile->pending_len; if ( to_write < DEFAULT_CHUNK_SIZE ) { /* Less than one chunk to write */ if( gfile->pending_data ) { gfile->pending_data = ( char * )bson_realloc( ( void * )gfile->pending_data, gfile->pending_len + to_write ); memcpy( gfile->pending_data + gfile->pending_len, data, length ); } else if ( to_write > 0 ) { gfile->pending_data = ( char * )bson_malloc( to_write ); memcpy( gfile->pending_data, data, length ); } gfile->pending_len += length; } else { /* At least one chunk of data to write */ chunks_to_write = to_write / DEFAULT_CHUNK_SIZE; bytes_left = to_write % DEFAULT_CHUNK_SIZE; /* If there's a pending chunk to be written, we need to combine * the buffer provided up to DEFAULT_CHUNK_SIZE. */ if ( gfile->pending_len > 0 ) { data_partial_len = DEFAULT_CHUNK_SIZE - gfile->pending_len; buffer = ( char * )bson_malloc( DEFAULT_CHUNK_SIZE ); memcpy( buffer, gfile->pending_data, gfile->pending_len ); memcpy( buffer + gfile->pending_len, data, data_partial_len ); oChunk = chunk_new( gfile->id, gfile->chunk_num, buffer, DEFAULT_CHUNK_SIZE ); mongo_insert( gfile->gfs->client, gfile->gfs->chunks_ns, oChunk, NULL ); chunk_free( oChunk ); gfile->chunk_num++; gfile->length += DEFAULT_CHUNK_SIZE; data += data_partial_len; chunks_to_write--; bson_free( buffer ); } while( chunks_to_write > 0 ) { oChunk = chunk_new( gfile->id, gfile->chunk_num, data, DEFAULT_CHUNK_SIZE ); mongo_insert( gfile->gfs->client, gfile->gfs->chunks_ns, oChunk, NULL ); chunk_free( oChunk ); gfile->chunk_num++; chunks_to_write--; gfile->length += DEFAULT_CHUNK_SIZE; data += DEFAULT_CHUNK_SIZE; } bson_free( gfile->pending_data ); /* If there are any leftover bytes, store them as pending data. */ if( bytes_left == 0 ) gfile->pending_data = NULL; else { gfile->pending_data = ( char * )bson_malloc( bytes_left ); memcpy( gfile->pending_data, data, bytes_left ); } gfile->pending_len = bytes_left; } } MONGO_EXPORT int gridfile_writer_done( gridfile *gfile ) { /* write any remaining pending chunk data. * pending data will always take up less than one chunk */ bson *oChunk; int response; if( gfile->pending_data ) { oChunk = chunk_new( gfile->id, gfile->chunk_num, gfile->pending_data, gfile->pending_len ); mongo_insert( gfile->gfs->client, gfile->gfs->chunks_ns, oChunk, NULL ); chunk_free( oChunk ); bson_free( gfile->pending_data ); gfile->length += gfile->pending_len; } /* insert into files collection */ response = gridfs_insert_file( gfile->gfs, gfile->remote_name, gfile->id, gfile->length, gfile->content_type ); bson_free( gfile->remote_name ); bson_free( gfile->content_type ); return response; } int gridfs_store_file( gridfs *gfs, const char *filename, const char *remotename, const char *contenttype ) { char buffer[DEFAULT_CHUNK_SIZE]; FILE *fd; bson_oid_t id; int chunkNumber = 0; gridfs_offset length = 0; gridfs_offset chunkLen = 0; bson *oChunk; /* Open the file and the correct stream */ if ( strcmp( filename, "-" ) == 0 ) fd = stdin; else { fd = fopen( filename, "rb" ); if (fd == NULL) return MONGO_ERROR; } /* Generate and append an oid*/ bson_oid_gen( &id ); /* Insert the file chunk by chunk */ chunkLen = fread( buffer, 1, DEFAULT_CHUNK_SIZE, fd ); do { oChunk = chunk_new( id, chunkNumber, buffer, chunkLen ); mongo_insert( gfs->client, gfs->chunks_ns, oChunk, NULL ); chunk_free( oChunk ); length += chunkLen; chunkNumber++; chunkLen = fread( buffer, 1, DEFAULT_CHUNK_SIZE, fd ); } while ( chunkLen != 0 ); /* Close the file stream */ if ( fd != stdin ) fclose( fd ); /* Large files Assertion */ /* assert(length <= 0xffffffff); */ /* Optional Remote Name */ if ( remotename == NULL || *remotename == '\0' ) { remotename = filename; } /* Inserts file's metadata */ return gridfs_insert_file( gfs, remotename, id, length, contenttype ); } MONGO_EXPORT void gridfs_remove_filename( gridfs *gfs, const char *filename ) { bson query; mongo_cursor *files; bson file; bson_iterator it; bson_oid_t id; bson b; bson_init( &query ); bson_append_string( &query, "filename", filename ); bson_finish( &query ); files = mongo_find( gfs->client, gfs->files_ns, &query, NULL, 0, 0, 0 ); bson_destroy( &query ); /* Remove each file and it's chunks from files named filename */ while ( mongo_cursor_next( files ) == MONGO_OK ) { file = files->current; bson_find( &it, &file, "_id" ); id = *bson_iterator_oid( &it ); /* Remove the file with the specified id */ bson_init( &b ); bson_append_oid( &b, "_id", &id ); bson_finish( &b ); mongo_remove( gfs->client, gfs->files_ns, &b, NULL ); bson_destroy( &b ); /* Remove all chunks from the file with the specified id */ bson_init( &b ); bson_append_oid( &b, "files_id", &id ); bson_finish( &b ); mongo_remove( gfs->client, gfs->chunks_ns, &b, NULL ); bson_destroy( &b ); } mongo_cursor_destroy( files ); } int gridfs_find_query( gridfs *gfs, bson *query, gridfile *gfile ) { bson uploadDate; bson finalQuery; bson out; int i; bson_init( &uploadDate ); bson_append_int( &uploadDate, "uploadDate", -1 ); bson_finish( &uploadDate ); bson_init( &finalQuery ); bson_append_bson( &finalQuery, "query", query ); bson_append_bson( &finalQuery, "orderby", &uploadDate ); bson_finish( &finalQuery ); i = ( mongo_find_one( gfs->client, gfs->files_ns, &finalQuery, NULL, &out ) == MONGO_OK ); bson_destroy( &uploadDate ); bson_destroy( &finalQuery ); if ( !i ) return MONGO_ERROR; else { gridfile_init( gfs, &out, gfile ); bson_destroy( &out ); return MONGO_OK; } } int gridfs_find_filename( gridfs *gfs, const char *filename, gridfile *gfile ) { bson query; int i; bson_init( &query ); bson_append_string( &query, "filename", filename ); bson_finish( &query ); i = gridfs_find_query( gfs, &query, gfile ); bson_destroy( &query ); return i; } int gridfile_init( gridfs *gfs, bson *meta, gridfile *gfile ) { gfile->gfs = gfs; gfile->pos = 0; gfile->meta = ( bson * )bson_malloc( sizeof( bson ) ); if ( gfile->meta == NULL ) return MONGO_ERROR; bson_copy( gfile->meta, meta ); return MONGO_OK; } MONGO_EXPORT void gridfile_destroy( gridfile *gfile ) { bson_destroy( gfile->meta ); bson_free( gfile->meta ); } bson_bool_t gridfile_exists( gridfile *gfile ) { return ( bson_bool_t )( gfile != NULL || gfile->meta == NULL ); } MONGO_EXPORT const char *gridfile_get_filename( gridfile *gfile ) { bson_iterator it; bson_find( &it, gfile->meta, "filename" ); return bson_iterator_string( &it ); } MONGO_EXPORT int gridfile_get_chunksize( gridfile *gfile ) { bson_iterator it; bson_find( &it, gfile->meta, "chunkSize" ); return bson_iterator_int( &it ); } MONGO_EXPORT gridfs_offset gridfile_get_contentlength( gridfile *gfile ) { bson_iterator it; bson_find( &it, gfile->meta, "length" ); if( bson_iterator_type( &it ) == BSON_INT ) return ( gridfs_offset )bson_iterator_int( &it ); else return ( gridfs_offset )bson_iterator_long( &it ); } MONGO_EXPORT const char *gridfile_get_contenttype( gridfile *gfile ) { bson_iterator it; if ( bson_find( &it, gfile->meta, "contentType" ) ) return bson_iterator_string( &it ); else return NULL; } MONGO_EXPORT bson_date_t gridfile_get_uploaddate( gridfile *gfile ) { bson_iterator it; bson_find( &it, gfile->meta, "uploadDate" ); return bson_iterator_date( &it ); } MONGO_EXPORT const char *gridfile_get_md5( gridfile *gfile ) { bson_iterator it; bson_find( &it, gfile->meta, "md5" ); return bson_iterator_string( &it ); } const char *gridfile_get_field( gridfile *gfile, const char *name ) { bson_iterator it; bson_find( &it, gfile->meta, name ); return bson_iterator_value( &it ); } bson_bool_t gridfile_get_boolean( gridfile *gfile, const char *name ) { bson_iterator it; bson_find( &it, gfile->meta, name ); return bson_iterator_bool( &it ); } MONGO_EXPORT void gridfile_get_metadata( gridfile *gfile, bson* out ) { bson_iterator it; if ( bson_find( &it, gfile->meta, "metadata" ) ) bson_iterator_subobject( &it, out ); else bson_empty( out ); } MONGO_EXPORT int gridfile_get_numchunks( gridfile *gfile ) { bson_iterator it; gridfs_offset length; gridfs_offset chunkSize; double numchunks; bson_find( &it, gfile->meta, "length" ); if( bson_iterator_type( &it ) == BSON_INT ) length = ( gridfs_offset )bson_iterator_int( &it ); else length = ( gridfs_offset )bson_iterator_long( &it ); bson_find( &it, gfile->meta, "chunkSize" ); chunkSize = bson_iterator_int( &it ); numchunks = ( ( double )length/( double )chunkSize ); return ( numchunks - ( int )numchunks > 0 ) ? ( int )( numchunks+1 ) : ( int )( numchunks ); } MONGO_EXPORT void gridfile_get_chunk( gridfile *gfile, int n, bson* out ) { bson query; bson_iterator it; bson_oid_t id; int result; bson_init( &query ); bson_find( &it, gfile->meta, "_id" ); id = *bson_iterator_oid( &it ); bson_append_oid( &query, "files_id", &id ); bson_append_int( &query, "n", n ); bson_finish( &query ); result = (mongo_find_one(gfile->gfs->client, gfile->gfs->chunks_ns, &query, NULL, out ) == MONGO_OK ); bson_destroy( &query ); if (!result) { bson empty; bson_empty(&empty); bson_copy(out, &empty); } } MONGO_EXPORT mongo_cursor *gridfile_get_chunks( gridfile *gfile, int start, int size ) { bson_iterator it; bson_oid_t id; bson gte; bson query; bson orderby; bson command; mongo_cursor *cursor; bson_find( &it, gfile->meta, "_id" ); id = *bson_iterator_oid( &it ); bson_init( &query ); bson_append_oid( &query, "files_id", &id ); if ( size == 1 ) { bson_append_int( &query, "n", start ); } else { bson_init( >e ); bson_append_int( >e, "$gte", start ); bson_finish( >e ); bson_append_bson( &query, "n", >e ); bson_destroy( >e ); } bson_finish( &query ); bson_init( &orderby ); bson_append_int( &orderby, "n", 1 ); bson_finish( &orderby ); bson_init( &command ); bson_append_bson( &command, "query", &query ); bson_append_bson( &command, "orderby", &orderby ); bson_finish( &command ); cursor = mongo_find( gfile->gfs->client, gfile->gfs->chunks_ns, &command, NULL, size, 0, 0 ); bson_destroy( &command ); bson_destroy( &query ); bson_destroy( &orderby ); return cursor; } gridfs_offset gridfile_write_file( gridfile *gfile, FILE *stream ) { int i; size_t len; bson chunk; bson_iterator it; const char *data; const int num = gridfile_get_numchunks( gfile ); for ( i=0; ipos < size ) ? contentlength - gfile->pos : size; bytes_left = size; first_chunk = ( gfile->pos )/chunksize; last_chunk = ( gfile->pos+size-1 )/chunksize; total_chunks = last_chunk - first_chunk + 1; chunks = gridfile_get_chunks( gfile, first_chunk, total_chunks ); for ( i = 0; i < total_chunks; i++ ) { mongo_cursor_next( chunks ); chunk = chunks->current; bson_find( &it, &chunk, "data" ); chunk_len = bson_iterator_bin_len( &it ); chunk_data = bson_iterator_bin_data( &it ); if ( i == 0 ) { chunk_data += ( gfile->pos )%chunksize; chunk_len -= ( gfile->pos )%chunksize; } if ( bytes_left > chunk_len ) { memcpy( buf, chunk_data, chunk_len ); bytes_left -= chunk_len; buf += chunk_len; } else { memcpy( buf, chunk_data, bytes_left ); } } mongo_cursor_destroy( chunks ); gfile->pos = gfile->pos + size; return size; } MONGO_EXPORT gridfs_offset gridfile_seek( gridfile *gfile, gridfs_offset offset ) { gridfs_offset length; length = gridfile_get_contentlength( gfile ); gfile->pos = length < offset ? length : offset; return gfile->pos; }