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