Skip to content

Commit 7c241c9

Browse files
authored
Implement filesystem watching via ReadDirectoryChangesW (#952)
1 parent c28fa07 commit 7c241c9

13 files changed

+517
-416
lines changed

Files/App.xaml.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ private async void Connection_RequestReceived(AppServiceConnection sender, AppSe
136136
{
137137
// If we are currently displaying the reycle bin lets refresh the items
138138
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
139-
async () =>
139+
() =>
140140
{
141-
await App.CurrentInstance.ViewModel.RefreshItems();
141+
App.CurrentInstance.ViewModel.RefreshItems();
142142
});
143143
}
144144
}

Files/BaseLayout.cs

+4-8
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
138138
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
139139
}
140140

141-
protected override async void OnNavigatedTo(NavigationEventArgs eventArgs)
141+
protected override void OnNavigatedTo(NavigationEventArgs eventArgs)
142142
{
143143
base.OnNavigatedTo(eventArgs);
144144
// Add item jumping handler
@@ -153,7 +153,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs eventArgs)
153153
}
154154
App.CurrentInstance.NavigationToolbar.CanRefresh = true;
155155
IsItemSelected = false;
156-
AssociatedViewModel.EmptyTextState.IsVisible = Visibility.Collapsed;
156+
AssociatedViewModel.IsFolderEmptyTextDisplayed = false;
157157
App.CurrentInstance.ViewModel.WorkingDirectory = parameters;
158158

159159
// pathRoot will be empty on recycle bin path
@@ -170,7 +170,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs eventArgs)
170170
App.InteractionViewModel.IsPageTypeNotRecycleBin =
171171
!App.CurrentInstance.ViewModel.WorkingDirectory.StartsWith(App.AppSettings.RecycleBinPath);
172172

173-
await App.CurrentInstance.ViewModel.RefreshItems();
173+
App.CurrentInstance.ViewModel.RefreshItems();
174174

175175
App.Clipboard_ContentChanged(null, null);
176176
App.CurrentInstance.NavigationToolbar.PathControlDisplayText = parameters;
@@ -181,10 +181,6 @@ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
181181
base.OnNavigatingFrom(e);
182182
// Remove item jumping handler
183183
Window.Current.CoreWindow.CharacterReceived -= Page_CharacterReceived;
184-
if (App.CurrentInstance.ViewModel._fileQueryResult != null)
185-
{
186-
App.CurrentInstance.ViewModel._fileQueryResult.ContentsChanged -= App.CurrentInstance.ViewModel.FileContentsChanged;
187-
}
188184
App.AppSettings.LayoutModeChangeRequested -= AppSettings_LayoutModeChangeRequested;
189185
}
190186

@@ -289,7 +285,7 @@ private void Page_Loaded(object sender, RoutedEventArgs e)
289285
AssociatedInteractions = App.CurrentInstance.InteractionOperations;
290286
if (App.CurrentInstance == null)
291287
{
292-
App.CurrentInstance = ItemViewModel.GetCurrentSelectedTabInstance<ModernShellPage>();
288+
App.CurrentInstance = InstanceTabsView.GetCurrentSelectedTabInstance<ModernShellPage>();
293289
}
294290
}
295291
}

Files/Dialogs/AddItemDialog.xaml.cs

+9-30
Original file line numberDiff line numberDiff line change
@@ -67,42 +67,21 @@ public static async void CreateFile(AddItemType fileType)
6767

