3 Stimmen

Fortschrittsberechnung (Balken) mit GZipStream

Ich lese eine .gz-Datei von einer langsamen Quelle (z. B. FTP-Server) und verarbeite die empfangenen Daten sofort. Das sieht in etwa so aus:

FtpWebResponse response = ftpclientRequest.GetResponse() as FtpWebResponse;
using (Stream ftpStream = response.GetResponseStream())
using (GZipStream unzipped = new GZipStream(ftpStream, CompressionMode.Decompress))
using (StreamReader linereader = new StreamReader(unzipped))
{
  String l;
  while ((l = linereader.ReadLine()) != null)
  {
    ...
  }
}

Mein Problem ist die Anzeige eines genauen Fortschrittsbalkens. Im Voraus kann ich die Größe der komprimierten .gz-Datei ermitteln, aber ich habe keine Ahnung, wie groß der Inhalt unkomprimiert sein würde. Wenn ich die Datei Zeile für Zeile lese, weiß ich ziemlich genau, wie viele unkomprimierte Bytes ich lese, aber ich weiß nicht, wie sich das auf die Größe der komprimierten Datei bezieht.

Gibt es also eine Möglichkeit, von GZipStream zu erfahren, wie weit der Dateizeiger in der komprimierten Datei fortgeschritten ist? Ich brauche nur die aktuelle Position, die gz-Dateigröße, die ich vor dem Lesen der Datei abrufen kann.

4voto

Lars Punkte 5803

Sie können einen Stream dazwischenschalten, der zählt, wie viele Bytes GZipStream gelesen hat.

  public class ProgressStream : Stream
  {
    public long BytesRead { get; set; }
    Stream _baseStream;
    public ProgressStream(Stream s)
    {
      _baseStream = s;
    }
    public override bool CanRead
    {
      get { return _baseStream.CanRead; }
    }
    public override bool CanSeek
    {
      get { return false; }
    }
    public override bool CanWrite
    {
      get { return false; }
    }
    public override void Flush()
    {
      _baseStream.Flush();
    }
    public override long Length
    {
      get { throw new NotImplementedException(); }
    }
    public override long Position
    {
      get
      {
        throw new NotImplementedException();
      }
      set
      {
        throw new NotImplementedException();
      }
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
      int rc = _baseStream.Read(buffer, offset, count);
      BytesRead += rc;
      return rc;
    }
    public override long Seek(long offset, SeekOrigin origin)
    {
      throw new NotImplementedException();
    }
    public override void SetLength(long value)
    {
      throw new NotImplementedException();
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
      throw new NotImplementedException();
    }
  }

// usage
FtpWebResponse response = ftpclientRequest.GetResponse() as FtpWebResponse;
using (Stream ftpStream = response.GetResponseStream())
using (ProgressStream progressStream = new ProgressStream(ftpstream))
using (GZipStream unzipped = new GZipStream(progressStream, CompressionMode.Decompress))
using (StreamReader linereader = new StreamReader(unzipped))
{
  String l;
  while ((l = linereader.ReadLine()) != null)
  {
    progressStream.BytesRead(); // does contain the # of bytes read from FTP so far.
  }
}

0 Stimmen

Toll, das war genau das, wonach ich gesucht habe! Schade, dass der Ftp-Stream die Rückgabe der bereits gelesenen Bytes nicht unterstützt!

0voto

Petar Petrov Punkte 2491

Ich empfehle Ihnen, sich den folgenden Code anzusehen:

public static readonly byte[] symbols = new byte[8 * 1024];

public static void Decompress(FileInfo inFile, FileInfo outFile)
{
    using (var inStream = inFile.OpenRead())
    {
        using (var zipStream = new GZipStream(inStream, CompressionMode.Decompress))
        {
            using (var outStream = outFile.OpenWrite())
            {
                var total = 0;
                do
                {
                    var async = zipStream.BeginRead(symbols, 0, symbols.Length, null, null);
                    total = zipStream.EndRead(async);
                    if (total != 0)
                    {
                        // Report progress. Read total bytes (8K) from the zipped file.
                        outStream.Write(symbols, 0, total);
                    }
                } while (total != 0);
            }
        }
    }
}

0 Stimmen

Übermäßiger und unnötiger Gebrauch des var-Schlüsselworts. Das macht den Code wirklich unleserlich.

0 Stimmen

Var' macht es einfach, ein Beispiel einzugeben und den Compiler es ausarbeiten zu lassen.

0 Stimmen

Es tut mir leid, aber ich verstehe nicht, wie ich damit berechnen kann, wie weit ich mich in der gz-Datei befinde. total" enthält den unkomprimierten Fortschritt, was mir nicht weiterhilft, da ich keine Ahnung habe, wie groß die Datei im unkomprimierten Zustand ist. Ich muss meine Position wissen in komprimiert Bytes.

0voto

Petar Petrov Punkte 2491

Ich habe meinen Code überarbeitet und einige Tests durchgeführt. IMHO hat Darin recht. Ich denke jedoch, dass es möglich ist, nur den Header (Größe ?) des gezippten Streams zu lesen und die resultierende Dateigröße herauszufinden. (WinRar "weiß", wie groß die entpackte Datei ist, ohne das gesamte Zip-Archiv zu entpacken. Es liest diese Information aus dem Header des Archivs.) Wenn Sie die resultierende Dateigröße herausfinden, hilft Ihnen dieser Code, einen genauen Fortschritt zu melden.

public static readonly byte[] symbols = new byte[8 * 1024];

public static void Decompress(FileInfo inFile, FileInfo outFile, double size, Action<double> progress)
{
    var percents = new List<double>(100);

    using (var inStream = inFile.OpenRead())
    {
        using (var zipStream = new GZipStream(inStream, CompressionMode.Decompress))
        {
            using (var outStream = outFile.OpenWrite())
            {
                var current = 0;

                var total = 0;
                while ((total = zipStream.Read(symbols, 0, symbols.Length)) != 0)
                {
                    outStream.Write(symbols, 0, total);
                    current += total;

                    var p = Math.Round(((double)current / size), 2) * 100;
                    if (!percents.Contains(p))
                    {
                        if (progress != null)
                        {
                            progress(p);
                        }
                        percents.Add(p);
                    }
                }
            }
        }
    }
}

Ich hoffe, das hilft.

0 Stimmen

Petar, wie in deinem ersten Beispiel ist die aktuelle Position in der unkomprimierten Datei korrekt, dennoch ist es für mich von keinem Nutzen, da ich die Größe der unkomprimierten Datei nicht kenne. Ich glaube nicht, dass GZip die Dateigröße speichert, wie Rar es tut, also gibt es keine Möglichkeit für mich, die unkomprimierte Größe zu erhalten.

0voto

TermoTux Punkte 578

Als Proxy für den Dekomprimierungsfortschritt können Sie versuchen, die Informationen über den Download-Fortschritt der Datei aus dem zugrunde liegenden Stream zu erhalten:

var percentageProgress = ftpStream.Position / (double)ftpWebResponse.ContentLength;

o

var percentageProgress = ftpStream.Position / (double)ftpStream.Length;

Es funktioniert auf einer FileStream und es sollte auf einem GetResponseStream() vorausgesetzt, sie implementiert Position und der FTP-Server liefert Informationen über die Länge der heruntergeladenen Datei: http://msdn.microsoft.com/en-us/library/system.net.ftpwebresponse.contentlength(v=vs.110).aspx

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X