Laden...

CopyComponent zum Kopieren von Dateien mit Fortschritts-, Datendurchsatz-, und Restzeitberechnung

Erstellt von t2t vor 14 Jahren Letzter Beitrag vor 9 Jahren 21.311 Views
T
t2t Themenstarter:in
415 Beiträge seit 2007
vor 14 Jahren
CopyComponent zum Kopieren von Dateien mit Fortschritts-, Datendurchsatz-, und Restzeitberechnung

Beschreibung:

Ich bin vor einiger Zeit über das Hindernis gestoßen, dass man im .NET Framework nur Dateien mit System.IO.File.Copy() als ein Ganzes kopieren kann. Möchte man nun z.B. eine große Datei kopieren, hat man keine Möglichkeit den Fortschritt des Kopiervorgangs auf der GUI, etwa durch eine Prozessbar, sichtbar zu machen.

Dieses Manko wollte ich beheben und hab mich dazu entschlossen eine Klasse zu schreiben, die Dateien binär kopiert und während dieses Vorgangs einen Fortschritt berechnet. Um das Ganze zu komplettieren habe ich auch gleich die Berechnung des Datendurchsatzes und der Restzeit des Kopiervorgangs mit eingebaut.

Um die Funktionalität nicht nur auf eine einzelne zu kopierende Datei zu beschränken, bietet die Klasse auch die Möglichkeit eine Liste von Dateien zu übergeben in Form einer System.Collections.Generic.List<string>, eines string[] Arrays oder eines System.IO.FileInfo[] Arrays. Die Klasse informiert dann während des Kopierens über den prozentualen Fortschritt der Dateiliste, der aktuell zu kopierenden Datei und bezogen auf die Datei die grad kopiert wird ebenfalls Fortschritt in Prozent, Datendurchsatz und Restzeit. Auf diese Weise ließe sich eine Kopier-Fortschritts-Ansicht erstellen, wie man es z.B. aus dem TotalCommander kennt. Eine Prozessbar für die gesamten zu kopierenden Dateien und eine für den Kopiervorgang der aktuellen Datei.

Die Klasse gibt alle Informationen über den Kopiervorgang per Events nach draußen, welche dann abonniert werden können. Der Komponente liegt eine Beispiel Applikation bei, die die CopyComponent in einem BackgroundWorker verwendet. Die wesentlichen Codestücke zur Verwendung habe ich noch mal hervorgehoben:


// Instanz der BinaryCopiers
BinaryCopy bCopy = new BinaryCopy();

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
      // Event des BinaryCopiers registrieren
     bCopy.BinaryCopyEvent += new BinaryCopyEventHandler(bCopy_BinaryCopyEvent);
     // Kopie der Datei starten
     bCopy.Copy(tbSource.Text, tbDestination.Text, true);
}

void bCopy_BinaryCopyEvent(object sender, BinaryCopyEventArgs e)
{
     // Auf das BinaryCopy Event reagieren und beim BGW das ProgressChanged Event auslösen und die BinaryCopyEventArgs übergeben
      backgroundWorker.ReportProgress(e.Percent, (object)e);
}

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
       // EventArgs abfragen
       BinaryCopyEventArgs binaryArgs = (e.UserState as BinaryCopyEventArgs);
       // Prozessbar aktualisiern
       tsProgressBar.Value = e.ProgressPercentage;
       // Datendurchsatz in einem Label anzeigen
       tsslRate.Text = binaryArgs.KbPSec.ToString("N").Substring(0, binaryArgs.KbPSec.ToString("N").Length - 3) + " Kb/sec"; 
       // Restzeit in einem Label anzeigen
       tsslRemaning.Text = binaryArgs.Remaning.ToString();
}

Die Klasse mit der Beispielanwendung befindet sich im Anhang. Für Korrekturen (auch am Stil) und Verbesserungsvorschläge bin ich natürlich offen.

UPDATE vom 11.10.2010 (Datei-Downloads vor Update: 229):

  • Verbesserungsvorschläge von Abt übernommen (siehe Postings weiter unten)
  • Eine Methode zum kopieren ganzer Ordner samt Inhalt hinzugefügt

UPDATE vom 26.03.2014 (Datei-Downloads vor Update: 459):

  • Verbessertes kopieren von Ordnern, welches nun auch per Event über den Fortschritt des kopierens eines ganzen Ordners informiert.
  • Sample App auf die Veränderung angepasst

Schlagwörter: kopieren, Daten kopieren, Datei kopieren, Dateien kopieren, Restzeit, Datendurchsatz, Prozessfortschritt

T
t2t Themenstarter:in
415 Beiträge seit 2007
vor 14 Jahren

Hier noch ein Screen der Beispielanwendung:

B
72 Beiträge seit 2009
vor 13 Jahren

Vielen Dank, sowas hatte ich gesucht und konnte es gut in meinem Projekt verwenden!

16.807 Beiträge seit 2008
vor 13 Jahren

Hi,

