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

前端 未结 4 1994
无人共我
无人共我 2020-12-30 03:35

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

相关标签:
4条回答
  • 2020-12-30 03:57

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

    0 讨论(0)
  • 2020-12-30 04:01

    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.

    0 讨论(0)
  • 2020-12-30 04:03

    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
    
    0 讨论(0)
  • 2020-12-30 04:06

    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

    0 讨论(0)
提交回复
热议问题