@@ -319,6 +319,122 @@ public bool IsNetworkDiscoveryInfoBarOpen
319319 set => SetProperty ( ref isNetworkDiscoveryInfoBarOpen , value ) ;
320320 }
321321
322+ private bool isLocationUnavailable ;
323+ public bool IsLocationUnavailable
324+ {
325+ get => isLocationUnavailable ;
326+ set
327+ {
328+ if ( SetProperty ( ref isLocationUnavailable , value ) )
329+ UpdateEmptyTextType ( ) ;
330+ }
331+ }
332+
333+ private string ? locationUnavailableGlyph ;
334+ public string ? LocationUnavailableGlyph
335+ {
336+ get => locationUnavailableGlyph ;
337+ set => SetProperty ( ref locationUnavailableGlyph , value ) ;
338+ }
339+
340+ private string ? locationUnavailableTitle ;
341+ public string ? LocationUnavailableTitle
342+ {
343+ get => locationUnavailableTitle ;
344+ set => SetProperty ( ref locationUnavailableTitle , value ) ;
345+ }
346+
347+ private string ? locationUnavailableMessage ;
348+ public string ? LocationUnavailableMessage
349+ {
350+ get => locationUnavailableMessage ;
351+ set => SetProperty ( ref locationUnavailableMessage , value ) ;
352+ }
353+
354+ private enum LocationUnavailableKind
355+ {
356+ AccessDenied ,
357+ NotFound ,
358+ DriveUnplugged ,
359+ }
360+
361+ private void ShowLocationUnavailable ( LocationUnavailableKind kind , string ? message = null )
362+ {
363+ ( LocationUnavailableGlyph , LocationUnavailableTitle , LocationUnavailableMessage ) = kind switch
364+ {
365+ LocationUnavailableKind . AccessDenied => ( "\uE72E " , Strings . AccessDenied . GetLocalizedResource ( ) , Strings . AccessDeniedToFolder . GetLocalizedResource ( ) ) ,
366+ LocationUnavailableKind . NotFound => ( "\uE838 " , Strings . FolderNotFoundDialogTitle . GetLocalizedResource ( ) , Strings . FolderNotFoundDialogText . GetLocalizedResource ( ) ) ,
367+ _ => ( "\uE7BA " , Strings . DriveUnpluggedDialogTitle . GetLocalizedResource ( ) , message ?? Strings . DriveUnpluggedDialogText . GetLocalizedResource ( ) ) ,
368+ } ;
369+
370+ IsLocationUnavailable = true ;
371+ }
372+
373+ private void ShowLocationInaccessibleOrMissing ( string path )
374+ {
375+ // A folder pending deletion fails enumeration with ERROR_ACCESS_DENIED;
376+ // Directory.Exists is false for it but true for folders that deny listing
377+ if ( Directory . Exists ( path ) )
378+ {
379+ ShowLocationUnavailable ( LocationUnavailableKind . AccessDenied ) ;
380+ }
381+ else
382+ {
383+ ShowLocationUnavailable ( LocationUnavailableKind . NotFound ) ;
384+ WatchForLocationRestoration ( path ) ;
385+ }
386+ }
387+
388+ private FileSystemWatcher ? locationRestorationWatcher ;
389+
390+ private void WatchForLocationRestoration ( string path )
391+ {
392+ StopWatchingForLocationRestoration ( ) ;
393+
394+ var trimmedPath = path . TrimEnd ( Path . DirectorySeparatorChar ) ;
395+ var parentPath = Path . GetDirectoryName ( trimmedPath ) ;
396+ var folderName = Path . GetFileName ( trimmedPath ) ;
397+ if ( string . IsNullOrEmpty ( parentPath ) || string . IsNullOrEmpty ( folderName ) || ! Directory . Exists ( parentPath ) )
398+ return ;
399+
400+ try
401+ {
402+ var restorationWatcher = new FileSystemWatcher ( parentPath , folderName )
403+ {
404+ NotifyFilter = NotifyFilters . DirectoryName
405+ } ;
406+ restorationWatcher . Created += LocationRestorationWatcher_Restored ;
407+ restorationWatcher . Renamed += LocationRestorationWatcher_Restored ;
408+ locationRestorationWatcher = restorationWatcher ;
409+ restorationWatcher . EnableRaisingEvents = true ;
410+ }
411+ catch ( Exception ex ) when ( ex is IOException or UnauthorizedAccessException )
412+ {
413+ // Parent folder was removed or is inaccessible
414+ StopWatchingForLocationRestoration ( ) ;
415+ return ;
416+ }
417+
418+ // The folder may have been restored before the watcher was armed
419+ if ( Directory . Exists ( path ) )
420+ LocationRestorationWatcher_Restored ( this , new FileSystemEventArgs ( WatcherChangeTypes . Created , parentPath , folderName ) ) ;
421+ }
422+
423+ private void StopWatchingForLocationRestoration ( )
424+ {
425+ if ( Interlocked . Exchange ( ref locationRestorationWatcher , null ) is FileSystemWatcher restorationWatcher )
426+ restorationWatcher . Dispose ( ) ;
427+ }
428+
429+ private async void LocationRestorationWatcher_Restored ( object sender , FileSystemEventArgs e )
430+ {
431+ if ( Interlocked . Exchange ( ref locationRestorationWatcher , null ) is not FileSystemWatcher restorationWatcher )
432+ return ;
433+
434+ restorationWatcher . Dispose ( ) ;
435+ await dispatcherQueue . EnqueueOrInvokeAsync ( ( ) => RefreshItems ( null ) ) ;
436+ }
437+
322438 private NetworkAvailability networkAvailability = NetworkAvailability . All ;
323439 public NetworkAvailability NetworkAvailability
324440 {
@@ -823,7 +939,7 @@ public string? SearchHeaderTitle
823939
824940 public void UpdateEmptyTextType ( )
825941 {
826- var isFolderEmpty = FilesAndFolders . Count == 0 ;
942+ var isFolderEmpty = FilesAndFolders . Count == 0 && ! IsLocationUnavailable ;
827943
828944 EmptyTextType = isFolderEmpty ? ( IsSearchResults ? EmptyTextType . NoSearchResultsFound : EmptyTextType . FolderEmpty ) : EmptyTextType . None ;
829945 }
@@ -1735,6 +1851,8 @@ private async Task RapidAddItemsToCollectionAsync(string path, string? previousD
17351851 {
17361852 IsSearchResults = false ;
17371853 HasNoWatcher = false ;
1854+ IsLocationUnavailable = false ;
1855+ StopWatchingForLocationRestoration ( ) ;
17381856 ItemLoadStatusChanged ? . Invoke ( this , new ItemLoadStatusChangedEventArgs ( ) { Status = ItemLoadStatusChangedEventArgs . ItemLoadStatus . Starting } ) ;
17391857
17401858 CancelLoadAndClearFiles ( ) ;
@@ -1938,25 +2056,19 @@ private async Task<int> EnumerateItemsFromStandardFolderAsync(string path, Cance
19382056 }
19392057 else if ( res == FileSystemStatusCode . Unauthorized )
19402058 {
1941- await DialogDisplayHelper . ShowDialogAsync (
1942- Strings . AccessDenied . GetLocalizedResource ( ) ,
1943- Strings . AccessDeniedToFolder . GetLocalizedResource ( ) ) ;
2059+ ShowLocationInaccessibleOrMissing ( path ) ;
19442060
19452061 return - 1 ;
19462062 }
19472063 else if ( res == FileSystemStatusCode . NotFound )
19482064 {
1949- await DialogDisplayHelper . ShowDialogAsync (
1950- Strings . FolderNotFoundDialogTitle . GetLocalizedResource ( ) ,
1951- Strings . FolderNotFoundDialogText . GetLocalizedResource ( ) ) ;
2065+ ShowLocationInaccessibleOrMissing ( path ) ;
19522066
19532067 return - 1 ;
19542068 }
19552069 else
19562070 {
1957- await DialogDisplayHelper . ShowDialogAsync (
1958- Strings . DriveUnpluggedDialogTitle . GetLocalizedResource ( ) ,
1959- res . ErrorCode . ToString ( ) ) ;
2071+ ShowLocationUnavailable ( LocationUnavailableKind . DriveUnplugged , res . ErrorCode . ToString ( ) ) ;
19602072
19612073 return - 1 ;
19622074 }
@@ -2056,7 +2168,7 @@ await DialogDisplayHelper.ShowDialogAsync(
20562168
20572169 if ( hFile == IntPtr . Zero )
20582170 {
2059- await DialogDisplayHelper . ShowDialogAsync ( Strings . DriveUnpluggedDialogTitle . GetLocalizedResource ( ) , "" ) ;
2171+ ShowLocationUnavailable ( LocationUnavailableKind . DriveUnplugged ) ;
20602172
20612173 return - 1 ;
20622174 }
@@ -2067,9 +2179,7 @@ await DialogDisplayHelper.ShowDialogAsync(
20672179 // errorCode == ERROR_ACCESS_DENIED
20682180 if ( filesAndFolders . Count == 0 && errorCode == 0x5 )
20692181 {
2070- await DialogDisplayHelper . ShowDialogAsync (
2071- Strings . AccessDenied . GetLocalizedResource ( ) ,
2072- Strings . AccessDeniedToFolder . GetLocalizedResource ( ) ) ;
2182+ ShowLocationInaccessibleOrMissing ( path ) ;
20732183
20742184 return - 1 ;
20752185 }
@@ -3029,6 +3139,7 @@ public void UpdateDateDisplay()
30293139 public void Dispose ( )
30303140 {
30313141 CancelLoadAndClearFiles ( ) ;
3142+ StopWatchingForLocationRestoration ( ) ;
30323143 filterDebounceCS ? . Cancel ( ) ;
30333144 filterDebounceCS ? . Dispose ( ) ;
30343145 networkAvailabilityCTS ? . Dispose ( ) ;
0 commit comments