6868
if (fileType == AddItemType.Folder)
6969
{
70-
StorageFolder folder;
71-
if (!string.IsNullOrWhiteSpace(userInput))
72-
{
73-
folder = await folderToCreateItem.CreateFolderAsync(userInput, CreationCollisionOption.GenerateUniqueName);
74-
}
75-
else
76-
{
77-
folder = await folderToCreateItem.CreateFolderAsync(ResourceController.GetTranslation("NewFolder"), CreationCollisionOption.GenerateUniqueName);
78-
}
79-
TabInstance.ViewModel.AddFileOrFolder(new ListedItem(folder.FolderRelativeId) { PrimaryItemAttribute = StorageItemTypes.Folder, ItemName = folder.DisplayName, ItemDateModifiedReal = DateTimeOffset.Now, LoadUnknownTypeGlyph = false, LoadFolderGlyph = true, LoadFileIcon = false, ItemType = "Folder", FileImage = null, ItemPath = folder.Path });
70+
userInput = !string.IsNullOrWhiteSpace(userInput) ? userInput : ResourceController.GetTranslation("NewFolder");
71+
72+
await folderToCreateItem.CreateFolderAsync(userInput, CreationCollisionOption.GenerateUniqueName);
8073
}
8174
else if (fileType == AddItemType.TextDocument)
8275
{
83-
StorageFile item;
84-
if (!string.IsNullOrWhiteSpace(userInput))
85-
{
86-
item = await folderToCreateItem.CreateFileAsync(userInput + ".txt", CreationCollisionOption.GenerateUniqueName);
87-
}
88-
else
89-
{
90-
item = await folderToCreateItem.CreateFileAsync(ResourceController.GetTranslation("NewTextDocument") + ".txt", CreationCollisionOption.GenerateUniqueName);
91-
}
92-
TabInstance.ViewModel.AddFileOrFolder(new ListedItem(item.FolderRelativeId) { PrimaryItemAttribute = StorageItemTypes.File, ItemName = item.DisplayName, ItemDateModifiedReal = DateTimeOffset.Now, LoadUnknownTypeGlyph = true, LoadFolderGlyph = false, LoadFileIcon = false, ItemType = item.DisplayType, FileImage = null, ItemPath = item.Path, FileExtension = item.FileType });
76+
userInput = !string.IsNullOrWhiteSpace(userInput) ? userInput : ResourceController.GetTranslation("NewTextDocument");
77+
78+
await folderToCreateItem.CreateFileAsync(userInput + ".txt", CreationCollisionOption.GenerateUniqueName);
9379
}
9480
else if (fileType == AddItemType.BitmapImage)
9581
{
96-
StorageFile item;
97-
if (!string.IsNullOrWhiteSpace(userInput))
98-
{
99-
item = await folderToCreateItem.CreateFileAsync(userInput + ".bmp", CreationCollisionOption.GenerateUniqueName);
100-
}
101-
else
102-
{
103-
item = await folderToCreateItem.CreateFileAsync(ResourceController.GetTranslation("NewBitmapImage") + ".bmp", CreationCollisionOption.GenerateUniqueName);
104-
}
105-
TabInstance.ViewModel.AddFileOrFolder(new ListedItem(item.FolderRelativeId) { PrimaryItemAttribute = StorageItemTypes.File, ItemName = item.DisplayName, ItemDateModifiedReal = DateTimeOffset.Now, LoadUnknownTypeGlyph = true, LoadFolderGlyph = false, LoadFileIcon = false, ItemType = item.DisplayType, FileImage = null, ItemPath = item.Path, FileExtension = item.FileType });
82+
userInput = !string.IsNullOrWhiteSpace(userInput) ? userInput : ResourceController.GetTranslation("NewBitmapImage");
83+
84+
await folderToCreateItem.CreateFileAsync(userInput + ".bmp", CreationCollisionOption.GenerateUniqueName);
10685
}
10786
}
10887
}

Files/Files.csproj

