I am trying to implement download a zip file and unzip it with progressbar. Roughly below how my code looks like
var handler = new HttpClientHandler() { AllowAutoRedirect = true };
var ph = new ProgressMessageHandler(handler);
ph.HttpReceiveProgress += (_, args) => { GetProgress(args.ProgressPercentage); };
var httpClient = new HttpClient(ph);
var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken.Token);
response.EnsureSuccessStatusCode();
using (var zipInputStream = new ZipInputStream(response.Content.ReadAsStreamAsync()))
{
while (zipInputStream.GetNextEntry() is { } zipEntry)
{
var entryFileName = zipEntry.Name;
var buffer = new byte[4096];
var directoryName = Path.GetDirectoryName(fullZipToPath);
if (directoryName?.Length > 0)
{
Directory.CreateDirectory(directoryName);
}
if (Path.GetFileName(fullZipToPath).Length == 0)
{
continue;
}
using (var streamWriter = File.Create(fullZipToPath))
{
StreamUtils.Copy(zipInputStream, streamWriter, buffer);
}
}
}
My problem here is when I use ResponseHeadersRead
instead of ResponseContentRead
, ProgressMessageHandler
is not reporting progress, using ResponseContentRead
I can see the progress incrementing correctly.
It also works fine using ResponseHeadersRead and copy the stream directly to a file as below.
await using (var fs = new FileStream(pathToNewFile + "/test.zip", FileMode.Create))
{
await response.Content.CopyToAsync(fs);
}
But I feel like this way is waste to download zip to a temp file and unzip again with another stream while i can directly pass the stream to ZipInputStream like I do above. I believe I do something wrong here as I possible misunderstand the usage of ZipInputStream or ResponseHeadersRead? Does ZipInputStream require entire stream loaded at once while ResponseHeadersRead can gradually download the stream, so at the end I cannot directly pass the stream like that?
Please give me a suggestion if that is bad usage or i miss something?
EDIT: Problem seems to be because StreamUtils.Copy
is sync, and Progress is only reported when this line is executed completed but it is already 100% once it is done. It looks like that ZipInputStream
doesn't provide any async option to copy stream into a file. I need to probably find an alternative.
EDIT 2: I have changed the code using the built in ZipArchive, but also implements as Sync
using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
zipArchive.ExtractToDirectory(directoryName, true)
}
EDIT 3 Working solution: like I said if I just copy the response first to filestream and write as zip file
await using (var fs = new FileStream(pathToNewFile + "/test.zip", FileMode.Create))
{
await response.Content.CopyToAsync(fs);
}
then read this zip file into stream and use this stream as below. it works, I can see the progress.
var fileToDecompress = new FileInfo(_pathToNewFile + $"/test.zip");
var stream = fileToDecompress.OpenRead();
using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
zipArchive.ExtractToDirectory(directoryName, true)
}