@@ -273,28 +273,33 @@ public async Task<IStorageHistory> CreateShortcutItemsAsync(IList<IStorageItemWi
273273
274274 FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress , source . Count ) ;
275275 fsProgress . Report ( ) ;
276+
276277 var items = source . Zip ( destination , ( src , dest , index ) => new { src , dest , index } ) . Where ( x => ! string . IsNullOrEmpty ( x . src . Path ) && ! string . IsNullOrEmpty ( x . dest ) ) ;
277278 foreach ( var item in items )
278279 {
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 )
280286 {
281287 createdSources . Add ( item . src ) ;
282288 createdDestination . Add ( StorageHelpers . FromPathAndType ( item . dest , FilesystemItemType . File ) ) ;
283289 }
290+
284291 fsProgress . ProcessedItemsCount = item . index ;
285292 fsProgress . Report ( ) ;
286293 }
287294
288295 fsProgress . ReportStatus ( createdSources . Count == source . Count ? FileSystemStatusCode . Success : FileSystemStatusCode . Generic ) ;
296+
289297 return new StorageHistory ( FileOperationType . CreateLink , createdSources , createdDestination ) ;
290298 }
291299
292300 public async Task < IStorageHistory > DeleteAsync ( IStorageItem source , IProgress < FileSystemProgress > progress , bool permanently , CancellationToken cancellationToken )
293301 {
294- return await DeleteAsync ( source . FromStorageItem ( ) ,
295- progress ,
296- permanently ,
297- cancellationToken ) ;
302+ return await DeleteAsync ( source . FromStorageItem ( ) , progress , permanently , cancellationToken ) ;
298303 }
299304
300305 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>
311316 {
312317 if ( source . Any ( x => string . IsNullOrWhiteSpace ( x . Path ) || x . Path . StartsWith ( @"\\?\" , StringComparison . Ordinal ) || FtpHelpers . IsFtpPath ( x . Path ) ) )
313318 {
314- // Fallback to builtin file operations
319+ // Fallback to built-in file operations
315320 return await filesystemOperations . DeleteItemsAsync ( source , progress , permanently , cancellationToken ) ;
316321 }
317322
318323 FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress ) ;
319324 fsProgress . Report ( ) ;
325+
320326 var deleleFilePaths = source . Select ( s => s . Path ) . Distinct ( ) ;
321327 var deleteFromRecycleBin = source . Any ( ) && RecycleBinHelpers . IsPathUnderRecycleBin ( source . ElementAt ( 0 ) . Path ) ;
328+
322329 permanently |= deleteFromRecycleBin ;
323330
324331 if ( deleteFromRecycleBin )
@@ -341,10 +348,12 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
341348 if ( result )
342349 {
343350 fsProgress . ReportStatus ( FileSystemStatusCode . Success ) ;
351+
344352 foreach ( var item in deleteResult . Items )
345353 {
346354 await associatedInstance . FilesystemViewModel . RemoveFileOrFolderAsync ( item . Source ) ;
347355 }
356+
348357 var recycledSources = deleteResult . Items . Where ( x => x . Succeeded && x . Destination is not null && x . Source != x . Destination ) ;
349358 if ( recycledSources . Any ( ) )
350359 {
@@ -356,22 +365,22 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
356365 await recycledSources . Zip ( sourceMatch , ( rSrc , oSrc ) => new { rSrc , oSrc } )
357366 . Select ( item => StorageHelpers . FromPathAndType ( item . rSrc . Destination , item . oSrc . ItemType ) ) . ToListAsync ( ) ) ;
358367 }
368+
359369 return new StorageHistory ( FileOperationType . Delete , source , null ) ;
360370 }
361371 else
362372 {
363373 if ( deleteResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . Unauthorized ) )
364374 {
365375 if ( await RequestAdminOperation ( ) )
366- {
367376 return await DeleteItemsAsync ( source , progress , permanently , cancellationToken ) ;
368- }
369377 }
370378 else if ( deleteResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) )
371379 {
372380 var failedSources = deleteResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) ;
373381 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
374382 var lockingProcess = WhoIsLocking ( filePath ) ;
383+
375384 switch ( await GetFileInUseDialog ( filePath , lockingProcess ) )
376385 {
377386 case DialogResult . Primary :
@@ -391,20 +400,18 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
391400 // Retry with StorageFile API
392401 var failedSources = deleteResult . Items . Where ( x => ! x . Succeeded ) ;
393402 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+
394404 return await filesystemOperations . DeleteItemsAsync ( sourceMatch , progress , permanently , cancellationToken ) ;
395405 }
396406 fsProgress . ReportStatus ( CopyEngineResult . Convert ( deleteResult . Items . FirstOrDefault ( x => ! x . Succeeded ) ? . HResult ) ) ;
407+
397408 return null ;
398409 }
399410 }
400411
401412 public async Task < IStorageHistory > MoveAsync ( IStorageItem source , string destination , NameCollisionOption collision , IProgress < FileSystemProgress > progress , CancellationToken cancellationToken )
402413 {
403- return await MoveAsync ( source . FromStorageItem ( ) ,
404- destination ,
405- collision ,
406- progress ,
407- cancellationToken ) ;
414+ return await MoveAsync ( source . FromStorageItem ( ) , destination , collision , progress , cancellationToken ) ;
408415 }
409416
410417 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
427434
428435 FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress ) ;
429436 fsProgress . Report ( ) ;
437+
430438 var sourceNoSkip = source . Zip ( collisions , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . Skip ) . Select ( item => item . src ) ;
431439 var destinationNoSkip = destination . Zip ( collisions , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . Skip ) . Select ( item => item . src ) ;
432440 var collisionsNoSkip = collisions . Where ( c => c != FileNameConflictResolveOptionType . Skip ) ;
433-
434441 var operationID = Guid . NewGuid ( ) . ToString ( ) ;
442+
435443 using var r = cancellationToken . Register ( CancelOperation , operationID , false ) ;
436444
437445 var sourceReplace = sourceNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll == FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
438446 var destinationReplace = destinationNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll == FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
439447 var sourceRename = sourceNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
440448 var destinationRename = destinationNoSkip . Zip ( collisionsNoSkip , ( src , coll ) => new { src , coll } ) . Where ( item => item . coll != FileNameConflictResolveOptionType . ReplaceExisting ) . Select ( item => item . src ) ;
441-
442449 var result = ( FilesystemResult ) true ;
443450 var moveResult = new ShellOperationResult ( ) ;
451+
444452 if ( sourceRename . Any ( ) )
445453 {
446454 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
461469 if ( result )
462470 {
463471 fsProgress . ReportStatus ( FileSystemStatusCode . Success ) ;
472+
464473 var movedSources = moveResult . Items . Where ( x => x . Succeeded && x . Destination is not null && x . Source != x . Destination ) ;
465474 if ( movedSources . Any ( ) )
466475 {
467476 var sourceMatch = await movedSources . Select ( x => sourceRename
468477 . SingleOrDefault ( s => s . Path . Equals ( x . Source , StringComparison . OrdinalIgnoreCase ) ) ) . Where ( x => x is not null ) . ToListAsync ( ) ;
478+
469479 return new StorageHistory ( FileOperationType . Move ,
470480 sourceMatch ,
471481 await movedSources . Zip ( sourceMatch , ( rSrc , oSrc ) => new { rSrc , oSrc } )
472482 . Select ( item => StorageHelpers . FromPathAndType ( item . rSrc . Destination , item . oSrc . ItemType ) ) . ToListAsync ( ) ) ;
473483 }
484+
474485 return null ; // Cannot undo overwrite operation
475486 }
476487 else
@@ -479,26 +490,27 @@ public async Task<IStorageHistory> MoveItemsAsync(IList<IStorageItemWithPath> so
479490 if ( moveResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . Unauthorized ) )
480491 {
481492 if ( await RequestAdminOperation ( ) )
482- {
483493 return await MoveItemsAsync ( source , destination , collisions , progress , cancellationToken ) ;
484- }
485494 }
486495 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 )
487496 {
488497 var destName = subtree . dest . Split ( Path . DirectorySeparatorChar , StringSplitOptions . RemoveEmptyEntries ) . Last ( ) ;
489498 var srcName = subtree . src . Path . Split ( Path . DirectorySeparatorChar , StringSplitOptions . RemoveEmptyEntries ) . Last ( ) ;
499+
490500 await DialogDisplayHelper . ShowDialogAsync ( "ErrorDialogThisActionCannotBeDone" . GetLocalizedResource ( ) , $ "{ "ErrorDialogTheDestinationFolder" . GetLocalizedResource ( ) } ({ destName } ) { "ErrorDialogIsASubfolder" . GetLocalizedResource ( ) } ({ srcName } )") ;
491501 }
492502 else if ( moveResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) )
493503 {
494504 var failedSources = moveResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) ;
495505 var filePath = failedSources . Select ( x => x . HResult == CopyEngineResult . COPYENGINE_E_SHARING_VIOLATION_SRC ? x . Source : x . Destination ) ;
496506 var lockingProcess = WhoIsLocking ( filePath ) ;
507+
497508 switch ( await GetFileInUseDialog ( filePath , lockingProcess ) )
498509 {
499510 case DialogResult . Primary :
500511 var moveZip = sourceNoSkip . Zip ( destinationNoSkip , ( src , dest ) => new { src , dest } ) . Zip ( collisionsNoSkip , ( z1 , coll ) => new { z1 . src , z1 . dest , coll } ) ;
501512 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+
502514 return await MoveItemsAsync (
503515 await sourceMatch . Select ( x => x . src ) . ToListAsync ( ) ,
504516 await sourceMatch . Select ( x => x . dest ) . ToListAsync ( ) ,
@@ -511,6 +523,7 @@ await sourceMatch.Select(x => x.dest).ToListAsync(),
511523 var failedSources = moveResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . NameTooLong ) ;
512524 var moveZip = sourceNoSkip . Zip ( destinationNoSkip , ( src , dest ) => new { src , dest } ) . Zip ( collisionsNoSkip , ( z1 , coll ) => new { z1 . src , z1 . dest , coll } ) ;
513525 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+
514527 return await filesystemOperations . MoveItemsAsync (
515528 await sourceMatch . Select ( x => x . src ) . ToListAsync ( ) ,
516529 await sourceMatch . Select ( x => x . dest ) . ToListAsync ( ) ,
@@ -530,11 +543,13 @@ await sourceMatch.Select(x => x.dest).ToListAsync(),
530543 var failedSources = moveResult . Items . Where ( x => ! x . Succeeded ) ;
531544 var moveZip = sourceNoSkip . Zip ( destinationNoSkip , ( src , dest ) => new { src , dest } ) . Zip ( collisionsNoSkip , ( z1 , coll ) => new { z1 . src , z1 . dest , coll } ) ;
532545 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+
533547 return await filesystemOperations . MoveItemsAsync (
534548 await sourceMatch . Select ( x => x . src ) . ToListAsync ( ) ,
535549 await sourceMatch . Select ( x => x . dest ) . ToListAsync ( ) ,
536550 await sourceMatch . Select ( x => x . coll ) . ToListAsync ( ) , progress , cancellationToken ) ;
537551 }
552+
538553 return null ;
539554 }
540555 }
@@ -554,41 +569,42 @@ public async Task<IStorageHistory> RenameAsync(IStorageItemWithPath source, stri
554569
555570 FileSystemProgress fsProgress = new ( progress , true , FileSystemStatusCode . InProgress ) ;
556571 fsProgress . Report ( ) ;
557- var renameResult = new ShellOperationResult ( ) ;
558572
573+ var renameResult = new ShellOperationResult ( ) ;
559574 var ( status , response ) = await FileOperationsHelpers . RenameItemAsync ( source . Path , newName , collision == NameCollisionOption . ReplaceExisting ) ;
560-
561575 var result = ( FilesystemResult ) status ;
576+
562577 renameResult . Items . AddRange ( response ? . Final ?? Enumerable . Empty < ShellOperationItemResult > ( ) ) ;
563578
564579 result &= ( FilesystemResult ) renameResult . Items . All ( x => x . Succeeded ) ;
565580
566581 if ( result )
567582 {
568583 fsProgress . ReportStatus ( FileSystemStatusCode . Success ) ;
584+
569585 var renamedSources = renameResult . Items . Where ( x => x . Succeeded && x . Destination is not null && x . Source != x . Destination )
570586 . Where ( x => new [ ] { source } . Select ( s => s . Path ) . Contains ( x . Source ) ) ;
571587 if ( renamedSources . Any ( ) )
572588 {
573589 return new StorageHistory ( FileOperationType . Rename , source ,
574590 StorageHelpers . FromPathAndType ( renamedSources . Single ( ) . Destination , source . ItemType ) ) ;
575591 }
592+
576593 return null ; // Cannot undo overwrite operation
577594 }
578595 else
579596 {
580597 if ( renameResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . Unauthorized ) )
581598 {
582599 if ( await RequestAdminOperation ( ) )
583- {
584600 return await RenameAsync ( source , newName , collision , progress , cancellationToken ) ;
585- }
586601 }
587602 else if ( renameResult . Items . Any ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) )
588603 {
589604 var failedSources = renameResult . Items . Where ( x => CopyEngineResult . Convert ( x . HResult ) == FileSystemStatusCode . InUse ) ;
590605 var filePath = failedSources . Select ( x => x . HResult == CopyEngineResult . COPYENGINE_E_SHARING_VIOLATION_SRC ? x . Source : x . Destination ) ;
591606 var lockingProcess = WhoIsLocking ( filePath ) ;
607+
592608 switch ( await GetFileInUseDialog ( filePath , lockingProcess ) )
593609 {
594610 case DialogResult . Primary :
@@ -613,7 +629,9 @@ public async Task<IStorageHistory> RenameAsync(IStorageItemWithPath source, stri
613629 // Retry with StorageFile API
614630 return await filesystemOperations . RenameAsync ( source , newName , collision , progress , cancellationToken ) ;
615631 }
632+
616633 fsProgress . ReportStatus ( CopyEngineResult . Convert ( renameResult . Items . FirstOrDefault ( x => ! x . Succeeded ) ? . HResult ) ) ;
634+
617635 return null ;
618636 }
619637 }
0 commit comments