How to unzip a big zip file containing one file and get the progress in bytes with swift?
Asked Answered
B

4

16

I try to unzip a big zip file containing only one item (more than 100MB) and like to show the progress during unzipping.

I found solutions where the progress can be determined based on the amount of files unzipped but in my case I have only one big file inside. So I guess it must be determined by the amount of bytes unzipped?

Actually I am using SSZipArchive with the following code which works fine:

    var myZipFile:NSString="/Users/user/Library/Developer/CoreSimulator/Devices/mydevice/ziptest/testzip.zip";
    var DestPath:NSString="/Users/user/Library/Developer/CoreSimulator/Devices/mydevice/ziptest/";


    let unZipped = SSZipArchive.unzipFileAtPath(myZipFile as! String, toDestination: DestPath as! String);

I found no solutions for this.

Does anyone have a hint, sample or link to a sample ?

UPDATE: Following code looks like it would work as intended, but the handler will be called only once (at the end of unzipping) when the only one file is unzipped:

func unzipFile(sZipFile: String, toDest: String){

        SSZipArchive.unzipFileAtPath(sZipFile, toDestination: toDest, progressHandler: {
            (entry, zipInfo, readByte, totalByte) -> Void in


            println("readByte : \(readByte)") // <- This will be only called once, at the end of unzipping. My 500MB Zipfile holds only one file. 
            println("totalByte : \(totalByte)")


            //Asynchrone task
            dispatch_async(dispatch_get_main_queue()) {
                println("readByte : \(readByte)")
                println("totalByte : \(totalByte)")

                //Change progress value

            }
            }, completionHandler: { (path, success, error) -> Void in
                if success {
                    //SUCCESSFUL!!
                } else {
                    println(error)
                }
        })

    }

UPDATE 2:

As "Martin R" analysed in SSArchive, its not possible. Is there any other way to unzip a file and show the progress based kbytes?

UPDATE 3:

I changed the SSZipArchive.m after the solution was explained by "roop" as follows. Probably someone else can use this too:

FILE *fp = fopen((const char*)[fullPath UTF8String], "wb");
                while (fp) {
                    int readBytes = unzReadCurrentFile(zip, buffer, 4096);

                    if (readBytes > 0) {
                        fwrite(buffer, readBytes, 1, fp );
                        totalbytesread=totalbytesread+4096;
                        // Added by me
                        if (progressHandler)
                        {
                            progressHandler(strPath, fileInfo, currentFileNumber, totalbytesread);
                        }
                        // End added by me

                    } else {
                        break;
                    }
                }
Bona answered 14/5, 2015 at 7:57 Comment(5)
What unzip library are you using?Catchings
I used with others SSZipArchiveBona
If I understand the source code of SSZipArchive correctly, the progress handler is called once per file and does not offer any options to get progress while unzipping a single file.Beneficent
Thanks for the confirmation. Thats what I suspected. Is there any other way to achieve this ? I mean I am not fixed to SSZipArchive.Bona
@mcfly soft, I think this can be achieved with help of zlib.Palaeography
U
2

To achieve what you want, you will have to modify SSZipArchive's internal code.

SSZipArchive uses minizip to provide the zipping functionality. You can see the minizip unzipping API here: unzip.h.

In SSZipArchive.m, you can get the uncompressed size of the file being unzipped from the fileInfo variable.

You can see that the unzipped contents are being read here:

 FILE *fp = fopen((const char*)[fullPath UTF8String], "wb");
 while (fp) {
     int readBytes = unzReadCurrentFile(zip, buffer, 4096);
     if (readBytes > 0) {
         fwrite(buffer, readBytes, 1, fp );
     } else {
         break;
     }
 }

You will need the readBytes and the uncompressed file size to compute the progress for a single file. You can add a new delegate to SSZipArchive to send these data back to the calling code.

