Question

I'm executing a zip command from within my app using NSTask. It's passed as arguments some paths which point to the files/folders to be zipped.

The problem is that without the -j option, the final zip ends up with absurd filepaths inside the zip, (like "/private/var/folders/A5/A5CusLQaEo4mop-reb-SYE+++TI/-Tmp-/9101A216-5A6A-4CD6-A477-E4B86E007476-51228-00014BCB9514323F/myfile.rtf"). However, if I add the -j option, then I constantly run into name collisions if any file anywhere deep inside a nested folder has

I've tried setting the path before executing the NSTask:

[[NSFileManager defaultManager] changeCurrentDirectoryPath:path];

In the hope that the documentation for zip was telling the truth:

By default, zip will store the full path (relative to the current directory)

But this did not work as expected. Adjusting settings of -j and -p and -r simply produces the above mentioned problems in different combinations.

QUESTION:

How can I take a set of directories like

  • /some/long/path/sub1/file1.txt
  • /some/long/path/sub2/file1.txt

and zip them into a zip whose contents are

  • /sub1/file1.txt
  • /sub2/file1.txt

Thanks for any advice on the subtleties of zip.

-----EDIT

One other thing I forgot to add is that the original directory being passed is "path", so the desired outcome is also to my mind the expected outcome.

Was it helpful?

Solution 2

This is not a universal solution, in that it won't handle multiple directories well, but the solution I'm using for a single directory of unknown contents (ie, mixed files/folders/bundles) is to enumerate the contents of the directory and add them individually as arguments to zip, rather than simply zipping the entire directory at once.

Specifically:

+ (BOOL)zipDirectory:(NSURL *)directoryURL toArchive:(NSString *)archivePath;
{
//Delete existing zip
if ( [[NSFileManager defaultManager] fileExistsAtPath:archivePath] ) {
    [[NSFileManager defaultManager] removeItemAtPath:archivePath error:nil];
}

//Specify action
NSString *toolPath = @"/usr/bin/zip";

//Get directory contents
NSArray *pathsArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[directoryURL path] error:nil];

//Add arguments
NSMutableArray *arguments = [[[NSMutableArray alloc] init] autorelease];
[arguments insertObject:@"-r" atIndex:0];
[arguments insertObject:archivePath atIndex:0];
for ( NSString *filePath in pathsArray ) {
    [arguments addObject:filePath]; //Maybe this would even work by specifying relative paths with ./ or however that works, since we set the working directory before executing the command
    //[arguments insertObject:@"-j" atIndex:0];
}

//Switch to a relative directory for working.
NSString *currentDirectory = [[NSFileManager defaultManager] currentDirectoryPath]; 
[[NSFileManager defaultManager] changeCurrentDirectoryPath:[directoryURL path]];
//NSLog(@"dir %@", [[NSFileManager defaultManager] currentDirectoryPath]);

//Create
NSTask *task = [[[NSTask alloc] init] autorelease];
[task setLaunchPath:toolPath];
[task setArguments:arguments];

//Run
[task launch];
[task waitUntilExit];

//Restore normal path
[[NSFileManager defaultManager] changeCurrentDirectoryPath:currentDirectory];

//Update filesystem
[[NSWorkspace sharedWorkspace] noteFileSystemChanged:archivePath];

return ([task terminationStatus] == 0);
}

Again, I make no claims this is bulletproof (and would love improvements) but it does work at correctly zipping any single folder.

OTHER TIPS

Instead of

[[NSFileManager defaultManager] changeCurrentDirectoryPath:path];

use -[NSTask setCurrentDirectoryPath:] prior to launching the task. For example:

NSString *targetZipPath = @"/tmp/foo.zip";
NSArray *args = [NSArray arrayWithObjects:@"-r", targetZipPath,
    @"sub1", @"sub2", nil];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/zip"];
[task setArguments:args];

// set path to be the parent directory of sub1, sub2
[task setCurrentDirectoryPath:path];
…
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top