Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
ecere-sdk/ecere/src/sys/EARArchive.ec
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1729 lines (1530 sloc)
47.9 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace sys; | |
| #define uint _uint | |
| #include "zlib.h" | |
| #undef uint | |
| import "System" | |
| import "BufferedFile" | |
| #define OFFSET(s, m) ((uint)(uintptr) (&((s *) 0)->m)) | |
| #define MDWORD(a,b) ((((uint32)((uint16)(b)))<<16)+((uint16)(a))) | |
| #define EAR_RECOGNITION { 'e', 'A', 'R', 228, 11, 12, 3, 0 } | |
| static byte earRecognition[] = EAR_RECOGNITION; | |
| static class FreeBlock : struct | |
| { | |
| FreeBlock prev, next; | |
| uint64 start, end; | |
| }; | |
| static struct EARHeader | |
| { | |
| byte recognition[sizeof(earRecognition)]; | |
| uint version __attribute__((packed)); | |
| FileSize totalSize __attribute__((packed)); | |
| }; | |
| static enum EAREntryType { ENTRY_FILE = 1, ENTRY_FOLDER = 2 }; | |
| static struct EAREntry | |
| { | |
| EAREntryType type __attribute__((packed)); | |
| TimeStamp32 created, modified __attribute__((packed)); | |
| FileSize size, cSize __attribute__((packed)); | |
| uint prev, next __attribute__((packed)); | |
| uint nameLen __attribute__((packed)); | |
| // null terminated file name follows | |
| }; | |
| static File EAROpenArchive(const char * archive, EARHeader header) | |
| { | |
| File f = null; | |
| if(archive[0] == ':') | |
| { | |
| char moduleName[MAX_LOCATION]; | |
| const char * name = archive + 1; | |
| #if defined(__ANDROID__) | |
| if(!name[0]) | |
| name = ((SubModule)__thisModule.application.modules.first).next.module.name; | |
| #endif | |
| #if defined(__EMSCRIPTEN__) | |
| if(!name[0]) | |
| f = FileOpen("resources.ear", read); | |
| #endif | |
| if(!f && LocateModule(name, moduleName)) | |
| f = FileOpen(moduleName, read); | |
| } | |
| else | |
| f = FileOpen(archive, read); | |
| if(f) | |
| { | |
| uint archiveSize; | |
| // First attempt to treat this as an archive file | |
| if(f.Read(header, sizeof(EARHeader), 1) == 1 && | |
| !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) | |
| return f; | |
| // Then try to see if an archive is at the end of the file | |
| f.Seek(-(int)sizeof(uint), end); | |
| f.Read(&archiveSize, sizeof(uint), 1); | |
| f.Seek(-(int)archiveSize, end); | |
| if(f.Read(header, sizeof(EARHeader), 1) == 1 && | |
| !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) | |
| return f; | |
| delete f; | |
| } | |
| return null; | |
| } | |
| static FileAttribs EARGetEntry(File f, EAREntry entry, const char * name, char * path) | |
| { | |
| uint first = 0, last = 0; | |
| if(!name[0]) | |
| return FileAttribs { isDirectory = true }; | |
| if(!f.Read(&first, sizeof(uint), 1)) | |
| return 0; | |
| f.Read(&last, sizeof(uint), 1); | |
| if(first) | |
| { | |
| char namePart[MAX_FILENAME], nameRest[MAX_LOCATION]; | |
| SplitDirectory(name, namePart, nameRest); | |
| if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) | |
| strcpy(namePart, DIR_SEPS); | |
| f.Seek(first, start); | |
| for(;;) | |
| { | |
| char fileName[MAX_FILENAME]; | |
| f.Read(entry, sizeof(EAREntry), 1); | |
| f.Read(fileName, 1, entry.nameLen); | |
| fileName[entry.nameLen] = '\0'; | |
| if(!strcmp(fileName, "/") || !strcmp(fileName, "\\")) | |
| strcpy(fileName, DIR_SEPS); | |
| if(!fstrcmp(fileName, namePart)) | |
| { | |
| if(path) | |
| PathCat(path, fileName); | |
| if(nameRest[0]) | |
| return EARGetEntry(f, entry, nameRest, path); | |
| else | |
| return (entry.type == ENTRY_FILE) ? FileAttribs { isFile = true } : FileAttribs { isDirectory = true }; | |
| } | |
| if(entry.next) | |
| f.Seek(entry.next, start); | |
| else | |
| break; | |
| } | |
| } | |
| return FileAttribs { }; | |
| } | |
| #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) | |
| class EARArchive : Archive | |
| { | |
| File f; | |
| //BufferedFile bf { }; | |
| // char path[MAX_LOCATION]; | |
| uint64 archiveStart; | |
| uint64 rootDir; | |
| OldList freeBlocks; | |
| bool writeAccess; | |
| uint64 Update() | |
| { | |
| if(rootDir) | |
| { | |
| uint64 end = ((FreeBlock)freeBlocks.last).start; | |
| // Update header | |
| f.Seek(archiveStart + OFFSET(EARHeader, totalSize), start); | |
| f.Write(&totalSize, sizeof(uint), 1); | |
| // Write Footer | |
| f.Seek(end, start); | |
| end += sizeof(uint); | |
| end -= archiveStart; | |
| f.Write(&end, sizeof(uint), 1); | |
| f.Truncate(archiveStart + end); | |
| return end; | |
| } | |
| return 0; | |
| } | |
| ~EARArchive() | |
| { | |
| if(f && rootDir && writeAccess) | |
| { | |
| // Perform Defrag | |
| Defrag(rootDir); | |
| archiveStart += Update(); | |
| } | |
| if(f && writeAccess) | |
| { | |
| f.Flush(); | |
| f.Unlock(0, 0, true); | |
| } | |
| delete f; | |
| /*if(rootDir && writeAccess) | |
| { | |
| // Fix the size of the archive | |
| FileTruncate(path, archiveStart); | |
| }*/ | |
| freeBlocks.Free(null); | |
| } | |
| bool Clear() | |
| { | |
| rootDir = 0; | |
| return true; | |
| } | |
| ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode) | |
| { | |
| ArchiveDir result = null; | |
| EARArchiveDir dir { readOnly = addMode == readOnlyDir }; | |
| if(dir) | |
| { | |
| char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION]; | |
| dir.archive = this; | |
| strcpy(nameRest, name); | |
| if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) | |
| strcpy(namePart, DIR_SEPS); | |
| // Search for directory | |
| if(rootDir) | |
| { | |
| dir.position = rootDir; | |
| if(f.Seek(dir.position, start)) | |
| { | |
| dir.first = 0; | |
| dir.last = 0; | |
| f.Read(&dir.first, sizeof(uint), 1); | |
| f.Read(&dir.last, sizeof(uint), 1); | |
| result = dir; | |
| } | |
| } | |
| // If directory doesn't exist already | |
| if(!result && addMode != refresh) | |
| { | |
| rootDir = Position(2*sizeof(uint)); | |
| dir.position = rootDir; | |
| } | |
| result = dir; | |
| // Open rest of directory... | |
| if(result && nameRest[0]) | |
| { | |
| result = dir.OpenDirectory(nameRest, stats, addMode); | |
| delete dir; | |
| } | |
| } | |
| return result; | |
| } | |
| uint64 Position(uint64 size) | |
| { | |
| FreeBlock block; | |
| for(block = freeBlocks.first; block; block = block.next) | |
| { | |
| if(block.end - block.start + 1 >= size) | |
| { | |
| uint64 position = block.start; | |
| if(block.end - block.start + 1 == size) | |
| freeBlocks.Delete(block); | |
| else | |
| block.start += size; | |
| return position; | |
| } | |
| } | |
| return 0; | |
| } | |
| bool DefragOffset(uint * offset) | |
| { | |
| FreeBlock block; | |
| uint subtract = 0; | |
| for(block = freeBlocks.first; block; block = block.next) | |
| { | |
| if(*offset > block.start) | |
| subtract += block.end - block.start + 1; | |
| else | |
| break; | |
| } | |
| if(subtract) | |
| { | |
| *offset -= subtract; | |
| return true; | |
| } | |
| return false; | |
| } | |
| #define MAX_BUFFERSIZE 0x400000 | |
| void Defrag(uint64 dirPosition) | |
| { | |
| // Update all offsets within the files | |
| uint first = 0, last = 0; | |
| uint64 position = 0, next = 0; | |
| f.Seek(dirPosition, start); | |
| f.Read(&first, sizeof(uint), 1); | |
| f.Read(&last, sizeof(uint), 1); | |
| position = first; | |
| if(first && DefragOffset(&first)) | |
| { | |
| if(f.Seek(dirPosition, start)) | |
| f.Write(&first, sizeof(uint), 1); | |
| } | |
| if(last && DefragOffset(&last)) | |
| { | |
| if(f.Seek(dirPosition + sizeof(uint), start)) | |
| f.Write(&last, sizeof(uint), 1); | |
| } | |
| for(; position; position = next) | |
| { | |
| EAREntry entry { }; | |
| if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1)) | |
| { | |
| next = entry.next; | |
| if(entry.prev && DefragOffset(&entry.prev)) | |
| { | |
| f.Seek(position + OFFSET(EAREntry, prev), start); | |
| f.Write(&entry.prev, sizeof(uint), 1); | |
| } | |
| if(entry.next && DefragOffset(&entry.next)) | |
| { | |
| f.Seek(position + OFFSET(EAREntry, next), start); | |
| f.Write(&entry.next, sizeof(uint), 1); | |
| } | |
| if(entry.type == ENTRY_FOLDER) | |
| Defrag(position + sizeof(EAREntry) + entry.nameLen); | |
| } | |
| else | |
| return; | |
| } | |
| // Move all the blocks | |
| if(dirPosition == rootDir) | |
| { | |
| uint64 bufferSize = 0; | |
| byte * buffer = null; | |
| FreeBlock block, nextBlock; | |
| for(block = freeBlocks.first; block && block.next; block = nextBlock) | |
| { | |
| uint64 dataSize, c; | |
| nextBlock = block.next; | |
| dataSize = nextBlock.start - (block.end + 1); | |
| if(dataSize > bufferSize && (!bufferSize || bufferSize < MAX_BUFFERSIZE)) | |
| { | |
| bufferSize = Min(dataSize, MAX_BUFFERSIZE); | |
| buffer = renew buffer byte[bufferSize]; | |
| } | |
| for(c = 0; c<dataSize; c += bufferSize) | |
| { | |
| uint64 size = (dataSize > c + bufferSize) ? bufferSize : (dataSize - c); | |
| // Read block of data | |
| f.Seek((block.end + 1) + c, start); | |
| f.Read(buffer, size, 1); | |
| // Write block of data | |
| f.Seek(block.start + c, start); | |
| f.Write(buffer, size, 1); | |
| } | |
| nextBlock.start -= (block.end - block.start) + 1; | |
| freeBlocks.Delete(block); | |
| } | |
| delete buffer; | |
| } | |
| } | |
| uint64 Find(EARArchiveDir directory, const char * namePart, EAREntry entry) | |
| { | |
| uint64 position; | |
| for(position = directory.first; position; position = entry.next) | |
| { | |
| char fileName[MAX_FILENAME]; | |
| if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1)) | |
| { | |
| if(entry.nameLen > MAX_FILENAME) | |
| return 0; // CORRUPTION ERROR | |
| f.Read(fileName, 1, entry.nameLen); | |
| fileName[entry.nameLen] = '\0'; | |
| if(!strcmp(fileName, "/") || !strcmp(fileName, "\\")) | |
| strcpy(fileName, DIR_SEPS); | |
| if(!fstrcmp(fileName, namePart)) | |
| return position; | |
| } | |
| else | |
| return 0; // ERROR OUT OF SPACE? | |
| } | |
| return 0; | |
| } | |
| void AddFreeBlock(uint64 position, uint64 size) | |
| { | |
| FreeBlock block, prevBlock, nextBlock = null; | |
| // Find the previous and next free block | |
| prevBlock = null; | |
| for(block = freeBlocks.first; block; block = block.next) | |
| if(block.end < position) | |
| prevBlock = block; | |
| else | |
| { | |
| nextBlock = block; | |
| break; | |
| } | |
| // Try to merge with previous block | |
| if(prevBlock && prevBlock.end + 1 == position) | |
| { | |
| prevBlock.end += size; | |
| // Try to merge with next block as well | |
| if(nextBlock && nextBlock.start == prevBlock.end + 1) | |
| { | |
| prevBlock.end = nextBlock.end; | |
| freeBlocks.Delete(nextBlock); | |
| } | |
| } | |
| // Try to merge with next block | |
| else if(nextBlock && nextBlock.start == position + size) | |
| { | |
| nextBlock.start = position; | |
| } | |
| // This free block is not connected to any other block | |
| else | |
| { | |
| freeBlocks.Insert(prevBlock, FreeBlock { start = position, end = position + size - 1 }); | |
| } | |
| } | |
| void SubtractBlock(uint start, uint size) | |
| { | |
| FreeBlock block; | |
| for(block = freeBlocks.first; block; block = block.next) | |
| { | |
| if(block.end >= start - 1L && block.start <= start + size) | |
| { | |
| if(block.end > start + size && block.start < start - 1L) | |
| { | |
| FreeBlock newBlock { start = start + size, end = block.end }; | |
| block.end = start - 1L; | |
| freeBlocks.Insert(block, newBlock); | |
| } | |
| else if(block.end > start + size) | |
| { | |
| block.start = start + size; | |
| } | |
| else if(block.start < start - 1L) | |
| { | |
| block.end = start - 1L; | |
| } | |
| else | |
| { | |
| freeBlocks.Remove(block); | |
| delete block; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| void Delete(EARArchiveDir dir, uint64 position, EAREntry entry) | |
| { | |
| uint64 size; | |
| if(entry.type == ENTRY_FOLDER) | |
| { | |
| EARArchiveDir subDir {}; | |
| uint64 filePosition; | |
| EAREntry fileEntry; | |
| subDir.position = dir.position; | |
| f.Read(&subDir.first, sizeof(uint), 1); | |
| f.Read(&subDir.last, sizeof(uint), 1); | |
| // Erase directory contents first | |
| for(filePosition = subDir.first; filePosition; filePosition = fileEntry.next) | |
| { | |
| f.Seek(filePosition, start); | |
| f.Read(&fileEntry, sizeof(EAREntry), 1); | |
| f.Seek(fileEntry.nameLen, current); | |
| Delete(subDir, filePosition, &fileEntry); | |
| } | |
| size = sizeof(EAREntry) + entry.nameLen + 2 * sizeof(uint); | |
| delete subDir; | |
| } | |
| else | |
| size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size); | |
| // Unlink this file | |
| if(entry.prev) | |
| { | |
| f.Seek(entry.prev + OFFSET(EAREntry, next), start); | |
| f.Write(&entry.next, sizeof(uint), 1); | |
| } | |
| if(entry.next) | |
| { | |
| f.Seek(entry.next + OFFSET(EAREntry, prev), start); | |
| f.Write(&entry.prev, sizeof(uint), 1); | |
| } | |
| if(dir.last == position) dir.last = entry.prev; | |
| if(dir.first == position) dir.first = entry.next; | |
| AddFreeBlock(position, size); | |
| totalSize -= entry.size; | |
| // Invalidate Buffer | |
| // bf.handle = f; | |
| } | |
| File FileOpen(const char * name) | |
| { | |
| File result = null; | |
| EARFile file {}; | |
| if(file) | |
| { | |
| EAREntry entry { }; | |
| f.Seek(archiveStart + sizeof(EARHeader), start); | |
| if(EARGetEntry(f, entry, name, null).isFile) | |
| { | |
| if(entry.cSize) | |
| { | |
| byte * uncompressed = new byte[entry.size]; | |
| if(uncompressed) | |
| { | |
| byte * compressed = new byte[entry.cSize]; | |
| if(compressed) | |
| { | |
| if(f.Read(compressed, 1, entry.cSize) == entry.cSize) | |
| { | |
| unsigned long destLen = entry.size; | |
| uncompress(uncompressed, &destLen, compressed, entry.cSize); | |
| entry.size = (FileSize)destLen; // TODO: Support 64 bit file sizes | |
| } | |
| delete compressed; | |
| } | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.buffer = uncompressed; | |
| result = file; | |
| } | |
| } | |
| else | |
| { | |
| file.start = f.Tell(); | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.f = f; | |
| incref file.f; | |
| file.f.Seek(file.start, start); | |
| result = file; | |
| } | |
| } | |
| if(!result) | |
| delete file; | |
| } | |
| return result; | |
| } | |
| File FileOpenAtPosition(uint position) | |
| { | |
| EARFile file {}; | |
| char fileName[MAX_LOCATION]; | |
| EAREntry entry { }; | |
| f.Seek(position, start); | |
| f.Read(entry, sizeof(EAREntry), 1); | |
| /*if(entry.nameLen > 1024) | |
| printf("");*/ | |
| f.Read(fileName, 1, entry.nameLen); | |
| if(entry.cSize) | |
| { | |
| byte * uncompressed = new byte[entry.size]; | |
| if(uncompressed) | |
| { | |
| byte * compressed = new byte[entry.cSize]; | |
| if(compressed) | |
| { | |
| if(f.Read(compressed, 1, entry.cSize) == entry.cSize) | |
| { | |
| unsigned long destLen = entry.size; | |
| uncompress(uncompressed, &destLen, compressed, entry.cSize); | |
| entry.size = (FileSize)destLen; | |
| } | |
| delete compressed; | |
| } | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.buffer = uncompressed; | |
| } | |
| } | |
| else | |
| { | |
| file.start = f.Tell(); | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.f = f; | |
| file.f.Seek(file.start, start); | |
| incref file.f; | |
| } | |
| return file; | |
| } | |
| FileAttribs FileExists(const char * fileName) | |
| { | |
| FileAttribs result; | |
| EAREntry entry { }; | |
| f.Seek(archiveStart + sizeof(EARHeader), start); | |
| result = EARGetEntry(f, entry, fileName, null); | |
| return result; | |
| } | |
| void SubtractUsedBlocks() | |
| { | |
| uint first, last; | |
| if(!f.Read(&first, sizeof(uint), 1)) | |
| return; | |
| #ifdef _DEBUG | |
| if(first > f.GetSize()) | |
| { | |
| printf("Error\n"); | |
| } | |
| #endif | |
| f.Read(&last, sizeof(uint), 1); | |
| while(first) | |
| { | |
| uint size = 0; | |
| char fileName[MAX_FILENAME]; | |
| EAREntry entry { }; | |
| f.Seek(first, start); | |
| f.Read(entry, sizeof(EAREntry), 1); | |
| if(entry.nameLen < MAX_FILENAME) | |
| { | |
| f.Read(fileName, 1, entry.nameLen); | |
| fileName[entry.nameLen] = 0; | |
| } | |
| else | |
| { | |
| fileName[0] = 0; | |
| break; | |
| } | |
| size += sizeof(EAREntry) + entry.nameLen; | |
| if(entry.type == ENTRY_FILE) | |
| { | |
| size += entry.cSize ? entry.cSize : entry.size; | |
| } | |
| else if(entry.type == ENTRY_FOLDER) | |
| { | |
| size += 2 * sizeof(uint); | |
| SubtractUsedBlocks(); | |
| } | |
| SubtractBlock(first, size); | |
| first = entry.next; | |
| } | |
| } | |
| void SetBufferSize(uint bufferSize) | |
| { | |
| if(f && f._class == class(BufferedFile)) | |
| ((BufferedFile)f).bufferSize = bufferSize; | |
| } | |
| void SetBufferRead(uint bufferRead) | |
| { | |
| if(f && f._class == class(BufferedFile)) | |
| ((BufferedFile)f).bufferRead = bufferRead; | |
| } | |
| } | |
| class EARArchiveDir : ArchiveDir | |
| { | |
| EARArchive archive; | |
| uint64 position; | |
| uint first, last; | |
| bool readOnly; | |
| ~EARArchiveDir() | |
| { | |
| if(!readOnly) | |
| { | |
| archive.f.Seek(position, start); | |
| archive.f.Write(&first, sizeof(uint), 1); | |
| archive.f.Write(&last, sizeof(uint), 1); | |
| archive.Update(); | |
| } | |
| } | |
| File FileOpen(const char * name) | |
| { | |
| File result = null; | |
| EARFile file {}; | |
| if(file) | |
| { | |
| EAREntry entry { }; | |
| archive.f.Seek(position, start); | |
| if(EARGetEntry(archive.f, entry, name, null).isFile) | |
| { | |
| if(entry.cSize) | |
| { | |
| byte * uncompressed = new byte[entry.size]; | |
| if(uncompressed) | |
| { | |
| byte * compressed = new byte[entry.cSize]; | |
| if(compressed) | |
| { | |
| if(archive.f.Read(compressed, 1, entry.cSize) == entry.cSize) | |
| { | |
| unsigned long destLen = entry.size; | |
| uncompress(uncompressed, &destLen, compressed, entry.cSize); | |
| entry.size = (FileSize)destLen; | |
| } | |
| delete compressed; | |
| } | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.buffer = uncompressed; | |
| result = file; | |
| } | |
| } | |
| else | |
| { | |
| file.start = archive.f.Tell(); | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.f = archive.f; | |
| file.f.Seek(file.start, start); | |
| incref file.f; | |
| result = file; | |
| } | |
| } | |
| if(!result) | |
| delete file; | |
| } | |
| return result; | |
| } | |
| FileAttribs FileExists(const char * fileName) | |
| { | |
| FileAttribs result; | |
| EAREntry entry { }; | |
| archive.f.Seek(position, start); | |
| result = EARGetEntry(archive.f, entry, fileName, null); | |
| return result; | |
| } | |
| ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode) | |
| { | |
| ArchiveDir result = null; | |
| EARArchiveDir dir { readOnly = addMode == readOnlyDir }; | |
| if(dir) | |
| { | |
| char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION]; | |
| uint64 position; | |
| EAREntry entry { }; | |
| dir.archive = archive; | |
| SplitDirectory(name, namePart, nameRest); | |
| if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) | |
| strcpy(namePart, DIR_SEPS); | |
| // Search for directory | |
| position = archive.Find(this, namePart, entry); | |
| if(position) | |
| { | |
| // Fail if file of same name already exists | |
| if(entry.type == ENTRY_FILE) | |
| return null; | |
| else | |
| { | |
| dir.position = position + sizeof(EAREntry) + entry.nameLen; | |
| dir.first = 0; | |
| dir.last = 0; | |
| archive.f.Read(&dir.first, sizeof(uint), 1); | |
| archive.f.Read(&dir.last, sizeof(uint), 1); | |
| result = dir; | |
| } | |
| } | |
| // If directory doesn't exist already | |
| if(!result && addMode != refresh) | |
| { | |
| // Write Header if it's not the root directory | |
| EAREntry entry {}; | |
| uint64 position; | |
| entry.nameLen = strlen(namePart); | |
| entry.prev = (uint)last; | |
| entry.next = 0; | |
| entry.type = ENTRY_FOLDER; | |
| if(!nameRest[0] && stats) | |
| { | |
| entry.created = (TimeStamp32)stats.created; | |
| entry.modified = (TimeStamp32)stats.modified; | |
| } | |
| position = archive.Position(sizeof(EAREntry) + entry.nameLen + 2*sizeof(uint)); | |
| archive.f.Seek(position, start); | |
| archive.f.Write(entry, sizeof(EAREntry), 1); | |
| archive.f.Write(namePart, entry.nameLen, 1); | |
| last = (uint)position; | |
| if(!first) first = (uint)position; | |
| // Update the next pointer of previous entry | |
| if(entry.prev) | |
| { | |
| archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); | |
| archive.f.Write(&position, sizeof(uint), 1); | |
| } | |
| // Make the dir position point after the header | |
| dir.position = position + sizeof(EAREntry) + entry.nameLen; | |
| } | |
| // Just update the time stamps | |
| else if(result && !nameRest[0] && stats) | |
| { | |
| archive.f.Seek(position + OFFSET(EAREntry, created), start); | |
| archive.f.Write(&stats.created, sizeof(uint), 1); | |
| archive.f.Write(&stats.modified, sizeof(uint), 1); | |
| } | |
| result = dir; | |
| // Open rest of directory... | |
| if(result && nameRest[0]) | |
| { | |
| result = dir.OpenDirectory(nameRest, stats, addMode); | |
| delete dir; | |
| } | |
| } | |
| return result; | |
| } | |
| bool Delete(const char * name) | |
| { | |
| EAREntry entry { }; | |
| uint64 position; | |
| char namePart[MAX_LOCATION]; | |
| strcpy(namePart, name); | |
| if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) | |
| strcpy(namePart, DIR_SEPS); | |
| position = archive.Find(this, namePart, entry); | |
| if(position) | |
| { | |
| archive.Delete(this, position, entry); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool Move(const char * name, EARArchiveDir to) | |
| { | |
| bool result = false; | |
| if(position != to.position) | |
| { | |
| EAREntry entry { }; | |
| uint64 position = 0; | |
| char namePart[MAX_LOCATION]; | |
| strcpy(namePart, name); | |
| if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) | |
| strcpy(namePart, DIR_SEPS); | |
| position = archive.Find(this, name, entry); | |
| if(position) | |
| { | |
| // Unlink from old directory | |
| if(entry.prev) | |
| { | |
| archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); | |
| archive.f.Write(&entry.next, sizeof(uint), 1); | |
| } | |
| if(entry.next) | |
| { | |
| archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start); | |
| archive.f.Write(&entry.prev, sizeof(uint), 1); | |
| } | |
| if(last == position) last = entry.prev; | |
| if(first == position) first = entry.next; | |
| // Relink to new directory | |
| entry.prev = to.last; | |
| entry.next = 0; | |
| if(entry.prev) | |
| { | |
| archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); | |
| archive.f.Write(&position, sizeof(uint), 1); | |
| } | |
| if(!to.first) | |
| to.first = (uint)position; | |
| to.last = (uint)position; | |
| archive.f.Seek(position + OFFSET(EAREntry, prev), start); | |
| archive.f.Write(&entry.prev, sizeof(uint), 1); | |
| archive.f.Write(&entry.next, sizeof(uint), 1); | |
| result = true; | |
| } | |
| } | |
| return result; | |
| } | |
| bool Rename(const char * name, const char * newName) | |
| { | |
| bool result = false; | |
| EAREntry entry { }; | |
| uint64 position = 0; | |
| char namePart[MAX_LOCATION]; | |
| strcpy(namePart, name); | |
| if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) | |
| strcpy(namePart, DIR_SEPS); | |
| position = archive.Find(this, namePart, entry); | |
| if(position) | |
| { | |
| uint64 dataSize; | |
| EAREntry newEntry = entry; | |
| uint64 newPosition = position; | |
| if(entry.type == ENTRY_FOLDER) | |
| dataSize = 2 * sizeof(uint); | |
| else | |
| dataSize = entry.cSize ? entry.cSize : entry.size; | |
| newEntry.nameLen = strlen(newName); | |
| if(newEntry.nameLen > entry.nameLen) | |
| { | |
| // Write new entry | |
| newPosition = archive.Position(sizeof(EAREntry) + newEntry.nameLen + dataSize); | |
| archive.f.Seek(newPosition, start); | |
| archive.f.Write(&newEntry, sizeof(EAREntry), 1); | |
| archive.f.Write(newName, sizeof(char), newEntry.nameLen); | |
| // Fix the links | |
| if(entry.prev) | |
| { | |
| archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); | |
| archive.f.Write(&newPosition, sizeof(uint), 1); | |
| } | |
| if(entry.next) | |
| { | |
| archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start); | |
| archive.f.Write(&newPosition, sizeof(uint), 1); | |
| } | |
| if(first == position) first = (uint)newPosition; | |
| if(last == position) last = (uint)newPosition; | |
| } | |
| else | |
| { | |
| // Change the name | |
| archive.f.Seek(position + OFFSET(EAREntry, nameLen), start); | |
| archive.f.Write(&newEntry.nameLen, sizeof(uint), 1); | |
| archive.f.Seek(position + sizeof(EAREntry), start); | |
| archive.f.Write(newName, sizeof(char), newEntry.nameLen); | |
| // There will be free space at the end of an entry with a shorter new name | |
| if(newEntry.nameLen < entry.nameLen) | |
| archive.AddFreeBlock(position + sizeof(EAREntry) + newEntry.nameLen + dataSize, entry.nameLen - newEntry.nameLen); | |
| } | |
| if(entry.nameLen != newEntry.nameLen) | |
| { | |
| byte * buffer; | |
| uint64 bufferSize = Min(dataSize, MAX_BUFFERSIZE); | |
| buffer = new byte[bufferSize]; | |
| if(buffer) | |
| { | |
| uint64 readPosition = position + sizeof(EAREntry) + entry.nameLen; | |
| uint64 writePosition = newPosition + sizeof(EAREntry) + newEntry.nameLen; | |
| uint64 c; | |
| for(c = 0; c<dataSize; c += bufferSize) | |
| { | |
| uint64 size = (dataSize > c + bufferSize) ? bufferSize : (dataSize - c); | |
| archive.f.Seek(readPosition + c, start); | |
| archive.f.Read(buffer, size, 1); | |
| archive.f.Seek(writePosition + c, start); | |
| archive.f.Write(buffer, size, 1); | |
| } | |
| delete buffer; | |
| } | |
| if(newEntry.nameLen > entry.nameLen) | |
| { | |
| // Prevent the children to be deleted | |
| if(entry.type == ENTRY_FOLDER) | |
| { | |
| uint first = 0, last = 0; | |
| archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start); | |
| archive.f.Write(&first, sizeof(uint), 1); | |
| archive.f.Write(&last, sizeof(uint), 1); | |
| } | |
| // Delete the old entry | |
| entry.prev = entry.next = 0; | |
| archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start); | |
| archive.Delete(this, position, entry); | |
| } | |
| } | |
| result = true; | |
| } | |
| return result; | |
| } | |
| bool AddFromFile(const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition) | |
| { | |
| // Search for identical entry | |
| EAREntry oldEntry; | |
| uint64 oldPosition = archive.Find(this, name, oldEntry); | |
| return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition); | |
| } | |
| bool AddFromFileAtPosition(uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition) | |
| { | |
| EAREntry oldEntry; | |
| if(oldPosition) | |
| { | |
| if(!archive.f.Seek(oldPosition, start) || !archive.f.Read(oldEntry, sizeof(EAREntry), 1)) | |
| return false; | |
| } | |
| return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition); | |
| } | |
| bool _AddFromFileAtPosition(EAREntry oldEntry, uint64 oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition) | |
| { | |
| bool skip = false; | |
| FileStats oldStats { }; | |
| if(oldPosition) | |
| { | |
| oldStats.modified = (TimeStamp)oldEntry.modified; | |
| oldStats.created = (TimeStamp)oldEntry.created; | |
| } | |
| if(stats == null) | |
| { | |
| oldStats.size = (uint)input.GetSize(); | |
| stats = &oldStats; | |
| } | |
| switch(addMode) | |
| { | |
| // Add all files | |
| case replace: | |
| if(oldPosition) | |
| archive.Delete(this, oldPosition, oldEntry); | |
| break; | |
| // Only updates changed files | |
| case refresh: | |
| if(oldPosition && | |
| (oldEntry.size != (uint)stats.size || | |
| oldEntry.modified != (TimeStamp32)stats.modified || | |
| oldEntry.created != (TimeStamp32)stats.created)) | |
| archive.Delete(this, oldPosition, oldEntry); | |
| else | |
| skip = true; | |
| break; | |
| // Only updates changed or new files | |
| case update: | |
| if(oldPosition) | |
| { | |
| if(oldEntry.size != (uint)stats.size || | |
| oldEntry.modified != (TimeStamp32)stats.modified || | |
| oldEntry.created != (TimeStamp32)stats.created) | |
| archive.Delete(this, oldPosition, oldEntry); | |
| else | |
| skip = true; | |
| } | |
| break; | |
| } | |
| if(!skip) | |
| { | |
| EAREntry entry { }; | |
| uint64 position, size; | |
| byte * compressed = null; | |
| // Add the file | |
| entry.nameLen = strlen(name); | |
| entry.prev = last; | |
| entry.next = 0; | |
| entry.type = ENTRY_FILE; | |
| entry.size = (uint)stats.size; | |
| entry.created = (TimeStamp32)stats.created; | |
| entry.modified = (TimeStamp32)stats.modified; | |
| if(compression) | |
| { | |
| byte * uncompressed = new byte[entry.size]; | |
| if(uncompressed) | |
| { | |
| if(input.Read(uncompressed, 1, entry.size) == entry.size) | |
| { | |
| unsigned long destLen = entry.size + entry.size / 1000 + 12; | |
| compressed = new byte[destLen]; | |
| if(compressed) | |
| { | |
| if(compression > 9 || compression < 0) compression = 9; | |
| compress2(compressed, &destLen, uncompressed, entry.size, compression); | |
| entry.cSize = (FileSize)destLen; | |
| } | |
| } | |
| delete uncompressed; | |
| } | |
| } | |
| if(!compressed) | |
| { | |
| entry.cSize = 0; | |
| if(ratio) | |
| *ratio = 0; | |
| } | |
| else if(ratio) | |
| *ratio = entry.size ? (entry.cSize * 1000 / entry.size) : 0; | |
| // Find block | |
| size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size); | |
| position = archive.Position(size); | |
| // Write Header | |
| if(!archive.f.Seek(position, start) || | |
| !archive.f.Write(entry, sizeof(EAREntry), 1) || | |
| !archive.f.Write(name, entry.nameLen, 1)) | |
| { | |
| delete compressed; | |
| return false; | |
| } | |
| // Write File Data | |
| if(compressed) | |
| { | |
| if(!archive.f.Write(compressed, 1, entry.cSize)) | |
| { | |
| delete compressed; | |
| return false; | |
| } | |
| delete compressed; | |
| } | |
| else | |
| { | |
| byte buffer[8192]; | |
| uint c; | |
| int64 count = 1; | |
| for(c = 0; c<entry.size && count; c+= count) | |
| { | |
| count = input.Read(buffer, 1, sizeof(buffer)); | |
| if(!archive.f.Write(buffer, 1, count)) | |
| return false; | |
| } | |
| } | |
| // Update the next pointer previous entry | |
| if(entry.prev) | |
| { | |
| archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); | |
| archive.f.Write(&position, sizeof(uint), 1); | |
| } | |
| // Update total size of archive | |
| archive.totalSize += entry.size; | |
| last = (uint)position; | |
| if(!first) first = (uint)position; | |
| if(newPosition) *newPosition = (uint)position; | |
| } | |
| else | |
| { | |
| if(newPosition) *newPosition = 0; | |
| } | |
| // archive.f.handle = archive.f; | |
| return true; | |
| } | |
| }; | |
| #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) | |
| // Directory Description for file listing | |
| class EARDir : struct | |
| { | |
| char path[MAX_LOCATION]; | |
| File f; | |
| uint next; | |
| }; | |
| class EARFile : File | |
| { | |
| uint64 position; | |
| uint64 size; | |
| // -- For reading compressed file (entirely buffered) | |
| byte * buffer; | |
| // -- For reading uncompressed file (not buffered) | |
| File f; | |
| uint64 start; | |
| ~EARFile() | |
| { | |
| delete buffer; | |
| delete f; | |
| } | |
| void CloseInput() | |
| { | |
| if(f) | |
| f.CloseInput(); | |
| } | |
| void CloseOutput() | |
| { | |
| if(f) | |
| f.CloseOutput(); | |
| } | |
| uintsize Read(byte * buffer, uintsize size, uintsize count) | |
| { | |
| uintsize read = 0; | |
| if(f) | |
| f.Seek(position + start, start); | |
| read = Min(count, (this.size - position) / size); | |
| if(this.buffer) | |
| CopyBytes(buffer, this.buffer + position, read * size); | |
| else | |
| read = f.Read(buffer, size, read); | |
| position += read * size; | |
| return read; | |
| } | |
| uintsize Write(const byte * buffer, uintsize size, uintsize count) | |
| { | |
| return 0; | |
| } | |
| bool Getc(char * ch) | |
| { | |
| if(position < size) | |
| { | |
| if(buffer) | |
| { | |
| char b = buffer[position++]; | |
| if(ch) *ch = b; | |
| return true; | |
| } | |
| else | |
| { | |
| f.Seek(position + start, start); | |
| position++; | |
| return f.Getc(ch); | |
| } | |
| } | |
| return false; | |
| } | |
| bool Putc(char ch) | |
| { | |
| return false; | |
| } | |
| bool Puts(const char * string) | |
| { | |
| return false; | |
| } | |
| bool Seek(int64 pos, FileSeekMode mode) | |
| { | |
| bool result = false; | |
| switch(mode) | |
| { | |
| case start: | |
| if(pos <= (int)size) | |
| { | |
| position = pos; | |
| if(f) | |
| result = f.Seek(position + start, start); | |
| else | |
| result = true; | |
| } | |
| break; | |
| case current: | |
| if(position + pos <= (int)size && (int)position >= -pos) | |
| { | |
| position += pos; | |
| if(f) | |
| result = f.Seek(position + start, start); | |
| else | |
| result = true; | |
| } | |
| break; | |
| case end: | |
| if(pos < 0 && -pos <= (int)size) | |
| { | |
| position = size + pos; | |
| if(f) | |
| f.Seek(position + start, start); | |
| else | |
| result = true; | |
| } | |
| break; | |
| } | |
| return result; | |
| } | |
| uint64 Tell() | |
| { | |
| return position; | |
| } | |
| bool Eof() | |
| { | |
| return position >= size || (f && f.Eof()); | |
| } | |
| uint64 GetSize() | |
| { | |
| return size; | |
| } | |
| }; | |
| class EARFileSystem : FileSystem | |
| { | |
| File ::Open(const char * archive, const char * name, FileOpenMode mode) | |
| { | |
| File result = null; | |
| if(mode == read) | |
| { | |
| EARFile file {}; | |
| if(file) | |
| { | |
| char fileName[MAX_LOCATION]; | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| strcpy(fileName, name); | |
| #ifdef ECERE_STATIC | |
| if(!f && archive[0] == ':') | |
| { | |
| f = EAROpenArchive(":", &header); | |
| if(f) | |
| { | |
| strcpy(fileName, archive + 1); | |
| PathCat(fileName, name); | |
| } | |
| } | |
| #endif | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| if(EARGetEntry(f, entry, fileName, null).isFile) | |
| { | |
| if(entry.cSize) | |
| { | |
| byte * uncompressed = new byte[entry.size]; | |
| if(uncompressed) | |
| { | |
| byte * compressed = new byte[entry.cSize]; | |
| if(compressed) | |
| { | |
| if(f.Read(compressed, 1, entry.cSize) == entry.cSize) | |
| { | |
| unsigned long destLen = entry.size; | |
| uncompress(uncompressed, &destLen, compressed, entry.cSize); | |
| entry.size = (FileSize)destLen; | |
| } | |
| delete compressed; | |
| } | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.buffer = uncompressed; | |
| result = file; | |
| } | |
| } | |
| else | |
| { | |
| file.start = f.Tell(); | |
| file.position = 0; | |
| file.size = entry.size; | |
| file.f = f; | |
| f = null; | |
| result = file; | |
| } | |
| } | |
| delete f; | |
| } | |
| if(!result) | |
| delete file; | |
| } | |
| } | |
| return result; | |
| } | |
| FileAttribs ::Exists(const char * archive, const char * fileName) | |
| { | |
| uint result = 0; | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| result = EARGetEntry(f, entry, fileName, null); | |
| delete f; | |
| } | |
| #ifdef ECERE_STATIC | |
| if(!f && archive[0] == ':') | |
| { | |
| f = EAROpenArchive(":", &header); | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| char fn[MAX_LOCATION]; | |
| strcpy(fn, archive + 1); | |
| PathCat(fn, fileName); | |
| result = EARGetEntry(f, entry, fn, null); | |
| } | |
| delete f; | |
| } | |
| #endif | |
| return result; | |
| } | |
| bool ::GetSize(const char * archive, const char * fileName, FileSize * size) | |
| { | |
| bool result = false; | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| if(EARGetEntry(f, entry, fileName, null)) | |
| *size = entry.size; | |
| delete f; | |
| result = true; | |
| } | |
| return result; | |
| } | |
| bool ::Stats(const char * archive, const char * fileName, FileStats stats) | |
| { | |
| bool result = false; | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| if(EARGetEntry(f, entry, fileName, null)) | |
| { | |
| stats.size = entry.size; | |
| stats.accessed = 0; | |
| stats.modified = (TimeStamp)entry.modified; | |
| stats.created = (TimeStamp)entry.created; | |
| result = true; | |
| } | |
| delete f; | |
| } | |
| return result; | |
| } | |
| void ::FixCase(const char * archive, char * name) | |
| { | |
| #ifdef __WIN32__ | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| char fileName[MAX_LOCATION] = ""; | |
| if(EARGetEntry(f, entry, name, fileName)) | |
| strcpy(name, fileName); | |
| delete f; | |
| } | |
| #endif | |
| } | |
| bool ::Find(FileDesc file, const char * archive, const char * name) | |
| { | |
| bool result = false; | |
| EARDir d {}; | |
| if(d) | |
| { | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| if(f) | |
| { | |
| EAREntry entry { }; | |
| if(EARGetEntry(f, entry, name, null).isDirectory) | |
| { | |
| uint first = 0, last = 0; | |
| sprintf(d.path, "<%s>%s", archive, name); | |
| d.f = f; | |
| f.Read(&first, sizeof(uint), 1); | |
| f.Read(&last, sizeof(uint), 1); | |
| d.next = first; | |
| if(d.next) | |
| { | |
| EAREntry entry { }; | |
| d.f.Seek(d.next, start); | |
| d.f.Read(entry, sizeof(EAREntry), 1); | |
| d.f.Read(file.name, 1, entry.nameLen); | |
| file.name[entry.nameLen] = '\0'; | |
| file.stats.attribs = { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) }; | |
| file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified; | |
| file.stats.created = (TimeStamp)entry.created; | |
| file.stats.size = entry.size; | |
| strcpy(file.path, d.path); | |
| PathCat(file.path, file.name); | |
| d.next = entry.next; | |
| file.dir = (Dir)d; | |
| result = true; | |
| } | |
| } | |
| if(!result) | |
| delete f; | |
| } | |
| if(!result) | |
| delete d; | |
| } | |
| return result; | |
| } | |
| bool ::FindNext(FileDesc file) | |
| { | |
| bool result = false; | |
| EARDir d = (EARDir)file.dir; | |
| if(d.next) | |
| { | |
| EAREntry entry { }; | |
| d.f.Seek(d.next, start); | |
| d.f.Read(entry, sizeof(EAREntry), 1); | |
| d.f.Read(file.name, 1, entry.nameLen); | |
| file.name[entry.nameLen] = '\0'; | |
| file.stats.attribs = FileAttribs { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) }; | |
| file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified; | |
| file.stats.created = (TimeStamp)entry.created; | |
| file.stats.size = entry.size; | |
| strcpy(file.path, d.path); | |
| PathCat(file.path, file.name); | |
| d.next = entry.next; | |
| result = true; | |
| } | |
| return result; | |
| } | |
| void ::CloseDir(FileDesc file) | |
| { | |
| EARDir d = (EARDir)file.dir; | |
| if(d.f) | |
| delete d.f; | |
| if(d) | |
| delete d; | |
| } | |
| #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) | |
| Archive ::OpenArchive(const char * fileName, ArchiveOpenFlags flags) | |
| { | |
| Archive result = null; | |
| EARArchive archive { writeAccess = flags.writeAccess }; | |
| if(archive) | |
| { | |
| int try = flags.waitLock ? 10 : 0; | |
| for(; try >= 0; try--) | |
| { | |
| // Check for existing Archive | |
| if((archive.f = fileName ? (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, flags.writeAccess ? readWrite : read) : TempFile { openMode = readWrite } )) | |
| { | |
| EARHeader header; | |
| bool opened = false; | |
| uint archiveSize = 0; | |
| archive.f.Seek(-(int)sizeof(uint), end); | |
| archive.f.Read(&archiveSize, sizeof(uint), 1); | |
| archive.f.Seek(-(int)archiveSize, end); | |
| archive.archiveStart = archive.f.Tell(); | |
| if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 && | |
| !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) | |
| opened = true; | |
| if(!opened) | |
| { | |
| archive.f.Seek(0, start); | |
| archive.archiveStart = archive.f.Tell(); | |
| archiveSize = (uint)archive.f.GetSize(); | |
| if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 && | |
| !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) | |
| opened = true; | |
| } | |
| if(opened) | |
| { | |
| // At this point we recognized the file as a valid eAR archive | |
| archive.rootDir = archive.archiveStart + sizeof(EARHeader); | |
| archive.totalSize = header.totalSize; | |
| archive.f.Seek(archive.rootDir, start); | |
| if(flags.writeAccess) | |
| { | |
| if(flags.buffered) | |
| { | |
| archive.freeBlocks.Add(FreeBlock { start = archive.rootDir + 2 * sizeof(uint), end = MAXDWORD }); | |
| archive.SubtractUsedBlocks(); | |
| } | |
| else | |
| { | |
| archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + (archiveSize - sizeof(uint)), end = MAXDWORD }); | |
| } | |
| } | |
| /* | |
| if(!flags.writeAccess) | |
| { | |
| delete archive.f; | |
| archive.f = FileOpen(fileName, readWrite); | |
| } | |
| */ | |
| if(archive.f) | |
| { | |
| incref archive.f; | |
| result = archive; | |
| } | |
| } | |
| break; | |
| } | |
| else if(try > 0) | |
| Sleep(0.01); | |
| } | |
| // This piece of code will create a new archive as a new file or at the footer | |
| // of an existing file. | |
| if(!result && flags.writeAccess) | |
| { | |
| // If the file doesn't exist, create it | |
| if(!archive.f) | |
| { | |
| archive.f = FileOpen(fileName, writeRead); | |
| delete archive.f; | |
| archive.f = (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, readWrite); | |
| } | |
| if(archive.f) | |
| { | |
| EARHeader header | |
| { | |
| EAR_RECOGNITION, | |
| MDWORD(0, 1) | |
| }; | |
| archive.f.Seek(0, end); | |
| archive.archiveStart = archive.f.Tell(); | |
| archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + sizeof(EARHeader), end = MAXDWORD }); | |
| // Write Header | |
| archive.f.Write(&header, sizeof(EARHeader), 1); | |
| { | |
| uint first = 0, last = 0; | |
| archive.f.Write(&first, sizeof(first), 1); | |
| archive.f.Write(&last, sizeof(last), 1); | |
| } | |
| archive.rootDir = 0; | |
| incref archive.f; | |
| result = archive; | |
| } | |
| } | |
| if(archive.f && flags.writeAccess && flags.exclusive && !archive.f.Lock(flags.exclusive ? exclusive : shared, 0, 0, flags.waitLock)) | |
| result = null; | |
| if(!result) | |
| { | |
| delete archive.f; | |
| delete archive; | |
| } | |
| else | |
| { | |
| // archive.f.handle = archive.f; | |
| } | |
| } | |
| return result; | |
| } | |
| #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) | |
| bool ::QuerySize(const char * archive, FileSize * size) | |
| { | |
| bool result = false; | |
| EARHeader header; | |
| File f = EAROpenArchive(archive, &header); | |
| if(f) | |
| { | |
| *size = header.totalSize; | |
| result = true; | |
| delete f; | |
| } | |
| return result; | |
| } | |
| }; |