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