]> git.cworth.org Git - vogl/blob - src/voglcommon/vogl_blob_manager.cpp
Initial vogl checkin
[vogl] / src / voglcommon / vogl_blob_manager.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26 // File: vogl_blob_manager.cpp
27 #include "vogl_common.h"
28 #include "vogl_blob_manager.h"
29 #include "vogl_buffer_stream.h"
30 #include "vogl_cfile_stream.h"
31 #include "vogl_file_utils.h"
32 #include "vogl_find_files.h"
33 #include "vogl_hash.h"
34
35 using namespace vogl;
36
37 //----------------------------------------------------------------------------------------------------------------------
38 // vogl_blob_manager
39 //----------------------------------------------------------------------------------------------------------------------
40 vogl_blob_manager::vogl_blob_manager()
41     : m_flags(0),
42       m_initialized(false)
43 {
44     VOGL_FUNC_TRACER
45 }
46
47 vogl_blob_manager::~vogl_blob_manager()
48 {
49     VOGL_FUNC_TRACER
50
51     deinit();
52 }
53
54 dynamic_string vogl_blob_manager::compute_unique_id(const void *pData, uint size, const dynamic_string &prefix, const dynamic_string &ext, const uint64_t *pCRC64) const
55 {
56     VOGL_FUNC_TRACER
57
58     VOGL_ASSERT(!prefix.contains('['));
59     VOGL_ASSERT(!prefix.contains(']'));
60
61     uint64_t crc64 = pCRC64 ? *pCRC64 : calc_crc64(CRC64_INIT, static_cast<const uint8 *>(pData), size);
62
63     dynamic_string actual_ext(ext);
64     if ((actual_ext.get_len() > 1) && (actual_ext[0] == '.'))
65         actual_ext.right(1);
66
67     if (prefix.get_len())
68         return dynamic_string(cVarArg, "[%s]_%" PRIX64 "_%u.radblob.%s", prefix.get_ptr(), crc64, size, actual_ext.get_len() ? actual_ext.get_ptr() : "raw");
69     else
70         return dynamic_string(cVarArg, "%" PRIX64 "_%u.radblob.%s", crc64, size, actual_ext.get_len() ? actual_ext.get_ptr() : "raw");
71 }
72
73 dynamic_string vogl_blob_manager::get_prefix(const dynamic_string &id) const
74 {
75     VOGL_FUNC_TRACER
76
77     if (!id.is_empty() && id.begins_with("["))
78     {
79         dynamic_string prefix(id);
80         int e = prefix.find_right(']');
81         return prefix.mid(1, e - 1);
82     }
83     return "";
84 }
85
86 dynamic_string vogl_blob_manager::get_extension(const dynamic_string &id) const
87 {
88     VOGL_FUNC_TRACER
89
90     dynamic_string extension(id);
91     file_utils::get_extension(extension);
92     return extension;
93 }
94
95 bool vogl_blob_manager::get(const dynamic_string &id, uint8_vec &data) const
96 {
97     VOGL_FUNC_TRACER
98
99     if (!is_initialized())
100     {
101         VOGL_ASSERT(0);
102         return false;
103     }
104
105     data_stream *pStream = open(id);
106     if (!pStream)
107     {
108         data.resize(0);
109         vogl_error_printf("%s: Failed finding blob ID %s\n", VOGL_METHOD_NAME, id.get_ptr());
110         return false;
111     }
112
113     // TODO
114     if (pStream->get_size() > static_cast<uint64_t>(cINT32_MAX))
115     {
116         close(pStream);
117
118         VOGL_ASSERT_ALWAYS;
119
120         vogl_error_printf("%s: Blob is too large: blob ID %s, size %" PRIu64 "\n", VOGL_METHOD_NAME, id.get_ptr(), pStream->get_size());
121
122         return false;
123     }
124
125     uint32 size = static_cast<uint32>(pStream->get_size());
126
127     if (!data.try_resize(size))
128     {
129         VOGL_ASSERT_ALWAYS;
130         vogl_error_printf("%s: Out of memory while trying to read blob ID %s, size %u\n", VOGL_METHOD_NAME, id.get_ptr(), size);
131         return false;
132     }
133
134     if (size)
135     {
136         if (pStream->read(data.get_ptr(), size) != size)
137         {
138             close(pStream);
139
140             data.clear();
141
142             vogl_error_printf("%s: Failed reading blob ID %s, size %u\n", VOGL_METHOD_NAME, id.get_ptr(), size);
143
144             return false;
145         }
146     }
147
148     close(pStream);
149     return true;
150 }
151
152 bool vogl_blob_manager::populate(const vogl_blob_manager &other)
153 {
154     VOGL_FUNC_TRACER
155
156     if (!is_initialized() || !other.is_initialized())
157     {
158         VOGL_ASSERT(0);
159         return false;
160     }
161
162     dynamic_string_array ids(other.enumerate());
163
164     bool success = true;
165     for (uint i = 0; i < ids.size(); i++)
166     {
167         const dynamic_string &id = ids[i];
168
169         data_stream *pStream = other.open(id);
170         if (!pStream)
171         {
172             success = false;
173             continue;
174         }
175
176         dynamic_string new_id(add_stream_using_id(*pStream, id));
177         if (new_id.is_empty())
178             success = false;
179
180         other.close(pStream);
181     }
182
183     return success;
184 }
185
186 dynamic_string vogl_blob_manager::add_buf_compute_unique_id(const void *pData, uint size, const vogl::dynamic_string &prefix, const dynamic_string &ext, const uint64_t *pCRC64)
187 {
188     VOGL_FUNC_TRACER
189
190     if (!is_initialized() || !is_writable())
191         return "";
192
193     dynamic_string id(compute_unique_id(pData, size, prefix, ext, pCRC64));
194
195     return add_buf_using_id(pData, size, id);
196 }
197
198 dynamic_string vogl_blob_manager::add_stream_compute_unique_id(data_stream &stream, const dynamic_string &prefix, const dynamic_string &ext, const uint64_t *pCRC64)
199 {
200     VOGL_FUNC_TRACER
201
202     if (!is_initialized() || !is_writable())
203     {
204         VOGL_ASSERT(0);
205         return "";
206     }
207
208     // TODO
209     if (stream.get_size() > static_cast<uint64_t>(cUINT32_MAX))
210     {
211         VOGL_VERIFY(0);
212         return "";
213     }
214
215     uint64_t size64 = stream.get_size();
216
217     // TODO
218     if ((size64 > static_cast<uint64_t>(VOGL_MAX_POSSIBLE_HEAP_BLOCK_SIZE)) || (size64 > cUINT32_MAX))
219     {
220         VOGL_VERIFY(0);
221         return "";
222     }
223
224     uint size = static_cast<uint>(size64);
225
226     void *pData = vogl_malloc(static_cast<size_t>(size));
227     if (!pData)
228         return "";
229
230     if (!stream.seek(0, false))
231     {
232         vogl_free(pData);
233         return "";
234     }
235
236     if (stream.read64(pData, size) != size)
237     {
238         vogl_free(pData);
239         return "";
240     }
241
242     dynamic_string id(compute_unique_id(pData, size, prefix, ext, pCRC64));
243
244     dynamic_string actual_id(add_buf_using_id(pData, size, id));
245
246     vogl_free(pData);
247     return actual_id;
248 }
249
250 dynamic_string vogl_blob_manager::add_stream_using_id(data_stream &stream, const dynamic_string &id)
251 {
252     VOGL_FUNC_TRACER
253
254     if (!is_initialized() || !is_writable())
255     {
256         VOGL_ASSERT(0);
257         return "";
258     }
259
260     uint64_t size64 = stream.get_size();
261
262     // TODO
263     if ((size64 > static_cast<uint64_t>(VOGL_MAX_POSSIBLE_HEAP_BLOCK_SIZE)) || (size64 > cUINT32_MAX))
264     {
265         VOGL_VERIFY(0);
266         return "";
267     }
268
269     uint size = static_cast<uint>(size64);
270
271     void *pData = vogl_malloc(static_cast<size_t>(size));
272     if (!pData)
273         return "";
274
275     if (!stream.seek(0, false))
276     {
277         vogl_free(pData);
278         return "";
279     }
280
281     if (stream.read64(pData, size) != size)
282     {
283         vogl_free(pData);
284         return "";
285     }
286
287     dynamic_string actual_id(add_buf_using_id(pData, size, id));
288
289     vogl_free(pData);
290     return actual_id;
291 }
292
293 //----------------------------------------------------------------------------------------------------------------------
294 // vogl_blob_manager::copy_file
295 //----------------------------------------------------------------------------------------------------------------------
296 vogl::dynamic_string vogl_blob_manager::copy_file(vogl_blob_manager &src_blob_manager, const vogl::dynamic_string &src_id, const vogl::dynamic_string &dst_id)
297 {
298     uint8_vec data;
299     if (!src_blob_manager.get(src_id, data))
300         return "";
301
302     return add_buf_using_id(data.get_ptr(), data.size(), dst_id);
303 }
304
305 //----------------------------------------------------------------------------------------------------------------------
306 // vogl_memory_blob_manager
307 //----------------------------------------------------------------------------------------------------------------------
308 vogl_memory_blob_manager::vogl_memory_blob_manager()
309     : vogl_blob_manager()
310 {
311     VOGL_FUNC_TRACER
312 }
313
314 vogl_memory_blob_manager::~vogl_memory_blob_manager()
315 {
316     VOGL_FUNC_TRACER
317
318     deinit();
319 }
320
321 bool vogl_memory_blob_manager::init(uint32 flags)
322 {
323     VOGL_FUNC_TRACER
324
325     deinit();
326
327     if (!vogl_blob_manager::init(flags))
328         return false;
329
330     m_initialized = true;
331     return true;
332 }
333
334 bool vogl_memory_blob_manager::deinit()
335 {
336     VOGL_FUNC_TRACER
337
338     m_blobs.clear();
339     return vogl_blob_manager::deinit();
340 }
341
342 dynamic_string vogl_memory_blob_manager::add_buf_using_id(const void *pData, uint size, const dynamic_string &id)
343 {
344     VOGL_FUNC_TRACER
345
346     if (!is_initialized() || !is_writable())
347     {
348         VOGL_ASSERT(0);
349         return "";
350     }
351
352     dynamic_string actual_id(id);
353     if (actual_id.is_empty())
354         actual_id = compute_unique_id(pData, size);
355
356     blob_map::insert_result insert_res(m_blobs.insert(actual_id));
357     if (!insert_res.second)
358         return (insert_res.first)->first;
359
360     blob &new_blob = (insert_res.first)->second;
361     new_blob.m_id = actual_id;
362     new_blob.m_blob.append(static_cast<const uint8 *>(pData), static_cast<uint32>(size));
363
364     return actual_id;
365 }
366
367 data_stream *vogl_memory_blob_manager::open(const dynamic_string &id) const
368 {
369     VOGL_FUNC_TRACER
370
371     if (!is_initialized() || !is_readable())
372     {
373         VOGL_ASSERT(0);
374         return NULL;
375     }
376
377     const blob *pBlob = m_blobs.find_value(id);
378     if (!pBlob)
379         return NULL;
380
381     return vogl_new(buffer_stream, pBlob->m_blob.get_ptr(), pBlob->m_blob.size());
382 }
383
384 void vogl_memory_blob_manager::close(data_stream *pStream) const
385 {
386     VOGL_FUNC_TRACER
387
388     vogl_delete(pStream);
389 }
390
391 bool vogl_memory_blob_manager::does_exist(const dynamic_string &id) const
392 {
393     VOGL_FUNC_TRACER
394
395     return m_blobs.contains(id);
396 }
397
398 uint64_t vogl_memory_blob_manager::get_size(const dynamic_string &id) const
399 {
400     VOGL_FUNC_TRACER
401
402     const blob *pBlob = m_blobs.find_value(id);
403     return pBlob ? pBlob->m_blob.size() : 0;
404 }
405
406 dynamic_string_array vogl_memory_blob_manager::enumerate() const
407 {
408     VOGL_FUNC_TRACER
409
410     if (!is_initialized())
411     {
412         VOGL_ASSERT(0);
413         return dynamic_string_array();
414     }
415
416     dynamic_string_array ids;
417     return m_blobs.get_keys(ids);
418 }
419
420 //----------------------------------------------------------------------------------------------------------------------
421 // vogl_file_blob_manager
422 //----------------------------------------------------------------------------------------------------------------------
423
424 vogl_loose_file_blob_manager::vogl_loose_file_blob_manager()
425     : vogl_blob_manager()
426 {
427     VOGL_FUNC_TRACER
428 }
429
430 vogl_loose_file_blob_manager::~vogl_loose_file_blob_manager()
431 {
432     VOGL_FUNC_TRACER
433
434     deinit();
435 }
436
437 bool vogl_loose_file_blob_manager::init(uint32 flags)
438 {
439     VOGL_FUNC_TRACER
440
441     deinit();
442
443     if (!vogl_blob_manager::init(flags))
444         return false;
445     m_initialized = true;
446     return true;
447 }
448
449 bool vogl_loose_file_blob_manager::init(uint32 flags, const char *pPath)
450 {
451     VOGL_FUNC_TRACER
452
453     deinit();
454
455     if (!vogl_blob_manager::init(flags))
456         return false;
457     m_path = pPath;
458     m_initialized = true;
459     return true;
460 }
461
462 bool vogl_loose_file_blob_manager::deinit()
463 {
464     VOGL_FUNC_TRACER
465
466     return vogl_blob_manager::deinit();
467 }
468
469 dynamic_string vogl_loose_file_blob_manager::add_buf_using_id(const void *pData, uint size, const dynamic_string &id)
470 {
471     VOGL_FUNC_TRACER
472
473     if (!is_writable())
474     {
475         VOGL_ASSERT(0);
476         return "";
477     }
478
479     dynamic_string actual_id(id);
480     if (actual_id.is_empty())
481         actual_id = compute_unique_id(pData, size);
482
483     dynamic_string filename(get_filename(actual_id));
484
485     if (does_exist(filename))
486     {
487         uint64_t cur_size = get_size(actual_id);
488         if (cur_size != size)
489             vogl_error_printf("%s: Not overwrite already existing blob %s desired size %u, but it has the wrong size on disk (%" PRIu64 " bytes)!\n", VOGL_METHOD_NAME, filename.get_ptr(), size, cur_size);
490         else
491             vogl_message_printf("%s: Not overwriting already existing blob %s size %u\n", VOGL_METHOD_NAME, filename.get_ptr(), size);
492         return actual_id;
493     }
494
495     cfile_stream out_file(filename.get_ptr(), cDataStreamWritable);
496     if (!out_file.is_opened())
497     {
498         vogl_error_printf("%s: Failed creating file \"%s\"!\n", VOGL_METHOD_NAME, filename.get_ptr());
499         return "";
500     }
501
502     if (out_file.write(pData, size) != size)
503     {
504         VOGL_VERIFY(0);
505
506         out_file.close();
507         file_utils::delete_file(filename.get_ptr());
508
509         vogl_error_printf("%s: Failed writing to file \"%s\"!\n", VOGL_METHOD_NAME, filename.get_ptr());
510
511         return "";
512     }
513
514     if (!out_file.close())
515     {
516         VOGL_VERIFY(0);
517
518         file_utils::delete_file(filename.get_ptr());
519
520         vogl_error_printf("%s: Failed writing to file \"%s\"!\n", VOGL_METHOD_NAME, filename.get_ptr());
521
522         return "";
523     }
524
525     return actual_id;
526 }
527
528 data_stream *vogl_loose_file_blob_manager::open(const dynamic_string &id) const
529 {
530     VOGL_FUNC_TRACER
531
532     if (!is_readable())
533     {
534         VOGL_ASSERT(0);
535         return NULL;
536     }
537
538     cfile_stream *pStream = vogl_new(cfile_stream, get_filename(id).get_ptr());
539     if (!pStream->is_opened())
540     {
541         vogl_delete(pStream);
542         return NULL;
543     }
544     return pStream;
545 }
546
547 void vogl_loose_file_blob_manager::close(data_stream *pStream) const
548 {
549     VOGL_FUNC_TRACER
550
551     vogl_delete(pStream);
552 }
553
554 bool vogl_loose_file_blob_manager::does_exist(const dynamic_string &id) const
555 {
556     VOGL_FUNC_TRACER
557
558     if (!is_initialized())
559     {
560         VOGL_ASSERT(0);
561         return false;
562     }
563
564     return file_utils::does_file_exist(get_filename(id).get_ptr());
565 }
566
567 uint64_t vogl_loose_file_blob_manager::get_size(const dynamic_string &id) const
568 {
569     VOGL_FUNC_TRACER
570
571     if (!is_initialized())
572     {
573         VOGL_ASSERT(0);
574         return false;
575     }
576
577     uint32 file_size;
578     file_utils::get_file_size(get_filename(id).get_ptr(), file_size);
579     return file_size;
580 }
581
582 dynamic_string vogl_loose_file_blob_manager::get_filename(const dynamic_string &id) const
583 {
584     VOGL_FUNC_TRACER
585
586     if (!is_initialized())
587     {
588         VOGL_ASSERT(0);
589         return "";
590     }
591
592     dynamic_string file_path;
593     file_utils::combine_path(file_path, m_path.get_ptr(), id.get_ptr());
594     return file_path;
595 }
596
597 dynamic_string_array vogl_loose_file_blob_manager::enumerate() const
598 {
599     VOGL_FUNC_TRACER
600
601     dynamic_string_array files;
602
603     if (!is_initialized())
604     {
605         VOGL_ASSERT(0);
606         return files;
607     }
608
609     dynamic_string find_path(get_filename("*.radblob.*"));
610
611     find_files finder;
612
613     bool success = finder.find(find_path.get_ptr());
614     if (success)
615     {
616         const find_files::file_desc_vec &found_files = finder.get_files();
617
618         for (uint i = 0; i < found_files.size(); i++)
619             files.push_back(found_files[i].m_name);
620     }
621
622     return files;
623 }
624
625 //----------------------------------------------------------------------------------------------------------------------
626 // vogl_archive_blob_manager
627 //----------------------------------------------------------------------------------------------------------------------
628 vogl_archive_blob_manager::vogl_archive_blob_manager()
629     : vogl_blob_manager()
630 {
631     VOGL_FUNC_TRACER
632
633     mz_zip_zero_struct(&m_zip);
634 }
635
636 vogl_archive_blob_manager::~vogl_archive_blob_manager()
637 {
638     VOGL_FUNC_TRACER
639
640     deinit();
641 }
642
643 bool vogl_archive_blob_manager::init_memory(uint32 flags, const void *pZip_data, size_t size)
644 {
645     VOGL_FUNC_TRACER
646
647     deinit();
648
649     if (!vogl_blob_manager::init(flags))
650         return false;
651
652     if (is_writable() || !is_readable())
653     {
654         deinit();
655         return false;
656     }
657
658     if (!mz_zip_reader_init_mem(&m_zip, pZip_data, size, 0))
659     {
660         mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
661         vogl_error_printf("%s: mz_zip_reader_init_mem() failed initing memory size %" PRIu64 ", error 0x%X (%s)\n", VOGL_METHOD_NAME, cast_val_to_uint64(size), mz_err, mz_zip_get_error_string(mz_err));
662
663         deinit();
664         return false;
665     }
666
667     populate_blob_map();
668
669     m_initialized = true;
670
671     return true;
672 }
673
674 bool vogl_archive_blob_manager::init_heap(uint32 flags)
675 {
676     VOGL_FUNC_TRACER
677
678     deinit();
679
680     if (!vogl_blob_manager::init(flags))
681         return false;
682
683     if (!is_writable())
684     {
685         deinit();
686         return false;
687     }
688
689     if (!mz_zip_writer_init_heap(&m_zip, 0, 1024, MZ_ZIP_FLAG_WRITE_ALLOW_READING | MZ_ZIP_FLAG_WRITE_ZIP64))
690     {
691         mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
692         vogl_error_printf("%s: mz_zip_writer_init_heap() failed initing heap with flags 0x%x, error 0x%X (%s)\n", VOGL_METHOD_NAME, flags, mz_err, mz_zip_get_error_string(mz_err));
693
694         deinit();
695         return false;
696     }
697
698     m_initialized = true;
699
700     return true;
701 }
702
703 void *vogl_archive_blob_manager::deinit_heap(size_t &size)
704 {
705     VOGL_FUNC_TRACER
706
707     size = 0;
708
709     if ((mz_zip_get_mode(&m_zip) == MZ_ZIP_MODE_INVALID) || (mz_zip_get_type(&m_zip) != MZ_ZIP_TYPE_HEAP))
710         return NULL;
711
712     void *pBuf = NULL;
713     if (!mz_zip_writer_finalize_heap_archive(&m_zip, &pBuf, &size))
714     {
715         mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
716         vogl_error_printf("%s: mz_zip_writer_finalize_heap_archive() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
717
718         pBuf = NULL;
719     }
720
721     deinit();
722
723     return pBuf;
724 }
725
726 bool vogl_archive_blob_manager::init_file(uint32 flags, const char *pFilename, uint64_t file_start_ofs, uint64_t actual_archive_size)
727 {
728     VOGL_FUNC_TRACER
729
730     deinit();
731
732     if (!vogl_blob_manager::init(flags))
733         return false;
734
735     m_archive_filename = pFilename;
736
737     // read existing
738     // create new
739     // open existing and read/append
740
741     if (is_writable() && (flags & (cBMFOpenExisting | cBMFOpenExistingOrCreateNew)))
742     {
743         bool does_file_exist = file_utils::does_file_exist(pFilename);
744
745         // open existing for read/write
746         if (does_file_exist)
747         {
748             if (!mz_zip_reader_init_file(&m_zip, pFilename, 0, file_start_ofs, actual_archive_size))
749             {
750                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
751                 vogl_error_printf("%s: mz_zip_reader_init_file() failed with filename \"%s\", error 0x%X (%s)\n", VOGL_METHOD_NAME, pFilename, mz_err, mz_zip_get_error_string(mz_err));
752
753                 deinit();
754                 return false;
755             }
756
757             if (!mz_zip_writer_init_from_reader(&m_zip, pFilename, MZ_ZIP_FLAG_WRITE_ZIP64))
758             {
759                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
760                 vogl_error_printf("%s: mz_zip_writer_init_from_reader() failed with filename \"%s\", error 0x%X (%s)\n", VOGL_METHOD_NAME, pFilename, mz_err, mz_zip_get_error_string(mz_err));
761
762                 deinit();
763                 return false;
764             }
765         }
766         else
767         {
768             // File does not exist - check if the caller allows us to create a new archive.
769             if ((flags & cBMFOpenExistingOrCreateNew) == 0)
770             {
771                 deinit();
772                 return false;
773             }
774
775             // Create new archive, write only or read/write.
776             if (!mz_zip_writer_init_file(&m_zip, pFilename, file_start_ofs, (is_readable() ? MZ_ZIP_FLAG_WRITE_ALLOW_READING : 0) | MZ_ZIP_FLAG_WRITE_ZIP64))
777             {
778                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
779                 vogl_error_printf("%s: mz_zip_writer_init_file() failed with filename \"%s\", error 0x%X (%s)\n", VOGL_METHOD_NAME, pFilename, mz_err, mz_zip_get_error_string(mz_err));
780
781                 deinit();
782                 return false;
783             }
784         }
785     }
786     else if (is_read_only())
787     {
788         // open existing for reading
789         if (flags & cBMFOpenExistingOrCreateNew)
790         {
791             deinit();
792             return false;
793         }
794
795         if (!mz_zip_reader_init_file(&m_zip, pFilename, 0, file_start_ofs, actual_archive_size))
796         {
797             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
798             vogl_error_printf("%s: mz_zip_reader_init_file() failed with filename \"%s\", error 0x%X (%s)\n", VOGL_METHOD_NAME, pFilename, mz_err, mz_zip_get_error_string(mz_err));
799
800             deinit();
801             return false;
802         }
803     }
804     else
805     {
806         VOGL_ASSERT(!actual_archive_size);
807
808         // create new archive, read/write or write only
809         if (!mz_zip_writer_init_file(&m_zip, pFilename, file_start_ofs, (is_readable() ? MZ_ZIP_FLAG_WRITE_ALLOW_READING : 0) | MZ_ZIP_FLAG_WRITE_ZIP64))
810         {
811             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
812             vogl_error_printf("%s: mz_zip_writer_init_file() failed with filename \"%s\", error 0x%X (%s)\n", VOGL_METHOD_NAME, pFilename, mz_err, mz_zip_get_error_string(mz_err));
813
814             deinit();
815             return false;
816         }
817     }
818
819     if (!populate_blob_map())
820     {
821         deinit();
822         return false;
823     }
824
825     m_initialized = true;
826
827     return true;
828 }
829
830 bool vogl_archive_blob_manager::init_file_temp(uint32 flags, const char *pPath)
831 {
832     VOGL_FUNC_TRACER
833
834     dynamic_string temp_filename(file_utils::generate_temp_filename(pPath));
835     return init_file(flags, temp_filename.get_ptr());
836 }
837
838 bool vogl_archive_blob_manager::init_cfile(uint32 flags, FILE *pFile, uint64_t cur_size)
839 {
840     VOGL_FUNC_TRACER
841
842     deinit();
843
844     if (!vogl_blob_manager::init(flags))
845         return false;
846
847     m_archive_filename = "<cfile>";
848
849     if (is_writable())
850     {
851         // Writable blob manager - see if there's any existing archive data
852         if (cur_size)
853         {
854             if (!mz_zip_reader_init_cfile(&m_zip, pFile, cur_size, 0))
855             {
856                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
857                 vogl_error_printf("%s: mz_zip_reader_init_cfile() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
858
859                 deinit();
860                 return false;
861             }
862
863             if (!mz_zip_writer_init_from_reader(&m_zip, NULL, MZ_ZIP_FLAG_WRITE_ZIP64))
864             {
865                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
866                 vogl_error_printf("%s: mz_zip_writer_init_from_reader() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
867
868                 deinit();
869                 return false;
870             }
871         }
872         else
873         {
874             // create new archive, write only or read/write
875             if (!mz_zip_writer_init_cfile(&m_zip, pFile, (is_readable() ? MZ_ZIP_FLAG_WRITE_ALLOW_READING : 0) | MZ_ZIP_FLAG_WRITE_ZIP64))
876             {
877                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
878                 vogl_error_printf("%s: mz_zip_writer_init_cfile() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
879
880                 deinit();
881                 return false;
882             }
883         }
884     }
885     else if (is_read_only())
886     {
887         // Open existing read-only archive
888         if (!mz_zip_reader_init_cfile(&m_zip, pFile, cur_size, 0))
889         {
890             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
891             vogl_error_printf("%s: mz_zip_reader_init_cfile() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
892
893             deinit();
894             return false;
895         }
896     }
897     else
898     {
899         // create new archive, read/write or write only
900         if (cur_size)
901         {
902             deinit();
903             return false;
904         }
905
906         if (!mz_zip_writer_init_cfile(&m_zip, pFile, (is_readable() ? MZ_ZIP_FLAG_WRITE_ALLOW_READING : 0) | MZ_ZIP_FLAG_WRITE_ZIP64))
907         {
908             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
909             vogl_error_printf("%s: mz_zip_writer_init_cfile() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
910
911             deinit();
912             return false;
913         }
914     }
915
916     if (!populate_blob_map())
917     {
918         deinit();
919         return false;
920     }
921
922     return true;
923 }
924
925 bool vogl_archive_blob_manager::populate_blob_map()
926 {
927     VOGL_FUNC_TRACER
928
929     m_blobs.clear();
930
931     for (uint file_index = 0; file_index < mz_zip_get_num_files(&m_zip); file_index++)
932     {
933         mz_zip_archive_file_stat stat;
934         if (!mz_zip_file_stat(&m_zip, file_index, &stat))
935         {
936             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
937             vogl_error_printf("%s: mz_zip_file_stat() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
938
939             return false;
940         }
941
942         if (!m_blobs.insert(stat.m_filename, blob(stat.m_filename, file_index, stat.m_uncomp_size)).second)
943         {
944             vogl_warning_printf("%s: Duplicate file %s in blob archive %s\n", VOGL_METHOD_NAME, stat.m_filename, m_archive_filename.get_ptr());
945         }
946     }
947
948     return true;
949 }
950
951 bool vogl_archive_blob_manager::deinit()
952 {
953     VOGL_FUNC_TRACER
954
955     bool status = true;
956     VOGL_NOTE_UNUSED(status);
957
958     if (mz_zip_get_mode(&m_zip) != MZ_ZIP_MODE_INVALID)
959     {
960         if ((mz_zip_get_type(&m_zip) == MZ_ZIP_TYPE_FILE) && (mz_zip_get_mode(&m_zip) == MZ_ZIP_MODE_WRITING))
961         {
962             if (!mz_zip_writer_finalize_archive(&m_zip))
963             {
964                 mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
965                 vogl_error_printf("%s: mz_zip_writer_finalize_archive() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
966
967                 status = false;
968             }
969         }
970
971         if (!mz_zip_end(&m_zip))
972         {
973             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
974             vogl_error_printf("%s: mz_zip_end() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
975
976             status = false;
977         }
978     }
979
980     mz_zip_zero_struct(&m_zip);
981
982     m_archive_filename.clear();
983
984     m_blobs.clear();
985
986     return vogl_blob_manager::deinit();
987 }
988
989 vogl::dynamic_string vogl_archive_blob_manager::add_buf_using_id(const void *pData, uint size, const vogl::dynamic_string &id)
990 {
991     VOGL_FUNC_TRACER
992
993     if (!is_initialized() || !is_writable())
994     {
995         VOGL_ASSERT(0);
996         return "";
997     }
998
999     if (mz_zip_get_mode(&m_zip) != MZ_ZIP_MODE_WRITING)
1000         return "";
1001
1002     dynamic_string actual_id(id);
1003     if (actual_id.is_empty())
1004         actual_id = compute_unique_id(pData, size);
1005
1006     // We don't support overwriting files already in the archive - it's up to the caller to not try adding redundant files into the archive.
1007     // We could support orphaning the previous copy of the file and updating the archive to point to the latest version, though.
1008     if (m_blobs.contains(actual_id))
1009     {
1010         vogl_debug_printf("%s: Archive already contains blob id \"%s\"! Not replacing file.\n", VOGL_METHOD_NAME, actual_id.get_ptr());
1011         return actual_id;
1012     }
1013
1014     uint file_index = mz_zip_get_num_files(&m_zip);
1015
1016     // TODO: Allow caller to control whether files are compressed
1017     if (!mz_zip_writer_add_mem(&m_zip, actual_id.get_ptr(), pData, size, MZ_BEST_SPEED))
1018     //if (!mz_zip_writer_add_mem(&m_zip, actual_id.get_ptr(), pData, size, 0))
1019     {
1020         mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
1021         vogl_error_printf("%s: mz_zip_writer_add_mem() failed adding blob \"%s\" size %u, error 0x%X (%s)\n", VOGL_METHOD_NAME, id.get_ptr(), size, mz_err, mz_zip_get_error_string(mz_err));
1022
1023         return "";
1024     }
1025
1026     bool success = m_blobs.insert(actual_id, blob(actual_id, file_index, size)).second;
1027     VOGL_NOTE_UNUSED(success);
1028     VOGL_ASSERT(success);
1029
1030     return actual_id;
1031 }
1032
1033 vogl::data_stream *vogl_archive_blob_manager::open(const vogl::dynamic_string &id) const
1034 {
1035     VOGL_FUNC_TRACER
1036
1037     if (!is_initialized() || !is_readable())
1038     {
1039         VOGL_ASSERT(0);
1040         return NULL;
1041     }
1042
1043     blob_map::const_iterator it = m_blobs.find(id);
1044     if (it == m_blobs.end())
1045         return NULL;
1046
1047     // TODO: Add some sort of streaming decompression support to miniz and this class.
1048
1049     mz_zip_clear_last_error(&m_zip);
1050
1051     size_t size;
1052     void *pBuf = mz_zip_extract_to_heap(&m_zip, it->second.m_file_index, &size, 0);
1053     if (!pBuf)
1054     {
1055         mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
1056         vogl_error_printf("%s: mz_zip_extract_to_heap() failed opening blob \"%s\", error 0x%X (%s)\n", VOGL_METHOD_NAME, id.get_ptr(), mz_err, mz_zip_get_error_string(mz_err));
1057
1058         return NULL;
1059     }
1060
1061     VOGL_VERIFY(size == it->second.m_size);
1062
1063     return vogl_new(vogl::buffer_stream, pBuf, size);
1064 }
1065
1066 void vogl_archive_blob_manager::close(vogl::data_stream *pStream) const
1067 {
1068     VOGL_FUNC_TRACER
1069
1070     if (pStream)
1071     {
1072         void *pBuf = const_cast<void *>(pStream->get_ptr());
1073
1074         vogl_delete(pStream);
1075
1076         mz_free(pBuf);
1077     }
1078 }
1079
1080 bool vogl_archive_blob_manager::does_exist(const vogl::dynamic_string &id) const
1081 {
1082     VOGL_FUNC_TRACER
1083
1084     if (!is_initialized())
1085     {
1086         VOGL_ASSERT(0);
1087         return false;
1088     }
1089
1090     return m_blobs.contains(id);
1091 }
1092
1093 uint64_t vogl_archive_blob_manager::get_size(const vogl::dynamic_string &id) const
1094 {
1095     VOGL_FUNC_TRACER
1096
1097     if (!is_initialized())
1098     {
1099         VOGL_ASSERT(0);
1100         return false;
1101     }
1102
1103     blob_map::const_iterator it = m_blobs.find(id);
1104     if (it == m_blobs.end())
1105         return 0;
1106
1107     return it->second.m_size;
1108 }
1109
1110 vogl::dynamic_string_array vogl_archive_blob_manager::enumerate() const
1111 {
1112     VOGL_FUNC_TRACER
1113
1114     if (!is_initialized())
1115     {
1116         VOGL_ASSERT(0);
1117         return vogl::dynamic_string_array();
1118     }
1119
1120     vogl::dynamic_string_array result(m_blobs.size());
1121
1122     uint index = 0;
1123     for (blob_map::const_iterator it = m_blobs.begin(); it != m_blobs.end(); ++it)
1124         result[index++] = it->first;
1125
1126     return result;
1127 }
1128
1129 uint64_t vogl_archive_blob_manager::get_archive_size() const
1130 {
1131     VOGL_FUNC_TRACER
1132
1133     if (!is_initialized())
1134         return false;
1135
1136     return mz_zip_get_archive_size(&m_zip);
1137 }
1138
1139 bool vogl_archive_blob_manager::write_archive_to_stream(vogl::data_stream &stream) const
1140 {
1141     VOGL_FUNC_TRACER
1142
1143     if (!is_initialized())
1144         return false;
1145
1146     uint8_vec buf(64 * 1024);
1147
1148     uint64_t bytes_remaining = mz_zip_get_archive_size(&m_zip);
1149     mz_uint64 src_file_ofs = 0;
1150
1151     while (bytes_remaining)
1152     {
1153         size_t n = static_cast<size_t>(math::minimum<uint64_t>(bytes_remaining, buf.size()));
1154
1155         if (mz_zip_read_archive_data(&m_zip, src_file_ofs, buf.get_ptr(), n) != n)
1156         {
1157             mz_zip_error mz_err = mz_zip_get_last_error(&m_zip);
1158             vogl_error_printf("%s: mz_zip_read_archive_data() failed, error 0x%X (%s)\n", VOGL_METHOD_NAME, mz_err, mz_zip_get_error_string(mz_err));
1159
1160             return false;
1161         }
1162
1163         if (stream.write(buf.get_ptr(), static_cast<uint>(n)) != n)
1164             return false;
1165
1166         src_file_ofs += n;
1167         bytes_remaining -= n;
1168     }
1169
1170     return true;
1171 }
1172
1173 //----------------------------------------------------------------------------------------------------------------------
1174 // vogl_multi_blob_manager
1175 //----------------------------------------------------------------------------------------------------------------------
1176 vogl_multi_blob_manager::vogl_multi_blob_manager()
1177 {
1178     VOGL_FUNC_TRACER
1179 }
1180
1181 vogl_multi_blob_manager::~vogl_multi_blob_manager()
1182 {
1183     VOGL_FUNC_TRACER
1184 }
1185
1186 bool vogl_multi_blob_manager::init(uint32 flags)
1187 {
1188     VOGL_FUNC_TRACER
1189
1190     deinit();
1191
1192     if (flags & (cBMFWritable | cBMFOpenExistingOrCreateNew))
1193         return false;
1194
1195     if (!vogl_blob_manager::init(flags))
1196         return false;
1197
1198     m_initialized = true;
1199
1200     return true;
1201 }
1202
1203 void vogl_multi_blob_manager::add_blob_manager(vogl_blob_manager *pBlob_manager)
1204 {
1205     VOGL_FUNC_TRACER
1206
1207     if (!is_initialized())
1208     {
1209         VOGL_ASSERT(0);
1210         return;
1211     }
1212
1213     m_blob_managers.push_back(pBlob_manager);
1214 }
1215
1216 void vogl_multi_blob_manager::remove_blob_manager(vogl_blob_manager *pBlob_manager)
1217 {
1218     VOGL_FUNC_TRACER
1219
1220     if (!is_initialized())
1221     {
1222         VOGL_ASSERT(0);
1223         return;
1224     }
1225
1226     int index = m_blob_managers.find(pBlob_manager);
1227     if (index >= 0)
1228         m_blob_managers.erase(index);
1229 }
1230
1231 bool vogl_multi_blob_manager::deinit()
1232 {
1233     VOGL_FUNC_TRACER
1234
1235     m_blob_managers.clear();
1236
1237     return vogl_blob_manager::deinit();
1238 }
1239
1240 vogl::dynamic_string vogl_multi_blob_manager::add_buf_using_id(const void *pData, uint size, const vogl::dynamic_string &id)
1241 {
1242     VOGL_FUNC_TRACER
1243
1244     VOGL_ASSERT(0);
1245     VOGL_NOTE_UNUSED(pData);
1246     VOGL_NOTE_UNUSED(size);
1247     VOGL_NOTE_UNUSED(id);
1248     return "";
1249 }
1250
1251 vogl::data_stream *vogl_multi_blob_manager::open(const dynamic_string &id) const
1252 {
1253     VOGL_FUNC_TRACER
1254
1255     if (!is_initialized())
1256     {
1257         VOGL_ASSERT(0);
1258         return NULL;
1259     }
1260
1261     for (uint i = 0; i < m_blob_managers.size(); i++)
1262     {
1263         if (!m_blob_managers[i]->is_initialized())
1264             continue;
1265
1266         vogl::data_stream *pStream = m_blob_managers[i]->open(id);
1267         if (pStream)
1268         {
1269             VOGL_ASSERT(!pStream->get_user_data());
1270             pStream->set_user_data(m_blob_managers[i]);
1271             return pStream;
1272         }
1273     }
1274
1275     return NULL;
1276 }
1277
1278 void vogl_multi_blob_manager::close(vogl::data_stream *pStream) const
1279 {
1280     VOGL_FUNC_TRACER
1281
1282     if (pStream)
1283     {
1284         vogl_blob_manager *pBlob_manager = static_cast<vogl_blob_manager *>(pStream->get_user_data());
1285         VOGL_ASSERT(pBlob_manager);
1286
1287         if (pBlob_manager)
1288         {
1289             pStream->set_user_data(NULL);
1290             pBlob_manager->close(pStream);
1291         }
1292     }
1293 }
1294
1295 bool vogl_multi_blob_manager::does_exist(const vogl::dynamic_string &id) const
1296 {
1297     VOGL_FUNC_TRACER
1298
1299     for (uint i = 0; i < m_blob_managers.size(); i++)
1300     {
1301         if (!m_blob_managers[i]->is_initialized())
1302             continue;
1303
1304         if (m_blob_managers[i]->does_exist(id))
1305             return true;
1306     }
1307     return false;
1308 }
1309
1310 uint64_t vogl_multi_blob_manager::get_size(const vogl::dynamic_string &id) const
1311 {
1312     VOGL_FUNC_TRACER
1313
1314     for (uint i = 0; i < m_blob_managers.size(); i++)
1315     {
1316         if (!m_blob_managers[i]->is_initialized())
1317             continue;
1318
1319         if (m_blob_managers[i]->does_exist(id))
1320             return m_blob_managers[i]->get_size(id);
1321     }
1322     return 0;
1323 }
1324
1325 vogl::dynamic_string_array vogl_multi_blob_manager::enumerate() const
1326 {
1327     VOGL_FUNC_TRACER
1328
1329     vogl::dynamic_string_array all_files;
1330
1331     for (uint i = 0; i < m_blob_managers.size(); i++)
1332     {
1333         if (!m_blob_managers[i]->is_initialized())
1334             continue;
1335
1336         vogl::dynamic_string_array files(m_blob_managers[i]->enumerate());
1337         all_files.append(files);
1338     }
1339
1340     all_files.sort();
1341     all_files.unique();
1342
1343     return all_files;
1344 }