Skip to content

Commit

Permalink
Auto refresh file list in network shares (#8831)
Browse files Browse the repository at this point in the history
  • Loading branch information
gave92 authored Apr 8, 2022
1 parent 3bb93d3 commit 34a1b02
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/Files.Launcher/MessageHandlers/RecycleBinHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ private async void RecycleBinWatcher_Changed(object sender, FileSystemEventArgs
};
if (e.ChangeType == WatcherChangeTypes.Created)
{
using var folderItem = new ShellItem(e.FullPath);
using var folderItem = SafetyExtensions.IgnoreExceptions(() => new ShellItem(e.FullPath));
if (folderItem == null) return;
var shellFileItem = ShellFolderExtensions.GetShellFileItem(folderItem);
response["Item"] = JsonConvert.SerializeObject(shellFileItem);
}
Expand Down
117 changes: 116 additions & 1 deletion src/Files.Launcher/MessageHandlers/Win32MessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Vanara.Windows.Shell;
using Windows.ApplicationModel;
using Windows.Foundation.Collections;
Expand All @@ -20,11 +23,18 @@ namespace FilesFullTrust.MessageHandlers
[SupportedOSPlatform("Windows10.0.10240")]
public class Win32MessageHandler : Disposable, IMessageHandler
{
private IList<FileSystemWatcher> dirWatchers;
private PipeStream connection;

public void Initialize(PipeStream connection)
{
this.connection = connection;

DetectIsSetAsDefaultFileManager();
DetectIsSetAsOpenFileDialog();
ApplicationData.Current.LocalSettings.Values["TEMP"] = Environment.GetEnvironmentVariable("TEMP");

dirWatchers = new List<FileSystemWatcher>();
}

private static void DetectIsSetAsDefaultFileManager()
Expand Down Expand Up @@ -159,7 +169,7 @@ public async Task ParseArgumentsAsync(PipeStream connection, Dictionary<string,
return;
}
}

var dataPath = Environment.ExpandEnvironmentVariables("%LocalAppData%\\Files");
if (enable)
{
Expand Down Expand Up @@ -234,6 +244,111 @@ public async Task ParseArgumentsAsync(PipeStream connection, Dictionary<string,
await Win32API.SendMessageAsync(connection, new ValueSet() { { "FileAssociation", await Win32API.GetFileAssociationAsync(filePath, true) } }, message.Get("RequestID", (string)null));
}
break;

case "WatchDirectory":
var watchAction = (string)message["action"];
await ParseWatchDirectoryActionAsync(connection, message, watchAction);
break;
}
}

private async Task ParseWatchDirectoryActionAsync(PipeStream connection, Dictionary<string, object> message, string action)
{
switch (action)
{
case "start":
{
var res = new ValueSet();
var folderPath = (string)message["folderPath"];
if (Directory.Exists(folderPath))
{
var watcher = new FileSystemWatcher
{
Path = folderPath,
Filter = "*.*",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName
};
watcher.Created += DirectoryWatcher_Changed;
watcher.Deleted += DirectoryWatcher_Changed;
watcher.Renamed += DirectoryWatcher_Changed;
watcher.EnableRaisingEvents = true;
res.Add("watcherID", watcher.GetHashCode());
dirWatchers.Add(watcher);
}
await Win32API.SendMessageAsync(connection, res, message.Get("RequestID", (string)null));
}
break;

case "cancel":
{
var watcherID = (long)message["watcherID"];
var watcher = dirWatchers.SingleOrDefault(x => x.GetHashCode() == watcherID);
if (watcher != null)
{
dirWatchers.Remove(watcher);
watcher.Dispose();
}
}
break;
}
}

private async void DirectoryWatcher_Changed(object sender, FileSystemEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"Directory watcher event: {e.ChangeType}, {e.FullPath}");
if (connection?.IsConnected ?? false)
{
var response = new ValueSet()
{
{ "FileSystem", Path.GetDirectoryName(e.FullPath) },
{ "Name", e.Name },
{ "Path", e.FullPath },
{ "Type", e.ChangeType.ToString() },
{ "WatcherID", sender.GetHashCode() },
};
if (e.ChangeType == WatcherChangeTypes.Created)
{
var shellFileItem = await GetShellFileItemAsync(e.FullPath);
if (shellFileItem == null) return;
response["Item"] = JsonConvert.SerializeObject(shellFileItem);
}
else if (e.ChangeType == WatcherChangeTypes.Renamed)
{
response["OldPath"] = (e as RenamedEventArgs).OldFullPath;
}
// Send message to UWP app to refresh items
await Win32API.SendMessageAsync(connection, response);
}
}

private async Task<ShellFileItem> GetShellFileItemAsync(string fullPath)
{
while (true)
{
using var hFile = Kernel32.CreateFile(fullPath, Kernel32.FileAccess.GENERIC_READ, FileShare.Read, null, FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS);
if (!hFile.IsInvalid)
{
using var folderItem = SafetyExtensions.IgnoreExceptions(() => new ShellItem(fullPath));
if (folderItem == null) return null;
return ShellFolderExtensions.GetShellFileItem(folderItem);
}
var lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (lastError != Win32Error.ERROR_SHARING_VIOLATION && lastError != Win32Error.ERROR_LOCK_VIOLATION)
{
return null;
}
await Task.Delay(200);
}
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var watcher in dirWatchers)
{
watcher.Dispose();
}
}
}
}
Expand Down
78 changes: 71 additions & 7 deletions src/Files.Uwp/ViewModels/ItemViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public class ItemViewModel : ObservableObject, IDisposable

private IFileListCache fileListCache = FileListCacheController.GetInstance();

