Как распаковать большой zip файл, содержащий один файл, и получить прогресс в байтах с быстрым?

Я пытаюсь распаковать большой zip файл, содержащий только один элемент (более 100 МБ), и хотел бы показать ход при распаковке.

Я нашел решения, в которых прогресс можно определить на основе количества распакованных файлов, но в моем случае у меня есть только один большой файл внутри. Поэтому я предполагаю, что это должно определяться количеством распакованных байтов?

На самом деле я использую SSZipArchive со следующим кодом, который отлично работает:

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

Я не нашел для этого решений.

Есть ли у кого-нибудь подсказка, образец или ссылка на образец?

UPDATE: Следующий код выглядит так, как будто он будет работать по назначению, но обработчик будет вызываться только один раз (в конце разархивирования), когда только один файл распаковывается:

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

    }

ОБНОВЛЕНИЕ 2:

Поскольку "Martin R" проанализирован в SSArchive, его невозможно. Есть ли другой способ распаковать файл и показать на основе kbytes?

ОБНОВЛЕНИЕ 3:

Я изменил SSZipArchive.m после того, как решение было объяснено "roop" следующим образом. Возможно, кто-то еще может это использовать:

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

Чтобы добиться того, чего вы хотите, вам придется изменить внутренний код SSZipArchive.

SSZipArchive использует minizip для обеспечения функциональности zipping. Здесь вы можете увидеть утилиту unizping minizip: unzip.h.

В SSZipArchive.m вы можете распаковать несжатый размер файла из fileInfo variable.

Вы можете видеть, что содержимое без распаковки читается здесь:

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

Для вычисления прогресса для одного файла вам понадобится readBytes и размер несжатого файла. Вы можете добавить нового делегата в SSZipArchive, чтобы отправить эти данные обратно на вызывающий код.

Ответ 2

Вы можете попробовать этот код:

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

Надеюсь, я помог вам!

Ysee

Ответ 3

Насколько я понял, наиболее очевидным ответом будет изменение внутреннего кода SSZipArchive. Но я решил пойти другим путем и написал это расширение. Это довольно просто понять, но не стесняйтесь задавать любые вопросы.

Кроме того, если вы считаете, что мое решение имеет недостатки или знаете, как его улучшить, я буду рад это услышать.

Вот решение:

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 a custom function for folder 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)
    }
}

И метод используется просто так:

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

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

Плюсы: вам не нужно изменять внутренний код, который будет перезаписан при обновлении pods

Минусы: Как видите, я обновляю информацию о размере файла с интервалом 0,1 секунды. Я не знаю, может ли выборка метаданных файла вызвать перегрузку производительности, и я не могу найти какую-либо информацию по этому вопросу.

В любом случае, я надеюсь помочь кому-нибудь :)

Ответ 4

SSZipArchive не обновлялся в течение шести лет, вам нужен новый выбор.

Zip: Swift Framework для архивирования и разархивирования файлов.

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