Ursulina answered 26/5, 2015 at 10:46 Comment(4)
Thanks a lot for helping. I guess this is the correct answer, thats why I accept it. I didn't try practically but I see the solution. Of course I would not be unhappy if someone could paste the code with the delegate in the SSZipArchive.m, because I am more familiar with swift :-)Bona
An alternate suggestion: If you're not comfortable with Obj-C, you can use minizip directly (instead of using SSZipArchive) - Swift can directly link to C code.Ursulina
I implemented the solution. (bit differently) and it works. I updated my changes in the question. Thanks a lot for helping again.Bona
Looks good. This should do, as long as you're dealing with zips with one file only. A small correction: you might want to say totalbytesread=totalbytesread+readBytes; so that totalbytesread never exceeds the uncompressed size (and therefore progress never exceeds 100%).Ursulina
C
2

You can try this code :

    SSZipArchive.unzipFileAtPath(filePath, toDestination: self.destinationPath, progressHandler: { 
(entry, zipInfo, readByte, totalByte) -> Void in
      //Create UIProgressView
      //Its an exemple, you can create it with the storyboard...
      var progressBar : UIProgressView?
      progressBar = UIProgressView(progressViewStyle: .Bar)
      progressBar?.center = view.center
      progressBar?.frame = self.view.center
      progressBar?.progress = 0.0
      progressBar?.trackTintColor = UIColor.lightGrayColor();
      progressBar?.tintColor = UIColor.redColor();
      self.view.addSubview(progressBar)

      //Asynchrone task                
      dispatch_async(dispatch_get_main_queue()) {
           println("readByte : \(readByte)")
           println("totalByte : \(totalByte)")                               

           //Change progress value
           progressBar?.setProgress(Float(readByte/totalByte), animated: true)
           //If progressView == 100% then hide it
           if readByte == totalByte {
               progressBar?.hidden = true
           }
       }
}, completionHandler: { (path, success, error) -> Void in
    if success {
        //SUCCESSFUL!!
    } else {
        println(error)
    }
})

I hope I have helped you!

Ysee

Chasse answered 22/5, 2015 at 9:54 Comment(4)
Thank you very much for helping. I will check asap. Sounds promising.Bona
Thanks for helping, but Its not working correctly. When I am debugging your code, it will only once call the progressHandler. This is at the time, when the Zipfile is unzipped. In my case I do only have one file in the zip, so I guess it will only call the handler for each unzipped file. In my case this would mean it will be called at the end of unzipping and it will not show any progress during unzipping. Any suggestions ?Bona
I pasted my actual code to the question. I simplified the code by removing the UIProgressView. We can see the progress also in the console with println.Bona
No. println works, but it will only be called once. I am looking for a solution where I see the progress during unzipping a large zipfile with only one big file in it. The proposed solution will show the progress if you have a lot of small files zipped in one zipfile, but not for a zipfile containing only one file.Bona
U
2

To achieve what you want, you will have to modify SSZipArchive's internal code.

SSZipArchive uses minizip to provide the zipping functionality. You can see the minizip unzipping API here: unzip.h.

In SSZipArchive.m, you can get the uncompressed size of the file being unzipped from the fileInfo variable.

You can see that the unzipped contents are being read here:

 FILE *fp = fopen((const char*)[fullPath UTF8String], "wb");
 while (fp) {
     int readBytes = unzReadCurrentFile(zip, buffer, 4096);
     if (readBytes > 0) {
         fwrite(buffer, readBytes, 1, fp );
     } else {
         break;
     }
 }

You will need the readBytes and the uncompressed file size to compute the progress for a single file. You can add a new delegate to SSZipArchive to send these data back to the calling code.

Ursulina answered 26/5, 2015 at 10:46 Comment(4)
Thanks a lot for helping. I guess this is the correct answer, thats why I accept it. I didn't try practically but I see the solution. Of course I would not be unhappy if someone could paste the code with the delegate in the SSZipArchive.m, because I am more familiar with swift :-)Bona
An alternate suggestion: If you're not comfortable with Obj-C, you can use minizip directly (instead of using SSZipArchive) - Swift can directly link to C code.Ursulina
I implemented the solution. (bit differently) and it works. I updated my changes in the question. Thanks a lot for helping again.Bona
Looks good. This should do, as long as you're dealing with zips with one file only. A small correction: you might want to say totalbytesread=totalbytesread+readBytes; so that totalbytesread never exceeds the uncompressed size (and therefore progress never exceeds 100%).Ursulina
I
1

