问题
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