-
Notifications
You must be signed in to change notification settings - Fork 1
/
CopyFiles.cs
445 lines (389 loc) · 16.4 KB
/
CopyFiles.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace CopyFiles
{
//http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx
/// <summary>
/// Copies a list of files or a directory tree to a destination
///
/// Support for GUI is implamented by the ICopyFilesDiag interface
/// and passed to the class in the copy() method.
/// </summary>
public class CopyFiles
{
// Variables
private List<String> files = new List<String>();
private List<String> newFilenames = new List<String>();
private List<ST_CopyFileDetails> filesCopied = new List<ST_CopyFileDetails>();
private Int32 totalFiles = 0;
private Int32 totalFilesCopied = 0;
private String destinationDir = "";
private String sourceDir = "";
private String currentFilename;
private int cancel = 0;
private IAsyncResult CopyResult;
private DEL_CopyFiles delCopy;
private ICopyFilesDiag digWindow;
// Structurs
public struct ST_CopyFileDetails
{
String OriginalURI;
String NewURI;
// Constructor
public ST_CopyFileDetails(String FromURI, String ToURI)
{
OriginalURI = FromURI;
NewURI = ToURI;
}
}
// Enums
// These Enums are used for the windows CopyFileEx function
[Flags]
private 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
}
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
}
// Events
public event DEL_copyComplete EV_copyComplete;
public event DEL_copyCanceled EV_copyCanceled;
// Delegates
private delegate CopyProgressResult CopyProgressRoutine(Int64 TotalFileSize, Int64 TotalBytesTransferred, Int64 StreamSize, Int64 StreamBytesTransferred, UInt32 dwStreamNumber, CopyProgressCallbackReason dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);
private delegate CopyProgressResult DEL_CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);
private delegate void DEL_CopyFiles();
private delegate void DEL_ShowDiag(ICopyFilesDiag diag);
private delegate void DEL_HideDiag(ICopyFilesDiag diag);
private delegate void DEL_CopyfilesCallback(IAsyncResult r);
public delegate void DEL_cancelCopy();
public delegate void DEL_copyComplete();
public delegate void DEL_copyCanceled(List<ST_CopyFileDetails> filescopied);
// Constructors
public CopyFiles(String source, String destination)
{
//As the directory tree might be large we work out the
//files in the threaded call Copyfiles()
sourceDir = source;
destinationDir = destination;
}
public CopyFiles(List<String> sourceFiles, String destination)
{
//The sourceDir does not need to be set if the user is supplying a
//list of files.
//
//Example :
// Source Destination
// c:\Temp1\Test.txt c:\DestFolder\Test.txt
// c:\temp2\temp1\test1.txt c:\DestFolder\Test1.txt
// c:\temp3\blah\Test.txt c:\DestFolder\Test (2).txt
//
//This is worked out in CheckFilenames()
files = sourceFiles;
totalFiles = files.Count;
destinationDir = destination;
}
// Kernal32 Calls
// Unsafe is need to show that we are using
// pointers which are classed as "unsafe" in .net
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);
// Methods
private List<String> GetFiles(String sourceDir)
{
// Variables
List<String> foundFiles = new List<String>();
String[] fileEntries;
String[] subdirEntries;
//Add root files in this DIR to the list
fileEntries = System.IO.Directory.GetFiles(sourceDir);
foreach (String filename in fileEntries)
{
foundFiles.Add(filename);
}
//Loop the DIR's in the current DIR
subdirEntries = System.IO.Directory.GetDirectories(sourceDir);
foreach (string subdir in subdirEntries)
{
//Dont open Folder Redirects as this can end up in an infinate loop
if ((System.IO.File.GetAttributes(subdir) &
System.IO.FileAttributes.ReparsePoint) !=
System.IO.FileAttributes.ReparsePoint)
{
//Run recursivly to follow this DIR tree
//adding all the files along the way
foundFiles.AddRange(GetFiles(subdir));
}
}
return foundFiles;
}
private CopyProgressResult CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
{
//Check to see if there is a dialog window to use
if (digWindow != null)
{
//Are we going to send the update on the correct thread?
if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
{
digWindow.SynchronizationObject.Invoke(new CopyProgressRoutine(CopyProgressHandler),
new Object[] { total, transferred, streamSize, StreamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData });
}
else
{
digWindow.update(totalFiles, totalFilesCopied, total, transferred, currentFilename);
};
}
return CopyProgressResult.PROGRESS_CONTINUE;
}
private void ShowDiag(ICopyFilesDiag diag)
{
//Check to see if there is a dialog window to use
if (digWindow != null)
{
//Are we going to send the update on the correct thread?
if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
{
digWindow.SynchronizationObject.Invoke(new DEL_ShowDiag(ShowDiag),
new Object[] { diag });
}
else
{
diag.Show();
}
}
}
private void HideDiag(ICopyFilesDiag diag)
{
//Check to see if there is a dialog window to use
if (digWindow != null)
{
//Are we going to send the update on the correct thread?
if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
{
digWindow.SynchronizationObject.Invoke(new DEL_HideDiag(HideDiag),
new Object[] { diag });
}
else
{
diag.Hide();
cancel = 0;
}
}
}
private void CancelCopy()
{
cancel = 1;
OnCopyCanceled();
}
private void Copyfiles()
{
Int32 index = 0;
//Show the dialog box and hook into its cancel event if
//a dialog box has been given
if (digWindow != null)
{
digWindow.EN_cancelCopy += CancelCopy;
ShowDiag(digWindow);
}
//If we have been a sourceDIR then find all the files to copy
if (sourceDir != "")
{
files = GetFiles(sourceDir);
}
else
{
CheckFilenames();
}
totalFiles = files.Count;
//Loop each file and copy it.
foreach (String _filename in files.ToArray())
{
string filename = _filename;
string nfn = "";
if (filename.IndexOf("?") >= 0)
{
nfn = filename.Substring(filename.IndexOf("?") + 1);
filename = filename.Remove(filename.IndexOf("?"));
};
String[] filepath;
String tempFilepath;
String tempDirPath = "";
//If we have a source directory, strip that off the filename
if (sourceDir != "")
{
tempFilepath = filename;
tempFilepath = tempFilepath.Replace(sourceDir, "");
tempFilepath = System.IO.Path.Combine(destinationDir, nfn == "" ? tempFilepath : nfn);
}
//otherwise strip off all the folder path
else
{
tempFilepath = System.IO.Path.Combine(destinationDir, nfn == "" ? newFilenames[index] : nfn);
}
//Save the new DIR path and check the DIR exsits,
//if it does not then create it so the files can copy
filepath = tempFilepath.Split('\\');
for (int i = 0; i < filepath.Length - 1; i++)
{
tempDirPath += filepath[i] + "\\";
}
if (!System.IO.Directory.Exists(tempDirPath))
{
System.IO.Directory.CreateDirectory(tempDirPath);
}
//Have be been told to stop copying files
if (cancel == 1)
{
break;
}
//Set the file thats just about to get copied
currentFilename = filename;
//Unsafe is need to show that we are using
//pointers which are classed as "unsafe" in .net
//
//CopyFileEx needs a pointer to the cancel boolean, it checks this
//constantly as the file copies, if it gets set to true it will stop
//
//Note :
// fixed is used to get the memory pointer of our local boolean.
// It is then saved in a pointer (declared like a normal type but
// with a * at the end)
//
// We can then pass this memory address to the Kernal32 call.
CopyFileEx(filename, tempFilepath, new CopyProgressRoutine(this.CopyProgressHandler), IntPtr.Zero, ref cancel, 0);
filesCopied.Add(new ST_CopyFileDetails(filename, tempFilepath));
totalFilesCopied += 1;
index += 1;
}
}
private void OnCopyComplete()
{
if (EV_copyComplete != null)
{
EV_copyComplete();
}
}
private void OnCopyCanceled()
{
if (EV_copyCanceled != null)
{
EV_copyCanceled(filesCopied);
}
}
private void CheckFilenames()
{
// Variables
String[] fileNames = new String[files.Count];
List<String> tempFileNameArr;
Int32 index = 0;
Int32 innerIndex = 0;
Int32 filenameIndex = 0;
Int32 filenameNumber = 0;
//Load filenames into an array
foreach (String tempFileName in files)
{
fileNames[index] = System.IO.Path.GetFileName(tempFileName);
index += 1;
}
//Loop each filename in the array
index = 0;
foreach (String originalFilename in fileNames)
{
//See if this filename is repeated in the list
innerIndex = 0;
filenameNumber = 2;
foreach (String dupeFilename in fileNames)
{
//dont compair the same index!
if (innerIndex != index)
{
if (originalFilename == dupeFilename)
{
//insert the duplicate number into the new filename e.g (2) and clear
//the current name.
tempFileNameArr = new List<String>(fileNames[innerIndex].Split('.'));
tempFileNameArr.Insert(tempFileNameArr.Count - 1, "[*REMOVEME*] (" + filenameNumber + ")");
fileNames[innerIndex] = "";
//Rebuild the new filename
filenameIndex = 0;
foreach (String newFilename in tempFileNameArr)
{
//put a dot before the file extension
if (filenameIndex == tempFileNameArr.Count - 1)
{ fileNames[innerIndex] += "."; }
//append the new filename
fileNames[innerIndex] += newFilename.Replace("[*REMOVEME*]", "");
//only add a . if its not the injected portion e.g (2)
if ((filenameIndex < tempFileNameArr.Count - 3 && newFilename.StartsWith("[*REMOVEME*]") == false))
{ fileNames[innerIndex] += "."; }
filenameIndex += 1;
}
//Trim any trailing .'s
fileNames[innerIndex].TrimEnd(new Char[] { '.' });
filenameNumber += 1;
}
}
innerIndex += 1;
}
index += 1;
}
//Update the list of new filenames.
newFilenames = new List<String>(fileNames);
}
//Copy the files
public void Copy()
{
Copyfiles();
}
public void CopyAsync(ICopyFilesDiag diag)
{
digWindow = diag;
if (digWindow != null && digWindow.SynchronizationObject == null)
{
throw new Exception("Dialog window sent with no SynchronizationObject");
}
delCopy = new DEL_CopyFiles(Copyfiles);
IsRunning = true;
CopyResult = delCopy.BeginInvoke(CopyfilesCallback, null);
}
// Async Callbacks
private void CopyfilesCallback(IAsyncResult r)
{
//Kill off the thread as its finished.
delCopy.EndInvoke(CopyResult);
HideDiag(digWindow);
IsRunning = false;
OnCopyComplete();
}
public bool IsRunning = false;
}
//The interface for the Dialog the CopyFiles class uses.
public interface ICopyFilesDiag
{
//needed to sync the CopyClass update events with the dialog thread
System.ComponentModel.ISynchronizeInvoke SynchronizationObject { get; set; }
//This event should fire when you want to cancel the copy
event CopyFiles.DEL_cancelCopy EN_cancelCopy;
//This is how the CopyClass will send your dialog information about
//the transfer
void update(Int32 totalFiles, Int32 copiedFiles, Int64 totalBytes, Int64 copiedBytes, String currentFilename);
void Show();
void Hide();
}
}