Puedo mostrar el progreso de copia de archivos utilizando FileInfo.El método CopyTo() en .NETA?

StackOverflow https://stackoverflow.com/questions/187768

  •  06-07-2019
  •  | 
  •  

Pregunta

He creado una utilidad de copia en c# (.NET 2.0 Framework) que las copias de archivos, directorios y recursiva sub directorios, etc.El programa tiene una interfaz gráfica de usuario que muestra el actual archivo de la copia, el número de archivo actual (secuencia), el número total de archivos que se copiarán y el porcentaje completado para las operaciones de copia.También hay una barra de progreso, que se basa en el archivo actual / total de archivos.

Mi problema está relacionado con la copia de archivos de gran tamaño.He sido incapaz de encontrar una manera de indicar el total de progreso de la copia de un archivo de gran tamaño (con mi actual estructura de la clase que utilitzes FileInfo.El método CopyTo).Como solución he separado la copia de archivos y la interfaz de usuario de pantalla para sus propios hilos y configurar una señal visual para mostrar que se está trabajando.Al menos el usuario es consciente de que el programa no está congelada y es todavía la copia de archivos.

Sería agradable ser capaz de mostrar el progreso basado en el número total de bytes o tienen algún tipo de evento que se desencadena a partir de la FileInfo.El método CopyTo que indica el número total de bytes copiados del archivo actual.

Soy consciente de la FileInfo.La longitud de la propiedad, así que estoy seguro de que hay una manera MacGuyver mi propio evento que se basa en esto y tiene un controlador de la interfaz de usuario del lado de las cosas de la lectura de las actualizaciones (tal vez basado en la comprobación de la FileInfo.La propiedad Length del objeto de destino el uso de algún tipo de temporizador?).

¿Alguien sabe de una forma de hacer esto que estoy con vistas.Si puedo evitarlo, prefiero no escribir mi clase para copiar los bytes a través de una secuencia y la pista de esa manera (aunque estoy pensando que podría ser pegado con ir a esa ruta).

Gracias De Antemano

PS - estoy atascado con el .NET framework 2.0 por ahora, por lo que cualquier solución que requiere de características disponibles en >= 3.0 sólo no son una opción para mí.

PPS - estoy abierto a soluciones .NET lenguaje de variedad, no sólo de c#.

¿Fue útil?

Solución

FileInfo.CopyTo es básicamente una envoltura alrededor de la llamada a la API Win32 " CopyFile " en el kernel32.dll. Este método no admite la devolución de llamada de progreso.

Sin embargo, el método CopyFileEx sí, y puede escribir su propio contenedor .NET en unos minutos, como se describe aquí: http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx

Otros consejos

También usé la implementación provista en la respuesta marcada . Sin embargo, luego creé un contenedor para proporcionar un & # 8482 más agradable; API para usar desde .NET.

Uso :

XCopy.Copy(networkFile.FullPath, temporaryFilename, true, true, (o, pce) => 
{
    worker.ReportProgress(pce.ProgressPercentage, networkFile);
});

Implementación

/// <summary>
/// PInvoke wrapper for CopyEx
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx
/// </summary>
public class XCopy
{
    public static void Copy(string source, string destination, bool overwrite, bool nobuffering)
    {
         new XCopy().CopyInternal(source, destination, overwrite, nobuffering, null);            
    }

    public static void Copy(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler)
    {            
         new XCopy().CopyInternal(source, destination, overwrite, nobuffering, handler);            
    }

    private event EventHandler Completed;
    private event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    private int IsCancelled;
    private int FilePercentCompleted;
    private string Source;
    private string Destination;        

    private XCopy()
    {
        IsCancelled = 0;
    }

    private void CopyInternal(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler)
    {
        try
        {
            CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE;
            if (!overwrite)
                copyFileFlags |= CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS;

            if (nobuffering)
                copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING;

            Source = source;
            Destination = destination;

            if (handler != null)
                ProgressChanged += handler;

            bool result = CopyFileEx(Source, Destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        catch (Exception)
        {
            if (handler != null)
                ProgressChanged -= handler;

            throw;
        }
    }

    private void OnProgressChanged(double percent)
    {
        // only raise an event when progress has changed
        if ((int)percent > FilePercentCompleted)
        {
            FilePercentCompleted = (int)percent;

            var handler = ProgressChanged;
            if (handler != null)
                handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null));
        }
    }

    private void OnCompleted()
    {
        var handler = Completed;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    #region PInvoke

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason,
                                                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

    private enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    private enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    private enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber,
                                                   CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED)
            OnProgressChanged((transferred / (double)total) * 100.0);

        if (transferred >= total)
            OnCompleted();

        return CopyProgressResult.PROGRESS_CONTINUE;
    }

    #endregion

}

Sé que estoy un poco tarde a la fiesta, pero he hecho un contenedor para CopyFileEx que devuelve un Task y acepta un CancellationToken y IProgress<double>.Lamentablemente no funciona en la .NET framework 2.0, pero para cualquier persona que utiliza 4.5, esto le permite usar el await la palabra clave.

