Как распаковать большой 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