As far as I understood, the most obvious answer would be modifying SSZipArchive's internal code. But I decided to go different way and wrote this extension. It's rather simple to understand, but don't hesitate to ask any questions.

Also, if you think my solution has flaws or you know how to make it better, I would be happy to hear it.

Here's a solution:

import Foundation
import SSZipArchive

typealias ZippingProgressClosure = (_ zipBytes: Int64, _ totalBytes: Int64) -> ()
private typealias ZipInfo = (contentSize: Int64, zipPath: String, progressHandler: ZippingProgressClosure)

extension SSZipArchive
{
    static func createZipFile(atPath destinationPath: String,
                              withContentsOfDirectory contentPath: String,
                              keepParentDirectory: Bool,
                              withPassword password: String? = nil,
                              byteProgressHandler: @escaping ZippingProgressClosure,
                              completionHandler: @escaping ClosureWithSuccess)
    {
        DispatchQueue.global(qos: .background).async {

            var timer: Timer? = nil
            DispatchQueue.main.async {

                //that's a custom function for folder's size calculation
                let contentSize = FileManager.default.sizeOfFolder(contentPath) 
                timer = Timer.scheduledTimer(timeInterval: 0.1,
                                             target: self,
                                             selector: #selector(progressUpdate(_:)),
                                             userInfo: ZipInfo(contentSize: contentSize,
                                                               zipPath: destinationPath,
                                                               progressHandler: byteProgressHandler),
                                             repeats: true)
            }

            let isSuccess = SSZipArchive.createZipFile(atPath: destinationPath,
                                                       withContentsOfDirectory: contentPath,
                                                       keepParentDirectory: keepParentDirectory,
                                                       withPassword: password,
                                                       andProgressHandler: nil)

            DispatchQueue.main.async {
                timer?.invalidate()
                timer = nil
                completionHandler(isSuccess)
            }
        }
    }

    @objc private static func progressUpdate(_ sender: Timer)
    {
        guard let info = sender.userInfo as? ZipInfo,
            FileManager.default.fileExists(atPath: info.zipPath),
            let zipBytesObj = try? FileManager.default.attributesOfItem(atPath: info.zipPath)[FileAttributeKey.size],
            let zipBytes = zipBytesObj as? Int64 else {
                return
        }

        info.progressHandler(zipBytes, info.contentSize)
    }
}

And method is used just like that:

SSZipArchive.createZipFile(atPath: destinationUrl.path,
                               withContentsOfDirectory: fileUrl.path,
                               keepParentDirectory: true,
                               byteProgressHandler: { (zipped, expected) in

                                //here's the progress code
    }) { (isSuccess) in
        //here's completion code
    }

Pros: You don't need to modify internal code which will be overwritten with pods update

Cons: As you can see, I'm updating file size info with 0.1 seconds interval. I do not know if fetching file metadata can cause performance overload and I can not find any information on that.

Anyway, I hope I help somebody :)

Imperium answered 25/3, 2019 at 15:45 Comment(0)
M
0

SSZipArchive has not been updated for six years, you need a new choice.

Zip: Swift framework for zipping and unzipping files.

let filePath = Bundle.main.url(forResource: "file", withExtension: "zip")!
let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask)[0]
try Zip.unzipFile(filePath, destination: documentsDirectory, overwrite: true, password: "password", progress: { (progress) -> () in
    print(progress)
}) // Unzip

let zipFilePath = documentsFolder.appendingPathComponent("archive.zip")
try Zip.zipFiles([filePath], zipFilePath: zipFilePath, password: "password", progress: { (progress) -> () in
    print(progress)
}) //Zip
Magulac answered 17/8, 2019 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.