public static class FileEx
{
    public static Task CopyAsync(string sourceFileName, string destFileName)
    {
        return CopyAsync(sourceFileName, destFileName, CancellationToken.None);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token)
    {
        return CopyAsync(sourceFileName, destFileName, token, null);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, IProgress<double> progress)
    {
        return CopyAsync(sourceFileName, destFileName, CancellationToken.None, progress);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token, IProgress<double> progress)
    {
        int pbCancel = 0;
        CopyProgressRoutine copyProgressHandler;
        if (progress != null)
        {
            copyProgressHandler = (total, transferred, streamSize, streamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData) =>
            {
                progress.Report((double)transferred / total * 100);
                return CopyProgressResult.PROGRESS_CONTINUE;
            };
        }
        else
        {
            copyProgressHandler = EmptyCopyProgressHandler;
        }
        token.ThrowIfCancellationRequested();
        var ctr = token.Register(() => pbCancel = 1);
        var copyTask = Task.Run(() =>
        {
            try
            {
                CopyFileEx(sourceFileName, destFileName, copyProgressHandler, IntPtr.Zero, ref pbCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
                token.ThrowIfCancellationRequested();
            }
            finally
            {
                ctr.Dispose();
            }
        }, token);
        return copyTask;
    }

    private static CopyProgressResult EmptyCopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        return CopyProgressResult.PROGRESS_CONTINUE;
    }

    #region DLL Import

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName,
       CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel,
       CopyFileFlags dwCopyFlags);

    delegate CopyProgressResult CopyProgressRoutine(
        long totalFileSize,
        long totalBytesTransferred,
        long streamSize,
        long streamBytesTransferred,
        uint dwStreamNumber,
        CopyProgressCallbackReason dwCallbackReason,
        IntPtr hSourceFile,
        IntPtr hDestinationFile,
        IntPtr lpData);

    enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    #endregion
}

¡Por el amor de Dios, no implemente su propia copia de archivo usando secuencias! La llamada Win32 CopyFile API que mencionó Gaspar puede aprovechar, p. DMA, mientras que apostaría dólares a donas que el código que Will escribió no sería & Quot; smart & Quot; suficiente para hacer eso.

CopyFileEx lo tratará bien, o podría implementar un BackgroundWorker que observe el tamaño creciente del archivo de destino y actualice una barra de progreso utilizando esa información. El último método le ahorra un PInvoke, pero el primero es probablemente un poco más limpio a largo plazo.

Para este tipo de cosas, he recurrido a Shell32 (¿o es ShellUI? Ya no lo sé). Esto le proporciona un diálogo nativo de Windows que los usuarios están acostumbrados a ver para las operaciones de copia. Supongo que reemplazaría su diálogo ya existente, por lo que puede no ser la respuesta correcta para usted, pero es útil recordarlo para aquellos & Quot; en un apuro & Quot; escenarios.

Microsoft.VisualBasic.FileIO.FileSystem.CopyFile(
    srcPath, 
    dstPath, 
    Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,    
    Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException
);

Sí, debe hacer referencia al ensamblado Microsoft.VisualBasic. He crecido hasta love este ensamblaje.

Gracias a @Gasper y @Dennis por señalar el método CopyFileEx. He extendido la respuesta de Dennis con abortar copia

    /// <summary>
    /// Type indicates how the copy gets completed.
    /// </summary>
    internal enum CopyCompletedType
    {
        Succeeded,
        Aborted,
        Exception
    }

/// <summary>
/// Event arguments for file copy 
/// </summary>
internal class FileCopyEventArgs : EventArgs
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="type">type of the copy completed type enum</param>
    /// <param name="exception">exception if any</param>
    public FileCopyEventArgs(CopyCompletedType type, Exception exception)
    {
        Type = type;
        Exception = exception;
    }

    /// <summary>
    /// Type of the copy completed type
    /// </summary>
    public CopyCompletedType Type
    {
        get;
        private set;

    }

    /// <summary>
    /// Exception if any happend during copy.
    /// </summary>
    public Exception Exception
    {
        get;
        private set;
    }

}

/// <summary>
/// PInvoke wrapper for CopyEx
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx
/// </summary>
internal class XCopy
{

    private int IsCancelled;
    private int FilePercentCompleted;

    public XCopy()
    {
        IsCancelled = 0;
    }

    /// <summary>
    /// Copies the file asynchronously
    /// </summary>
    /// <param name="source">the source path</param>
    /// <param name="destination">the destination path</param>
    /// <param name="nobuffering">Bufferig status</param>
    /// <param name="handler">Event handler to do file copy.</param>
    public void CopyAsync(string source, string destination, bool nobuffering)
    {
        try
        {
            //since we needed an async copy ..
            Action action = new Action(
                () => CopyInternal(source, destination, nobuffering)
                    );
            Task task = new Task(action);
            task.Start();
        }
        catch (AggregateException ex)
        {
            //handle the inner exception since exception thrown from task are wrapped in
            //aggreate exception.
            OnCompleted(CopyCompletedType.Exception, ex.InnerException);
        }
        catch (Exception ex)
        {
            OnCompleted(CopyCompletedType.Exception, ex);
        }
    }

