using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Developpez.Dotnet.Collections; using Developpez.Dotnet.System.Properties; namespace Developpez.Dotnet.System { /// /// Classe de base abstraite pour des opérations de copie et de déplacement /// de fichier qui peuvent signaler leur progression. /// public abstract class FileOperationBase { /// /// Initialise une nouvelle instance de FileOperationBase /// /// Fichier source /// Fichier destination protected FileOperationBase(string source, string destination) { Source = source; Destination = destination; } /// /// Exécute l'opération correspondant à l'objet courant /// public void Execute() { if (_executing) throw new InvalidOperationException(ExceptionMessages.OperationAlreadyInProgress); Execute(true, SynchronizationContext.Current); } /// /// Quand cette méthode est redéfinie dans une classe dérivée, /// exécute l'opération correspondant à l'objet courant /// protected abstract void ExecuteCore(); /// /// Exécute l'opération de façon asynchrone. Cette méthode retourne /// immédiatement. L'évènement Completed est déclenché à la fin de /// l'opération. /// public void ExecuteAsync() { if (_executing) throw new InvalidOperationException(ExceptionMessages.OperationAlreadyInProgress); var synchronizationContext = SynchronizationContext.Current; var thread = new Thread(() => Execute(false, synchronizationContext)); thread.Start(); } /// /// Quand cette propriété est redéfinie dans une classe dérivée, /// renvoie le type d'opération correspondant à l'objet courant /// public abstract FileOperationType OperationType { get; } /// /// Renvoie ou définit une valeur indiquant si le fichier cible /// doit être remplacé s'il existe déjà. Si cette propriété est false /// et que le fichier cible existe, une exception est levée. /// public bool ReplaceExisting { get; set; } /// /// Renvoie le nom du fichier source /// public string Source { get; private set; } /// /// Renvoie le nom du fichier de destination /// public string Destination { get; private set; } /// /// Se produit quand l'opération signale sa progression /// public event FileOperationProgressEventHandler ProgressChanged; /// /// Se produit quand l'opération est terminée. /// public event FileOperationCompletedEventHandler Completed; /// /// Déclenche l'évènement ProgressChanged /// /// Un FileOperationProgressEventArgs qui contient les données de l'évènement protected virtual void OnProgressChanged(FileOperationProgressEventArgs e) { var handler = ProgressChanged; if (handler != null) handler(this, e); } /// /// Déclenche l'évènement Completed /// /// Un FileOperationCompletedEventArgs qui contient les données de l'évènement protected virtual void OnCompleted(FileOperationCompletedEventArgs e) { var handler = Completed; if (handler != null) handler(this, e); } internal void CheckError(bool result) { if (!result) { int error = Marshal.GetLastWin32Error(); if (error != ErrorRequestAborted) { int hr = Marshal.GetHRForLastWin32Error(); throw Marshal.GetExceptionForHR(hr); } } } private SynchronizationContext _synchronizationContext; private bool _executing; private void Execute(bool rethrow, SynchronizationContext synchronizationContext) { try { _synchronizationContext = synchronizationContext; _executing = true; _progressBuffer.Clear(); _stopwatch.Start(); ExecuteCore(); _stopwatch.Stop(); ExecuteOnSynchronizationContext(() => OnCompleted(new FileOperationCompletedEventArgs(_stopwatch.Elapsed, null))); } catch (Exception ex) { _stopwatch.Stop(); ExecuteOnSynchronizationContext(() => OnCompleted(new FileOperationCompletedEventArgs(_stopwatch.Elapsed, ex))); if (rethrow) throw; } finally { _synchronizationContext = null; _executing = false; } } private void ExecuteOnSynchronizationContext(Action action) { if (SynchronizationContext.Current == _synchronizationContext || _synchronizationContext == null) { action(); } else { _synchronizationContext.Send(_ => action(), null); } } private class ProgressState { public ProgressState(TimeSpan elapsed, long transferred) { _elapsed = elapsed; _transferred = transferred; } private readonly TimeSpan _elapsed; public TimeSpan Elapsed { get { return _elapsed; } } private readonly long _transferred; public long Transferred { get { return _transferred; } } } private readonly Stopwatch _stopwatch = new Stopwatch(); private readonly LinkedList _progressBuffer = new LinkedList(); internal int ProgressCallback( long totalFileSize, long totalBytesTransferred, long streamSize, long streamBytesTransferred, int dwStreamNumber, int dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData) { TimeSpan elapsed = _stopwatch.Elapsed; _progressBuffer.AddLast(new ProgressState(elapsed, totalBytesTransferred)); if (_progressBuffer.Count > 20) _progressBuffer.RemoveFirst(); var deltas = _progressBuffer.Zip(_progressBuffer.Prepend(new ProgressState(TimeSpan.Zero, 0)), (current, previous) => new { Elapsed = current.Elapsed - previous.Elapsed, Transferred = current.Transferred - previous.Transferred }); long speed = (long)deltas.Average(x => x.Transferred / x.Elapsed.TotalSeconds); var args = new FileOperationProgressEventArgs( OperationType, Source, Destination, totalBytesTransferred, totalFileSize, elapsed, speed); ExecuteOnSynchronizationContext(() => OnProgressChanged(args)); return (int) args.Action; } #region Interop declarations internal delegate int CopyProgressRoutine( long totalFileSize, long totalBytesTransferred, long streamSize, long streamBytesTransferred, int dwStreamNumber, int dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData); internal const int ErrorRequestAborted = 1235; #endregion } }