1' 2' FileSystemOperation.vb 3' 4' Authors: 5' Rolf Bjarne Kvinge (RKvinge@novell.com> 6' 7' Copyright (C) 2007 Novell (http://www.novell.com) 8' 9' Permission is hereby granted, free of charge, to any person obtaining 10' a copy of this software and associated documentation files (the 11' "Software"), to deal in the Software without restriction, including 12' without limitation the rights to use, copy, modify, merge, publish, 13' distribute, sublicense, and/or sell copies of the Software, and to 14' permit persons to whom the Software is furnished to do so, subject to 15' the following conditions: 16' 17' The above copyright notice and this permission notice shall be 18' included in all copies or substantial portions of the Software. 19' 20' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23' NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24' LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25' OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26' WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27' 28 29Imports System.IO 30Imports System.Text 31Imports System.Collections.ObjectModel 32Imports System.Collections.Generic 33 34Namespace Microsoft.VisualBasic.FileIO 35 Friend Class FileSystemOperation 36 Private m_Source As String 37 Private m_Destination As String 38 Private m_Overwrite As Boolean 39 Private m_DirectoryNotEmpty As DeleteDirectoryOption 40 Private m_ShowUI As Boolean 41 Private m_ShowUIOption As UIOption 42 Private m_UICancelOption As UICancelOption 43 Private m_Recycle As RecycleOption 44#If TARGET_JVM = False Then 'Windows.Forms Not Supported by Grasshopper 45 Private m_UI As FileSystemOperationUI 46#End If 47 Private m_Sources As New Generic.List(Of Info) 48 Private m_TotalSize As Long 49 Private m_Cancelled As Boolean 50 Private m_Errors As New Generic.Dictionary(Of String, String) 51 52 Private Class Info 53 Public Name As String 54 Public Size As Long 55 Public IsDir As Boolean 56 End Class 57 58#Region "Constructors" 59 Sub New(ByVal Source As String, ByVal Destination As String, ByVal ShowUIOption As UIOption, ByVal UICancelOption As UICancelOption, ByVal ShowUI As Boolean, ByVal overwrite As Boolean) 60 Me.m_Source = Source 61 Me.m_Destination = Destination 62 Me.m_ShowUI = ShowUI 63 Me.m_ShowUIOption = ShowUIOption 64 Me.m_UICancelOption = UICancelOption 65 Me.m_Overwrite = overwrite 66 End Sub 67 68 Sub New(ByVal Source As String, ByVal Destination As String, ByVal ShowUIOption As UIOption, ByVal UICancelOption As UICancelOption) 69 Me.m_Source = Source 70 Me.m_Destination = Destination 71 Me.m_ShowUI = True 72 Me.m_ShowUIOption = ShowUIOption 73 Me.m_UICancelOption = UICancelOption 74 Me.m_Overwrite = False 75 End Sub 76 77 Sub New(ByVal Source As String, ByVal Destination As String, ByVal Overwrite As Boolean) 78 Me.m_Source = Source 79 Me.m_Destination = Destination 80 Me.m_ShowUI = False 81 Me.m_Overwrite = Overwrite 82 End Sub 83 84 Sub New(ByVal Directory As String, ByVal ShowUIOption As UIOption, ByVal Recycle As RecycleOption, ByVal UICancelOption As UICancelOption, ByVal onDirectoryNotEmpty As DeleteDirectoryOption, ByVal ShowUI As Boolean) 85 Me.m_Source = Directory 86 Me.m_ShowUI = ShowUI 87 Me.m_ShowUIOption = ShowUIOption 88 Me.m_UICancelOption = UICancelOption 89 Me.m_Recycle = Recycle 90 Me.m_DirectoryNotEmpty = onDirectoryNotEmpty 91 Me.m_Overwrite = False 92 End Sub 93 94 Sub New(ByVal Directory As String, ByVal ShowUIOption As UIOption, ByVal Recycle As RecycleOption, ByVal UICancelOption As UICancelOption, ByVal ShowUI As Boolean) 95 Me.m_Source = Directory 96 Me.m_ShowUI = ShowUI 97 Me.m_ShowUIOption = ShowUIOption 98 Me.m_UICancelOption = UICancelOption 99 Me.m_Recycle = Recycle 100 Me.m_Overwrite = False 101 End Sub 102#End Region 103#Region "Public Methods" 104 Sub Cancel() 105 m_Cancelled = True 106 End Sub 107 108 Sub ExecuteFileCopy() 109 Try 110 Init("Copy") 111 LoadSources(False, True) 112 Copy() 113 Finally 114 CleanUp() 115 End Try 116 End Sub 117 118 Sub ExecuteFileMove() 119 Try 120 Init("Move") 121 LoadSources(False, True) 122 Move() 123 Finally 124 CleanUp() 125 End Try 126 End Sub 127 128 Sub ExecuteDirCopy() 129 Try 130 Init("Copy") 131 LoadSources(True, True) 132 Copy() 133 Finally 134 CleanUp() 135 End Try 136 End Sub 137 138 Sub ExecuteDirMove() 139 Try 140 Init("Move") 141 142 If IO.Directory.Exists(m_Source) = False Then 143 Throw New IOException(String.Format("Could not find directory '{0}'.", m_Source)) 144 End If 145 146 LoadSources(True, False) 147 Move() 148 Finally 149 CleanUp() 150 End Try 151 End Sub 152 153 Sub ExecuteDirDelete() 154 Try 155 Init("Delete") 156 If IO.Directory.Exists(m_Source) = False Then 157 Return 158 End If 159 160 LoadSources(m_Source, m_DirectoryNotEmpty = DeleteDirectoryOption.DeleteAllContents, False) 161 If m_DirectoryNotEmpty = DeleteDirectoryOption.ThrowIfDirectoryNonEmpty AndAlso m_Sources.Count > 1 Then 162 Throw New IOException("Directory not empty") 163 End If 164 Delete() 165 Finally 166 CleanUp() 167 End Try 168 End Sub 169 170 Sub ExecuteFileDelete() 171 Try 172 Init("Delete") 173 LoadSources(m_Source, False, False) 174 Delete() 175 Finally 176 CleanUp() 177 End Try 178 End Sub 179#End Region 180 181 Private Function GetDestination(ByVal Source As String) As String 182 Dim result As String 183 184 result = Source.Replace(m_Source, m_Destination) 185 186 Return result 187 End Function 188 189 Private Sub Move() 190 Dim counter As Integer 191 Dim size As Long 192 193 size = 0 194 195 If OnSameVolume Then 196 If IsDirectory(m_Source) Then 197 UpdateUI(Path.GetDirectoryName(m_Source), m_Destination, Path.GetFileName(m_Source), 0, 0) 198 System.IO.Directory.Move(m_Source, m_Destination) 199 Else 200 If IO.File.Exists(m_Destination) Then 201 If DoOverwrite(m_Source, m_Destination) Then 202 System.IO.File.Delete(m_Destination) 203 Else 204 m_Cancelled = True 205 Return 206 End If 207 End If 208 System.IO.File.Move(m_Source, m_Destination) 209 End If 210 Else 211 For i As Integer = m_Sources.Count - 1 To 0 Step -1 212 Dim info As Info = m_Sources(i) 213 Dim item As String = info.Name 214 Dim destination As String 215 216 destination = GetDestination(item) 217 218 CopyItem(info, destination, counter, size) 219 If m_Cancelled Then Return 220 DeleteItem(info, counter, False) 221 counter += 1 222 223 224 If m_Cancelled Then Return 225 Next 226 End If 227 228 UpdateUI(Nothing, Nothing, Nothing, m_Sources.Count, 0) 229 End Sub 230 231 Private Sub Copy() 232 Dim counter As Integer 233 Dim size As Long 234 235 size = 0 236 For i As Integer = 0 To m_Sources.Count - 1 237 Dim info As Info = m_Sources(i) 238 Dim item As String = info.Name 239 Dim destination As String 240 241 destination = GetDestination(item) 242 243 CopyItem(info, destination, counter, size) 244 counter += 1 245 246 If m_Cancelled Then Return 247 Next 248 UpdateUI(Nothing, Nothing, Nothing, m_Sources.Count, size) 249 End Sub 250 251 Private Sub CopyItem(ByVal Info As Info, ByVal Destination As String, ByVal Counter As Integer, ByRef Size As Long) 252 Dim Source As String = Info.Name 253 If Info.IsDir Then 254 UpdateUI(Source, Nothing, Nothing, Counter, 0) 255 If IO.Directory.Exists(Destination) = False Then 256 IO.Directory.CreateDirectory(Destination) 257 End If 258 Else 259 UpdateUI(Path.GetDirectoryName(Source), Path.GetDirectoryName(Destination), Path.GetFileName(Source), Counter, Size) 260 CopyFile(Source, Destination, Size) 261 End If 262 End Sub 263 264 Private Sub DeleteItem(ByVal Info As Info, ByVal Counter As Integer, ByVal DoUpdate As Boolean) 265 Dim Item As String = Info.Name 266 If Info.IsDir Then 267 If DoUpdate Then UpdateUI(Item, Nothing, Nothing, Counter, 0) 268 System.IO.Directory.Delete(Item, False) 269 Else 270 If DoUpdate Then UpdateUI(Path.GetDirectoryName(Item), Nothing, Path.GetFileName(Item), Counter, 0) 271 System.IO.File.Delete(Item) 272 End If 273 End Sub 274 275 Private Function IsDirectory(ByVal Path As String) As Boolean 276 Return CBool(System.IO.File.GetAttributes(Path) And FileAttributes.Directory) 277 End Function 278 279 Private Sub CopyFile(ByVal Source As String, ByVal Destination As String, ByRef Size As Long) 280 Dim newFileMode As FileMode 281 282 If IO.File.Exists(Destination) Then 283 If DoOverwrite(Source, Destination) = False Then Return 284 newFileMode = FileMode.Create 285 Else 286 newFileMode = FileMode.CreateNew 287 End If 288 289 Try 290#If TARGET_JVM = False Then 'FileStream ctor with FileOptions Not Supported by Grasshopper 291 Using reader As New IO.FileStream(Source, FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.SequentialScan) 292 Using writer As New IO.FileStream(Destination, newFileMode, FileAccess.Write, FileShare.Read, 1024, FileOptions.SequentialScan) 293#Else 294 Using reader As New IO.FileStream(Source, FileMode.Open, FileAccess.Read, FileShare.Read, 1024) 295 Using writer As New IO.FileStream(Destination, newFileMode, FileAccess.Write, FileShare.Read) 296#End If 297 Dim read As Integer 298 Dim buffer(1023) As Byte 299 300 Do 301 read = reader.Read(buffer, 0, 1024) 302 writer.Write(buffer, 0, read) 303 Size = Size + CLng(read) 304 UpdateUI(Size) 305 Loop Until read = 0 OrElse m_Cancelled 306 End Using 307 End Using 308 Catch ex As IOException 309 m_Errors.Add(Source, ex.Message) 310 End Try 311 End Sub 312 313 Private Sub Delete() 314 Dim counter As Integer 315 316 If m_Recycle = RecycleOption.SendToRecycleBin Then 317 Throw New NotImplementedException 318 Else 319 For i As Integer = m_Sources.Count - 1 To 0 Step -1 320 Dim info As Info = m_Sources(i) 321 Dim item As String = info.Name 322 323 DeleteItem(info, counter, True) 324 counter += 1 325 326 If m_Cancelled Then Return 327 Next 328 End If 329 330 UpdateUI(Nothing, Nothing, Nothing, m_Sources.Count, 0) 331 End Sub 332 333 Private Sub Recycle(ByVal Source As String) 334 335 End Sub 336 337 Private Sub LoadSources(ByVal Recursive As Boolean, ByVal SizeMatters As Boolean) 338 m_TotalSize = 0 339 340 LoadSources(m_Source, Recursive, SizeMatters, 0) 341 342 End Sub 343 344 Private Sub LoadSources(ByVal Source As String, ByVal Recursive As Boolean, ByVal SizeMatters As Boolean, Optional ByRef DirSize As Long = 0) 345 Dim subdirs() As String 346 Dim files() As String 347 348 Dim info2 As Info = Nothing 349 Dim subsize As Long 350 If CBool(System.IO.File.GetAttributes(Source) And FileAttributes.Directory) Then 351 info2 = New Info 352 info2.Name = Source 353 info2.IsDir = True 354 m_Sources.Add(info2) 355 Debug.WriteLine(info2.Name) 356 357 358 files = System.IO.Directory.GetFiles(Source) 359 For Each file As String In files 360 Dim info As New Info 361 info.Name = file 362 info.IsDir = False 363 If SizeMatters Then 364 info.Size = (New FileInfo(file)).Length 365 m_TotalSize += info.Size 366 DirSize += info.Size 367 End If 368 m_Sources.Add(info) 369 Debug.WriteLine(info.Name) 370 Next 371 372 If Recursive Then 373 subdirs = System.IO.Directory.GetDirectories(Source) 374 For Each subdir As String In subdirs 375 LoadSources(subdir, Recursive, SizeMatters, subsize) 376 Next 377 End If 378 379 If info2 IsNot Nothing Then 380 info2.Size = subsize 381 DirSize += subsize 382 End If 383 Else 384 Dim info As New Info 385 info.Name = Source 386 m_Sources.Add(info) 387 If SizeMatters Then 388 info.Size = (New FileInfo(Source)).Length 389 m_TotalSize = info.Size 390 End If 391 End If 392 393 End Sub 394 395 Private Sub Init(ByVal Title As String) 396 m_Source = Path.GetFullPath(m_Source) 397 If Not m_Destination Is Nothing AndAlso m_Destination.Length <> 0 Then 398 m_Destination = Path.GetFullPath(m_Destination) 399 End If 400 401 If m_ShowUI = False Then Return 402#If TARGET_JVM = False Then 'Windows.Forms Not Supported by Grasshopper 403 m_UI = New FileSystemOperationUI(Me) 404 m_UI.Text = Title 405 m_UI.lblDirs.Text = "Calculating time..." 406 m_UI.lblFile.Text = String.Empty 407 m_UI.lblTimeLeft.Text = "..." 408 m_UI.barProgress.Value = 0 409 m_UI.Show() 410#End If 411 End Sub 412 413 Private Sub UpdateUI(ByVal SizeDone As Long) 414 If m_ShowUI = False Then Return 415 416 Dim PercentDone As Double 417 If SizeDone > 0 AndAlso m_TotalSize > 0 Then 418 PercentDone = SizeDone / m_TotalSize * 100 419 Else 420 PercentDone = 0 421 End If 422#If TARGET_JVM = False Then 'Windows.Forms Not Supported by Grasshopper 423 m_UI.UpdateInfo(PercentDone) 424#End If 425 End Sub 426 427 Private Sub UpdateUI(ByVal SourceDirectory As String, ByVal DestinationDirectory As String, ByVal File As String, ByVal ItemsDone As Integer, ByVal SizeDone As Long) 428 If m_ShowUI = False Then Return 429 430 Dim PercentDone As Double 431 If SizeDone > 0 AndAlso m_TotalSize > 0 Then 432 PercentDone = SizeDone / m_TotalSize * 100 433 ElseIf ItemsDone > 0 AndAlso m_Sources.Count > 0 Then 434 PercentDone = ItemsDone / m_Sources.Count 435 Else 436 PercentDone = 0 437 End If 438#If TARGET_JVM = False Then 'Windows.Forms Not Supported by Grasshopper 439 m_UI.UpdateInfo(SourceDirectory, DestinationDirectory, File, PercentDone) 440#End If 441 End Sub 442 443 Private Sub CleanUp() 444 If m_ShowUI Then 445#If TARGET_JVM = False Then 'Windows.Forms Not Supported by Grasshopper 446 If m_UI IsNot Nothing Then m_UI.Dispose() 447 m_UI = Nothing 448#End If 449 450 If m_Cancelled AndAlso m_UICancelOption = UICancelOption.ThrowException Then 451 Throw New OperationCanceledException("The operation was canceled.") 452 End If 453 End If 454 455 If m_Errors.Count > 0 Then 456 If CBool(File.GetAttributes(m_Source) And FileAttributes.Directory) = False Then 457 Throw New IOException(m_Errors(m_Source)) 458 Else 459 Dim ex As New IOException("Could not complete operation on some files and directories. See the Data property of the exception for more details.") 460 For Each entry As KeyValuePair(Of String, String) In m_Errors 461 ex.Data.Add(entry.Key, entry.Value) 462 Next 463 Throw ex 464 End If 465 End If 466 End Sub 467 468 Private Sub CopyDir(ByVal SourceDir As String, ByVal DestinationDir As String) 469 If IO.Directory.Exists(DestinationDir) = False Then 470 IO.Directory.CreateDirectory(DestinationDir) 471 End If 472 473 Dim files() As String = IO.Directory.GetFiles(SourceDir) 474 Dim subdirs() As String = IO.Directory.GetDirectories(SourceDir) 475 476 For Each file As String In files 477 System.IO.File.Copy(file, Path.Combine(DestinationDir, Path.GetFileName(file))) 478 Next 479 480 For Each subdir As String In subdirs 481 Dim name As String = Path.GetFileName(subdir) 482 CopyDir(Path.Combine(SourceDir, name), Path.Combine(DestinationDir, name)) 483 Next 484 End Sub 485 486 Private Function DoOverwrite(ByVal Source As String, ByVal Destination As String) As Boolean 487 If m_ShowUI Then 488 Static overWriteAll As Boolean 489 Static overWriteNone As Boolean 490 491 If overWriteAll Then Return True 492 If overWriteNone Then Return False 493 494 If m_ShowUIOption = UIOption.OnlyErrorDialogs Then Return True 495#If TARGET_JVM = False Then 'Windows.Forms Not Supported by Grasshopper 496 Using frm As New FileSystemOperationUIQuestion 497 Dim result As FileSystemOperationUIQuestion.Answer 498 Dim infoA As FileInfo = New FileInfo(Source) 499 Dim infoB As FileInfo = New FileInfo(Destination) 500 501 frm.Text = "Confirm file overwrite" 502 frm.lblTitle.Text = String.Format("This folder already has a file called '{0}'.", Path.GetFileName(Source)) 503 frm.lblText1.Text = "Do you want to replace the existing file" 504 frm.lblText2.Text = "with this other file?" 505 frm.lblSizeA.Text = String.Format("{0} bytes", infoA.Length) 506 frm.lblSizeB.Text = String.Format("{0} bytes", infoB.Length) 507 frm.lblDateA.Text = String.Format("modified: {0}", infoA.LastWriteTime) 508 frm.lblDateB.Text = String.Format("modified: {0}", infoB.LastWriteTime) 509 frm.iconA.Image = System.Drawing.Icon.ExtractAssociatedIcon(Source).ToBitmap 510 frm.iconB.Image = System.Drawing.Icon.ExtractAssociatedIcon(Destination).ToBitmap 511 512 result = frm.ShowDialog() 513 514 Select Case result 515 Case FileSystemOperationUIQuestion.Answer.Cancel 516 m_Cancelled = True 517 Return False 518 Case FileSystemOperationUIQuestion.Answer.No 519 Return False 520 Case FileSystemOperationUIQuestion.Answer.NoToAll 521 overWriteNone = True 522 Return False 523 Case FileSystemOperationUIQuestion.Answer.Yes 524 Return True 525 Case FileSystemOperationUIQuestion.Answer.YesToAll 526 overWriteAll = True 527 Return True 528 Case Else 529 Return False 530 End Select 531 End Using 532#End If 533 Else 534 If m_Overwrite = False Then 535 m_Errors.Add(Source, String.Format("The file '{0}' already exists.", Destination)) 536 End If 537 Return m_Overwrite 538 End If 539 End Function 540 541 Private ReadOnly Property OnSameVolume() As Boolean 542 Get 543 Return IsOnSameVolume(m_Source, m_Destination) 544 End Get 545 End Property 546 547 Private Shared Function IsOnSameVolume(ByVal Source As String, ByVal Destination As String) As Boolean 548 Return Char.ToUpperInvariant(Source(0)) = Char.ToUpperInvariant(Destination(0)) 549 End Function 550 End Class 551 552End Namespace 553