#include "LCCompoundFileWriter.h" #include "LCIndexOutput.h" #include "GNUstep.h" /** * Combines multiple files into a single compound file. * The file format:
* * * The fileCount integer indicates how many files are contained in this compound * file. The {directory} that follows has that many entries. Each directory entry * contains an encoding identifier, a long pointer to the start of this file's * data section, and a UTF String with that file's extension. * * @author Dmitry Serebrennikov * @version $Id: LCCompoundFileWriter.m 456 2006-01-18 01:59:29Z yjchen $ */ @interface LCWriterFileEntry: NSObject { /** source file */ NSString *file; /** temporary holder for the start of directory entry for this file */ long long directoryOffset; /** temporary holder for the start of this file's data section */ long long dataOffset; } - (NSString *) file; - (long long) directoryOffset; - (long long) dataOffset; - (void) setFile: (NSString *) f; - (void) setDirectoryOffset: (long long) f; - (void) setDataOffset: (long long) f; @end @implementation LCWriterFileEntry - (void) dealloc { DESTROY(file); [super dealloc]; } - (NSString *) file { return file; } - (long long) directoryOffset { return directoryOffset; } - (long long) dataOffset { return dataOffset; } - (void) setFile: (NSString *) f { ASSIGN(file, f); } - (void) setDirectoryOffset: (long long) f { directoryOffset = f; } - (void) setDataOffset: (long long) f { dataOffset = f; } @end @interface LCCompoundFileWriter (LCPrivate) - (void) copyFile: (LCWriterFileEntry *) source indexOutput: (LCIndexOutput *) os data: (NSMutableData *) buffer; @end @implementation LCCompoundFileWriter - (id) init { self = [super init]; merged = NO; return self; } /** Create the compound stream in the specified file. The file name is the * entire name (no extensions are added). * @throws NullPointerException if dir or name is null */ - (id) initWithDirectory: (id ) dir name: (NSString *) name { if (dir == nil) { NSLog(@"directory cannot be null"); return nil; } if (name == nil) { NSLog(@"name cannot be null"); return nil; } self = [self init]; ASSIGN(directory, dir); ASSIGN(fileName, name); ids = [[NSMutableSet alloc] init]; entries = [[NSMutableArray alloc] init]; return self; } - (void) dealloc { DESTROY(directory); DESTROY(fileName); DESTROY(ids); DESTROY(entries); [super dealloc]; } /** Returns the directory of the compound file. */ - (id ) directory { return directory; } /** Returns the name of the compound file. */ - (NSString *) name { return fileName; } /** Add a source stream. file is the string by which the * sub-stream will be known in the compound stream. * * @throws IllegalStateException if this writer is closed * @throws NullPointerException if file is null * @throws IllegalArgumentException if a file with the same name * has been added already */ - (void) addFile: (NSString *) file { if (merged) { NSLog(@"Can't add extensions after merge has been called"); return; } if (file == nil) { NSLog(@"file cannot be null"); return; } if ([ids containsObject: file]) { NSLog(@"File %@ already added", file); return; } LCWriterFileEntry *entry = [[LCWriterFileEntry alloc] init]; [entry setFile: file]; [entries addObject: entry]; [ids addObject: file]; DESTROY(entry); } /** Merge files with the extensions added up to now. * All files with these extensions are combined sequentially into the * compound stream. After successful merge, the source files * are deleted. * @throws IllegalStateException if close() had been called before or * if no file has been added to this object */ - (void) close { if (merged) { NSLog(@"Merge already performed"); return; } if ([entries count] == 0) { NSLog(@"No entries to merge have been defined"); return; } merged = YES; // open the compound stream LCIndexOutput *os = nil; os = [directory createOutput: fileName]; // Write the number of entries [os writeVInt: [entries count]]; // Write the directory with all offsets at 0. // Remember the positions of directory entries so that we can // adjust the offsets later NSEnumerator *e = [entries objectEnumerator]; LCWriterFileEntry *fe; while ((fe = [e nextObject])) { [fe setDirectoryOffset: [os offsetInFile]]; [os writeLong: 0]; // for now [os writeString: [fe file]]; } // Open the files and copy their data into the stream. // Remember the locations of each file's data section. NSMutableData *buffer; e = [entries objectEnumerator]; while ((fe = [e nextObject])) { buffer = [[NSMutableData alloc] init]; [fe setDataOffset: [os offsetInFile]]; [self copyFile: fe indexOutput: os data: buffer]; DESTROY(buffer); } // Write the data offsets into the directory of the compound stream e = [entries objectEnumerator]; while ((fe = [e nextObject])) { [os seekToFileOffset: [fe directoryOffset]]; [os writeLong: [fe dataOffset]]; } // Close the output stream. Set the os to null before trying to // close so that if an exception occurs during the close, the // finally clause below will not attempt to close the stream // the second time. LCIndexOutput *tmp = os; os = nil; [tmp close]; if (os != nil) { [os close]; } } /** Copy the contents of the file with specified extension into the * provided output stream. Use the provided buffer for moving data * to reduce memory allocation. */ - (void) copyFile: (LCWriterFileEntry *) source indexOutput: (LCIndexOutput *) os data: (NSMutableData *) buffer { LCIndexInput *is = nil; long startPtr = [os offsetInFile]; is = [directory openInput: [source file]]; long length = [is length]; // long remainder = length; // int chunk = [buffer length]; [is readBytes: buffer offset: 0 length: length]; [os writeBytes: buffer length: length]; // Verify that the output length diff is equal to original file long endPtr = [os offsetInFile]; long diff = endPtr - startPtr; if (diff != length) { NSLog(@"Difference in the output file offsets %l does not match the original file length %l", diff, length); return; } if (is != nil) [is close]; } @end