web-dev-qa-db-fra.com

Copie de fichier avec barre de progression

J'ai utilisé ce code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;

namespace WindowsApplication1 {
  public partial class Form1 : Form {
    // Class to report progress
    private class UIProgress {
      public UIProgress(string name_, long bytes_, long maxbytes_) {
        name = name_; bytes = bytes_; maxbytes = maxbytes_;
      }
      public string name;
      public long bytes;
      public long maxbytes;
    }
    // Class to report exception {
    private class UIError {
      public UIError(Exception ex, string path_) {
        msg = ex.Message; path = path_; result = DialogResult.Cancel;
      }
      public string msg;
      public string path;
      public DialogResult result;
    }
    private BackgroundWorker mCopier;
    private delegate void ProgressChanged(UIProgress info);
    private delegate void CopyError(UIError err);
    private ProgressChanged OnChange;
    private CopyError OnError;

    public Form1() {
      InitializeComponent();
      mCopier = new BackgroundWorker();
      mCopier.DoWork += Copier_DoWork;
      mCopier.RunWorkerCompleted += Copier_RunWorkerCompleted;
      mCopier.WorkerSupportsCancellation = true;
      OnChange += Copier_ProgressChanged;
      OnError += Copier_Error;
      button1.Click += button1_Click;
      ChangeUI(false);
    }

    private void Copier_DoWork(object sender, DoWorkEventArgs e) {
      // Create list of files to copy
      string[] theExtensions = { "*.jpg", "*.jpeg", "*.bmp", "*.png", "*.gif" };
      List<FileInfo> files = new List<FileInfo>();
      string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
      DirectoryInfo dir = new DirectoryInfo(path);
      long maxbytes = 0;
      foreach (string ext in theExtensions) {
        FileInfo[] folder = dir.GetFiles(ext, SearchOption.AllDirectories);
        foreach (FileInfo file in folder) {
          if ((file.Attributes & FileAttributes.Directory) != 0) continue;
          files.Add(file);
          maxbytes += file.Length;
        }
      }
      // Copy files
      long bytes = 0;
      foreach (FileInfo file in files) {
        try {
          this.BeginInvoke(OnChange, new object[] { new UIProgress(file.Name, bytes, maxbytes) });
          File.Copy(file.FullName, @"c:\temp\" + file.Name, true);
        }
        catch (Exception ex) {
          UIError err = new UIError(ex, file.FullName); 
          this.Invoke(OnError, new object[] { err });
          if (err.result == DialogResult.Cancel) break;
        }
        bytes += file.Length;
      }
    }
    private void Copier_ProgressChanged(UIProgress info) {
      // Update progress
      progressBar1.Value = (int)(100.0 * info.bytes / info.maxbytes);
      label1.Text = "Copying " + info.name;
    }
    private void Copier_Error(UIError err) {
      // Error handler
      string msg = string.Format("Error copying file {0}\n{1}\nClick OK to continue copying files", err.path, err.msg);
      err.result = MessageBox.Show(msg, "Copy error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
    }
    private void Copier_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
      // Operation completed, update UI
      ChangeUI(false);
    }
    private void ChangeUI(bool docopy) {
      label1.Visible = docopy;
      progressBar1.Visible = docopy;
      button1.Text = docopy ? "Cancel" : "Copy";
      label1.Text = "Starting copy...";
      progressBar1.Value = 0;
    }
    private void button1_Click(object sender, EventArgs e) {
      bool docopy = button1.Text == "Copy";
      ChangeUI(docopy);
      if (docopy) mCopier.RunWorkerAsync();
      else mCopier.CancelAsync();
    }
  }
}

posté ici (celui que nobugz a posté) dans la copie des fichiers et l'affichage de la barre d'état en cours.

Je voulais incrémenter en continu la valeur de la barre de progression pendant la copie, en particulier les fichiers volumineux. Ce qui se passe dans cet exemple de code, c'est que la barre de valeur en cours s'arrête sur chaque fichier copié et après qu'un fichier a été copié, il augmentera ensuite à la taille du fichier suivant à copier. Je voulais que cela fonctionne comme CopyFileEx dans Windows qui incrémente la barre de progression en continu lors de la copie (je ne peux pas utiliser CopyFileEx parce que je voulais avoir ma propre implémentation).

31
patlimosnero

Vous avez besoin de quelque chose comme ça:

