@@ -273,28 +273,33 @@ public async Task<IStorageHistory> CreateShortcutItemsAsync(IList<IStorageItemWi
273
273
274
274
FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress , source . Count ) ;
275
275
fsProgress . Report ( ) ;
276
+
276
277
var items = source . Zip ( destination , ( src , dest , index ) => new { src , dest , index } ) . Where ( x => ! string . IsNullOrEmpty ( x . src . Path ) && ! string . IsNullOrEmpty ( x . dest ) ) ;
277
278
foreach ( var item in items )
278
279
{
279
- if ( await FileOperationsHelpers . CreateOrUpdateLinkAsync ( item . dest , item . src . Path ) )
280
+ var result = await FileOperationsHelpers . CreateOrUpdateLinkAsync ( item . dest , item . src . Path ) ;
281
+
282
+ if ( ! result )
283
+ result = await UIFilesystemHelpers . HandleShortcutCannotBeCreated ( Path . GetFileName ( item . dest ) , item . src . Path ) ;
284
+
285
+ if ( result )
280
286
{
281
287
createdSources . Add ( item . src ) ;
282
288
createdDestination . Add ( StorageHelpers . FromPathAndType ( item . dest , FilesystemItemType . File ) ) ;
283
289
}
290
+
284
291
fsProgress . ProcessedItemsCount = item . index ;
285
292
fsProgress . Report ( ) ;
286
293
}
287
294
288
295
fsProgress . ReportStatus ( createdSources . Count == source . Count ? FileSystemStatusCode . Success : FileSystemStatusCode . Generic ) ;
296
+
289
297
return new StorageHistory ( FileOperationType . CreateLink , createdSources , createdDestination ) ;
290
298
}
291
299
292
300
public async Task < IStorageHistory > DeleteAsync ( IStorageItem source , IProgress < FileSystemProgress > progress , bool permanently , CancellationToken cancellationToken )
293
301
{
294
- return await DeleteAsync ( source . FromStorageItem ( ) ,
295
- progress ,
296
- permanently ,
297
- cancellationToken ) ;
302
+ return await DeleteAsync ( source . FromStorageItem ( ) , progress , permanently , cancellationToken ) ;
298
303
}
299
304
300
305
public async Task < IStorageHistory > DeleteAsync ( IStorageItemWithPath source , IProgress < FileSystemProgress > progress , bool permanently , CancellationToken cancellationToken )
@@ -311,14 +316,16 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
311
316
{
312
317
if ( source . Any ( x => string . IsNullOrWhiteSpace ( x . Path ) || x . Path . StartsWith ( @"\\?\" , StringComparison . Ordinal ) || FtpHelpers . IsFtpPath ( x . Path ) ) )
313
318
{
314
- // Fallback to builtin file operations
319
+ // Fallback to built-in file operations
315
320
return await filesystemOperations . DeleteItemsAsync ( source , progress , permanently , cancellationToken ) ;
316
321
}
317
322
318
323
FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress ) ;
319
324
fsProgress . Report ( ) ;
325
+
320
326
var deleleFilePaths = source . Select ( s => s . Path ) . Distinct ( ) ;
321
327
var deleteFromRecycleBin = source . Any ( ) && RecycleBinHelpers . IsPathUnderRecycleBin ( source . ElementAt ( 0 ) . Path ) ;
328
+
322
329
permanently |= deleteFromRecycleBin ;
323
330
324
331
if ( deleteFromRecycleBin )
@@ -341,10 +348,12 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
341
348
if ( result )
342
349
{
343
350
fsProgress . ReportStatus ( FileSystemStatusCode . Success ) ;
351
+
344
352
foreach ( var item in deleteResult . Items )
345
353
{
346
354
await associatedInstance . FilesystemViewModel . RemoveFileOrFolderAsync ( item . Source ) ;
347
355
}
356
+
348
357
var recycledSources = deleteResult . Items . Where ( x => x . Succeeded && x . Destination is not null && x . Source != x . Destination ) ;
349
358
if ( recycledSources . Any ( ) )
350
359
{
@@ -356,22 +365,22 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
356
365
await recycledSources . Zip ( sourceMatch , ( rSrc , oSrc ) => new { rSrc , oSrc } )
357
366
. Select ( item => StorageHelpers . FromPathAndType ( item . rSrc . Destination , item . oSrc . ItemType ) ) . ToListAsync ( ) ) ;
358
367
}
368
+
359
369
return new StorageHistory ( FileOperationType . Delete , source , null ) ;
360
370
}
361
371
else
362
372
{
363
373
if ( deleteResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . Unauthorized ) )
364
374
{
365
375
if ( await RequestAdminOperation ( ) )
366
- {
367
376
return await DeleteItemsAsync ( source , progress , permanently , cancellationToken ) ;
368
- }
369
377
}
370
378
else if ( deleteResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) )
371
379
{
372
380
var failedSources = deleteResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) ;
373
381
var filePath = failedSources . Select ( x => x . Source ) ; // When deleting only source can be in use but shell returns COPYENGINE_E_SHARING_VIOLATION_DEST for folders
374
382
var lockingProcess = WhoIsLocking ( filePath ) ;
383
+
375
384
switch ( await GetFileInUseDialog ( filePath , lockingProcess ) )
376
385
{
377
386
case DialogResult . Primary :
@@ -391,20 +400,18 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
391
400
// Retry with StorageFile API
392
401
var failedSources = deleteResult . Items . Where ( x => ! x . Succeeded ) ;
393
402
var sourceMatch = await failedSources . Select ( x => source . DistinctBy ( x => x . Path ) . SingleOrDefault ( s => s . Path . Equals ( x . Source , StringComparison . OrdinalIgnoreCase ) ) ) . Where ( x => x is not null ) . ToListAsync ( ) ;
403
+
394
404
return await filesystemOperations . DeleteItemsAsync ( sourceMatch , progress , permanently , cancellationToken ) ;
395
405
}
396
406
fsProgress . ReportStatus ( CopyEngineResult . Convert ( deleteResult . Items . FirstOrDefault ( x => ! x . Succeeded ) ? . HResult ) ) ;
407
+
397
408
return null ;
398
409
}
399
410
}
400
411
401
412
public async Task < IStorageHistory > MoveAsync ( IStorageItem source , string destination , NameCollisionOption collision , IProgress < FileSystemProgress > progress , CancellationToken cancellationToken )
402
413
{
403
- return await MoveAsync ( source . FromStorageItem ( ) ,
404
- destination ,
405
- collision ,
406
- progress ,
407
- cancellationToken ) ;
414
+ return await MoveAsync ( source . FromStorageItem ( ) , destination , collision , progress , cancellationToken ) ;
408
415
}
409
416
410
417
public async Task < IStorageHistory > MoveAsync ( IStorageItemWithPath source , string destination , NameCollisionOption collision , IProgress < FileSystemProgress > progress , CancellationToken cancellationToken )
@@ -427,20 +434,21 @@ public async Task<IStorageHistory> MoveItemsAsync(IList<IStorageItemWithPath> so
427
434
428
435
FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress ) ;
429
436
fsProgress . Report ( ) ;
437
+
430
438
var sourceNoSkip = source . Zip ( collisions , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . Skip ) . Select ( item => item . src ) ;
431
439
var destinationNoSkip = destination . Zip ( collisions , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . Skip ) . Select ( item => item . src ) ;
432
440
var collisionsNoSkip = collisions . Where ( c => c != FileNameConflictResolveOptionType . Skip ) ;
433
-
434
441
var operationID = Guid . NewGuid ( ) . ToString ( ) ;
442
+
435
443
using var r = cancellationToken . Register ( CancelOperation , operationID , false ) ;
436
444
437
445
var sourceReplace = sourceNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll == FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
438
446
var destinationReplace = destinationNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll == FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
439
447
var sourceRename = sourceNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
440
448
var destinationRename = destinationNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
441
-
442
449
var result = ( FilesystemResult ) true ;
443
450
var moveResult = new ShellOperationResult ( ) ;
451
+
444
452
if ( sourceRename . Any ( ) )
445
453
{
446
454
var ( status , response ) = await FileOperationsHelpers . MoveItemAsync ( sourceRename . Select ( s => s . Path ) . ToArray ( ) , destinationRename . ToArray ( ) , false , NativeWinApiHelper . CoreWindowHandle . ToInt64 ( ) , operationID , progress ) ;
@@ -461,16 +469,19 @@ public async Task<IStorageHistory> MoveItemsAsync(IList<IStorageItemWithPath> so
461
469
if ( result )
462
470
{
463
471
fsProgress . ReportStatus ( FileSystemStatusCode . Success ) ;
472
+
464
473
var movedSources = moveResult . Items . Where ( x => x . Succeeded && x . Destination is not null && x . Source != x . Destination ) ;
465
474
if ( movedSources . Any ( ) )
466
475
{
467
476
var sourceMatch = await movedSources . Select ( x => sourceRename
468
477
. SingleOrDefault ( s => s . Path . Equals ( x . Source , StringComparison . OrdinalIgnoreCase ) ) ) . Where ( x => x is not null ) . ToListAsync ( ) ;
478
+
469
479
return new StorageHistory ( FileOperationType . Move ,
470
480
sourceMatch ,
471
481
await movedSources . Zip ( sourceMatch , ( rSrc , oSrc ) => new { rSrc , oSrc } )
472
482
. Select ( item => StorageHelpers . FromPathAndType ( item . rSrc . Destination , item . oSrc . ItemType ) ) . ToListAsync ( ) ) ;
473
483
}
484
+
474
485
return null ; // Cannot undo overwrite operation
475
486
}
476
487
else
@@ -479,26 +490,27 @@ public async Task<IStorageHistory> MoveItemsAsync(IList<IStorageItemWithPath> so
479
490
if ( moveResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . Unauthorized ) )
480
491
{
481
492
if ( await RequestAdminOperation ( ) )
482
- {
483
493
return await MoveItemsAsync ( source , destination , collisions , progress , cancellationToken ) ;
484
- }
485
494
}
486
495
else if ( source . Zip ( destination , ( src , dest ) => ( src , dest ) ) . FirstOrDefault ( x => x . src . ItemType == FilesystemItemType . Directory && PathNormalization . GetParentDir ( x . dest ) . IsSubPathOf ( x . src . Path ) ) is ( IStorageItemWithPath , string ) subtree )
487
496
{
488
497
var destName = subtree . dest . Split ( Path . DirectorySeparatorChar , StringSplitOptions . RemoveEmptyEntries ) . Last ( ) ;
489
498
var srcName = subtree . src . Path . Split ( Path . DirectorySeparatorChar , StringSplitOptions . RemoveEmptyEntries ) . Last ( ) ;
499
+
490
500
await DialogDisplayHelper . ShowDialogAsync ( "ErrorDialogThisActionCannotBeDone" . GetLocalizedResource ( ) , $ "{ "ErrorDialogTheDestinationFolder" . GetLocalizedResource ( ) } ({ destName } ) { "ErrorDialogIsASubfolder" . GetLocalizedResource ( ) } ({ srcName } )") ;
491
501
}
492
502
else if ( moveResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) )
493
503
{
494
504
var failedSources = moveResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) ;
495
505
var filePath = failedSources . Select ( x => x . HResult == CopyEngineResult . COPYENGINE_E_SHARING_VIOLATION_SRC ? x . Source : x . Destination ) ;
496
506
var lockingProcess = WhoIsLocking ( filePath ) ;
507
+
497
508
switch ( await GetFileInUseDialog ( filePath , lockingProcess ) )
498
509
{
499
510
case DialogResult . Primary :
500
511
var moveZip = sourceNoSkip . Zip ( destinationNoSkip , ( src , dest ) => new { src , dest } ) . Zip ( collisionsNoSkip , ( z1 , coll ) => new { z1 . src , z1 . dest , coll } ) ;
501
512
var sourceMatch = await failedSources . Select ( x => moveZip . SingleOrDefault ( s => s . src . Path . Equals ( x . Source , StringComparison . OrdinalIgnoreCase ) ) ) . Where ( x => x is not null ) . ToListAsync ( ) ;
513
+
502
514
return await MoveItemsAsync (
503
515
await sourceMatch . Select ( x => x . src ) . ToListAsync ( ) ,
504
516
await sourceMatch . Select ( x => x . dest ) . ToListAsync ( ) ,
@@ -511,6 +523,7 @@ await sourceMatch.Select(x => x.dest).ToListAsync(),
511
523
var failedSources = moveResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . NameTooLong ) ;
512
524
var moveZip = sourceNoSkip . Zip ( destinationNoSkip , ( src , dest ) => new { src , dest } ) . Zip ( collisionsNoSkip , ( z1 , coll ) => new { z1 . src , z1 . dest , coll } ) ;
513
525
var sourceMatch = await failedSources . Select ( x => moveZip . SingleOrDefault ( s => s . src . Path . Equals ( x . Source , StringComparison . OrdinalIgnoreCase ) ) ) . Where ( x => x is not null ) . ToListAsync ( ) ;
526
+
514
527
return await filesystemOperations . MoveItemsAsync (
515
528
await sourceMatch . Select ( x => x . src ) . ToListAsync ( ) ,
516
529
await sourceMatch . Select ( x => x . dest ) . ToListAsync ( ) ,
@@ -530,11 +543,13 @@ await sourceMatch.Select(x => x.dest).ToListAsync(),
530
543
var failedSources = moveResult . Items . Where ( x => ! x . Succeeded ) ;
531
544
var moveZip = sourceNoSkip . Zip ( destinationNoSkip , ( src , dest ) => new { src , dest } ) . Zip ( collisionsNoSkip , ( z1 , coll ) => new { z1 . src , z1 . dest , coll } ) ;
532
545
var sourceMatch = await failedSources . Select ( x => moveZip . SingleOrDefault ( s => s . src . Path . Equals ( x . Source , StringComparison . OrdinalIgnoreCase ) ) ) . Where ( x => x is not null ) . ToListAsync ( ) ;
546
+
533
547
return await filesystemOperations . MoveItemsAsync (
534
548
await sourceMatch . Select ( x => x . src ) . ToListAsync ( ) ,
535
549
await sourceMatch . Select ( x => x . dest ) . ToListAsync ( ) ,
536
550
await sourceMatch . Select ( x => x . coll ) . ToListAsync ( ) , progress , cancellationToken ) ;
537
551
}
552
+
538
553
return null ;
539
554
}
540
555
}
@@ -554,41 +569,42 @@ public async Task<IStorageHistory> RenameAsync(IStorageItemWithPath source, stri
554
569
555
570
FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress ) ;
556
571
fsProgress . Report ( ) ;
557
- var renameResult = new ShellOperationResult ( ) ;
558
572
573
+ var renameResult = new ShellOperationResult ( ) ;
559
574
var ( status , response ) = await FileOperationsHelpers . RenameItemAsync ( source . Path , newName , collision == NameCollisionOption . ReplaceExisting ) ;
560
-
561
575
var result = ( FilesystemResult ) status ;
576
+
562
577
renameResult . Items . AddRange ( response ? . Final ?? Enumerable . Empty < ShellOperationItemResult > ( ) ) ;
563
578
564
579
result &= ( FilesystemResult ) renameResult . Items . All ( x => x . Succeeded ) ;
565
580
566
581
if ( result )
567
582
{
568
583
fsProgress . ReportStatus ( FileSystemStatusCode . Success ) ;
584
+
569
585
var renamedSources = renameResult . Items . Where ( x => x . Succeeded && x . Destination is not null && x . Source != x . Destination )
570
586
. Where ( x => new [ ] { source } . Select ( s => s . Path ) . Contains ( x . Source ) ) ;
571
587
if ( renamedSources . Any ( ) )
572
588
{
573
589
return new StorageHistory ( FileOperationType . Rename , source ,
574
590
StorageHelpers . FromPathAndType ( renamedSources . Single ( ) . Destination , source . ItemType ) ) ;
575
591
}
592
+
576
593
return null ; // Cannot undo overwrite operation
577
594
}
578
595
else
579
596
{
580
597
if ( renameResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . Unauthorized ) )
581
598
{
582
599
if ( await RequestAdminOperation ( ) )
583
- {
584
600
return await RenameAsync ( source , newName , collision , progress , cancellationToken ) ;
585
- }
586
601
}
587
602
else if ( renameResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) )
588
603
{
589
604
var failedSources = renameResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) ;
590
605
var filePath = failedSources . Select ( x => x . HResult == CopyEngineResult . COPYENGINE_E_SHARING_VIOLATION_SRC ? x . Source : x . Destination ) ;
591
606
var lockingProcess = WhoIsLocking ( filePath ) ;
607
+
592
608
switch ( await GetFileInUseDialog ( filePath , lockingProcess ) )
593
609
{
594
610
case DialogResult . Primary :
@@ -613,7 +629,9 @@ public async Task<IStorageHistory> RenameAsync(IStorageItemWithPath source, stri
613
629
// Retry with StorageFile API
614
630
return await filesystemOperations . RenameAsync ( source , newName , collision , progress , cancellationToken ) ;
615
631
}
632
+
616
633
fsProgress . ReportStatus ( CopyEngineResult . Convert ( renameResult . Items . FirstOrDefault ( x => ! x . Succeeded ) ? . HResult ) ) ;
634
+
617
635
return null ;
618
636
}
619
637
}
0 commit comments