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

旧巷老猫 提交于 2019-12-31 19:40:07

问题


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;
                    }
                }

回答1:


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.




回答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




回答3:


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 :)




回答4:


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


来源:https://stackoverflow.com/questions/30232207/how-to-unzip-a-big-zip-file-containing-one-file-and-get-the-progress-in-bytes-wi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!