    /// <summary>
    /// Event which will notify the subscribers if the copy gets completed
    /// There are three scenarios in which completed event will be thrown when
    /// 1.Copy succeeded
    /// 2.Copy aborted.
    /// 3.Any exception occured.
    /// These information can be obtained from the Event args.
    /// </summary>
    public event EventHandler<FileCopyEventArgs> Completed;
    /// <summary>
    /// Event which will notify the subscribers if there is any progress change while copying.
    /// This will indicate the progress percentage in its event args.
    /// </summary>
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    /// <summary>
    /// Aborts the copy asynchronously and throws Completed event when done.
    /// User may not want to wait for completed event in case of Abort since 
    /// the event will tell that copy has been aborted.
    /// </summary>
    public void AbortCopyAsync()
    {
        Trace.WriteLine("Aborting the copy");
        //setting this will cancel an operation since we pass the
        //reference to copyfileex and it will periodically check for this.
        //otherwise also We can check for iscancelled on onprogresschanged and return 
        //Progress_cancelled .
        IsCancelled = 1;

        Action completedEvent = new Action(() =>
            {
                //wait for some time because we ll not know when IsCancelled is set , at what time windows stops copying.
                //so after sometime this may become valid .
                Thread.Sleep(500);
                //do we need to wait for some time and send completed event.
                OnCompleted(CopyCompletedType.Aborted);
                //reset the value , otherwise if we try to copy again since value is 1 , 
                //it thinks that its aborted and wont allow to copy.
                IsCancelled = 0;
            });

        Task completedTask = new Task(completedEvent);
        completedTask.Start();
    }


    /// <summary>
    /// Copies the file using asynchronos task
    /// </summary>
    /// <param name="source">the source path</param>
    /// <param name="destination">the destination path</param>
    /// <param name="nobuffering">Buffering status</param>
    /// <param name="handler">Delegate to handle Progress changed</param>
    private void CopyInternal(string source, string destination, bool nobuffering)
    {
        CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE;

        if (nobuffering)
        {
            copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING;
        }

        try
        {
            Trace.WriteLine("File copy started with Source: " + source + " and destination: " + destination);
            //call win32 api.
            bool result = CopyFileEx(source, destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
            {
                //when ever we get the result as false it means some error occured so get the last win 32 error.
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        catch (Exception ex)
        {
            //the mesage will contain the requested operation was aborted when the file copy
            //was cancelled. so we explicitly check for that and do a graceful exit
            if (ex.Message.Contains("aborted"))
            {
                Trace.WriteLine("Copy aborted.");
            }
            else
            {
                OnCompleted(CopyCompletedType.Exception, ex.InnerException);
            }
        }
    }

    private void OnProgressChanged(double percent)
    {
        // only raise an event when progress has changed
        if ((int)percent > FilePercentCompleted)
        {
            FilePercentCompleted = (int)percent;

            var handler = ProgressChanged;
            if (handler != null)
            {
                handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null));
            }
        }
    }

    private void OnCompleted(CopyCompletedType type, Exception exception = null)
    {
        var handler = Completed;
        if (handler != null)
        {
            handler(this, new FileCopyEventArgs(type, exception));
        }
    }

    #region PInvoke

    /// <summary>
    /// Delegate which will be called by Win32 API for progress change
    /// </summary>
    /// <param name="total">the total size</param>
    /// <param name="transferred">the transferrred size</param>
    /// <param name="streamSize">size of the stream</param>
    /// <param name="streamByteTrans"></param>
    /// <param name="dwStreamNumber">stream number</param>
    /// <param name="reason">reason for callback</param>
    /// <param name="hSourceFile">the source file handle</param>
    /// <param name="hDestinationFile">the destination file handle</param>
    /// <param name="lpData">data passed by users</param>
    /// <returns>indicating whether to continue or do somthing else.</returns>
    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber,
                                                   CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        //when a chunk is finished call the progress changed.
        if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED)
        {
            OnProgressChanged((transferred / (double)total) * 100.0);
        }

        //transfer completed
        if (transferred >= total)
        {
            if (CloseHandle(hDestinationFile))
            {
                OnCompleted(CopyCompletedType.Succeeded, null);
            }
            else
            {
                OnCompleted(CopyCompletedType.Exception,
                    new System.IO.IOException("Unable to close the file handle"));
            }
        }

        return CopyProgressResult.PROGRESS_CONTINUE;
    }
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason,
                                                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

    private enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    private enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    private enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    #endregion

}

Los clientes pueden crear un objeto de clase XCopy y llamar al método copiar / abortar.

Si alguien aún se encuentra con este problema (¡10 años después!) como lo hice yo, he creado un contenedor alrededor de las funciones CopyFileEx y MoveFileWithProgress (como algunas de las respuestas aquí) con algunas funcionalidades adicionales útiles (como async, acceso verificación de derechos, formateo de bytes, copia de directorios ...)

Míralo aquí - GitHub y Nuget

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top