public event EventHandler ConnectionChanged;

private NamedPipeAsAppServiceConnection connection;

private NamedPipeAsAppServiceConnection Connection
Expand All @@ -96,6 +98,7 @@ private NamedPipeAsAppServiceConnection Connection
{
connection.RequestReceived += Connection_RequestReceived;
}
ConnectionChanged?.Invoke(this, EventArgs.Empty);
}
}

Expand Down Expand Up @@ -431,6 +434,20 @@ private async void Connection_RequestReceived(object sender, Dictionary<string,
}
break;

case "Renamed":
var matchingItem = filesAndFolders.FirstOrDefault(x => x.ItemPath.Equals((string)message["OldPath"], StringComparison.OrdinalIgnoreCase));
if (matchingItem != null)
{
await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() =>
{
matchingItem.ItemPath = itemPath;
matchingItem.ItemNameRaw = (string)message["Name"];
});
await OrderFilesAndFoldersAsync();
await ApplySingleFileChangeAsync(matchingItem);
}
break;

case "Deleted":
// get the item that immediately follows matching item to be removed
// if the matching item is the last item, try to get the previous item; otherwise, null
Expand Down Expand Up @@ -1287,7 +1304,6 @@ private async Task RapidAddItemsToCollection(string path, LibraryItem library =
currentStorageFolder ??= await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderWithPathFromPathAsync(path));
var syncStatus = await CheckCloudDriveSyncStatusAsync(currentStorageFolder?.Item);
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = syncStatus != CloudDriveSyncStatus.NotSynced && syncStatus != CloudDriveSyncStatus.Unknown });

WatchForDirectoryChanges(path, syncStatus);
break;

Expand All @@ -1297,7 +1313,11 @@ private async Task RapidAddItemsToCollection(string path, LibraryItem library =
WatchForStorageFolderChanges(currentStorageFolder?.Folder);
break;

case 2: // Do no watch for changes in Box Drive folder to avoid constant refresh (#7428)
case 2: // Watch for changes using FTP in Box Drive folder (#7428) and network drives (#5869)
PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false });
WatchForWin32FolderChanges(path);
break;

case -1: // Enumeration failed
default:
break;
Expand Down Expand Up @@ -1489,9 +1509,10 @@ await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () =>
public async Task<int> EnumerateItemsFromStandardFolderAsync(string path, Type sourcePageType, CancellationToken cancellationToken, LibraryItem library = null)
{
// Flag to use FindFirstFileExFromApp or StorageFolder enumeration
var isBoxFolder = App.CloudDrivesManager.Drives.FirstOrDefault(x => x.Text == "Box")?.Path?.TrimEnd('\\') is string boxFolder ?
path.StartsWith(boxFolder) : false;
bool enumFromStorageFolder = isBoxFolder; // Use storage folder for Box Drive (#4629)
var isBoxFolder = App.CloudDrivesManager.Drives.FirstOrDefault(x => x.Text == "Box")?.Path?.TrimEnd('\\') is string boxFolder ?
path.StartsWith(boxFolder) : false; // Use storage folder for Box Drive (#4629)
var isNetworkFolder = System.Text.RegularExpressions.Regex.IsMatch(path, @"^\\\\(?!\?)"); // Use storage folder for network drives (*FromApp methods return access denied)
bool enumFromStorageFolder = isBoxFolder || isNetworkFolder;

BaseStorageFolder rootFolder = null;

Expand Down Expand Up @@ -1582,7 +1603,7 @@ await DialogDisplayHelper.ShowDialogAsync(
}
CurrentFolder = currentFolder;
await EnumFromStorageFolderAsync(path, currentFolder, rootFolder, currentStorageFolder, sourcePageType, cancellationToken);
return isBoxFolder ? 2 : 1; // Workaround for #7428
return isBoxFolder || isNetworkFolder ? 2 : 1; // Workaround for #7428
}
else
{
Expand Down Expand Up @@ -1758,6 +1779,45 @@ await Task.Run(() =>
});
}

private async void WatchForWin32FolderChanges(string folderPath)
{
void RestartWatcher(object sender, EventArgs e)
{
if (Connection != null)
{
ConnectionChanged -= RestartWatcher;
WatchForWin32FolderChanges(folderPath);
}
}
if (Connection != null)
{
var (status, response) = await Connection.SendMessageForResponseAsync(new ValueSet()
{
{ "Arguments", "WatchDirectory" },
{ "action", "start" },
{ "folderPath", folderPath }
});
if (status == AppServiceResponseStatus.Success
&& response.ContainsKey("watcherID"))
{
ConnectionChanged += RestartWatcher;
watcherCTS.Token.Register(async () =>
{
ConnectionChanged -= RestartWatcher;
if (Connection != null)
{
await Connection.SendMessageAsync(new ValueSet()
{
{ "Arguments", "WatchDirectory" },
{ "action", "cancel" },
{ "watcherID", (long)response["watcherID"] }
});
}
});
}
}
}

private async void ItemQueryResult_ContentsChanged(IStorageQueryResultBase sender, object args)
{
//query options have to be reapplied otherwise old results are returned
Expand Down Expand Up @@ -2122,7 +2182,11 @@ private async Task AddFileOrFolderAsync(ListedItem item)
return;
}

filesAndFolders.Add(item);
if (!filesAndFolders.Any(x => x.ItemPath.Equals(item.ItemPath, StringComparison.OrdinalIgnoreCase))) // Avoid adding duplicate items
{
filesAndFolders.Add(item);
}

enumFolderSemaphore.Release();
}

Expand Down

0 comments on commit 34a1b02

Please sign in to comment.