erst mal danke für dein Projekt!


 if (newFileName != string.Empty)
            {
                if (File.Exists(destination + @"\" + newFileName) && !overwrite)
                    throw new IOException("Die im Zielverzeichnis vorhandene Zieldatei kann nicht überschrieben werden");
            }
            else
            {
                if (File.Exists(destination + @"\" + srcFileInfo.Name) && !overwrite)
                    throw new IOException("Die im Zielverzeichnis vorhandene Zieldatei kann nicht überschrieben werden");
            }

habe ich bei mir in


            if ( newFileName != string.Empty )
            {
                if ( File.Exists( Path.Combine( destination, newFileName ) ) && !overwrite )
                    throw new IOException( "Die im Zielverzeichnis vorhandene Zieldatei kann nicht überschrieben werden" );
            }
            else
            {
                if ( File.Exists( Path.Combine( destination, srcFileInfo.Name ) )&& !overwrite )
                    throw new IOException( "Die im Zielverzeichnis vorhandene Zieldatei kann nicht überschrieben werden" );
            }

ändern müssen, da ich bei mir keine fixen Pfadkomponenten im Quelltext stehen haben darf/möchte und stattdessen auf Path.Combine() setze.

Bei der Berechnung der kbPSec könnte es zu einem Umrechnungsfehler kommen, wenn bytesReceived und sec beides Ganzzahlen sind, kbPSec aber ein Double darstellt - erklärt jedenfalls die Anzeige bei mir die mitten drin plötzlich 0.0 anzeigt.

                if ( sec > 0 )
                    kbPSec = bytesReceived / sec;

in

                if ( sec > 0 )
                    kbPSec = 1.0 * bytesReceived / sec;

Weiterhin hab ich bei mir den Event BinaryCopyEventArgs um das Argument bytesReceived erweitert, sodass ich bei einer Vielzahl von Copy-Prozessen den Gesamtfortschritt haargenau anzeigen kann.

Aber klappt ansonsten prima 👍

16.807 Beiträge seit 2008
vor 13 Jahren

Also ich hab mal die Hauptfunktion überarbeitet: 🙂


public void Copy( string source, string destination, string newFileName, bool overwrite )
{
    FileInfo srcFileInfo = new FileInfo( source );

    // Verify that the source file exists
    if ( !srcFileInfo.Exists )
        throw new IOException( "Source file does not exist" );

    // Verify that the destionation folder exists
    if ( !Directory.Exists( destination ) )
        throw new IOException( "Target directory does not exist." );

    // Verify that the given new file name does not exist ( if overwrite is forbidden )
    if ( !string.IsNullOrEmpty( newFileName ) && File.Exists( Path.Combine( destination, newFileName ) ) && !overwrite )
        throw new IOException( "The target file exists and can not be overwritten." );

    // Verify that the source file does not exist ( if overwrite is forbidden )
    if ( File.Exists( Path.Combine( destination, srcFileInfo.Name ) ) && !overwrite )
        throw new IOException( "The target file exists and can not be overwritten." );

    // Standard target file
    FileInfo currentTargetFileInfo = new FileInfo( Path.Combine( destination, srcFileInfo.Name ) );

    // Change target file to given new file name
    if ( !string.IsNullOrEmpty( newFileName ) )
        currentTargetFileInfo = new FileInfo( Path.Combine( destination, newFileName ) );

    // Creating Streams
    FileStream destStream = new FileStream( currentTargetFileInfo.FullName, FileMode.Create, FileAccess.Write );
    FileStream srcStream = new FileStream( source, FileMode.Open, FileAccess.Read );

    // Standard buffer
    byte[ ] streamBuffer = new byte[ 32768 ];

    // Increase buffer
    if ( srcFileInfo.Length < 32768 )
        streamBuffer = new byte[ srcFileInfo.Length ];


    const int offset = 0;
    int remainSec = 0;
    int remain = streamBuffer.Length;
    long iMax = srcFileInfo.Length;
    long bytesReceived = 0;
    double kbPSec = 0;

    DateTime start = DateTime.Now;

    while ( srcStream.Read( streamBuffer, offset, remain ) > 0 )
    {
        // Bufferinhalt in die Zieldatei schreiben
        destStream.Write( streamBuffer, offset, remain );

        // Prozentualen Fortschritt berechnen
        double percent = 1.0 * ( bytesReceived * 100 ) / iMax;

        // Datendurchsatz berechnen (Empfangene Bytes / verstrichene Zeit)
        bytesReceived += streamBuffer.Length;
        int sec = ( int ) Math.Round( DateTime.Now.Subtract( start ).TotalMilliseconds, MidpointRounding.AwayFromZero );

        if ( sec > 0 )
            kbPSec = 1.0 * bytesReceived / sec;

        // Restzeit des Kopiervorgangs berechnen (verbleibende Bytes / Datendurchsatz)
        if ( kbPSec > 0 )
        {
            remainSec = ( int ) Math.Round( ( ( ( ( iMax - bytesReceived ) / kbPSec ) / 1000 ) ), MidpointRounding.AwayFromZero );
        }
        TimeSpan remaning = new TimeSpan( 0, 0, remainSec );

        // Kopierevent feuern
        OnBinaryCopy( new BinaryCopyEventArgs( bytesReceived, percent, kbPSec, remaning ) );

        // StreamBuffer ggf. anpassen, um keine Nullen in die Datei zu schreiben
        if ( ( iMax - bytesReceived ) >= 32768 ) continue;

        streamBuffer = new byte[ iMax - bytesReceived ];
        remain = streamBuffer.Length;
    }

    destStream.Close( );
    srcStream.Close( );
}

Und den dazugehörigen Event vereinfacht:

public class BinaryCopyEventArgs : EventArgs
{
    public BinaryCopyEventArgs(long bytesReceived, double percent, double kbPSec, TimeSpan remaning)
    {
        this.BytesReceived = bytesReceived;
        this.Percent = percent;
        this.KbPSec = kbPSec;
        this.Remaning = remaning;
    }

    /// <summary>
    /// Fortschrittsangabe des Kopiervorgangs in Prozent
    /// </summary>
    public double Percent { private set; get; }

    /// <summary>
    /// Datendurchsatz des Kopiervorgangs in KB pro Sekunde
    /// </summary>
    public double KbPSec{ private set; get; }

    /// <summary>
    /// Verbleibende Zeitspanne des aktuellen Kopiervorgangs
    /// </summary>
    public TimeSpan Remaning{ private set; get; }

    /// <summary>
    /// Gets or sets the bytes received.
    /// </summary>
    /// <value>The bytes received.</value>
    public long BytesReceived { private set; get; }
}

Ich hoff' das geht so in Ordnung 😉

T
t2t Themenstarter:in
415 Beiträge seit 2007
vor 13 Jahren

Hey Abt,

vielen Dank für deine Anmerkungen und Verbesserungsvorschläge. Ich habe die Snipp überarbeitet und im ersten Post aktualisiert. Zusätzlich bietet die Komponente nun noch eine Methode zum kopieren ganzer Ordner samt Unterordner und Dateien.

16.807 Beiträge seit 2008
vor 13 Jahren

Hi,

hier ist nochmal ein Fehler:


OnFileListCopy(new FileListCopyEventArgs((runner * 100) / sourceFiles.Length, sourceFile.Name));

Sollte 100.0 sein (possible loss of fraction)

Okay, sehe grade, dass das ja nur den "runner" betrifft und somit egal ist

T
t2t Themenstarter:in
415 Beiträge seit 2007
vor 10 Jahren

Es gab noch mal ein kleines Update auf Version 1.2 siehe Beschreibung oben.

D
1 Beiträge seit 2014
vor 9 Jahren

Hallo,

das Tool ist nicht schlecht, jedoch werden immer alle Dateien kopiert die in der Quelle vorhanden sind. gibt es eine möglichkeit vorher zu vergleichen was im Ziel vorhanden ist bzw. älter ist, da nur neue und fehlende Dateien kopiert werden sollen?

Danke für die Antwort im Vorraus

2.078 Beiträge seit 2012
vor 9 Jahren

Das geht über den Aufgabenbereich der Klasse hinaus, die soll schließlich nur kopieren und dabei genau über den aktuellen Stand informieren.

Wenn du die Dateien filtern möchtest, dann musst du das vorher tun, also der Klasse schon gefiltert übergeben.

PS:

Mir fällt auch gerade auf, dass das Thema schon ewig alt ist.
Ich denke nicht, dass sich da der Thread-Starter noch darum kümmert.

16.807 Beiträge seit 2008
vor 9 Jahren

...und mittlerweile hab ich QuickIO.NET - Performante Dateioperationen veröffentlich, mit dem so etwas sehr einfach realisierbar ist.
Was aber genau "gleich" bei Dateien bedeutet verstehen viele anders:

Einfach zwei Listen mit den Dateien aus Quelle und Ziel erstellen.
Beide nach eigenen Kriterien vergleichen.
Aktualisierte / Neue Dateien kopiere.
-> Fertig.

T
t2t Themenstarter:in
415 Beiträge seit 2007
vor 9 Jahren

Diese gewünschte Funktionalität wird in der Tat nicht unterstützt. Da aber die Sourcen vorhanden sind, kann man sich das leicht nach-implementieren. Oder wie von den Vorrednern erwähnt sich die Listen vorbereiten und dann erst zum kopieren schicken.

Der Fokus dieser Snippet liegt auf der einfachen Einbindung ins eigene Projekt und der Lösung eines konkreten Problems. Wer ein mächtigeres Kopier-Werkzeug mit deutlich mehr Funktionalität haben will, der sollte sich bei Abt's QuickIO bedienen.

@digicorder Solltest du dir die Komponente erweitern, dann kannst dir mir deine Änderungen gerne schicken und ich aktualisiere den Thread damit.