Czy mogę pokazać postęp kopiowania plików za pomocą FileInfo.CopyTo () w .NET?


Stworzyłem narzędzie kopiowania w c # (.NET 2.0 Framework), które kopiuje pliki, katalogi i podkatalogi rekurencyjne itp. Program posiada GUI, który pokazuje aktualnie kopiowany plik, aktualny numer pliku (sekwencję), łączną liczbę plików do skopiowania oraz procent wykonanych operacji kopiowania. Istnieje również pasek postępu, który jest oparty na bieżącym pliku/łącznej liczbie plików.
Mój problem polega na kopiowaniu dużych plików. Nie udało mi się znaleźć sposobu, aby wskazać ogólny postęp kopiowania dużego pliku (przy użyciu mojej obecnej struktury klasy przy użyciu metody FileInfo.CopyTo). Aby obejść ten problem, podzieliłem operacje kopiowania plików i wyświetlania GUI na własne wątki i ustawiłem wizualną wskazówkę, aby pokazać, że praca jest w toku. Przynajmniej użytkownik wie, że program nie jest zawieszony i nadal kopiuje pliki.
Lepiej byłoby móc pokazać postęp na podstawie całkowitej liczby bajtów lub mieć jakiś rodzaj zdarzenia, które jest uruchamiane z metody FileInfo.CopyTo, wskazując całkowitą liczbę bajtów skopiowanych z bieżącego pliku.
Wiem o właściwości FileInfo.Length, więc jestem prawie pewien, że istnieje sposób MacGuyver na moje własne zdarzenie, które jest na niej oparte, i istnieje program obsługi GUI dla rzeczy odczytujących aktualizacje (prawdopodobnie oparty na sprawdzeniu FileInfo.Length właściwość celu z jakim zegarem?).
Czy ktoś zna sposób na zrobienie tego, którego ja przeoczam? Jeśli mogę tego uniknąć, wolałbym raczej nie przepisywać mojej klasy, aby kopiować bajty w strumieniu i śledzić to w ten sposób (chociaż myślę, że utknę w tej trasie).
z góry dziękuję
PS - W tej chwili utknąłem na frameworku .NET 2.0, więc żadne rozwiązanie wymagające funkcji dostępnych tylko w & > = 3.0 nie działa dla mnie.
PPS jest otwarty na rozwiązania w dowolnym języku .NET, nie tylko w języku C #.
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

FileInfo.CopyTo to w zasadzie opakowanie wokół interfejsu API Win32 wywołujące „CopyFile” w kernel32.dll. Ta metoda nie obsługuje wywołań zwrotnych postępu.
Jednak metoda CopyFileEx robi to i możesz napisać wokół niego własne opakowanie .NET w ciągu kilku minut, jak opisano tutaj:

http://www.pinvoke.net/ default.aspx- i kernel32.CopyFileEx
http://www.pinvoke.net/default ... ileEx
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Skorzystałem również z implementacji dostarczonej w

zaznaczona odpowiedź
https://stackoverflow.com/a/187842/73025... Jednak później stworzyłem opakowanie, aby zapewnić lepsze ™ API do korzystania z .NET.

Za pomocą

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


Realizacja

/// <summary>
/// PInvoke wrapper for CopyEx
/// [url=http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx]http://msdn.microsoft.com/en-u ... .aspx[/url]
/// </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}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Wiem, że trochę się spóźniłem na imprezę, ale utworzyłem opakowanie dla
CopyFileEx
, które zwraca
Task
i akceptuje
CancellationToken
i
IProgress & < double & >
. Niestety nie zadziała we frameworku .NET 2.0, ale dla tych, którzy używają 4.5 pozwala na użycie słowa kluczowego
await
.
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
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Na miłość boską, nie twórz własnej kopii pliku za pomocą strumieni! Wywołanie Win32 CopyFile API, o którym wspomniał Gaspard, jest w stanie na przykład wykorzystać DMA, podczas gdy ja postawiłbym dolary na pączki, że kod Willa nie będzie wystarczająco inteligentny, aby to zrobić.
CopyFileEx potraktuje Cię dobrze lub możesz zaimplementować BackgroundWorker, który monitoruje rosnący rozmiar pliku docelowego i aktualizuje pasek postępu o te informacje. Ta ostatnia metoda oszczędza PInvoke, ale ta pierwsza jest prawdopodobnie trochę czystsza na dłuższą metę.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

W takich przypadkach wróciłem do Shell32 (czy może to ShellUI? Już nie wiem). Zapewnia to natywne okno dialogowe systemu Windows, które użytkownicy są przyzwyczajeni do wykonywania operacji kopiowania. Domyślam się, że to zastąpi istniejące okno dialogowe, więc może to nie być właściwa odpowiedź dla Ciebie, ale warto pamiętać o tych scenariuszach „w razie potrzeby”.
Microsoft.VisualBasic.FileIO.FileSystem.CopyFile( srcPath, 
dstPath,
Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException);

Tak, musisz połączyć się z zestawem Microsoft.VisualBasic. ja

zakochać się
http://www.deploymentzone.com/ ... asic/
to jest montaż.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Podziękowania dla @Gasper i @Dennis za wskazanie metody CopyFileEx.
Rozszerzyłem odpowiedź Dennisa o kopię przerwania
/// <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
/// [url=http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx]http://msdn.microsoft.com/en-u ... .aspx[/url]
/// </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}

Klienci mogą utworzyć obiekt klasy XCopy i wywołać metodę copy/abort.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli ktoś nadal napotyka ten problem (10 lat później!) Podobnie jak ja, owinęłam funkcje CopyFileEx i MoveFileWithProgress (jak niektóre z odpowiedzi tutaj) dodatkowymi, przydatnymi funkcjami (takimi jak asynchronizacja, sprawdzanie dostępu, formatowanie bajtów, kopiowanie) katalogi ...)
Sprawdź to tutaj -

GitHub
https://github.com/martinchrza ... nager
i

Nuget
https://www.nuget.org/packages ... ager/

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się