Index: utils/ofzip/Archive.h ================================================================== --- utils/ofzip/Archive.h +++ utils/ofzip/Archive.h @@ -17,11 +17,15 @@ #import "OFObject.h" #import "OFFile.h" #import "OFArray.h" @protocol Archive -+ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream; -- initWithStream: (OF_KINDOF(OFStream *))stream; ++ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode; +- initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode; - (void)listFiles; - (void)extractFiles: (OFArray OF_GENERIC(OFString *) *)files; -- (void)printFiles: (OFArray OF_GENERIC(OFString* ) *)files; +- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files; +@optional +- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files; @end Index: utils/ofzip/GZIPArchive.m ================================================================== --- utils/ofzip/GZIPArchive.m +++ utils/ofzip/GZIPArchive.m @@ -44,21 +44,24 @@ if (self == [GZIPArchive class]) app = (OFZIP *)[[OFApplication sharedApplication] delegate]; } + (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode { - return [[[self alloc] initWithStream: stream] autorelease]; + return [[[self alloc] initWithStream: stream + mode: mode] autorelease]; } - initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode { self = [super init]; @try { _stream = [[OFGZIPStream alloc] initWithStream: stream - mode: @"r"]; + mode: mode]; } @catch (id e) { [self release]; @throw e; } Index: utils/ofzip/OFZIP.h ================================================================== --- utils/ofzip/OFZIP.h +++ utils/ofzip/OFZIP.h @@ -34,12 +34,13 @@ OFString *_archivePath; int _exitStatus; } - (id )openArchiveWithPath: (OFString *)path - type: (OFString *)type; + type: (OFString *)type + mode: (char)mode; - (bool)shouldExtractFile: (OFString *)fileName outFileName: (OFString *)outFileName; - (ssize_t)copyBlockFromStream: (OFStream *)input toStream: (OFStream *)output fileName: (OFString *)fileName; @end Index: utils/ofzip/OFZIP.m ================================================================== --- utils/ofzip/OFZIP.m +++ utils/ofzip/OFZIP.m @@ -31,11 +31,13 @@ #import "GZIPArchive.h" #import "TarArchive.h" #import "ZIPArchive.h" #import "OFCreateDirectoryFailedException.h" +#import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" +#import "OFNotImplementedException.h" #import "OFOpenItemFailedException.h" #import "OFReadFailedException.h" #import "OFSeekFailedException.h" #import "OFWriteFailedException.h" @@ -45,17 +47,19 @@ static void help(OFStream *stream, bool full, int status) { [stream writeLine: OF_LOCALIZED(@"usage", - @"Usage: %[prog] -[Cfhlnpqtvx] archive.zip [file1 file2 ...]", + @"Usage: %[prog] -[acCfhlnpqtvx] archive.zip [file1 file2 ...]", @"prog", [OFApplication programName])]; if (full) { [stream writeString: @"\n"]; [stream writeLine: OF_LOCALIZED(@"full_usage", @"Options:\n" + @" -a --append Append to archive\n" + @" -c --create Create archive\n" @" -C --directory Extract into the specified " @"directory\n" @" -f --force Force / overwrite files\n" @" -h --help Show this help\n" @" -l --list List all files in the archive\n" @@ -91,39 +95,62 @@ @"longopt2", longOption2)]; [OFApplication terminateWithStatus: 1]; } static void -mutuallyExclusiveError3(of_unichar_t shortOption1, OFString *longOption1, +mutuallyExclusiveError5(of_unichar_t shortOption1, OFString *longOption1, of_unichar_t shortOption2, OFString *longOption2, - of_unichar_t shortOption3, OFString *longOption3) + of_unichar_t shortOption3, OFString *longOption3, + of_unichar_t shortOption4, OFString *longOption4, + of_unichar_t shortOption5, OFString *longOption5) { OFString *shortOption1Str = [OFString stringWithFormat: @"%C", shortOption1]; OFString *shortOption2Str = [OFString stringWithFormat: @"%C", shortOption2]; OFString *shortOption3Str = [OFString stringWithFormat: @"%C", shortOption3]; + OFString *shortOption4Str = [OFString stringWithFormat: @"%C", + shortOption4]; + OFString *shortOption5Str = [OFString stringWithFormat: @"%C", + shortOption5]; - [of_stderr writeLine: OF_LOCALIZED(@"3_options_mutually_exclusive", + [of_stderr writeLine: OF_LOCALIZED(@"5_options_mutually_exclusive", @"Error: -%[shortopt1] / --%[longopt1], " - @"-%[shortopt2] / --%[longopt2] and -%[shortopt3] / --%[longopt3] " - @"are mutually exclusive!", + @"-%[shortopt2] / --%[longopt2], -%[shortopt3] / --%[longopt3], " + @"-%[shortopt4] / --%[longopt4] and\n" + @" -%[shortopt5] / --%[longopt5] are mutually exclusive!", @"shortopt1", shortOption1Str, @"longopt1", longOption1, @"shortopt2", shortOption2Str, @"longopt2", longOption2, @"shortopt3", shortOption3Str, - @"longopt3", longOption3)]; + @"longopt3", longOption3, + @"shortopt4", shortOption4Str, + @"longopt4", longOption4, + @"shortopt5", shortOption5Str, + @"longopt5", longOption5)]; + [OFApplication terminateWithStatus: 1]; +} + +static void +writingNotSupported(OFString *type) +{ + [of_stderr writeLine: OF_LOCALIZED( + @"writing_not_supported", + @"Writing archives of type %[type] is not (yet) supported!", + @"type", type)]; [OFApplication terminateWithStatus: 1]; } @implementation OFZIP - (void)applicationDidFinishLaunching { OFString *outputDir = nil, *type = nil; const of_options_parser_option_t options[] = { + { 'a', @"append", 0, NULL, NULL }, + { 'c', @"create", 0, NULL, NULL }, { 'C', @"directory", 1, NULL, &outputDir }, { 'f', @"force", 0, NULL, NULL }, { 'h', @"help", 0, NULL, NULL }, { 'l', @"list", 0, NULL, NULL }, { 'n', @"no-clobber", 0, NULL, NULL }, @@ -189,17 +216,22 @@ mutuallyExclusiveError( 'q', @"quiet", 'v', @"verbose"); _outputLevel--; break; + case 'a': + case 'c': case 'l': - case 'x': case 'p': + case 'x': if (mode != '\0') - mutuallyExclusiveError3( - 'l', @"list", 'x', @"extract", - 'p', @"print"); + mutuallyExclusiveError5( + 'a', @"append", + 'c', @"create", + 'l', @"list", + 'p', @"print", + 'x', @"extract"); mode = option; break; case 'h': help(of_stdout, true, 0); @@ -235,11 +267,12 @@ } } remainingArguments = [optionsParser remainingArguments]; archive = [self openArchiveWithPath: [remainingArguments firstObject] - type: type]; + type: type + mode: mode]; if (outputDir != nil) [[OFFileManager defaultManager] changeCurrentDirectoryPath: outputDir]; @@ -301,23 +334,42 @@ [OFApplication terminateWithStatus: _exitStatus]; } - (id )openArchiveWithPath: (OFString *)path type: (OFString *)type + mode: (char)mode { + OFString *modeString, *fileModeString; OFFile *file = nil; id archive = nil; [_archivePath release]; _archivePath = [path copy]; if (path == nil) return nil; + + switch (mode) { + case 'a': + modeString = @"a"; + fileModeString = @"r+"; + break; + case 'c': + modeString = fileModeString = @"w"; + break; + case 'l': + case 'p': + case 'x': + modeString = fileModeString = @"r"; + break; + default: + @throw [OFInvalidArgumentException exception]; + } @try { file = [OFFile fileWithPath: path - mode: @"r"]; + mode: fileModeString]; } @catch (OFOpenItemFailedException *e) { OFString *error = [OFString stringWithCString: strerror([e errNo]) encoding: [OFLocalization encoding]]; [of_stderr writeString: @"\r"]; @@ -342,26 +394,37 @@ type = @"zip"; } @try { if ([type isEqual: @"gz"]) - archive = [GZIPArchive archiveWithStream: file]; + archive = [GZIPArchive archiveWithStream: file + mode: modeString]; else if ([type isEqual: @"tar"]) - archive = [TarArchive archiveWithStream: file]; - else if ([type isEqual: @"tgz"]) - archive = [TarArchive archiveWithStream: - [OFGZIPStream streamWithStream: file - mode: @"r"]]; - else if ([type isEqual: @"zip"]) - archive = [ZIPArchive archiveWithStream: file]; + archive = [TarArchive archiveWithStream: file + mode: modeString]; + else if ([type isEqual: @"tgz"]) { + OFStream *GZIPStream = [OFGZIPStream + streamWithStream: file + mode: modeString]; + archive = [TarArchive archiveWithStream: GZIPStream + mode: modeString]; + } else if ([type isEqual: @"zip"]) + archive = [ZIPArchive archiveWithStream: file + mode: modeString]; else { [of_stderr writeLine: OF_LOCALIZED( @"unknown_archive_type", @"Unknown archive type: %[type]", @"type", type)]; [OFApplication terminateWithStatus: 1]; } + } @catch (OFNotImplementedException *e) { + if ((mode == 'a' || mode == 'c') && + sel_isEqual([e selector], @selector(initWithStream:mode:))) + writingNotSupported(type); + + @throw e; } @catch (OFReadFailedException *e) { OFString *error = [OFString stringWithCString: strerror([e errNo]) encoding: [OFLocalization encoding]]; [of_stderr writeLine: OF_LOCALIZED(@"failed_to_read_file", @@ -383,10 +446,14 @@ @"file_is_not_a_valid_archive", @"File %[file] is not a valid archive!", @"file", path)]; [OFApplication terminateWithStatus: 1]; } + + if ((mode == 'a' || mode == 'c') && + ![archive respondsToSelector: @selector(addFiles:)]) + writingNotSupported(type); return archive; } - (bool)shouldExtractFile: (OFString *)fileName Index: utils/ofzip/TarArchive.m ================================================================== --- utils/ofzip/TarArchive.m +++ utils/ofzip/TarArchive.m @@ -46,21 +46,24 @@ if (self == [TarArchive class]) app = (OFZIP *)[[OFApplication sharedApplication] delegate]; } + (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode { - return [[[self alloc] initWithStream: stream] autorelease]; + return [[[self alloc] initWithStream: stream + mode: mode] autorelease]; } - initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode { self = [super init]; @try { _archive = [[OFTarArchive alloc] initWithStream: stream - mode: @"r"]; + mode: mode]; } @catch (id e) { [self release]; @throw e; } Index: utils/ofzip/ZIPArchive.m ================================================================== --- utils/ofzip/ZIPArchive.m +++ utils/ofzip/ZIPArchive.m @@ -55,22 +55,24 @@ if (self == [ZIPArchive class]) app = (OFZIP *)[[OFApplication sharedApplication] delegate]; } + (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode { - return [[[self alloc] initWithStream: stream] autorelease]; + return [[[self alloc] initWithStream: stream + mode: mode] autorelease]; } - initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode { self = [super init]; @try { - _archive = [[OFZIPArchive alloc] - initWithSeekableStream: stream - mode: @"r"]; + _archive = [[OFZIPArchive alloc] initWithSeekableStream: stream + mode: mode]; } @catch (id e) { [self release]; @throw e; } Index: utils/ofzip/lang/de.json ================================================================== --- utils/ofzip/lang/de.json +++ utils/ofzip/lang/de.json @@ -1,34 +1,41 @@ { "usage": [ - "Benutzung: %[prog] -[Cfhlnpqtvx] archiv.zip [datei1 datei2 ...]" + "Benutzung: %[prog] -[acCfhlnpqtvx] archiv.zip [datei1 datei2 ...]" ], "full_usage": [ "Optionen:\n", + " -a --append Zu Archiv hinzufügen\n", + " -c --create Archiv erstellen\n", " -C --directory In angegebenes Verzeichnis entpacken\n", " -f --force Existierende Dateien überschreiben\n", " -h --help Diese Hilfe anzeigen\n", " -l --list Alle Dateien im Archiv auflisten\n", " -n --no-clobber Dateien niemals überschreiben\n", " -p --print Eine oder mehr Dateien aus dem Archiv ausgeben", - "\n", + "\n", " -q --quiet Ruhiger Modus (keine Ausgabe außer Fehler)\n", " -t --type Archiv-Typ (gz, tar, tgz, zip)\n", " -v --verbose Ausführlicher Modus für Datei-Liste\n", " -x --extract Dateien entpacken" ], "2_options_mutually_exclusive": [ "Fehler: -%[shortopt1] / --%[longopt1] und ", "-%[shortopt2] / --%[longopt2] schließen sich gegenseitig aus!" ], - "3_options_mutually_exclusive": [ - "Fehler: -%[shortopt1] / --%[longopt1], -%[shortopt2] / --%[longopt2] ", - "und -%[shortopt3] / --%[longopt3] schließen sich gegenseitig aus!" + "5_options_mutually_exclusive": [ + "Fehler: -%[shortopt1] / --%[longopt1], -%[shortopt2] / ", + "--%[longopt2], -%[shortopt3] / --%[longopt3], ", + "-%[shortopt4] / --%[longopt4] und\n", + " -%[shortopt5] / --%[longopt5] schließen sich gegenseitig aus!" ], "option_takes_no_argument": "%[prog]: Option --%[opt] nimmt kein Argument", "unknown_long_option": "%[prog]: Unbekannte Option: --%[opt]", "unknown_option": "%[prog]: Unbekannte Option: -%[opt]", + "writing_not_supported": [ + "Schreiben von Dateien des Typs %[type] wird (noch) nicht unterstützt!" + ], "failed_to_create_directory": [ "Fehler beim Erstellen des Verzeichnis %[dir]: %[error]" ], "failed_to_open_file": "Fehler beim Öffnen der Datei %[file]: %[error]", "unknown_archive_type": "Unbekannter Archivtyp: %[type]",