    public delegate void ProgressChangeDelegate(double Persentage, ref bool Cancel);
    public delegate void Completedelegate();

    class CustomFileCopier
    {
        public CustomFileCopier(string Source, string Dest)
        {
            this.SourceFilePath = Source;
            this.DestFilePath = Dest;

            OnProgressChanged += delegate { };
            OnComplete += delegate { };
        }

        public void Copy()
        {
            byte[] buffer = new byte[1024 * 1024]; // 1MB buffer
            bool cancelFlag = false;

            using (FileStream source = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read))
            {
                long fileLength = source.Length;
                using (FileStream dest = new FileStream(DestFilePath, FileMode.CreateNew, FileAccess.Write))
                {
                    long totalBytes = 0;
                    int currentBlockSize = 0;

                    while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        totalBytes += currentBlockSize;
                        double persentage = (double)totalBytes * 100.0 / fileLength;

                        dest.Write(buffer, 0, currentBlockSize);

                        cancelFlag = false;
                        OnProgressChanged(persentage, ref cancelFlag);

                        if (cancelFlag == true)
                        {
                            // Delete dest file here
                            break;
                        }
                    }
                }
            }

            OnComplete();
        }

        public string SourceFilePath { get; set; }
        public string DestFilePath { get; set; }

        public event ProgressChangeDelegate OnProgressChanged;
        public event Completedelegate OnComplete;
    }

Il suffit de l'exécuter dans un thread séparé et de vous abonner à l'événement OnProgressChanged.

43
Anton Semenov

J'aime cette solution, car

Le moteur de copie est dans le framework

public delegate void IntDelegate(int Int);

public static event IntDelegate FileCopyProgress;
public static void CopyFileWithProgress(string source, string destination)
{
    var webClient = new WebClient();
    webClient.DownloadProgressChanged += DownloadProgress;
    webClient.DownloadFileAsync(new Uri(source), destination);
}

private static void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
    if(FileCopyProgress != null)
        FileCopyProgress(e.ProgressPercentage);
}

Chemins UNC

Cela devrait fonctionner avec les chemins UNC tant que les autorisations sont configurées. Sinon, vous obtiendrez cette erreur, dans ce cas, je vote pour l'itinéraire utilisateur de la demande authentifiée.

System.UnauthorizedAccessException: L'accès au chemin '\ testws01\c $\foo' est refusé.

ASP.NET n'est pas autorisé à accéder à la ressource demandée. Envisagez d'accorder des droits d'accès à la ressource à l'identité de la demande ASP.NET. ASP.NET a une identité de processus de base (généralement {MACHINE}\ASPNET sur IIS 5 ou Service réseau sur IIS 6 et IIS 7, et l'identité du pool d'applications configuré sur IIS 7.5) qui est utilisée si l'application n'emprunte pas l'identité. Si l'application emprunte l'identité via <identity impersonate="true"/>, l'identité sera l'utilisateur anonyme (généralement IUSR_MACHINENAME) ou l'utilisateur de la demande authentifiée.

25
toddmo

Faire votre propre logique de copie de fichiers en utilisant 2 flux comme présenté par Gal est une option viable mais ce n'est pas recommandé uniquement parce qu'il existe une opération Windows profondément intégrée qui est optimisée en termes de fiabilité, de sécurité et de performances nommée CopyFileEx.

cela dit, dans l'article suivant: http://msdn.Microsoft.com/en-us/magazine/cc163851.aspx ils font exactement ce que vous voulez, mais vous devez bien sûr utiliser CopyFileEx

Bonne chance

** EDIT ** (correction de ma réponse, mal écrite)

8
Polity

Voici une solution optimisée qui utilise des extensions .NET et un double tampon pour de meilleures performances. Une nouvelle surcharge de CopyTo est ajoutée à FileInfo avec une action qui indique la progression uniquement lorsqu'elle a changé.

