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