Asynchrones Kopieren von Dateien und Ordnern

    • VB.NET

    Es gibt 1 Antwort in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

      Asynchrones Kopieren von Dateien und Ordnern

      Hi
      habe soeben eine Klasse geschrieben, die das asynchrone Kopieren von Dateien und Ordnern ermöglicht:

      VB.NET-Quellcode

      1. ''' <summary>
      2. ''' Ermöglicht das asynchrone Kopieren mit Fortschrittsüberwachung von Dateien, Verzeichnissen und Datenströmen.
      3. ''' </summary>
      4. Public Class FileCopy
      5. ''' <summary>
      6. ''' Stellt eine Instanz bereit, die für Standardkopiervorgänge von Dateien verwendet werden kann.
      7. ''' </summary>
      8. Public Shared ReadOnly General As FileCopy = New FileCopy()
      9. Private ReadOnly _bufferSize As Integer
      10. ''' <summary>
      11. ''' Initialisiert eine neue Instanz der FileCopy-Klasse unter Verwendung der Standardpuffergröße von 4096.
      12. ''' </summary>
      13. Protected Sub New()
      14. Me.New(4096)
      15. End Sub
      16. ''' <summary>
      17. ''' Initialisiert eine neue Instanz der FileCopy-Klasse unter Verwendung der angegebenen Puffergröße.
      18. ''' </summary>
      19. ''' <param name="bufferSize">Die Größe der Puffer, die zum Kopieren verwendet werden soll.</param>
      20. Public Sub New(ByVal bufferSize As Integer)
      21. _bufferSize = bufferSize
      22. End Sub
      23. ''' <summary>
      24. ''' Kopiert die angegebene Datei an die Zieldatei.
      25. ''' </summary>
      26. ''' <param name="sourceFile">Der Pfad zur Quelldatei.</param>
      27. ''' <param name="destinationFile">Der Pfad mit Dateinamen zur Zieldatei.</param>
      28. ''' <returns>Ein Objekt, das zum Überwachen des Fortschritts verwendet werden kann.</returns>
      29. Public Function CopyFile(ByVal sourceFile As String, ByVal destinationFile As String) As CopyProgress
      30. Dim sf As IO.Stream = IO.File.OpenRead(sourceFile), df = IO.File.OpenWrite(destinationFile)
      31. Dim progress As CopyProgress = CreateProgress(sf.Length, 1)
      32. AddHandler progress.Finished, Sub(sender, e)
      33. sf.Close()
      34. df.Close()
      35. End Sub
      36. CopyAsync(progress, New KeyValuePair(Of IO.Stream, IO.Stream)(sf, df))
      37. Return progress
      38. End Function
      39. ''' <summary>
      40. ''' Kopiert das angegebene Verzeichnis in das Zielverzeichnis.
      41. ''' </summary>
      42. ''' <param name="sourceDirectory">Das Quellverzeichnis.</param>
      43. ''' <param name="destinationDirectory">Das Zielverzeichnis.</param>
      44. ''' <param name="searchOption">Gibt an, ob nur das angegebene Verzeichnis oder alle Unterverzeichnisse kopiert werden sollen.</param>
      45. ''' <returns>Ein Objekt, das zum Überwachen des Fortschritts verwendet werden kann.</returns>
      46. Public Function CopyDirectory(ByVal sourceDirectory As String, ByVal destinationDirectory As String, ByVal searchPattern As String, ByVal searchOption As IO.SearchOption) As CopyProgress
      47. Dim files() As String = System.IO.Directory.GetFiles(sourceDirectory, searchPattern, searchOption)
      48. Dim pl(files.Length - 1) As KeyValuePair(Of System.IO.Stream, System.IO.Stream)
      49. Dim len As Long = 0
      50. Dim progress As CopyProgress
      51. For i As Integer = 0 To files.Length - 1
      52. Dim target As String = IO.Path.Combine(destinationDirectory, files(i).Substring(sourceDirectory.Length + 1))
      53. IO.Directory.CreateDirectory(IO.Path.GetDirectoryName(target))
      54. Dim sf As IO.Stream = IO.File.OpenRead(files(i)), df = System.IO.File.OpenWrite(target)
      55. len += sf.Length
      56. pl(i) = New KeyValuePair(Of System.IO.Stream, System.IO.Stream)(sf, df)
      57. Next
      58. progress = CreateProgress(len, files.Length)
      59. AddHandler progress.Finished, Sub()
      60. For Each cp As KeyValuePair(Of System.IO.Stream, System.IO.Stream) In pl
      61. cp.Key.Close()
      62. cp.Value.Close()
      63. Next
      64. End Sub
      65. CopyAsync(pl, progress)
      66. Return progress
      67. End Function
      68. ''' <summary>
      69. ''' Kopiert die Daten des <see cref="System.IO.Stream"/> an das Ziel.
      70. ''' </summary>
      71. ''' <param name="source">Die Datenquelle.</param>
      72. ''' <param name="destination">Das Datenziel.</param>
      73. ''' <returns>Ein Objekt, das zum Überwachen des Fortschritts verwendet werden kann.</returns>
      74. Public Function CopyStream(ByVal source As IO.Stream, ByVal destination As IO.Stream) As CopyProgress
      75. Dim cp As CopyProgress = CreateProgress(-1, source.Length - source.Position, 0)
      76. CopyAsync(cp, New KeyValuePair(Of IO.Stream, IO.Stream)(source, destination))
      77. Return cp
      78. End Function
      79. ''' <summary>
      80. ''' Erzeugt eine neue Fortschritts-Überwachung.
      81. ''' </summary>
      82. ''' <param name="amount">Die Gesamtzahl der gelesenen Bytes.</param>
      83. ''' <param name="fileCount">Die Anzahl der gelesenen Dateien.</param>
      84. ''' <returns>Das Überwachungsobjekt.</returns>
      85. Protected Function CreateProgress(ByVal amount As Long, ByVal fileCount As Integer) As CopyProgress
      86. Return CreateProgress(amount, amount, fileCount)
      87. End Function
      88. ''' <summary>
      89. ''' Erzeugt eine neue Fortschritts-Überwachung.
      90. ''' </summary>
      91. ''' <param name="amount">Die Gesamtzahl der gelesenen Bytes.</param>
      92. ''' <param name="estimatedAmount">Die geschätzte Anzahl an Bytes, die gelesen wird.
      93. ''' Dieser Wert wird verwendet, wenn die Quelle die ermittelten Werte verändern kann.</param>
      94. ''' <param name="fileCount">Die Anzahl der gelesenen Dateien.</param>
      95. ''' <returns>Das Überwachungsobjekt.</returns>
      96. Protected Overridable Function CreateProgress(ByVal amount As Long, ByVal estimatedAmount As Long, ByVal fileCount As Integer) As CopyProgress
      97. Return New CopyProgress(amount, estimatedAmount)
      98. End Function
      99. Private Shared Sub CopyAsync(ByVal progress As CopyProgress, ByVal ParamArray streams() As KeyValuePair(Of IO.Stream, IO.Stream))
      100. CopyAsync(streams, progress)
      101. End Sub
      102. Private Shared Sub CopyAsync(ByVal streams As IEnumerable(Of KeyValuePair(Of IO.Stream, IO.Stream)), ByVal progress As CopyProgress)
      103. System.Threading.ThreadPool.QueueUserWorkItem(Sub(o)
      104. Copy(streams, progress, CInt(Math.Min(progress.Amount, 4096)))
      105. End Sub)
      106. End Sub
      107. Private Shared Sub Copy(ByVal streams As IEnumerable(Of KeyValuePair(Of IO.Stream, IO.Stream)), ByVal progress As CopyProgress, ByVal bufferSize As Integer)
      108. Dim buffer(bufferSize - 1) As Byte
      109. Dim len As Integer
      110. Dim canceled As Boolean
      111. For Each strm As KeyValuePair(Of IO.Stream, IO.Stream) In streams
      112. Do
      113. If progress.Canceled Then
      114. canceled = True
      115. Exit For
      116. End If
      117. len = strm.Key.Read(buffer, 0, buffer.Length)
      118. If progress.Canceled Then
      119. canceled = True
      120. Exit For
      121. End If
      122. strm.Value.Write(buffer, 0, len)
      123. If progress.Canceled Then
      124. canceled = True
      125. Exit For
      126. End If
      127. progress.AdvanceProgress(len)
      128. Loop While len <> 0
      129. progress.StreamCompleted()
      130. Next
      131. progress.SetFinished(canceled)
      132. End Sub
      133. Private Shared Sub CreateDirectories(ByVal src As String, ByVal dest As String)
      134. CreateDirectories(New IO.DirectoryInfo(src), System.IO.Directory.CreateDirectory(dest))
      135. End Sub
      136. Private Shared Sub CreateDirectories(ByVal src As IO.DirectoryInfo, ByVal dest As IO.DirectoryInfo)
      137. For Each di As System.IO.DirectoryInfo In src.GetDirectories()
      138. CreateDirectories(di, dest.CreateSubdirectory(di.Name))
      139. Next
      140. End Sub
      141. End Class
      142. ''' <summary>
      143. ''' Stellt eine Möglichkeit zur Überwachung von Kopiervorgängen bereit.
      144. ''' </summary>
      145. Public Class CopyProgress
      146. ''' <summary>
      147. ''' Tritt ein, sobald ein weiterer Datenblock kopiert wurde.
      148. ''' </summary>
      149. ''' <remarks></remarks>
      150. Public Event ProgressChanged As EventHandler
      151. ''' <summary>
      152. ''' Tritt ein, sobald der Kopiervorgang abgeschlossen wird.
      153. ''' </summary>
      154. ''' <remarks></remarks>
      155. Public Event Finished As EventHandler
      156. ''' <summary>
      157. ''' Tritt ein, sobald sich der Fortschritt zum ersten mal ändert.
      158. ''' </summary>
      159. ''' <remarks></remarks>
      160. Public Event Started As EventHandler
      161. Private _amount, _estimatedAmount As Long
      162. Private _progress As Long
      163. Private _canceled, _started As Boolean
      164. Private _cancelSyncObj As System.Threading.ManualResetEventSlim
      165. ''' <summary>
      166. ''' Bricht den Kopiervorgang ab.
      167. ''' </summary>
      168. ''' <remarks></remarks>
      169. Public Sub Cancel()
      170. If Not _canceled Then
      171. _cancelSyncObj = New System.Threading.ManualResetEventSlim()
      172. _canceled = True
      173. End If
      174. _cancelSyncObj.Wait()
      175. End Sub
      176. Friend ReadOnly Property CancelSyncObj As System.Threading.ManualResetEventSlim
      177. Get
      178. Return _cancelSyncObj
      179. End Get
      180. End Property
      181. ''' <summary>
      182. ''' Gibt an, ob der Vorgang abgebrochen wurde.
      183. ''' </summary>
      184. Public ReadOnly Property Canceled As Boolean
      185. Get
      186. Return _canceled
      187. End Get
      188. End Property
      189. ''' <summary>
      190. ''' Gibt die Gesamtzahl der geschätzten Bytes an, die gelesen wird oder -1, sofern die Gesamtzahl unbekannt ist.
      191. ''' </summary>
      192. Public ReadOnly Property EstimatedAmount As Long
      193. Get
      194. Return _amount
      195. End Get
      196. End Property
      197. ''' <summary>
      198. ''' Gibt die Gesamtzahl der Bytes an, die gelesen wird oder -1, sofern die Gesamtzahl unbekannt ist.
      199. ''' </summary>
      200. Public ReadOnly Property Amount As Long
      201. Get
      202. Return _amount
      203. End Get
      204. End Property
      205. ''' <summary>
      206. ''' Gibt die aktuelle Anzahl der gelesenen Bytes an.
      207. ''' </summary>
      208. Public ReadOnly Property Progress As Long
      209. Get
      210. Return _progress
      211. End Get
      212. End Property
      213. ''' <summary>
      214. ''' Gibt den Fortschritt in Prozent an. Sofern die Anzahl geschätzt wird, wird die geschätzte Zahl als Referenzwert verwendet.
      215. ''' </summary>
      216. Public Function GetPercentage() As Double
      217. Return If(Amount = 0, 1.0, Progress / GetAmount())
      218. End Function
      219. ''' <summary>
      220. ''' Gibt den Fortschritt relativ zur angegebenen Zahl an. Sofern die Anzahl geschätzt wird, wird die geschätzte Zahl als Referenzwert verwendet.
      221. ''' </summary>
      222. Public Function GetPerValue(ByVal value As Integer) As Integer
      223. Return If(Amount = 0, value, CInt(Progress * value \ GetAmount()))
      224. End Function
      225. Private Function GetAmount() As Long
      226. Return If(Amount = -1, EstimatedAmount, Amount)
      227. End Function
      228. Friend Sub AdvanceProgress(ByVal delta As Long)
      229. SyncLock Me
      230. Dim progress As Long = progress + delta
      231. If progress < 0 OrElse progress > Amount Then
      232. Throw New ArgumentOutOfRangeException("progress")
      233. End If
      234. If Not _started Then
      235. _started = True
      236. RaiseEvent Started(Me, EventArgs.Empty)
      237. End If
      238. _progress = progress
      239. End SyncLock
      240. OnProgressChanged(EventArgs.Empty)
      241. End Sub
      242. ''' <summary>
      243. ''' Erhöht den Fortschritt manuell um den angegebenen Wert.
      244. ''' </summary>
      245. ''' <param name="delta">Die Zahl der Bytes, um die der Fortschritt erhöht werden soll.</param>
      246. Protected Sub Advance(ByVal delta As Long)
      247. AdvanceProgress(delta)
      248. End Sub
      249. Friend Sub SetFinished(ByVal canceled As Boolean)
      250. _amount = Progress
      251. RaiseEvent Finished(Me, EventArgs.Empty)
      252. If canceled Then
      253. CancelSyncObj.Set()
      254. OnCanceled()
      255. End If
      256. End Sub
      257. Friend Sub StreamCompleted()
      258. OnStreamCompleted()
      259. End Sub
      260. ''' <summary>
      261. ''' Setzt den Vorgang manuell auf abgeschlossen.
      262. ''' </summary>
      263. Protected Sub SetFinished()
      264. SetFinished(False)
      265. End Sub
      266. ''' <summary>
      267. ''' Initialisiert eine neue Instanz der CopyProgress-Klasse unter Angabe der Gesamtzahl der zu lesenden Bytes.
      268. ''' </summary>
      269. ''' <param name="amount">Die Anzahl der zu lesenden Bytes.</param>
      270. Public Sub New(ByVal amount As Long)
      271. Me.New(amount, amount)
      272. End Sub
      273. ''' <summary>
      274. ''' Initialisiert eine neue Instanz der CopyProgress-Klasse unter Angabe der Gesamtzahl der zu lesenden Bytes.
      275. ''' </summary>
      276. ''' <param name="amount">Die Anzahl der zu lesenden Bytes.</param>
      277. ''' <param name="estimatedAmount">Die geschätzte Anzahl der zu lesenden Bytes.
      278. ''' Dieser Wert wird verwendet, sofern die Gesamtzahl der zu lesenden Bytes unbekannt und somit -1 ist.</param>
      279. Public Sub New(ByVal amount As Long, ByVal estimatedAmount As Long)
      280. _amount = amount
      281. _estimatedAmount = estimatedAmount
      282. End Sub
      283. ''' <summary>
      284. ''' Wird aufgerufen, um den Fortschritt an Ereignisabonnenten weiterzugeben.
      285. ''' </summary>
      286. ''' <param name="e">Enthält nähere Informationen zum Fortschritt.</param>
      287. Protected Overridable Sub OnProgressChanged(ByVal e As EventArgs)
      288. RaiseEvent ProgressChanged(Me, e)
      289. End Sub
      290. ''' <summary>
      291. ''' Wird aufgerufen, sobald ein abgebrochener Vorgang abgeschlossen wurde.
      292. ''' </summary>
      293. Protected Overridable Sub OnCanceled()
      294. End Sub
      295. ''' <summary>
      296. ''' Wird aufgerufen, sobald ein einzelner Stream vollständig kopiert wurde.
      297. ''' </summary>
      298. Protected Overridable Sub OnStreamCompleted()
      299. End Sub
      300. End Class

      Um weitere Informationen, wie z.B. das Startdatum des Kopiervorgangs weiterzugeben, kann man eine Klasse von CopyProgress erben lassen und diese Information manuell setzen. Dazu sollte man dann eben FileCopy.CreateProgress überschreiben. Für allgemeine Kopiervorgänge, für die das nicht benötigt wird, genügt eine FileCopy-Instanz; wenn man die Standardpuffergröße von 4096 Bytes verwenden will, sollte man einfach auf die Instanz FileCopy.General zugreifen, sonst kann man eine neue Instanz erzeugen mit der Puffergröße erzeugen.
      Für frühere Frameworks kann statt des System.Threading.ManualResetEventSlims in CopyProgress auch einfach die System.Threading.ManualResetEvent-Klasse verwendet werden.
      Es handelt sich leider nur um einen Auszug aus der Datei, da sonst die max. Anzahl an Zeichen überschritten wird. Die vollständige Datei befindet sich im Anhang. Ein Beispiel folgt im nächsten Posting.

      Gruß
      ~blaze~
      Dateien
      • FileCopy.vb

        (15,33 kB, 280 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

      Im folgendem Beispiel wird das Kopieren von einem Ordner aus einer Form heraus gezeigt:
      Benötigt wird eine ProgressBar _progressBar, die den Fortschritt darstellt und ein Button _startCopyButton, der den Kopiervorgang ausführt.

      VB.NET-Quellcode

      1. Private _progress As CopyProgress
      2. Private Sub _startCopyButton(ByVal sender As Object, ByVal e As EventArgs) Handles _startCopyButton.Click
      3. 'Fortschrittsueberwachung in _progress behalten, einen 32 KiB Puffer verwenden
      4. _progress = New FileCopy(32768).CopyDirectory("quellverzeichnis", "zielverzeichnis", IO.SearchOption.AllDirectories)
      5. AddHandler _progress.ProgressChanged, Sub(s, ev)
      6. 'Fortschritt an den Thread fuer ProgressBar _progressBar weiterleiten
      7. BeginInvoke(Sub()
      8. If Not _progress.Canceled Then
      9. 'Fortschritt in der ProgressBar anzeigen
      10. _progressBar.Value = _progressBar.Minimum + _progress.GetPerValue(_progressBar.Maximum - _progressBar.Minimum)
      11. End If
      12. End Sub)
      13. End Sub
      14. End Sub
      15. Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
      16. _progress.Cancel() 'beim Schliessen der Form abbrechen. Der Vorgang wird nur unterbrochen, das Rueckgaengigmachen muss der Benutzer selbst uebernehmen!
      17. MyBase.OnFormClosing(e)
      18. End Sub


      Gruß
      ~blaze~