Cet exemple d'implémentation dans WPF avec une barre de progression nommée progressBar1 qui effectue l'opération de copie en arrière-plan.

private FileInfo _source = new FileInfo(@"C:\file.bin");
private FileInfo _destination = new FileInfo(@"C:\file2.bin");

private void CopyFile()
{
  if(_destination.Exists)
    _destination.Delete();

  Task.Run(()=>{
    _source.CopyTo(_destination, x=>Dispatcher.Invoke(()=>progressBar1.Value = x));
  }).GetAwaiter().OnCompleted(() => MessageBox.Show("File Copied!"));
}

Voici un exemple pour une application console

class Program
{
  static void Main(string[] args)
  {
    var _source = new FileInfo(@"C:\Temp\bigfile.rar");
    var _destination = new FileInfo(@"C:\Temp\bigfile2.rar");

    if (_destination.Exists) _destination.Delete();

    _source.CopyTo(_destination, x => Console.WriteLine($"{x}% Complete"));
    Console.WriteLine("File Copied.");
  }
}

Pour l'utiliser, créez un nouveau fichier, tel que FileInfoExtensions.cs et ajoutez ce code:

public static class FileInfoExtensions
{
  public static void CopyTo(this FileInfo file, FileInfo destination, Action<int> progressCallback)
  {
    const int bufferSize = 1024 * 1024;  //1MB
    byte[] buffer = new byte[bufferSize], buffer2 = new byte[bufferSize];
    bool swap = false;
    int progress = 0, reportedProgress = 0, read = 0;
    long len = file.Length;
    float flen = len;
    Task writer = null;

    using (var source = file.OpenRead())
    using (var dest = destination.OpenWrite())
    {
      dest.SetLength(source.Length);
      for (long size = 0; size < len; size += read)
      {
        if ((progress = ((int)((size / flen) * 100))) != reportedProgress)
          progressCallback(reportedProgress = progress);
        read = source.Read(swap ? buffer : buffer2, 0, bufferSize);
        writer?.Wait();  // if < .NET4 // if (writer != null) writer.Wait(); 
        writer = dest.WriteAsync(swap ? buffer : buffer2, 0, read);
        swap = !swap;
      }
      writer?.Wait();  //Fixed - Thanks @sam-hocevar
    }
  }
}

Le double tampon fonctionne en utilisant un thread pour lire et un thread pour écrire, donc la vitesse maximale n'est dictée que par le plus lent des deux. Deux tampons sont utilisés (un double tampon), garantissant que les threads de lecture et d'écriture n'utilisent jamais le même tampon en même temps.

Exemple: le code lit dans le tampon 1, puis à la fin de la lecture, une opération d'écriture commence à écrire le contenu du tampon 1. Sans attendre la fin de l'écriture, le tampon est échangé vers le tampon 2 et les données sont lues dans le tampon 2 tandis que le tampon 1 est encore en cours d'écriture. Une fois la lecture terminée dans le tampon 2, elle attend que l'écriture se termine sur le tampon 1, commence à écrire le tampon 2 et le processus se répète. Essentiellement, 1 thread lit toujours, et un écrit toujours.

WriteAsync utilise E/S superposées , qui utilise ports d'achèvement d'E/S , qui dépendent du matériel pour effectuer des opérations asynchrones plutôt que des threads, ce qui en fait très efficace. TLDR: J'ai menti sur le fait qu'il y ait 2 threads, mais le concept est le même.

4
Robear

Vous pouvez copier des parties du flux de fichiers à partir de chaque fichier et mettre à jour après chaque "bloc" que vous mettez à jour. Ainsi, il sera plus continu - vous pouvez également calculer facilement la taille relative du "bloc" actuel que vous copiez par rapport à la taille totale du flux afin d'afficher le pourcentage correct effectué.

3
Gal

vous pouvez utiliser Dispatcher pour mettre à jour votre ProgressBar.

UpdateProgressBarDelegate updatePbDelegate = new UpdateProgressBarDelegate(ProgressBar1.SetValue);

Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { ProgressBar.ValueProperty, value });
0
Akrem