d9fd49a995f8436a8ca610ab8a2d8e43d7ba7c85
[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                      compress2(compressed, &destLen, uncompressed, entry.size, compression);
1127                      entry.cSize = (FileSize)destLen;
1128                   }
1129                }
1130                delete uncompressed;
1131             }
1132          }
1133
1134          if(!compressed)
1135          {
1136             entry.cSize = 0;
1137             if(ratio)
1138                *ratio = 0;
1139          }
1140          else if(ratio)
1141             *ratio = entry.size ? (entry.cSize * 1000 / entry.size) : 0;
1142
1143          // Find block
1144          size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size);
1145          position = archive.Position(size);
1146
1147          // Write Header
1148          if(!archive.f.Seek(position, start) ||
1149             !archive.f.Write(entry, sizeof(EAREntry), 1) ||
1150             !archive.f.Write(name, entry.nameLen, 1))
1151          {
1152             delete compressed;
1153             return false;
1154          }
1155
1156          // Write File Data
1157          if(compressed)
1158          {
1159             if(!archive.f.Write(compressed, 1, entry.cSize))
1160             {
1161                delete compressed;
1162                return false;
1163             }
1164             delete compressed;
1165          }
1166          else
1167          {
1168             byte buffer[8192];
1169             uint c;
1170             int count = 1;
1171             for(c = 0; c<entry.size && count; c+= count)
1172             {
1173                count = input.Read(buffer, 1, sizeof(buffer));
1174                if(!archive.f.Write(buffer, 1, count))
1175                   return false;
1176             }
1177          }
1178
1179          // Update the next pointer previous entry
1180          if(entry.prev)
1181          {
1182             archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
1183             archive.f.Write(&position, sizeof(uint), 1);
1184          }
1185
1186          // Update total size of archive
1187          archive.totalSize += entry.size;
1188
1189          last = position;
1190          if(!first) first = position;
1191          if(newPosition) *newPosition = position;
1192       }
1193       else
1194       {
1195          if(newPosition) *newPosition = 0;
1196       }
1197
1198       // archive.f.handle = archive.f;
1199       return true;
1200    }
1201 };
1202 #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1203
1204 // Directory Description for file listing
1205 class EARDir : struct
1206 {
1207    char path[MAX_LOCATION];
1208    File f;
1209    uint next;
1210 };
1211
1212 class EARFile : File
1213 {
1214    uint position;
1215    uint size;
1216
1217    // -- For reading compressed file (entirely buffered)
1218    byte * buffer;
1219
1220    // -- For reading uncompressed file (not buffered)
1221    File f;
1222    uint start;
1223
1224    ~EARFile()
1225    {
1226       delete buffer;
1227       delete f;
1228    }
1229
1230    void CloseInput()
1231    {
1232       if(f)
1233          f.CloseInput();
1234    }
1235
1236    void CloseOutput()
1237    {
1238       if(f)
1239          f.CloseOutput();
1240    }
1241
1242    int Read(byte * buffer, uint size, uint count)
1243    {
1244       int read = 0;
1245       if(f)
1246          f.Seek(position + start, start);
1247       read = Min(count, (this.size - position) / size);
1248       if(this.buffer)
1249          CopyBytes(buffer, this.buffer + position, read * size);
1250       else
1251          read = f.Read(buffer, size, read);
1252       position += read * size;
1253       return read;
1254    }
1255
1256    int Write(const byte * buffer, uint size, uint count)
1257    {
1258       return 0;
1259    }
1260
1261    bool Getc(char * ch)
1262    {
1263       if(position < size)
1264       {
1265          if(buffer)
1266          {
1267             char b = buffer[position++];
1268             if(ch) *ch = b;
1269             return true;
1270          }
1271          else
1272          {
1273             f.Seek(position + start, start);
1274             position++;
1275             return f.Getc(ch);
1276          }
1277       }
1278       return false;
1279    }
1280
1281    bool Putc(char ch)
1282    {
1283       return false;
1284    }
1285
1286    bool Puts(const char * string)
1287    {
1288       return false;
1289    }
1290
1291    bool Seek(int pos, FileSeekMode mode)
1292    {
1293       bool result = false;
1294       switch(mode)
1295       {
1296          case start:
1297             if(pos <= (int)size)
1298             {
1299                position = pos;
1300                if(f)
1301                   result = f.Seek(position + start, start);
1302                else
1303                   result = true;
1304             }
1305             break;
1306          case current:
1307             if(position + pos <= (int)size && (int)position >= -pos)
1308             {
1309                position += pos;
1310                if(f)
1311                   result = f.Seek(position + start, start);
1312                else
1313                   result = true;
1314             }
1315             break;
1316          case end:
1317             if(pos < 0 && -pos <= (int)size)
1318             {
1319                position = size + pos;
1320                if(f)
1321                   f.Seek(position + start, start);
1322                else
1323                   result = true;
1324             }
1325             break;
1326       }
1327       return result;
1328    }
1329
1330    uint Tell()
1331    {
1332       return position;
1333    }
1334
1335    bool Eof()
1336    {
1337       return position >= size || (f && f.Eof());
1338    }
1339
1340    uint GetSize()
1341    {
1342       return size;
1343    }
1344 };
1345
1346 class EARFileSystem : FileSystem
1347 {
1348    File ::Open(const char * archive, const char * name, FileOpenMode mode)
1349    {
1350       File result = null;
1351       if(mode == read)
1352       {
1353          EARFile file {};
1354          if(file)
1355          {
1356             char fileName[MAX_LOCATION];
1357             EARHeader header;
1358             File f = EAROpenArchive(archive, &header);
1359             strcpy(fileName, name);
1360    #ifdef ECERE_STATIC
1361             if(!f && archive[0] == ':')
1362             {
1363                f = EAROpenArchive(":", &header);
1364                if(f)
1365                {
1366                   strcpy(fileName, archive + 1);
1367                   PathCat(fileName, name);
1368                }
1369             }
1370    #endif
1371             if(f)
1372             {
1373                EAREntry entry { };
1374                if(EARGetEntry(f, entry, fileName, null).isFile)
1375                {
1376                   if(entry.cSize)
1377                   {
1378                      byte * uncompressed = new byte[entry.size];
1379                      if(uncompressed)
1380                      {
1381                         byte * compressed = new byte[entry.cSize];
1382                         if(compressed)
1383                         {
1384                            if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
1385                            {
1386                               unsigned long destLen = entry.size;
1387                               uncompress(uncompressed, &destLen, compressed, entry.cSize);
1388                               entry.size = (FileSize)destLen;
1389                            }
1390                            delete compressed;
1391                         }
1392
1393                         file.position = 0;
1394                         file.size = entry.size;
1395                         file.buffer = uncompressed;
1396
1397                         result = file;
1398                      }
1399                   }
1400                   else
1401                   {
1402                      file.start = f.Tell();
1403                      file.position = 0;
1404                      file.size = entry.size;
1405                      file.f = f;
1406                      f = null;
1407
1408                      result = file;
1409                   }
1410                }
1411                delete f;
1412             }
1413             if(!result)
1414                delete file;
1415          }
1416       }
1417       return result;
1418    }
1419
1420    FileAttribs ::Exists(const char * archive, const char * fileName)
1421    {
1422       uint result = 0;
1423       EARHeader header;
1424       File f = EAROpenArchive(archive, &header);
1425       if(f)
1426       {
1427          EAREntry entry { };
1428          result = EARGetEntry(f, entry, fileName, null);
1429          delete f;
1430       }
1431       return result;
1432    }
1433
1434    bool ::GetSize(const char * archive, const char * fileName, FileSize * size)
1435    {
1436       bool result = false;
1437       EARHeader header;
1438       File f = EAROpenArchive(archive, &header);
1439       if(f)
1440       {
1441          EAREntry entry { };
1442          if(EARGetEntry(f, entry, fileName, null))
1443             *size = entry.size;
1444          delete f;
1445          result = true;
1446       }
1447       return result;
1448    }
1449
1450    bool ::Stats(const char * archive, const char * fileName, FileStats stats)
1451    {
1452       bool result = false;
1453       EARHeader header;
1454       File f = EAROpenArchive(archive, &header);
1455       if(f)
1456       {
1457          EAREntry entry { };
1458          if(EARGetEntry(f, entry, fileName, null))
1459          {
1460             stats.size = entry.size;
1461             stats.accessed = 0;
1462             stats.modified = (TimeStamp)entry.modified;
1463             stats.created = (TimeStamp)entry.created;
1464             result = true;
1465          }
1466          delete f;
1467       }
1468       return result;
1469    }
1470
1471    void ::FixCase(const char * archive, char * name)
1472    {
1473    #ifdef __WIN32__
1474       EARHeader header;
1475       File f = EAROpenArchive(archive, &header);
1476       if(f)
1477       {
1478          EAREntry entry { };
1479          char fileName[MAX_LOCATION] = "";
1480          if(EARGetEntry(f, entry, name, fileName))
1481             strcpy(name, fileName);
1482          delete f;
1483       }
1484    #endif
1485    }
1486
1487    bool ::Find(FileDesc file, const char * archive, const char * name)
1488    {
1489       bool result = false;
1490       EARDir d {};
1491       if(d)
1492       {
1493          EARHeader header;
1494          File f = EAROpenArchive(archive, &header);
1495          if(f)
1496          {
1497             EAREntry entry { };
1498             if(EARGetEntry(f, entry, name, null).isDirectory)
1499             {
1500                uint first, last;
1501
1502                sprintf(d.path, "<%s>%s", archive, name);
1503                d.f = f;
1504                f.Read(&first, sizeof(uint), 1);
1505                f.Read(&last, sizeof(uint), 1);
1506                d.next = first;
1507                if(d.next)
1508                {
1509                   EAREntry entry { };
1510                   d.f.Seek(d.next, start);
1511                   d.f.Read(entry, sizeof(EAREntry), 1);
1512                   d.f.Read(file.name, 1, entry.nameLen);
1513                   file.name[entry.nameLen] = '\0';
1514                   file.stats.attribs = { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) };
1515                   file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified;
1516                   file.stats.created = (TimeStamp)entry.created;
1517                   file.stats.size = entry.size;
1518
1519                   strcpy(file.path, d.path);
1520                   PathCat(file.path, file.name);
1521                   d.next = entry.next;
1522
1523                   file.dir = (Dir)d;
1524
1525                   result = true;
1526                }
1527             }
1528             if(!result)
1529                delete f;
1530          }
1531          if(!result)
1532             delete d;
1533       }
1534       return result;
1535    }
1536
1537    bool ::FindNext(FileDesc file)
1538    {
1539       bool result = false;
1540       EARDir d = (EARDir)file.dir;
1541       if(d.next)
1542       {
1543          EAREntry entry { };
1544          d.f.Seek(d.next, start);
1545          d.f.Read(entry, sizeof(EAREntry), 1);
1546          d.f.Read(file.name, 1, entry.nameLen);
1547          file.name[entry.nameLen] = '\0';
1548          file.stats.attribs = FileAttribs { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) };
1549          file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified;
1550          file.stats.created = (TimeStamp)entry.created;
1551          file.stats.size = entry.size;
1552
1553          strcpy(file.path, d.path);
1554          PathCat(file.path, file.name);
1555          d.next = entry.next;
1556
1557          result = true;
1558       }
1559       return result;
1560    }
1561
1562    void ::CloseDir(FileDesc file)
1563    {
1564       EARDir d = (EARDir)file.dir;
1565       if(d.f)
1566          delete d.f;
1567       if(d)
1568          delete d;
1569    }
1570
1571 #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1572    Archive ::OpenArchive(const char * fileName, ArchiveOpenFlags flags)
1573    {
1574       Archive result = null;
1575       EARArchive archive { writeAccess = flags.writeAccess };
1576       if(archive)
1577       {
1578          int try = flags.waitLock ? 10 : 0;
1579          for(; try >= 0; try--)
1580          {
1581             // Check for existing Archive
1582             if((archive.f = fileName ? (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, flags.writeAccess ? readWrite : read) : TempFile { openMode = readWrite } ))
1583             {
1584                EARHeader header;
1585                bool opened = false;
1586                uint archiveSize = 0;
1587                archive.f.Seek(-(int)sizeof(uint), end);
1588                archive.f.Read(&archiveSize, sizeof(uint), 1);
1589                archive.f.Seek(-(int)archiveSize, end);
1590
1591                archive.archiveStart = archive.f.Tell();
1592                if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 &&
1593                   !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
1594                   opened = true;
1595
1596                if(!opened)
1597                {
1598                   archive.f.Seek(0, start);
1599                   archive.archiveStart = archive.f.Tell();
1600                   archiveSize = archive.f.GetSize();
1601                   if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 &&
1602                      !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
1603                      opened = true;
1604                }
1605
1606                if(opened)
1607                {
1608                   // At this point we recognized the file as a valid eAR archive
1609                   archive.rootDir = archive.archiveStart + sizeof(EARHeader);
1610                   archive.totalSize = header.totalSize;
1611
1612                   archive.f.Seek(archive.rootDir, start);
1613                   if(flags.buffered)
1614                   {
1615                      archive.freeBlocks.Add(FreeBlock { start = archive.rootDir + 2 * sizeof(uint), end = MAXDWORD });
1616                      archive.SubtractUsedBlocks();
1617                   }
1618                   else
1619                   {
1620                      archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + (archiveSize - sizeof(uint)), end = MAXDWORD });
1621                   }
1622
1623                   /*
1624                   if(!flags.writeAccess)
1625                   {
1626                      delete archive.f;
1627                      archive.f = FileOpen(fileName, readWrite);
1628                   }
1629                   */
1630                   if(archive.f)
1631                   {
1632                      incref archive.f;
1633                      result = archive;
1634                   }
1635                }
1636                break;
1637             }
1638             else if(try > 0)
1639                Sleep(0.01);
1640          }
1641
1642          // This piece of code will create a new archive as a new file or at the footer
1643          // of an existing file.
1644          if(!result && flags.writeAccess)
1645          {
1646             // If the file doesn't exist, create it
1647             if(!archive.f)
1648             {
1649                archive.f = FileOpen(fileName, writeRead);
1650                delete archive.f;
1651                archive.f = (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, readWrite);
1652             }
1653             if(archive.f)
1654             {
1655                EARHeader header
1656                {
1657                   EAR_RECOGNITION,
1658                   MDWORD(0, 1)
1659                };
1660
1661                archive.f.Seek(0, end);
1662
1663                archive.archiveStart = archive.f.Tell();
1664                archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + sizeof(EARHeader), end = MAXDWORD });
1665
1666                // Write Header
1667                archive.f.Write(&header, sizeof(EARHeader), 1);
1668                {
1669                   uint first = 0, last = 0;
1670                   archive.f.Write(&first, sizeof(first), 1);
1671                   archive.f.Write(&last, sizeof(last), 1);
1672                }
1673
1674                archive.rootDir = 0;
1675                incref archive.f;
1676                result = archive;
1677             }
1678          }
1679          if(archive.f && flags.writeAccess && flags.exclusive && !archive.f.Lock(flags.exclusive ? exclusive : shared, 0, 0, flags.waitLock))
1680             result = null;
1681          if(!result)
1682          {
1683             delete archive.f;
1684             delete archive;
1685          }
1686          else
1687          {
1688             // archive.f.handle = archive.f;
1689          }
1690       }
1691       return result;
1692    }
1693 #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1694    bool ::QuerySize(const char * archive, FileSize * size)
1695    {
1696       bool result = false;
1697       EARHeader header;
1698       File f = EAROpenArchive(archive, &header);
1699       if(f)
1700       {
1701          *size = header.totalSize;
1702          result = true;
1703          delete f;
1704       }
1705       return result;
1706    }
1707 };