ZIPFoundation icon indicating copy to clipboard operation
ZIPFoundation copied to clipboard

Unzipping in-memory archive

Open MaxDesiatov opened this issue 4 years ago • 8 comments

Is your feature request related to a problem? Please describe. It doesn't seem like there's a way to unzip an in-memory archive created with Archive(data:) initializer.

Describe the solution you'd like An extension function on FileManager that takes an instance of Archive as an argument and unzips the archive in a given directory.

Describe alternatives you've considered I've tried writing an in-memory archive to the filesystem first and then using the existing unzipItem extension function on FileManager, but that has a performance hit due to the fact that this requires writing the archive itself to disk.

Additional context Writing the archive to disk is not needed in my case, it's held entirely in memory as it was donwloaded from network and there's no need to write the archive itself to disk, the app needs to write unzipped archive content directly to disk. The in-memory zip data is immediately discarded.

MaxDesiatov avatar Jun 05 '20 21:06 MaxDesiatov

Hi Max,

Sorry for the delayed reply.

There are currently no convenience methods on FileManager to extract in-memory archives. There is a pending PR that implements this though. I will need to find some time to review it and fix some minor issues before we can merge this into master - but at a first glance the PR additions look good. So you can maybe use the PR branch or copy the unzipItem code into a category.

weichsel avatar Jun 15 '20 07:06 weichsel

A workaround I'm currently using is extracting each entry individually in an archive. The destination could be either on disk or in memory. This is also how zipping and unzipping an entire archive works internally in ZIP Foundation.

WowbaggersLiquidLunch avatar Jun 16 '20 04:06 WowbaggersLiquidLunch

A workaround I'm currently using is extracting each entry individually in an archive. The destination could be either on disk or in memory. This is also how zipping and unzipping an entire archive works internally in ZIP Foundation.

Can u tell me how u did it? I struggle to unpack the password protected archive to memory.

danio0701 avatar Oct 03 '22 21:10 danio0701

This is really awesome ... in-memory unzipping! I wrote a small app to test this, using cocoapods to import the dependency. It is unclear to me how I can get the unzipped file. Perhaps somebody can explain that?

I would expect I have to do something like this:

        // data contains the zipped data
        let archive = ZIPFoundation.Archive(data: data, accessMode: .read)
        
        for entry in archive! {
            print(entry.path)  // Here I succesfully print the paths of the zipped components inside the zipfile
            if entry.type == .file,
               let data = entry.dataDescriptor?.data {
                
            }
        }

But that gives me an error: "'dataDescriptor' is inaccessible due to 'internal' protection level"

How do I get the unzipped data for an entry? Or should I not look at the entries?

Yourney avatar Mar 09 '23 11:03 Yourney

I solved it:

        if let archive = ZIPFoundation.Archive(data: data, accessMode: .read) {
            for entry in archive {
                do {
                    if entry.path == "myfile.text" {
                        var txtData = Data()
                        _ = try archive.extract(entry) { data in
                            txtData.append(data)
                        }
                        // here we have our unzipped text (in txtData)!!
                    }
                } catch {
                    print(error)
                }
            }
        }

Awesome!

Yourney avatar Mar 13 '23 08:03 Yourney

@danio0701 Sorry for this really really late response!

The way I did it (here in this semi-abandoned project) is mostly the same as how @Yourney did it. The only thing I want to mention is that, depending on whether you want the entire directory structure, or just the files, or any specific file, you can guard against the rest or supply a where clause to the loop:

guard let archive = Archive(data: yourData, accessMode: .read) else { ... }

for entry in archive where entry.type == .file /* or .directory depending on your need */ {
    do {
        _ = try archive.extract(entry, skipCRC32: true, progress: nil) { data in
            // do what you need with the data
            // e.g. reassemble the directory structure here if you need it
        }
    } catch { ... }
}

WowbaggersLiquidLunch avatar Mar 13 '23 15:03 WowbaggersLiquidLunch

Thanks for the added tips!

Yourney avatar Mar 13 '23 15:03 Yourney