ide,ecere: <wip> fix ear support for emscripten.
[sdk] / ecere / src / sys / EARArchive.ec
1 namespace sys;
2
3 #define uint _uint
4 #include "zlib.h"
5 #undef uint
6
7 import "System"
8 import "BufferedFile"
9
10 #define OFFSET(s, m) ((uint)(uintptr) (&((s *) 0)->m))
11 #define MDWORD(a,b) ((((uint32)((uint16)(b)))<<16)+((uint16)(a)))
12
13 #define EAR_RECOGNITION { 'e', 'A', 'R', 228, 11, 12, 3, 0 }
14
15 static byte earRecognition[] = EAR_RECOGNITION;
16
17 static class FreeBlock : struct
18 {
19    FreeBlock prev, next;
20    uint start, end;
21 };
22
23 static struct EARHeader
24 {
25    byte recognition[sizeof(earRecognition)];
26    uint version                             __attribute__((packed));
27    FileSize totalSize                       __attribute__((packed));
28 };
29
30 static enum EAREntryType { ENTRY_FILE = 1, ENTRY_FOLDER = 2 };
31
32 static struct EAREntry
33 {
34    EAREntryType type             __attribute__((packed));
35    TimeStamp32 created, modified __attribute__((packed));
36    FileSize size, cSize          __attribute__((packed));
37    uint prev, next               __attribute__((packed));
38    uint nameLen                  __attribute__((packed));
39    // null terminated file name follows
40 };
41
42 static File EAROpenArchive(const char * archive, EARHeader header)
43 {
44    File f = null;
45    if(archive[0] == ':')
46    {
47       char moduleName[MAX_LOCATION];
48       const char * name = archive + 1;
49 #if defined(__ANDROID__) || defined(__EMSCRIPTEN__)
50       if(!name[0])
51          name = ((SubModule)__thisModule.application.modules.first).next.module.name;
52 #endif
53 #if defined(__EMSCRIPTEN__)
54       //sprintf(moduleName, "__%s.ear", name);
55       sprintf(moduleName, "__%s.ear", "HelloForm");
56       f = FileOpen(moduleName, read);
57 #else
58       if(LocateModule(name, moduleName))
59          f = FileOpen(moduleName, read);
60 #endif
61    }
62    else
63       f = FileOpen(archive, read);
64    if(f)
65    {
66       uint archiveSize;
67
68       // First attempt to treat this as an archive file
69       if(f.Read(header, sizeof(EARHeader), 1) == 1 &&
70          !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
71          return f;
72
73       // Then try to see if an archive is at the end of the file
74       f.Seek(-(int)sizeof(uint), end);
75       f.Read(&archiveSize, sizeof(uint), 1);
76       f.Seek(-(int)archiveSize, end);
77       if(f.Read(header, sizeof(EARHeader), 1) == 1 &&
78          !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
79          return f;
80
81       delete f;
82    }
83    return null;
84 }
85
86 static FileAttribs EARGetEntry(File f, EAREntry entry, const char * name, char * path)
87 {
88    uint first = 0, last = 0;
89    if(!name[0])
90       return FileAttribs { isDirectory = true };
91    if(!f.Read(&first, sizeof(uint), 1))
92       return 0;
93    f.Read(&last, sizeof(uint), 1);
94    if(first)
95    {
96       char namePart[MAX_FILENAME], nameRest[MAX_LOCATION];
97       SplitDirectory(name, namePart, nameRest);
98
99       if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
100          strcpy(namePart, DIR_SEPS);
101
102       f.Seek(first, start);
103       for(;;)
104       {
105          char fileName[MAX_FILENAME];
106
107          f.Read(entry, sizeof(EAREntry), 1);
108          f.Read(fileName, 1, entry.nameLen);
109          fileName[entry.nameLen] = '\0';
110
111          if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
112             strcpy(fileName, DIR_SEPS);
113
114          if(!fstrcmp(fileName, namePart))
115          {
116             if(path)
117                PathCat(path, fileName);
118             if(nameRest[0])
119                return EARGetEntry(f, entry, nameRest, path);
120             else
121                return (entry.type == ENTRY_FILE) ? FileAttribs { isFile = true } : FileAttribs { isDirectory = true };
122          }
123          if(entry.next)
124             f.Seek(entry.next, start);
125          else
126             break;
127       }
128    }
129    return FileAttribs { };
130 }
131
132 #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
133 class EARArchive : Archive
134 {
135    File f;
136    //BufferedFile bf { };
137    // char path[MAX_LOCATION];
138    uint archiveStart;
139    uint rootDir;
140    OldList freeBlocks;
141    bool writeAccess;
142
143    uint Update()
144    {
145       if(rootDir)
146       {
147          uint end;
148
149          end = ((FreeBlock)freeBlocks.last).start;
150
151          // Update header
152          f.Seek(archiveStart + OFFSET(EARHeader, totalSize), start);
153          f.Write(&totalSize, sizeof(uint), 1);
154
155          // Write Footer
156          f.Seek(end, start);
157
158          end += sizeof(uint);
159
160          end -= archiveStart;
161
162          f.Write(&end, sizeof(uint), 1);
163          f.Truncate(archiveStart + end);
164
165          return end;
166       }
167       return 0;
168    }
169
170    ~EARArchive()
171    {
172       if(f && rootDir && writeAccess)
173       {
174          // Perform Defrag
175          Defrag(rootDir);
176          archiveStart += Update();
177       }
178       if(f && writeAccess)
179       {
180          f.Flush();
181          f.Unlock(0, 0, true);
182       }
183       delete f;
184
185       /*if(rootDir && writeAccess)
186       {
187          // Fix the size of the archive
188          FileTruncate(path, archiveStart);
189       }*/
190
191       freeBlocks.Free(null);
192    }
193
194    bool Clear()
195    {
196       rootDir = 0;
197       return true;
198    }
199
200    ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode)
201    {
202       ArchiveDir result = null;
203       EARArchiveDir dir { readOnly = addMode == readOnlyDir };
204       if(dir)
205       {
206          char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION];
207
208          dir.archive = this;
209
210          strcpy(nameRest, name);
211
212          if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
213             strcpy(namePart, DIR_SEPS);
214
215          // Search for directory
216          if(rootDir)
217          {
218             dir.position = rootDir;
219             if(f.Seek(dir.position, start))
220             {
221                dir.first = 0;
222                dir.last = 0;
223
224                f.Read(&dir.first, sizeof(uint), 1);
225                f.Read(&dir.last, sizeof(uint), 1);
226
227                result = dir;
228             }
229          }
230
231          // If directory doesn't exist already
232          if(!result && addMode != refresh)
233          {
234             rootDir = Position(2*sizeof(uint));
235             dir.position = rootDir;
236          }
237
238          result = dir;
239
240          // Open rest of directory...
241          if(result && nameRest[0])
242          {
243             result = dir.OpenDirectory(nameRest, stats, addMode);
244             delete dir;
245          }
246       }
247       return result;
248    }
249
250    uint Position(uint size)
251    {
252       FreeBlock block;
253       for(block = freeBlocks.first; block; block = block.next)
254       {
255          if(block.end - block.start + 1 >= size)
256          {
257             uint position = block.start;
258             if(block.end - block.start + 1 == size)
259                freeBlocks.Delete(block);
260             else
261                block.start += size;
262             return position;
263          }
264       }
265       return 0;
266    }
267
268    bool DefragOffset(uint * offset)
269    {
270       FreeBlock block;
271       uint subtract = 0;
272       for(block = freeBlocks.first; block; block = block.next)
273       {
274          if(*offset > block.start)
275             subtract += block.end - block.start + 1;
276          else
277             break;
278       }
279       if(subtract)
280       {
281          *offset -= subtract;
282          return true;
283       }
284       return false;
285    }
286
287    #define MAX_BUFFERSIZE 0x400000
288
289    void Defrag(uint dirPosition)
290    {
291       // Update all offsets within the files
292       uint first = 0, last = 0;
293       uint position = 0, next = 0;
294
295       f.Seek(dirPosition, start);
296       f.Read(&first, sizeof(uint), 1);
297       f.Read(&last, sizeof(uint), 1);
298
299       position = first;
300
301       if(first && DefragOffset(&first))
302       {
303          if(f.Seek(dirPosition, start))
304             f.Write(&first, sizeof(uint), 1);
305       }
306       if(last && DefragOffset(&last))
307       {
308          if(f.Seek(dirPosition + sizeof(uint), start))
309             f.Write(&last, sizeof(uint), 1);
310       }
311
312       for(; position; position = next)
313       {
314          EAREntry entry { };
315
316          if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1))
317          {
318             next = entry.next;
319
320             if(entry.prev && DefragOffset(&entry.prev))
321             {
322                f.Seek(position + OFFSET(EAREntry, prev), start);
323                f.Write(&entry.prev, sizeof(uint), 1);
324             }
325             if(entry.next && DefragOffset(&entry.next))
326             {
327                f.Seek(position + OFFSET(EAREntry, next), start);
328                f.Write(&entry.next, sizeof(uint), 1);
329             }
330
331             if(entry.type == ENTRY_FOLDER)
332                Defrag(position + sizeof(EAREntry) + entry.nameLen);
333          }
334          else
335             return;
336       }
337
338       // Move all the blocks
339       if(dirPosition == rootDir)
340       {
341          uint bufferSize = 0;
342          byte * buffer = null;
343          FreeBlock block, nextBlock;
344          for(block = freeBlocks.first; block && block.next; block = nextBlock)
345          {
346             uint dataSize, c;
347
348             nextBlock = block.next;
349             dataSize = nextBlock.start - (block.end + 1);
350
351             if(dataSize > bufferSize && (!bufferSize || bufferSize < MAX_BUFFERSIZE))
352             {
353                bufferSize = Min(dataSize, MAX_BUFFERSIZE);
354                buffer = renew buffer byte[bufferSize];
355             }
356
357             for(c = 0; c<dataSize; c += bufferSize)
358             {
359                uint size = (dataSize > c + bufferSize) ? bufferSize : (dataSize - c);
360
361                // Read block of data
362                f.Seek((block.end + 1) + c, start);
363                f.Read(buffer, size, 1);
364
365                // Write block of data
366                f.Seek(block.start + c, start);
367                f.Write(buffer, size, 1);
368             }
369
370             nextBlock.start -= (block.end - block.start) + 1;
371
372             freeBlocks.Delete(block);
373          }
374          delete buffer;
375       }
376    }
377
378    uint Find(EARArchiveDir directory, const char * namePart, EAREntry entry)
379    {
380       uint position;
381       for(position = directory.first; position; position = entry.next)
382       {
383          char fileName[MAX_FILENAME];
384
385          if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1))
386          {
387             if(entry.nameLen > MAX_FILENAME)
388                return 0;   // CORRUPTION ERROR
389             f.Read(fileName, 1, entry.nameLen);
390             fileName[entry.nameLen] = '\0';
391
392             if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
393                strcpy(fileName, DIR_SEPS);
394
395             if(!fstrcmp(fileName, namePart))
396                return position;
397          }
398          else
399             return 0;   // ERROR OUT OF SPACE?
400       }
401       return 0;
402    }
403
404    void AddFreeBlock(uint position, uint size)
405    {
406       FreeBlock block, prevBlock, nextBlock = null;
407
408       // Find the previous and next free block
409       prevBlock = null;
410       for(block = freeBlocks.first; block; block = block.next)
411          if(block.end < position)
412             prevBlock = block;
413          else
414          {
415             nextBlock = block;
416             break;
417          }
418
419       // Try to merge with previous block
420       if(prevBlock && prevBlock.end + 1 == position)
421       {
422           prevBlock.end += size;
423          // Try to merge with next block as well
424          if(nextBlock && nextBlock.start == prevBlock.end + 1)
425          {
426             prevBlock.end = nextBlock.end;
427             freeBlocks.Delete(nextBlock);
428          }
429       }
430       // Try to merge with next block
431       else if(nextBlock && nextBlock.start == position + size)
432       {
433          nextBlock.start = position;
434       }
435       // This free block is not connected to any other block
436       else
437       {
438          freeBlocks.Insert(prevBlock, FreeBlock { start = position, end = position + size - 1 });
439       }
440    }
441
442    void SubtractBlock(uint start, uint size)
443    {
444       FreeBlock block;
445       for(block = freeBlocks.first; block; block = block.next)
446       {
447          if(block.end >= start - 1L && block.start <= start + size)
448          {
449             if(block.end > start + size && block.start < start - 1L)
450             {
451                FreeBlock newBlock { start = start + size, end = block.end };
452                block.end = start - 1L;
453                freeBlocks.Insert(block, newBlock);
454             }
455             else if(block.end > start + size)
456             {
457                block.start = start + size;
458             }
459             else if(block.start < start - 1L)
460             {
461                block.end = start - 1L;
462             }
463             else
464             {
465                freeBlocks.Remove(block);
466                delete block;
467             }
468             break;
469          }
470       }
471    }
472
473    void Delete(EARArchiveDir dir, uint position, EAREntry entry)
474    {
475       uint size;
476       if(entry.type == ENTRY_FOLDER)
477       {
478          EARArchiveDir subDir {};
479          uint filePosition;
480          EAREntry fileEntry;
481
482          subDir.position = dir.position;
483          f.Read(&subDir.first, sizeof(uint), 1);
484          f.Read(&subDir.last, sizeof(uint), 1);
485
486          // Erase directory contents first
487          for(filePosition = subDir.first; filePosition; filePosition = fileEntry.next)
488          {
489             f.Seek(filePosition, start);
490             f.Read(&fileEntry, sizeof(EAREntry), 1);
491             f.Seek(fileEntry.nameLen, current);
492             Delete(subDir, filePosition, &fileEntry);
493          }
494          size = sizeof(EAREntry) + entry.nameLen + 2 * sizeof(uint);
495          delete subDir;
496       }
497       else
498          size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size);
499
500       // Unlink this file
501       if(entry.prev)
502       {
503          f.Seek(entry.prev + OFFSET(EAREntry, next), start);
504          f.Write(&entry.next, sizeof(uint), 1);
505       }
506       if(entry.next)
507       {
508          f.Seek(entry.next + OFFSET(EAREntry, prev), start);
509          f.Write(&entry.prev, sizeof(uint), 1);
510       }
511       if(dir.last == position) dir.last = entry.prev;
512       if(dir.first == position) dir.first = entry.next;
513
514       AddFreeBlock(position, size);
515       totalSize -= entry.size;
516
517       // Invalidate Buffer
518       // bf.handle = f;
519    }
520
521    File FileOpen(const char * name)
522    {
523       File result = null;
524       EARFile file {};
525       if(file)
526       {
527          EAREntry entry { };
528
529          f.Seek(archiveStart + sizeof(EARHeader), start);
530          if(EARGetEntry(f, entry, name, null).isFile)
531          {
532             if(entry.cSize)
533             {
534                byte * uncompressed = new byte[entry.size];
535                if(uncompressed)
536                {
537                   byte * compressed = new byte[entry.cSize];
538                   if(compressed)
539                   {
540                      if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
541                      {
542                         unsigned long destLen = entry.size;
543                         uncompress(uncompressed, &destLen, compressed, entry.cSize);
544                         entry.size = (FileSize)destLen;  // TODO: Support 64 bit file sizes
545                      }
546                      delete compressed;
547                   }
548
549                   file.position = 0;
550                   file.size = entry.size;
551                   file.buffer = uncompressed;
552
553                   result = file;
554                }
555             }
556             else
557             {
558                file.start = f.Tell();
559                file.position = 0;
560                file.size = entry.size;
561                file.f = f;
562                incref file.f;
563                file.f.Seek(file.start, start);
564                result = file;
565             }
566          }
567          if(!result)
568             delete file;
569       }
570       return result;
571    }
572
573    File FileOpenAtPosition(uint position)
574    {
575       EARFile file {};
576       char fileName[MAX_LOCATION];
577       EAREntry entry { };
578       f.Seek(position, start);
579       f.Read(entry, sizeof(EAREntry), 1);
580       /*if(entry.nameLen > 1024)
581          printf("");*/
582       f.Read(fileName, 1, entry.nameLen);
583       if(entry.cSize)
584       {
585          byte * uncompressed = new byte[entry.size];
586          if(uncompressed)
587          {
588             byte * compressed = new byte[entry.cSize];
589             if(compressed)
590             {
591                if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
592                {
593                   unsigned long destLen = entry.size;
594                   uncompress(uncompressed, &destLen, compressed, entry.cSize);
595                   entry.size = (FileSize)destLen;
596                }
597                delete compressed;
598             }
599
600             file.position = 0;
601             file.size = entry.size;
602             file.buffer = uncompressed;
603          }
604       }
605       else
606       {
607          file.start = f.Tell();
608          file.position = 0;
609          file.size = entry.size;
610          file.f = f;
611          file.f.Seek(file.start, start);
612          incref file.f;
613       }
614       return file;
615    }
616
617    FileAttribs FileExists(const char * fileName)
618    {
619       FileAttribs result;
620       EAREntry entry { };
621       f.Seek(archiveStart + sizeof(EARHeader), start);
622       result = EARGetEntry(f, entry, fileName, null);
623       return result;
624    }
625
626    void SubtractUsedBlocks()
627    {
628       uint first, last;
629       if(!f.Read(&first, sizeof(uint), 1))
630          return;
631 #ifdef _DEBUG
632       if(first > f.GetSize())
633       {
634          printf("Error\n");
635       }
636 #endif
637       f.Read(&last, sizeof(uint), 1);
638
639       while(first)
640       {
641          uint size = 0;
642
643          char fileName[MAX_FILENAME];
644          EAREntry entry { };
645
646          f.Seek(first, start);
647          f.Read(entry, sizeof(EAREntry), 1);
648          if(entry.nameLen < MAX_FILENAME)
649          {
650             f.Read(fileName, 1, entry.nameLen);
651             fileName[entry.nameLen] = 0;
652          }
653          else
654          {
655             fileName[0] = 0;
656             break;
657          }
658
659          size += sizeof(EAREntry) + entry.nameLen;
660
661          if(entry.type == ENTRY_FILE)
662          {
663             size += entry.cSize ? entry.cSize : entry.size;
664          }
665          else if(entry.type == ENTRY_FOLDER)
666          {
667             size += 2 * sizeof(uint);
668             SubtractUsedBlocks();
669          }
670          SubtractBlock(first, size);
671          first = entry.next;
672       }
673    }
674
675    void SetBufferSize(uint bufferSize)
676    {
677       if(f && f._class == class(BufferedFile))
678          ((BufferedFile)f).bufferSize = bufferSize;
679    }
680
681    void SetBufferRead(uint bufferRead)
682    {
683       if(f && f._class == class(BufferedFile))
684          ((BufferedFile)f).bufferRead = bufferRead;
685    }
686 }
687
688 class EARArchiveDir : ArchiveDir
689 {
690    EARArchive archive;
691    uint position;
692    uint first, last;
693    bool readOnly;
694
695    ~EARArchiveDir()
696    {
697       if(!readOnly)
698       {
699          archive.f.Seek(position, start);
700          archive.f.Write(&first, sizeof(uint), 1);
701          archive.f.Write(&last, sizeof(uint), 1);
702          archive.Update();
703       }
704    }
705
706    File FileOpen(const char * name)
707    {
708       File result = null;
709       EARFile file {};
710       if(file)
711       {
712          EAREntry entry { };
713
714          archive.f.Seek(position, start);
715          if(EARGetEntry(archive.f, entry, name, null).isFile)
716          {
717             if(entry.cSize)
718             {
719                byte * uncompressed = new byte[entry.size];
720                if(uncompressed)
721                {
722                   byte * compressed = new byte[entry.cSize];
723                   if(compressed)
724                   {
725                      if(archive.f.Read(compressed, 1, entry.cSize) == entry.cSize)
726                      {
727                         unsigned long destLen = entry.size;
728                         uncompress(uncompressed, &destLen, compressed, entry.cSize);
729                         entry.size = (FileSize)destLen;
730                      }
731                      delete compressed;
732                   }
733
734                   file.position = 0;
735                   file.size = entry.size;
736                   file.buffer = uncompressed;
737
738                   result = file;
739                }
740             }
741             else
742             {
743                file.start = archive.f.Tell();
744                file.position = 0;
745                file.size = entry.size;
746                file.f = archive.f;
747                file.f.Seek(file.start, start);
748                incref file.f;
749                result = file;
750             }
751          }
752          if(!result)
753             delete file;
754       }
755       return result;
756    }
757
758    FileAttribs FileExists(const char * fileName)
759    {
760       FileAttribs result;
761       EAREntry entry { };
762       archive.f.Seek(position, start);
763       result = EARGetEntry(archive.f, entry, fileName, null);
764       return result;
765    }
766
767    ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode)
768    {
769       ArchiveDir result = null;
770       EARArchiveDir dir { readOnly = addMode == readOnlyDir };
771       if(dir)
772       {
773          char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION];
774          uint position;
775          EAREntry entry { };
776
777          dir.archive = archive;
778
779          SplitDirectory(name, namePart, nameRest);
780
781          if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
782             strcpy(namePart, DIR_SEPS);
783
784          // Search for directory
785
786          position = archive.Find(this, namePart, entry);
787          if(position)
788          {
789             // Fail if file of same name already exists
790             if(entry.type == ENTRY_FILE)
791                return null;
792             else
793             {
794                dir.position = position + sizeof(EAREntry) + entry.nameLen;
795                dir.first = 0;
796                dir.last = 0;
797
798                archive.f.Read(&dir.first, sizeof(uint), 1);
799                archive.f.Read(&dir.last, sizeof(uint), 1);
800
801                result = dir;
802             }
803          }
804
805          // If directory doesn't exist already
806          if(!result && addMode != refresh)
807          {
808             // Write Header if it's not the root directory
809             EAREntry entry {};
810             uint position;
811
812             entry.nameLen = strlen(namePart);
813             entry.prev = last;
814             entry.next = 0;
815             entry.type = ENTRY_FOLDER;
816             if(!nameRest[0] && stats)
817             {
818                entry.created = (TimeStamp32)stats.created;
819                entry.modified = (TimeStamp32)stats.modified;
820             }
821
822             position = archive.Position(sizeof(EAREntry) + entry.nameLen + 2*sizeof(uint));
823
824             archive.f.Seek(position, start);
825             archive.f.Write(entry, sizeof(EAREntry), 1);
826             archive.f.Write(namePart, entry.nameLen, 1);
827
828             last = position;
829             if(!first) first = position;
830
831             // Update the next pointer of previous entry
832             if(entry.prev)
833             {
834                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
835                archive.f.Write(&position, sizeof(uint), 1);
836             }
837
838             // Make the dir position point after the header
839             dir.position = position + sizeof(EAREntry) + entry.nameLen;
840          }
841          // Just update the time stamps
842          else if(result && !nameRest[0] && stats)
843          {
844             archive.f.Seek(position + OFFSET(EAREntry, created), start);
845             archive.f.Write(&stats.created, sizeof(uint), 1);
846             archive.f.Write(&stats.modified, sizeof(uint), 1);
847          }
848          result = dir;
849
850          // Open rest of directory...
851          if(result && nameRest[0])
852          {
853             result = dir.OpenDirectory(nameRest, stats, addMode);
854             delete dir;
855          }
856       }
857       return result;
858    }
859
860    bool Delete(const char * name)
861    {
862       EAREntry entry { };
863       uint position;
864       char namePart[MAX_LOCATION];
865
866       strcpy(namePart, name);
867       if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
868          strcpy(namePart, DIR_SEPS);
869
870       position = archive.Find(this, namePart, entry);
871       if(position)
872       {
873          archive.Delete(this, position, entry);
874          return true;
875       }
876       return false;
877    }
878
879    bool Move(const char * name, EARArchiveDir to)
880    {
881       bool result = false;
882       if(position != to.position)
883       {
884          EAREntry entry { };
885          uint position = 0;
886          char namePart[MAX_LOCATION];
887
888          strcpy(namePart, name);
889          if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
890             strcpy(namePart, DIR_SEPS);
891
892          position = archive.Find(this, name, entry);
893          if(position)
894          {
895             // Unlink from old directory
896             if(entry.prev)
897             {
898                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
899                archive.f.Write(&entry.next, sizeof(uint), 1);
900             }
901             if(entry.next)
902             {
903                archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start);
904                archive.f.Write(&entry.prev, sizeof(uint), 1);
905             }
906             if(last == position) last = entry.prev;
907             if(first == position) first = entry.next;
908
909             // Relink to new directory
910             entry.prev = to.last;
911             entry.next = 0;
912
913             if(entry.prev)
914             {
915                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
916                archive.f.Write(&position, sizeof(uint), 1);
917             }
918             if(!to.first)
919                to.first = position;
920             to.last = position;
921
922             archive.f.Seek(position + OFFSET(EAREntry, prev), start);
923             archive.f.Write(&entry.prev, sizeof(uint), 1);
924             archive.f.Write(&entry.next, sizeof(uint), 1);
925
926             result = true;
927          }
928       }
929       return result;
930    }
931
932    bool Rename(const char * name, const char * newName)
933    {
934       bool result = false;
935       EAREntry entry { };
936       uint position = 0;
937       char namePart[MAX_LOCATION];
938
939       strcpy(namePart, name);
940       if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
941          strcpy(namePart, DIR_SEPS);
942
943       position = archive.Find(this, namePart, entry);
944       if(position)
945       {
946          uint dataSize;
947          EAREntry newEntry = entry;
948          uint newPosition = position;
949
950          if(entry.type == ENTRY_FOLDER)
951             dataSize = 2 * sizeof(uint);
952          else
953             dataSize = entry.cSize ? entry.cSize : entry.size;
954
955          newEntry.nameLen = strlen(newName);
956          if(newEntry.nameLen > entry.nameLen)
957          {
958             // Write new entry
959             newPosition = archive.Position(sizeof(EAREntry) + newEntry.nameLen + dataSize);
960
961             archive.f.Seek(newPosition, start);
962             archive.f.Write(&newEntry, sizeof(EAREntry), 1);
963             archive.f.Write(newName, sizeof(char), newEntry.nameLen);
964
965             // Fix the links
966             if(entry.prev)
967             {
968                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
969                archive.f.Write(&newPosition, sizeof(uint), 1);
970             }
971             if(entry.next)
972             {
973                archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start);
974                archive.f.Write(&newPosition, sizeof(uint), 1);
975             }
976             if(first == position) first = newPosition;
977             if(last == position) last = newPosition;
978          }
979          else
980          {
981             // Change the name
982             archive.f.Seek(position + OFFSET(EAREntry, nameLen), start);
983             archive.f.Write(&newEntry.nameLen, sizeof(uint), 1);
984             archive.f.Seek(position + sizeof(EAREntry), start);
985             archive.f.Write(newName, sizeof(char), newEntry.nameLen);
986
987             // There will be free space at the end of an entry with a shorter new name
988             if(newEntry.nameLen < entry.nameLen)
989                archive.AddFreeBlock(position + sizeof(EAREntry) + newEntry.nameLen + dataSize, entry.nameLen - newEntry.nameLen);
990          }
991          if(entry.nameLen != newEntry.nameLen)
992          {
993             byte * buffer;
994             uint bufferSize = Min(dataSize, MAX_BUFFERSIZE);
995             buffer = new byte[bufferSize];
996             if(buffer)
997             {
998                uint readPosition = position + sizeof(EAREntry) + entry.nameLen;
999                uint writePosition = newPosition + sizeof(EAREntry) + newEntry.nameLen;
1000                uint c;
1001
1002                for(c = 0; c<dataSize; c += bufferSize)
1003                {
1004                   uint size = (dataSize > c + bufferSize) ? bufferSize : (dataSize - c);
1005
1006                   archive.f.Seek(readPosition + c, start);
1007                   archive.f.Read(buffer, size, 1);
1008
1009                   archive.f.Seek(writePosition + c, start);
1010                   archive.f.Write(buffer, size, 1);
1011                }
1012                delete buffer;
1013             }
1014
1015             if(newEntry.nameLen > entry.nameLen)
1016             {
1017                // Prevent the children to be deleted
1018                if(entry.type == ENTRY_FOLDER)
1019                {
1020                   uint first = 0, last = 0;
1021                   archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start);
1022                   archive.f.Write(&first, sizeof(uint), 1);
1023                   archive.f.Write(&last, sizeof(uint), 1);
1024                }
1025
1026                // Delete the old entry
1027                entry.prev = entry.next = 0;
1028                archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start);
1029                archive.Delete(this, position, entry);
1030             }
1031          }
1032          result = true;
1033       }
1034       return result;
1035    }
1036
1037    bool AddFromFile(const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition)
1038    {
1039       // Search for identical entry
1040       EAREntry oldEntry;
1041       uint oldPosition = archive.Find(this, name, oldEntry);
1042       return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition);
1043    }
1044
1045    bool AddFromFileAtPosition(uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition)
1046    {
1047       EAREntry oldEntry;
1048       if(oldPosition)
1049       {
1050          if(!archive.f.Seek(oldPosition, start) || !archive.f.Read(oldEntry, sizeof(EAREntry), 1))
1051             return false;
1052       }
1053       return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition);
1054    }
1055
1056    bool _AddFromFileAtPosition(EAREntry oldEntry, uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition)
1057    {
1058       bool skip = false;
1059       FileStats oldStats { };
1060
1061       if(oldPosition)
1062       {
1063          oldStats.modified = (TimeStamp)oldEntry.modified;
1064          oldStats.created = (TimeStamp)oldEntry.created;
1065       }
1066       if(stats == null)
1067       {
1068          oldStats.size = input.GetSize();
1069          stats = &oldStats;
1070       }
1071
1072       switch(addMode)
1073       {
1074          // Add all files
1075          case replace:
1076             if(oldPosition)
1077                archive.Delete(this, oldPosition, oldEntry);
1078             break;
1079          // Only updates changed files
1080          case refresh:
1081             if(oldPosition &&
1082                  (oldEntry.size != stats.size ||
1083                   oldEntry.modified != (TimeStamp32)stats.modified ||
1084                   oldEntry.created != (TimeStamp32)stats.created))
1085                   archive.Delete(this, oldPosition, oldEntry);
1086             else
1087                skip = true;
1088             break;
1089          // Only updates changed or new files
1090          case update:
1091             if(oldPosition)
1092             {
1093                if(oldEntry.size != stats.size ||
1094                   oldEntry.modified != (TimeStamp32)stats.modified ||
1095                   oldEntry.created != (TimeStamp32)stats.created)
1096                   archive.Delete(this, oldPosition, oldEntry);
1097                else
1098                   skip = true;
1099             }
1100             break;
1101       }
1102
1103       if(!skip)
1104       {
1105          EAREntry entry { };
1106          uint position, size;
1107          byte * compressed = null;
1108
1109          // Add the file
1110          entry.nameLen = strlen(name);
1111          entry.prev = last;
1112          entry.next = 0;
1113          entry.type = ENTRY_FILE;
1114
1115          entry.size = stats.size;
1116          entry.created = (TimeStamp32)stats.created;
1117          entry.modified = (TimeStamp32)stats.modified;
1118
1119          if(compression)
1120          {
1121             byte * uncompressed = new byte[entry.size];
1122             if(uncompressed)
1123             {
1124                if(input.Read(uncompressed, 1, entry.size) == entry.size)
1125                {
1126                   unsigned long destLen = entry.size + entry.size / 1000 + 12;
1127
1128                   compressed = new byte[destLen];
1129                   if(compressed)
1130                   {
1131                      compress2(compressed, &destLen, uncompressed, entry.size, compression);
1132                      entry.cSize = (FileSize)destLen;
1133                   }
1134                }
1135                delete uncompressed;
1136             }
1137          }
1138
1139          if(!compressed)
1140          {
1141             entry.cSize = 0;
1142             if(ratio)
1143                *ratio = 0;
1144          }
1145          else if(ratio)
1146             *ratio = entry.size ? (entry.cSize * 1000 / entry.size) : 0;
1147
1148          // Find block
1149          size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size);
1150          position = archive.Position(size);
1151
1152          // Write Header
1153          if(!archive.f.Seek(position, start) ||
1154             !archive.f.Write(entry, sizeof(EAREntry), 1) ||
1155             !archive.f.Write(name, entry.nameLen, 1))
1156          {
1157             delete compressed;
1158             return false;
1159          }
1160
1161          // Write File Data
1162          if(compressed)
1163          {
1164             if(!archive.f.Write(compressed, 1, entry.cSize))
1165             {
1166                delete compressed;
1167                return false;
1168             }
1169             delete compressed;
1170          }
1171          else
1172          {
1173             byte buffer[8192];
1174             uint c;
1175             int count = 1;
1176             for(c = 0; c<entry.size && count; c+= count)
1177             {
1178                count = input.Read(buffer, 1, sizeof(buffer));
1179                if(!archive.f.Write(buffer, 1, count))
1180                   return false;
1181             }
1182          }
1183
1184          // Update the next pointer previous entry
1185          if(entry.prev)
1186          {
1187             archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
1188             archive.f.Write(&position, sizeof(uint), 1);
1189          }
1190
1191          // Update total size of archive
1192          archive.totalSize += entry.size;
1193
1194          last = position;
1195          if(!first) first = position;
1196          if(newPosition) *newPosition = position;
1197       }
1198       else
1199       {
1200          if(newPosition) *newPosition = 0;
1201       }
1202
1203       // archive.f.handle = archive.f;
1204       return true;
1205    }
1206 };
1207 #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1208
1209 // Directory Description for file listing
1210 class EARDir : struct
1211 {
1212    char path[MAX_LOCATION];
1213    File f;
1214    uint next;
1215 };
1216
1217 class EARFile : File
1218 {
1219    uint position;
1220    uint size;
1221
1222    // -- For reading compressed file (entirely buffered)
1223    byte * buffer;
1224
1225    // -- For reading uncompressed file (not buffered)
1226    File f;
1227    uint start;
1228
1229    ~EARFile()
1230    {
1231       delete buffer;
1232       delete f;
1233    }
1234
1235    void CloseInput()
1236    {
1237       if(f)
1238          f.CloseInput();
1239    }
1240
1241    void CloseOutput()
1242    {
1243       if(f)
1244          f.CloseOutput();
1245    }
1246
1247    int Read(byte * buffer, uint size, uint count)
1248    {
1249       int read = 0;
1250       if(f)
1251          f.Seek(position + start, start);
1252       read = Min(count, (this.size - position) / size);
1253       if(this.buffer)
1254          CopyBytes(buffer, this.buffer + position, read * size);
1255       else
1256          read = f.Read(buffer, size, read);
1257       position += read * size;
1258       return read;
1259    }
1260
1261    int Write(const byte * buffer, uint size, uint count)
1262    {
1263       return 0;
1264    }
1265
1266    bool Getc(char * ch)
1267    {
1268       if(position < size)
1269       {
1270          if(buffer)
1271          {
1272             char b = buffer[position++];
1273             if(ch) *ch = b;
1274             return true;
1275          }
1276          else
1277          {
1278             f.Seek(position + start, start);
1279             position++;
1280             return f.Getc(ch);
1281          }
1282       }
1283       return false;
1284    }
1285
1286    bool Putc(char ch)
1287    {
1288       return false;
1289    }
1290
1291    bool Puts(const char * string)
1292    {
1293       return false;
1294    }
1295
1296    bool Seek(int pos, FileSeekMode mode)
1297    {
1298       bool result = false;
1299       switch(mode)
1300       {
1301          case start:
1302             if(pos <= (int)size)
1303             {
1304                position = pos;
1305                if(f)
1306                   result = f.Seek(position + start, start);
1307                else
1308                   result = true;
1309             }
1310             break;
1311          case current:
1312             if(position + pos <= (int)size && (int)position >= -pos)
1313             {
1314                position += pos;
1315                if(f)
1316                   result = f.Seek(position + start, start);
1317                else
1318                   result = true;
1319             }
1320             break;
1321          case end:
1322             if(pos < 0 && -pos <= (int)size)
1323             {
1324                position = size + pos;
1325                if(f)
1326                   f.Seek(position + start, start);
1327                else
1328                   result = true;
1329             }
1330             break;
1331       }
1332       return result;
1333    }
1334
1335    uint Tell()
1336    {
1337       return position;
1338    }
1339
1340    bool Eof()
1341    {
1342       return position >= size || (f && f.Eof());
1343    }
1344
1345    uint GetSize()
1346    {
1347       return size;
1348    }
1349 };
1350
1351 class EARFileSystem : FileSystem
1352 {
1353    File ::Open(const char * archive, const char * name, FileOpenMode mode)
1354    {
1355       File result = null;
1356       if(mode == read)
1357       {
1358          EARFile file {};
1359          if(file)
1360          {
1361             char fileName[MAX_LOCATION];
1362             EARHeader header;
1363             File f = EAROpenArchive(archive, &header);
1364             strcpy(fileName, name);
1365    #ifdef ECERE_STATIC
1366             if(!f && archive[0] == ':')
1367             {
1368                f = EAROpenArchive(":", &header);
1369                if(f)
1370                {
1371                   strcpy(fileName, archive + 1);
1372                   PathCat(fileName, name);
1373                }
1374             }
1375    #endif
1376             if(f)
1377             {
1378                EAREntry entry { };
1379                if(EARGetEntry(f, entry, fileName, null).isFile)
1380                {
1381                   if(entry.cSize)
1382                   {
1383                      byte * uncompressed = new byte[entry.size];
1384                      if(uncompressed)
1385                      {
1386                         byte * compressed = new byte[entry.cSize];
1387                         if(compressed)
1388                         {
1389                            if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
1390                            {
1391                               unsigned long destLen = entry.size;
1392                               uncompress(uncompressed, &destLen, compressed, entry.cSize);
1393                               entry.size = (FileSize)destLen;
1394                            }
1395                            delete compressed;
1396                         }
1397
1398                         file.position = 0;
1399                         file.size = entry.size;
1400                         file.buffer = uncompressed;
1401
1402                         result = file;
1403                      }
1404                   }
1405                   else
1406                   {
1407                      file.start = f.Tell();
1408                      file.position = 0;
1409                      file.size = entry.size;
1410                      file.f = f;
1411                      f = null;
1412
1413                      result = file;
1414                   }
1415                }
1416                delete f;
1417             }
1418             if(!result)
1419                delete file;
1420          }
1421       }
1422       return result;
1423    }
1424
1425    FileAttribs ::Exists(const char * archive, const char * fileName)
1426    {
1427       uint result = 0;
1428       EARHeader header;
1429       File f = EAROpenArchive(archive, &header);
1430       if(f)
1431       {
1432          EAREntry entry { };
1433          result = EARGetEntry(f, entry, fileName, null);
1434          delete f;
1435       }
1436       return result;
1437    }
1438
1439    bool ::GetSize(const char * archive, const char * fileName, FileSize * size)
1440    {
1441       bool result = false;
1442       EARHeader header;
1443       File f = EAROpenArchive(archive, &header);
1444       if(f)
1445       {
1446          EAREntry entry { };
1447          if(EARGetEntry(f, entry, fileName, null))
1448             *size = entry.size;
1449          delete f;
1450          result = true;
1451       }
1452       return result;
1453    }
1454
1455    bool ::Stats(const char * archive, const char * fileName, FileStats stats)
1456    {
1457       bool result = false;
1458       EARHeader header;
1459       File f = EAROpenArchive(archive, &header);
1460       if(f)
1461       {
1462          EAREntry entry { };
1463          if(EARGetEntry(f, entry, fileName, null))
1464          {
1465             stats.size = entry.size;
1466             stats.accessed = 0;
1467             stats.modified = (TimeStamp)entry.modified;
1468             stats.created = (TimeStamp)entry.created;
1469             result = true;
1470          }
1471          delete f;
1472       }
1473       return result;
1474    }
1475
1476    void ::FixCase(const char * archive, char * name)
1477    {
1478    #ifdef __WIN32__
1479       EARHeader header;
1480       File f = EAROpenArchive(archive, &header);
1481       if(f)
1482       {
1483          EAREntry entry { };
1484          char fileName[MAX_LOCATION] = "";
1485          if(EARGetEntry(f, entry, name, fileName))
1486             strcpy(name, fileName);
1487          delete f;
1488       }
1489    #endif
1490    }
1491
1492    bool ::Find(FileDesc file, const char * archive, const char * name)
1493    {
1494       bool result = false;
1495       EARDir d {};
1496       if(d)
1497       {
1498          EARHeader header;
1499          File f = EAROpenArchive(archive, &header);
1500          if(f)
1501          {
1502             EAREntry entry { };
1503             if(EARGetEntry(f, entry, name, null).isDirectory)
1504             {
1505                uint first, last;
1506
1507                sprintf(d.path, "<%s>%s", archive, name);
1508                d.f = f;
1509                f.Read(&first, sizeof(uint), 1);
1510                f.Read(&last, sizeof(uint), 1);
1511                d.next = first;
1512                if(d.next)
1513                {
1514                   EAREntry entry { };
1515                   d.f.Seek(d.next, start);
1516                   d.f.Read(entry, sizeof(EAREntry), 1);
1517                   d.f.Read(file.name, 1, entry.nameLen);
1518                   file.name[entry.nameLen] = '\0';
1519                   file.stats.attribs = { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) };
1520                   file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified;
1521                   file.stats.created = (TimeStamp)entry.created;
1522                   file.stats.size = entry.size;
1523
1524                   strcpy(file.path, d.path);
1525                   PathCat(file.path, file.name);
1526                   d.next = entry.next;
1527
1528                   file.dir = (Dir)d;
1529
1530                   result = true;
1531                }
1532             }
1533             if(!result)
1534                delete f;
1535          }
1536          if(!result)
1537             delete d;
1538       }
1539       return result;
1540    }
1541
1542    bool ::FindNext(FileDesc file)
1543    {
1544       bool result = false;
1545       EARDir d = (EARDir)file.dir;
1546       if(d.next)
1547       {
1548          EAREntry entry { };
1549          d.f.Seek(d.next, start);
1550          d.f.Read(entry, sizeof(EAREntry), 1);
1551          d.f.Read(file.name, 1, entry.nameLen);
1552          file.name[entry.nameLen] = '\0';
1553          file.stats.attribs = FileAttribs { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) };
1554          file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified;
1555          file.stats.created = (TimeStamp)entry.created;
1556          file.stats.size = entry.size;
1557
1558          strcpy(file.path, d.path);
1559          PathCat(file.path, file.name);
1560          d.next = entry.next;
1561
1562          result = true;
1563       }
1564       return result;
1565    }
1566
1567    void ::CloseDir(FileDesc file)
1568    {
1569       EARDir d = (EARDir)file.dir;
1570       if(d.f)
1571          delete d.f;
1572       if(d)
1573          delete d;
1574    }
1575
1576 #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1577    Archive ::OpenArchive(const char * fileName, ArchiveOpenFlags flags)
1578    {
1579       Archive result = null;
1580       EARArchive archive { writeAccess = flags.writeAccess };
1581       if(archive)
1582       {
1583          int try = flags.waitLock ? 10 : 0;
1584          for(; try >= 0; try--)
1585          {
1586             // Check for existing Archive
1587             if((archive.f = fileName ? (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, flags.writeAccess ? readWrite : read) : TempFile { openMode = readWrite } ))
1588             {
1589                EARHeader header;
1590                bool opened = false;
1591                uint archiveSize = 0;
1592                archive.f.Seek(-(int)sizeof(uint), end);
1593                archive.f.Read(&archiveSize, sizeof(uint), 1);
1594                archive.f.Seek(-(int)archiveSize, end);
1595
1596                archive.archiveStart = archive.f.Tell();
1597                if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 &&
1598                   !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
1599                   opened = true;
1600
1601                if(!opened)
1602                {
1603                   archive.f.Seek(0, start);
1604                   archive.archiveStart = archive.f.Tell();
1605                   archiveSize = archive.f.GetSize();
1606                   if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 &&
1607                      !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
1608                      opened = true;
1609                }
1610
1611                if(opened)
1612                {
1613                   // At this point we recognized the file as a valid eAR archive
1614                   archive.rootDir = archive.archiveStart + sizeof(EARHeader);
1615                   archive.totalSize = header.totalSize;
1616
1617                   archive.f.Seek(archive.rootDir, start);
1618                   if(flags.buffered)
1619                   {
1620                      archive.freeBlocks.Add(FreeBlock { start = archive.rootDir + 2 * sizeof(uint), end = MAXDWORD });
1621                      archive.SubtractUsedBlocks();
1622                   }
1623                   else
1624                   {
1625                      archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + (archiveSize - sizeof(uint)), end = MAXDWORD });
1626                   }
1627
1628                   /*
1629                   if(!flags.writeAccess)
1630                   {
1631                      delete archive.f;
1632                      archive.f = FileOpen(fileName, readWrite);
1633                   }
1634                   */
1635                   if(archive.f)
1636                   {
1637                      incref archive.f;
1638                      result = archive;
1639                   }
1640                }
1641                break;
1642             }
1643             else if(try > 0)
1644                Sleep(0.01);
1645          }
1646
1647          // This piece of code will create a new archive as a new file or at the footer
1648          // of an existing file.
1649          if(!result && flags.writeAccess)
1650          {
1651             // If the file doesn't exist, create it
1652             if(!archive.f)
1653             {
1654                archive.f = FileOpen(fileName, writeRead);
1655                delete archive.f;
1656                archive.f = (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, readWrite);
1657             }
1658             if(archive.f)
1659             {
1660                EARHeader header
1661                {
1662                   EAR_RECOGNITION,
1663                   MDWORD(0, 1)
1664                };
1665
1666                archive.f.Seek(0, end);
1667
1668                archive.archiveStart = archive.f.Tell();
1669                archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + sizeof(EARHeader), end = MAXDWORD });
1670
1671                // Write Header
1672                archive.f.Write(&header, sizeof(EARHeader), 1);
1673                {
1674                   uint first = 0, last = 0;
1675                   archive.f.Write(&first, sizeof(first), 1);
1676                   archive.f.Write(&last, sizeof(last), 1);
1677                }
1678
1679                archive.rootDir = 0;
1680                incref archive.f;
1681                result = archive;
1682             }
1683          }
1684          if(archive.f && flags.writeAccess && flags.exclusive && !archive.f.Lock(flags.exclusive ? exclusive : shared, 0, 0, flags.waitLock))
1685             result = null;
1686          if(!result)
1687          {
1688             delete archive.f;
1689             delete archive;
1690          }
1691          else
1692          {
1693             // archive.f.handle = archive.f;
1694          }
1695       }
1696       return result;
1697    }
1698 #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1699    bool ::QuerySize(const char * archive, FileSize * size)
1700    {
1701       bool result = false;
1702       EARHeader header;
1703       File f = EAROpenArchive(archive, &header);
1704       if(f)
1705       {
1706          *size = header.totalSize;
1707          result = true;
1708          delete f;
1709       }
1710       return result;
1711    }
1712 };