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