-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMainForm.cs
More file actions
1460 lines (1307 loc) · 59.2 KB
/
MainForm.cs
File metadata and controls
1460 lines (1307 loc) · 59.2 KB
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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using FileHelper.Dialogs;
using FileHelper.Models;
using FileHelper.Services;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FileHelper
{
public partial class MainForm : Form
{
private readonly ConfigurationService _configService;
private readonly FileCopyService _copyService;
private readonly LinkService _linkService;
private CancellationTokenSource _cancellationTokenSource;
private bool _isCopying = false;
private bool _isLinking = false;
public MainForm()
{
InitializeComponent();
// 设置程序图标(从嵌入资源加载)
this.Icon = new Icon(typeof(MainForm).Assembly.GetManifestResourceStream("FileHelper.FileCopy.ico"));
_configService = new ConfigurationService();
_copyService = new FileCopyService();
_linkService = new LinkService();
InitializeForm();
LoadConfigurations();
// 自动加载上次使用的配置方案
LoadLastUsedConfiguration();
}
private void LoadLastUsedConfiguration()
{
try
{
// 只加载文件复制配置到文件复制界面
var allCopyConfigs = _configService.GetAllConfigurations();
if (allCopyConfigs != null && allCopyConfigs.Count > 0)
{
// 获取最近修改的配置(GetAllConfigurations已按LastModified降序排列)
var lastUsedCopyConfig = allCopyConfigs.FirstOrDefault();
if (lastUsedCopyConfig != null)
{
LoadConfiguration(lastUsedCopyConfig);
// 选择对应的配置项
cmbConfigurations.SelectedItem = lastUsedCopyConfig.Name;
}
}
// 只加载链接配置到链接界面
var allLinkConfigs = _configService.GetAllLinkConfigurations();
if (allLinkConfigs != null && allLinkConfigs.Count > 0)
{
// 获取最近修改的链接配置
var lastUsedLinkConfig = allLinkConfigs.FirstOrDefault();
if (lastUsedLinkConfig != null)
{
LoadLinkConfiguration(lastUsedLinkConfig);
// 选择对应的配置项
cmbLinkConfigurations.SelectedItem = lastUsedLinkConfig.Name;
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"自动加载最近配置失败: {ex.Message}");
}
}
private void InitializeForm()
{
// 设置拖拽功能
this.AllowDrop = true;
txtSourcePath.AllowDrop = true;
txtTargetPaths.AllowDrop = true;
txtBlacklist.AllowDrop = true;
txtLinkSourcePath.AllowDrop = true;
txtLinkTargetPaths.AllowDrop = true;
// 绑定事件
this.DragEnter += MainForm_DragEnter;
this.DragDrop += MainForm_DragDrop;
txtSourcePath.DragEnter += MainForm_DragEnter;
txtSourcePath.DragDrop += MainForm_DragDrop;
txtTargetPaths.DragEnter += TxtTargetPaths_DragEnter;
txtTargetPaths.DragDrop += TxtTargetPaths_DragDrop;
txtBlacklist.DragEnter += TxtBlacklist_DragEnter;
txtBlacklist.DragDrop += TxtBlacklist_DragDrop;
// 文件链接拖拽事件
txtLinkSourcePath.DragEnter += MainForm_DragEnter;
txtLinkSourcePath.DragDrop += MainForm_DragDrop;
txtLinkTargetPaths.DragEnter += TxtTargetPaths_DragEnter;
txtLinkTargetPaths.DragDrop += TxtTargetPaths_DragDrop;
btnBrowseSource.Click += BtnBrowseSource_Click;
btnStartCopy.Click += BtnStartCopy_Click;
btnCancel.Click += BtnCancel_Click;
btnSaveConfig.Click += BtnSaveConfig_Click;
// 移除重复的事件绑定,因为在InitializeComponent()中已经绑定
// btnNewConfig.Click += BtnNewConfig_Click;
// 文件链接按钮事件
btnBrowseLinkSource.Click += BtnBrowseLinkSource_Click;
btnCreateLink.Click += BtnCreateLink_Click;
btnValidateLink.Click += BtnValidateLink_Click;
btnDeleteLink.Click += BtnDeleteLink_Click;
btnLinkHelp.Click += BtnLinkHelp_Click;
btnLinkSaveConfig.Click += BtnLinkSaveConfig_Click;
btnLinkNewConfig.Click += BtnLinkNewConfig_Click;
btnLinkDeleteConfig.Click += BtnLinkDeleteConfig_Click;
cmbLinkConfigurations.SelectedIndexChanged += CmbLinkConfigurations_SelectedIndexChanged;
// 设置默认黑名单
txtBlacklist.Text = "ArkApi\r\nlogs\r\nShooterGame";
// 设置初始状态提示
lblProgress.Text = "准备就绪";
progressBarCopy.Value = 0;
lblLinkProgress.Text = "准备就绪";
progressBarLink.Value = 0;
// 加载链接配置
LoadLinkConfigurations();
}
private void MainForm_DragEnter(object sender, DragEventArgs e)
{
if (e.Data != null && e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
}
private void MainForm_DragDrop(object sender, DragEventArgs e)
{
if (e.Data != null)
{
var files = e.Data.GetData(DataFormats.FileDrop) as string[];
if (files != null && files.Length > 0)
{
string path = files[0];
if (Directory.Exists(path))
{
// 根据当前活动的选项卡决定将路径设置到哪个文本框
if (tabControlMain.SelectedTab == tabPageCopy)
{
txtSourcePath.Text = path;
}
else if (tabControlMain.SelectedTab == tabPageLink)
{
txtLinkSourcePath.Text = path;
}
}
else if (File.Exists(path))
{
// 根据当前活动的选项卡决定将路径设置到哪个文本框
if (tabControlMain.SelectedTab == tabPageCopy)
{
// 对于文件复制,使用文件所在目录
var dirPath = Path.GetDirectoryName(path);
txtSourcePath.Text = dirPath ?? "";
}
else if (tabControlMain.SelectedTab == tabPageLink)
{
// 对于文件链接,直接使用文件路径
txtLinkSourcePath.Text = path;
}
}
}
}
}
private void BtnBrowseSource_Click(object sender, EventArgs e)
{
using (var dialog = new FolderBrowserDialog())
{
dialog.Description = "选择源文件夹";
if (dialog.ShowDialog() == DialogResult.OK)
{
txtSourcePath.Text = dialog.SelectedPath;
}
}
}
private void BtnBrowseLinkSource_Click(object sender, EventArgs e)
{
using (var dialog = new FolderBrowserDialog())
{
dialog.Description = "选择源文件夹";
if (dialog.ShowDialog() == DialogResult.OK)
{
txtLinkSourcePath.Text = dialog.SelectedPath;
}
}
}
private async void BtnStartCopy_Click(object sender, EventArgs e)
{
if (_isCopying)
return;
if (string.IsNullOrWhiteSpace(txtSourcePath.Text))
{
MessageBox.Show("请选择源文件夹", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 验证路径格式是否有效
if (!IsValidPath(txtSourcePath.Text))
{
MessageBox.Show("源文件夹路径格式无效", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 验证路径是否存在且是目录
if (!Directory.Exists(txtSourcePath.Text))
{
var result = MessageBox.Show("源文件夹不存在,是否要创建该目录?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
try
{
Directory.CreateDirectory(txtSourcePath.Text);
}
catch (Exception ex)
{
MessageBox.Show($"创建目录失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
else
{
return;
}
}
var targetPaths = GetTargetPaths();
if (targetPaths.Count == 0)
{
MessageBox.Show("请至少输入一个目标路径", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 验证目标路径
var invalidPaths = targetPaths.Where(path => !IsValidPath(path)).ToList();
if (invalidPaths.Count > 0)
{
MessageBox.Show($"以下路径无效:\n{string.Join("\n", invalidPaths)}",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
await StartCopyOperation();
}
private async void BtnCreateLink_Click(object sender, EventArgs e)
{
if (_isLinking)
return;
if (string.IsNullOrWhiteSpace(txtLinkSourcePath.Text))
{
MessageBox.Show("请选择源文件夹", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 验证路径格式是否有效
if (!IsValidPath(txtLinkSourcePath.Text))
{
MessageBox.Show("源文件夹路径格式无效", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 验证路径是否存在
if (!Directory.Exists(txtLinkSourcePath.Text) && !File.Exists(txtLinkSourcePath.Text))
{
MessageBox.Show("源文件夹或文件不存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
var targetPaths = GetLinkTargetPaths();
if (targetPaths.Count == 0)
{
MessageBox.Show("请至少输入一个目标路径", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 验证目标路径
var invalidPaths = targetPaths.Where(path => !IsValidPath(path)).ToList();
if (invalidPaths.Count > 0)
{
MessageBox.Show($"以下路径无效:\n{string.Join("\n", invalidPaths)}",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
await StartLinkOperation(LinkOperationType.Create);
}
private async void BtnValidateLink_Click(object sender, EventArgs e)
{
if (_isLinking)
return;
if (string.IsNullOrWhiteSpace(txtLinkSourcePath.Text))
{
MessageBox.Show("请选择源文件夹", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var targetPaths = GetLinkTargetPaths();
if (targetPaths.Count == 0)
{
MessageBox.Show("请至少输入一个目标路径", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
await StartLinkOperation(LinkOperationType.Validate);
}
private async void BtnDeleteLink_Click(object sender, EventArgs e)
{
if (_isLinking)
return;
var targetPaths = GetLinkTargetPaths();
if (targetPaths.Count == 0)
{
MessageBox.Show("请至少输入一个目标路径", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var result = MessageBox.Show("确定要删除这些链接吗?此操作不可撤销。", "确认删除", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
{
await StartLinkOperation(LinkOperationType.Delete);
}
}
private void BtnCancel_Click(object sender, EventArgs e)
{
if (_cancellationTokenSource != null)
_cancellationTokenSource.Cancel();
}
private void BtnHelp_Click(object sender, EventArgs e)
{
// 创建使用帮助对话框内容
var helpContent = "文件复制助手 v3.0 作者:唐小布 无极交流群1016741666\n\n" +
"【工具功能】\n" +
"- 将一个源文件夹中的文件同时复制到多个目标路径\n" +
"- 支持黑名单过滤,可排除特定文件或文件夹\n" +
"- 支持配置保存和加载,方便重复使用\n" +
"- 显示复制进度和详细信息\n" +
"- 处理文件冲突情况\n\n" +
"【使用方法】\n" +
"1. 源文件夹设置\n" +
" - 在上方\"源文件夹\"文本框中输入或拖拽文件夹路径\n" +
" - 点击\"浏览...\"按钮选择源文件夹\n\n" +
"2. 目标路径设置\n" +
" - 在\"目标路径\"文本框中输入多个目标文件夹路径\n" +
" - 每行一个路径,支持多个目标同时复制\n\n" +
"3. 黑名单设置\n" +
" - 在\"黑名单设置\"文本框中输入要忽略的文件或文件夹名称\n" +
" - 每行一个名称,支持通配符(*匹配任意字符,?匹配单个字符)\n" +
" - 例如:*.tmp, Thumbs.db, .git\n\n" +
"4. 开始复制\n" +
" - 点击\"开始复制\"按钮启动复制操作\n" +
" - 复制过程中可点击\"取消复制\"按钮中断操作\n\n" +
"5. 配置管理\n" +
" - 点击\"保存\"可保存当前设置为配置方案\n" +
" - 点击\"新增\"可创建新的配置方案\n" +
" - 点击\"删除\"可删除选中的配置方案\n" +
" - 程序会自动加载上次使用的配置方案\n\n" +
"【提示】\n" +
"- 源文件夹路径在复制过程中会被锁定,防止修改\n" +
"- 黑名单可用于排除临时文件、系统文件或特定类型文件\n" +
"- 配置方案保存在本地,可随时调用重复使用";
MessageBox.Show(helpContent, "使用帮助", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void BtnLinkHelp_Click(object sender, EventArgs e)
{
// 创建链接使用帮助对话框内容
var helpContent = "文件链接助手 v3.0 作者:唐小布 无极交流群1016741666\n\n" +
"【工具功能】\n" +
"- 创建目录链接、符号链接或硬链接\n" +
"- 支持一个源文件夹/文件链接到多个目标路径\n" +
"- 支持链接验证和删除\n" +
"- 支持配置保存和加载,方便重复使用\n\n" +
"【链接类型说明】\n" +
"- 目录链接 (Junction): 只能链接目录,适用于本地NTFS卷\n" +
"- 符号链接 (Symbolic): 可链接文件或目录,支持跨卷链接\n" +
"- 硬链接 (HardLink): 只能链接文件,创建指向同一文件数据的多个入口\n\n" +
"【使用方法】\n" +
"1. 链接类型选择\n" +
" - 选择要创建的链接类型(默认为目录链接)\n\n" +
"2. 源文件夹/文件设置\n" +
" - 在\"源文件夹\"文本框中输入或拖拽文件夹/文件路径\n" +
" - 点击\"浏览...\"按钮选择源文件夹/文件\n\n" +
"3. 目标路径设置\n" +
" - 在\"目标路径\"文本框中输入多个目标路径\n" +
" - 每行一个路径,支持多个目标同时创建链接\n\n" +
"4. 创建链接\n" +
" - 点击\"创建链接\"按钮启动链接创建操作\n\n" +
"5. 验证链接\n" +
" - 点击\"验证链接\"按钮验证链接是否有效\n\n" +
"6. 删除链接\n" +
" - 点击\"删除链接\"按钮删除已创建的链接\n\n" +
"7. 配置管理\n" +
" - 点击\"保存\"可保存当前设置为配置方案\n" +
" - 点击\"新增\"可创建新的配置方案\n" +
" - 点击\"删除\"可删除选中的配置方案\n\n" +
"【注意事项】\n" +
"- 创建符号链接可能需要管理员权限\n" +
"- 硬链接只能用于文件,不能用于目录\n" +
"- 配置方案保存在本地,可随时调用重复使用";
MessageBox.Show(helpContent, "链接使用帮助", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private async Task StartCopyOperation()
{
_isCopying = true;
_cancellationTokenSource = new CancellationTokenSource();
SetUIState(false);
try
{
var config = GetCurrentConfiguration();
var progress = new Progress<(int current, int total, string currentFile, string targetFile, long currentBytes, long totalBytes)>(
value => UpdateProgress(value.current, value.total, value.currentFile, value.targetFile, value.currentBytes, value.totalBytes));
// 重置冲突对话框状态,确保每次复制操作都能重新显示冲突对话框
FileCopyService.ResetConflictDialogState();
var result = await _copyService.CopyFilesAsync(
config.SourcePath,
config.TargetPaths,
config.BlackList,
progress,
_cancellationTokenSource.Token);
if (result.Success)
{
lblProgress.Text = "复制完成";
MessageBox.Show($"复制完成!\n\n成功复制: {result.CopiedFiles} 个文件\n跳过文件: {result.SkippedFiles} 个",
"完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
lblProgress.Text = "复制失败";
MessageBox.Show($"复制失败: {result.ErrorMessage}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (OperationCanceledException)
{
lblProgress.Text = "复制已取消";
progressBarCopy.Value = 0;
}
catch (Exception ex)
{
lblProgress.Text = "复制出错";
MessageBox.Show($"复制过程中发生错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
_isCopying = false;
SetUIState(true);
// 重置进度条并将状态提示恢复为准备就绪
progressBarCopy.Value = 0;
lblProgress.Text = "准备就绪";
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
}
}
private async Task StartLinkOperation(LinkOperationType operationType)
{
_isLinking = true;
_cancellationTokenSource = new CancellationTokenSource();
SetLinkUIState(false);
try
{
var config = GetCurrentLinkConfiguration();
var progress = new Progress<(int current, int total, string targetPath, LinkOperationStatus status)>(
value => UpdateLinkProgress(value.current, value.total, value.targetPath, value.status));
// 为创建操作注册冲突处理事件
if (operationType == LinkOperationType.Create)
{
_linkService.OnConflict += HandleLinkConflict;
}
LinkResult result = null;
switch (operationType)
{
case LinkOperationType.Create:
result = await _linkService.CreateLinksAsync(
config.SourcePath,
config.TargetPaths,
config.LinkType,
progress);
break;
case LinkOperationType.Validate:
result = await _linkService.ValidateLinksAsync(
config.SourcePath,
config.TargetPaths,
config.LinkType,
progress);
break;
case LinkOperationType.Delete:
result = await _linkService.DeleteLinksAsync(
config.TargetPaths,
progress);
break;
}
// 取消事件注册
if (operationType == LinkOperationType.Create)
{
_linkService.OnConflict -= HandleLinkConflict;
}
if (result.Success)
{
switch (operationType)
{
case LinkOperationType.Create:
lblLinkProgress.Text = "链接创建完成";
MessageBox.Show($"链接创建完成!\n\n成功创建: {result.CreatedLinks} 个链接\n跳过: {result.SkippedLinks} 个\n失败: {result.FailedLinks} 个",
"完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
break;
case LinkOperationType.Validate:
lblLinkProgress.Text = "链接验证完成";
MessageBox.Show($"链接验证完成!\n\n有效链接: {result.CreatedLinks} 个\n无效链接: {result.FailedLinks} 个",
"完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
break;
case LinkOperationType.Delete:
lblLinkProgress.Text = "链接删除完成";
MessageBox.Show($"链接删除完成!\n\n成功删除: {result.CreatedLinks} 个链接\n跳过: {result.SkippedLinks} 个\n失败: {result.FailedLinks} 个",
"完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
break;
}
}
else
{
lblLinkProgress.Text = "操作失败";
// 显示更详细的错误信息
string errorMessage = $"操作失败: {result.ErrorMessage}";
if (result.FailedLinks > 0)
{
errorMessage += $"\n\n失败的链接数量: {result.FailedLinks}";
}
MessageBox.Show(errorMessage, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (OperationCanceledException)
{
lblLinkProgress.Text = "操作已取消";
progressBarLink.Value = 0;
}
catch (Exception ex)
{
lblLinkProgress.Text = "操作出错";
// 显示更详细的异常信息
string errorMessage = $"操作过程中发生错误: {ex.Message}";
if (!string.IsNullOrEmpty(ex.StackTrace))
{
errorMessage += $"\n\n详细信息: {ex}";
}
MessageBox.Show(errorMessage, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
_isLinking = false;
SetLinkUIState(true);
// 重置进度条并将状态提示恢复为准备就绪
progressBarLink.Value = 0;
lblLinkProgress.Text = "准备就绪";
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
}
}
private void UpdateProgress(int current, int total, string sourceFile, string targetFile, long currentBytes, long totalBytes)
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateProgress(current, total, sourceFile, targetFile, currentBytes, totalBytes)));
return;
}
if (total > 0)
{
int percentage = (int)((double)current / total * 100);
progressBarCopy.Value = Math.Min(percentage, 100);
}
// 如果没有当前文件或已完成,则显示相应的状态
if (string.IsNullOrEmpty(sourceFile))
{
return; // 不更新状态文本,由调用者处理
}
// 显示目标路径和源文件名
string fileName = Path.GetFileName(sourceFile);
if (!string.IsNullOrEmpty(targetFile))
{
// 显示正在复制到的具体目标路径
lblProgress.Text = $"正在复制到 {targetFile}: {fileName}";
}
else
{
// 显示准备复制的文件名称
var config = GetCurrentConfiguration();
if (config.TargetPaths.Count > 1)
{
// 如果有多个目标,显示目标数量
lblProgress.Text = $"准备复制到 {config.TargetPaths.Count} 个目标: {fileName}";
}
else
{
// 单个目标时的简洁显示
lblProgress.Text = $"正在复制: {fileName}";
}
}
}
private void UpdateLinkProgress(int current, int total, string targetPath, LinkOperationStatus status)
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateLinkProgress(current, total, targetPath, status)));
return;
}
if (total > 0)
{
int percentage = (int)((double)current / total * 100);
progressBarLink.Value = Math.Min(percentage, 100);
}
// 更新状态文本,显示完整路径而不是仅文件名
switch (status)
{
case LinkOperationStatus.Creating:
lblLinkProgress.Text = $"正在创建链接: {targetPath}";
break;
case LinkOperationStatus.Validating:
lblLinkProgress.Text = $"正在验证链接: {targetPath}";
break;
case LinkOperationStatus.Deleting:
lblLinkProgress.Text = $"正在删除链接: {targetPath}";
break;
case LinkOperationStatus.Success:
lblLinkProgress.Text = $"操作成功: {targetPath}";
break;
case LinkOperationStatus.Skipped:
lblLinkProgress.Text = $"已跳过: {targetPath}";
break;
case LinkOperationStatus.Failed:
lblLinkProgress.Text = $"操作失败: {targetPath}";
break;
}
}
private string FormatBytes(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
private void SetUIState(bool enabled)
{
// 文件复制UI状态
btnStartCopy.Enabled = enabled;
btnCancel.Enabled = !enabled;
btnBrowseSource.Enabled = enabled;
txtSourcePath.Enabled = enabled;
txtTargetPaths.Enabled = enabled;
txtBlacklist.Enabled = enabled;
btnSaveConfig.Enabled = enabled;
btnNewConfig.Enabled = enabled;
btnDeleteConfig.Enabled = enabled;
cmbConfigurations.Enabled = enabled;
// 当操作完成恢复UI状态时,重置进度条和状态提示
if (enabled && !_isCopying)
{
progressBarCopy.Value = 0;
// 注意:不要在这里直接重置lblProgress,因为不同的操作结果有不同的提示文本
}
}
private void SetLinkUIState(bool enabled)
{
// 文件链接UI状态
btnCreateLink.Enabled = enabled;
btnValidateLink.Enabled = enabled;
btnDeleteLink.Enabled = enabled;
btnBrowseLinkSource.Enabled = enabled;
txtLinkSourcePath.Enabled = enabled;
txtLinkTargetPaths.Enabled = enabled;
btnLinkSaveConfig.Enabled = enabled;
btnLinkNewConfig.Enabled = enabled;
btnLinkDeleteConfig.Enabled = enabled;
cmbLinkConfigurations.Enabled = enabled;
radioJunction.Enabled = enabled;
radioSymbolic.Enabled = enabled;
radioHardLink.Enabled = enabled;
// 当操作完成恢复UI状态时,重置进度条和状态提示
if (enabled && !_isLinking)
{
progressBarLink.Value = 0;
// 注意:不要在这里直接重置lblLinkProgress,因为不同的操作结果有不同的提示文本
}
}
private List<string> GetTargetPaths()
{
return txtTargetPaths.Text
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(path => path.Trim())
.Where(path => !string.IsNullOrWhiteSpace(path))
.ToList();
}
private List<string> GetLinkTargetPaths()
{
return txtLinkTargetPaths.Text
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(path => path.Trim())
.Where(path => !string.IsNullOrWhiteSpace(path))
.ToList();
}
private List<string> GetBlackList()
{
return txtBlacklist.Text
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(pattern => pattern.Trim())
.Where(pattern => !string.IsNullOrWhiteSpace(pattern))
.ToList();
}
private CopyConfiguration GetCurrentConfiguration()
{
return new CopyConfiguration
{
SourcePath = txtSourcePath.Text.Trim(),
TargetPaths = GetTargetPaths(),
BlackList = GetBlackList()
};
}
private LinkConfiguration GetCurrentLinkConfiguration()
{
LinkType linkType = LinkType.Junction;
if (radioSymbolic.Checked)
linkType = LinkType.Symbolic;
else if (radioHardLink.Checked)
linkType = LinkType.HardLink;
return new LinkConfiguration
{
LinkType = linkType,
SourcePath = txtLinkSourcePath.Text.Trim(),
TargetPaths = GetLinkTargetPaths()
};
}
private bool IsValidPath(string path)
{
try
{
return !string.IsNullOrWhiteSpace(path) &&
Path.IsPathRooted(path) &&
!path.Any(c => Path.GetInvalidPathChars().Contains(c));
}
catch
{
return false;
}
}
private void TxtTargetPaths_DragEnter(object sender, DragEventArgs e)
{
if (e.Data != null && e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
}
private void TxtTargetPaths_DragDrop(object sender, DragEventArgs e)
{
if (e.Data != null)
{
var files = e.Data.GetData(DataFormats.FileDrop) as string[];
if (files != null && files.Length > 0)
{
TextBox targetTextBox = sender == txtTargetPaths ? txtTargetPaths : txtLinkTargetPaths;
// 获取当前已有的目标路径
var existingPaths = new HashSet<string>(
targetTextBox.Text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(path => path.Trim())
.Where(path => !string.IsNullOrWhiteSpace(path)),
StringComparer.OrdinalIgnoreCase);
var newPaths = new List<string>();
// 检查拖拽的所有路径
foreach (var path in files)
{
if ((Directory.Exists(path) || File.Exists(path)) && !existingPaths.Contains(path))
{
newPaths.Add(path);
existingPaths.Add(path);
}
}
// 如果有新路径,添加到文本框中
if (newPaths.Count > 0)
{
if (!string.IsNullOrEmpty(targetTextBox.Text))
{
targetTextBox.AppendText(Environment.NewLine);
}
targetTextBox.AppendText(string.Join(Environment.NewLine, newPaths));
}
}
}
}
private void TxtBlacklist_DragEnter(object sender, DragEventArgs e)
{
if (e.Data != null && e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
}
private void TxtBlacklist_DragDrop(object sender, DragEventArgs e)
{
if (e.Data != null)
{
var items = e.Data.GetData(DataFormats.FileDrop) as string[];
if (items != null && items.Length > 0)
{
// 获取当前已有的黑名单项目
var existingItems = new HashSet<string>(GetBlackList(), StringComparer.OrdinalIgnoreCase);
var newItems = new List<string>();
// 检查拖拽的所有项目
foreach (var item in items)
{
// 对于文件和文件夹,只提取名称作为黑名单项目
string name;
if (Directory.Exists(item))
{
name = Path.GetFileName(item);
}
else if (File.Exists(item))
{
name = Path.GetFileName(item);
}
else
{
continue; // 忽略无效的项目
}
// 只添加新的、非空的项目
if (!string.IsNullOrEmpty(name) && !existingItems.Contains(name))
{
newItems.Add(name);
existingItems.Add(name);
}
}
// 如果有新项目,添加到文本框中
if (newItems.Count > 0)
{
if (!string.IsNullOrEmpty(txtBlacklist.Text))
{
txtBlacklist.AppendText(Environment.NewLine);
}
txtBlacklist.AppendText(string.Join(Environment.NewLine, newItems));
}
}
}
}
private void BtnSaveConfig_Click(object sender, EventArgs e)
{
var config = GetCurrentConfiguration();
if (string.IsNullOrWhiteSpace(config.SourcePath))
{
MessageBox.Show("请先设置源文件夹", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 检查是否有选中的配置方案
string configName = null;
string originalConfigName = null;
if (cmbConfigurations.SelectedItem is string selectedConfigName)
{
originalConfigName = selectedConfigName;
// 使用选中的配置名称作为默认值打开保存对话框
using var dialog = new SaveConfigDialog(selectedConfigName);
if (dialog.ShowDialog() == DialogResult.OK)
{
configName = dialog.ConfigName;
}
else
{
return; // 用户取消操作
}
}
else
{
// 如果没有选中的配置,显示对话框让用户输入新名称
using var dialog = new SaveConfigDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
configName = dialog.ConfigName;
}
else
{
return; // 用户取消操作
}
}
// 检查是否需要覆盖现有配置
if (!string.IsNullOrWhiteSpace(configName))
{
bool saveSuccess = false;
// 如果当前有选中的配置,并且用户更改了配置名称
if (!string.IsNullOrEmpty(originalConfigName) && originalConfigName != configName)
{
// 直接更新当前解决方案,而不是创建新的
config.Name = configName;
saveSuccess = _configService.RenameConfiguration(originalConfigName, configName);
}
else
{
// 正常保存配置
config.Name = configName;
saveSuccess = _configService.SaveConfiguration(config);
}
if (saveSuccess)
{
MessageBox.Show("配置保存成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
LoadConfigurations();
// 重新选择保存后的配置
cmbConfigurations.SelectedItem = configName;
}
else
{
MessageBox.Show("配置保存失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}