+10-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<ErrorReport>prompt</ErrorReport>
3939
<Prefer32Bit>true</Prefer32Bit>
4040
<LangVersion>8.0</LangVersion>
41+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
4142
</PropertyGroup>
4243
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
4344
<OutputPath>bin\x86\Release\</OutputPath>
@@ -51,6 +52,7 @@
5152
<Prefer32Bit>true</Prefer32Bit>
5253
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
5354
<LangVersion>8.0</LangVersion>
55+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5456
</PropertyGroup>
5557
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
5658
<DebugSymbols>true</DebugSymbols>
@@ -63,6 +65,7 @@
6365
<ErrorReport>prompt</ErrorReport>
6466
<Prefer32Bit>true</Prefer32Bit>
6567
<LangVersion>8.0</LangVersion>
68+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
6669
</PropertyGroup>
6770
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
6871
<OutputPath>bin\ARM\Release\</OutputPath>
@@ -76,6 +79,7 @@
7679
<Prefer32Bit>true</Prefer32Bit>
7780
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
7881
<LangVersion>8.0</LangVersion>
82+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
7983
</PropertyGroup>
8084
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
8185
<DebugSymbols>true</DebugSymbols>
@@ -89,6 +93,7 @@
8993
<Prefer32Bit>true</Prefer32Bit>
9094
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
9195
<LangVersion>8.0</LangVersion>
96+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9297
</PropertyGroup>
9398
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
9499
<OutputPath>bin\ARM64\Release\</OutputPath>
@@ -102,6 +107,7 @@
102107
<Prefer32Bit>true</Prefer32Bit>
103108
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
104109
<LangVersion>8.0</LangVersion>
110+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
105111
</PropertyGroup>
106112
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
107113
<DebugSymbols>true</DebugSymbols>
@@ -114,6 +120,7 @@
114120
<ErrorReport>prompt</ErrorReport>
115121
<Prefer32Bit>true</Prefer32Bit>
116122
<LangVersion>8.0</LangVersion>
123+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
117124
</PropertyGroup>
118125
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
119126
<OutputPath>bin\x64\Release\</OutputPath>
@@ -127,6 +134,7 @@
127134
<Prefer32Bit>true</Prefer32Bit>
128135
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
129136
<LangVersion>8.0</LangVersion>
137+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
130138
</PropertyGroup>
131139
<PropertyGroup>
132140
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
@@ -154,6 +162,8 @@
154162
<Compile Include="Helpers\DialogDisplayHelper.cs" />
155163
<Compile Include="Helpers\DispatcherHelper.cs" />
156164
<Compile Include="Helpers\ItemsDataTemplateSelector.cs" />
165+
<Compile Include="Helpers\NativeDirectoryChangesHelper.cs" />
166+
<Compile Include="Helpers\NativeFindStorageItemHelper.cs" />
157167
<Compile Include="Helpers\NaturalStringComparer.cs" />
158168
<Compile Include="Helpers\PackageHelper.cs" />
159169
<Compile Include="Helpers\StringExtensions.cs" />
@@ -211,7 +221,6 @@
211221
<Compile Include="Views\InstanceTabsView.xaml.cs">
212222
<DependentUpon>InstanceTabsView.xaml</DependentUpon>
213223
</Compile>
214-
<Compile Include="Interacts\EmptyFolderTextState.cs" />
215224
<Compile Include="Interacts\Interaction.cs" />
216225
<Compile Include="Interacts\ItemInteractions.cs" />
217226
<Compile Include="IShellPage.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using Microsoft.Win32.SafeHandles;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Runtime.InteropServices;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Files.Helpers
11+
{
12+
public class NativeDirectoryChangesHelper
13+
{
14+
[DllImport("api-ms-win-core-handle-l1-1-0.dll")]
15+
public static extern bool CloseHandle(IntPtr hObject);
16+
17+
[DllImport("api-ms-win-core-io-l1-1-1.dll")]
18+
public static extern bool GetOverlappedResult(IntPtr hFile, OVERLAPPED lpOverlapped, out int lpNumberOfBytesTransferred, bool bWait);
19+
20+
[DllImport("api-ms-win-core-io-l1-1-1.dll")]
21+
public static extern bool CancelIo(IntPtr hFile);
22+
23+
[DllImport("api-ms-win-core-synch-l1-2-0.dll")]
24+
public static extern uint WaitForMultipleObjectsEx(uint nCount, IntPtr[] lpHandles, bool bWaitAll, uint dwMilliseconds, bool bAlertable);
25+
26+
[DllImport("api-ms-win-core-synch-l1-2-0.dll", SetLastError = true)]
27+
public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
28+
29+
[DllImport("api-ms-win-core-synch-l1-2-0.dll", SetLastError = true)]
30+
public static extern bool ResetEvent(IntPtr hEvent);
31+
32+
[DllImport("api-ms-win-core-synch-l1-2-0.dll", SetLastError = true)]
33+
public static extern UInt32 WaitForSingleObjectEx(IntPtr hHandle, UInt32 dwMilliseconds, bool bAlertable);
34+
35+
public enum File_Attributes : uint
36+
{
37+
Readonly = 0x00000001,
38+
Hidden = 0x00000002,
39+
System = 0x00000004,
40+
Directory = 0x00000010,
41+
Archive = 0x00000020,
42+
Device = 0x00000040,
43+
Normal = 0x00000080,
44+
Temporary = 0x00000100,
45+
SparseFile = 0x00000200,
46+
ReparsePoint = 0x00000400,
47+
Compressed = 0x00000800,
48+
Offline = 0x00001000,
49+
NotContentIndexed = 0x00002000,
50+
Encrypted = 0x00004000,
51+
Write_Through = 0x80000000,
52+
Overlapped = 0x40000000,
53+
NoBuffering = 0x20000000,
54+
RandomAccess = 0x10000000,
55+
SequentialScan = 0x08000000,
56+
DeleteOnClose = 0x04000000,
57+
BackupSemantics = 0x02000000,
58+
PosixSemantics = 0x01000000,
59+
OpenReparsePoint = 0x00200000,
60+
OpenNoRecall = 0x00100000,
61+
FirstPipeInstance = 0x00080000
62+
}
63+
64+
public const uint GENERIC_READ = 0x80000000;
65+
66+
[DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto,
67+
CallingConvention = CallingConvention.StdCall,
68+
SetLastError = true)]
69+
public static extern IntPtr CreateFileFromApp(
70+
string lpFileName,
71+
uint dwDesiredAccess,
72+
uint dwShareMode,
73+
IntPtr SecurityAttributes,
74+
uint dwCreationDisposition,
75+
uint dwFlagsAndAttributes,
76+
IntPtr hTemplateFile
77+
);
78+
79+
[DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto,
80+
CallingConvention = CallingConvention.StdCall,
81+
SetLastError = true)]
82+
public static extern IntPtr CreateFile2FromApp(
83+
string lpFileName,
84+
uint dwDesiredAccess,
85+
uint dwShareMode,
86+
uint dwCreationDisposition,
87+
IntPtr pCreateExParams
88+
);
89+
90+
public delegate void LpoverlappedCompletionRoutine(uint dwErrorCode,
91+
uint dwNumberOfBytesTransfered,
92+
OVERLAPPED lpOverlapped
93+
);
94+
95+
public unsafe struct OVERLAPPED
96+
{
97+
public IntPtr Internal;
98+
public IntPtr InternalHigh;
99+
public Union PointerAndOffset;
100+
public IntPtr hEvent;
101+
102+
[StructLayout(LayoutKind.Explicit)]
103+
public struct Union
104+
{
105+
[FieldOffset(0)] public void* IntPtr;
106+
[FieldOffset(0)] public OffsetPair Offset;
107+
108+
public struct OffsetPair { public uint Offset; public uint OffsetHigh; }
109+
}
110+
}
111+
112+
public const int FILE_NOTIFY_CHANGE_FILE_NAME = 1;
113+
public const int FILE_NOTIFY_CHANGE_DIR_NAME = 2;
114+
115+
public unsafe struct FILE_NOTIFY_INFORMATION
116+
{
117+
public uint NextEntryOffset;
118+
public uint Action;
119+
public uint FileNameLength;
120+
public fixed char FileName[1];
121+
}
122+
123+
[DllImport("api-ms-win-core-file-l2-1-0.dll", SetLastError = true, CharSet = CharSet.Unicode)]
124+
public unsafe static extern bool ReadDirectoryChangesW(IntPtr hDirectory, byte* lpBuffer,
125+
int nBufferLength, bool bWatchSubtree, int dwNotifyFilter, int*
126+
lpBytesReturned, ref OVERLAPPED lpOverlapped,
127+
LpoverlappedCompletionRoutine lpCompletionRoutine);
128+
}
129+
}

0 commit comments

Comments
 (0)