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
}
}