1
1
using Files . Common ;
2
2
using Newtonsoft . Json ;
3
- using NLog ;
4
3
using System ;
5
4
using System . Collections . Generic ;
6
5
using System . ComponentModel ;
15
14
using Windows . ApplicationModel . AppService ;
16
15
using Windows . Foundation . Collections ;
17
16
using Windows . Storage ;
17
+ using static Vanara . PInvoke . Shell32 ;
18
18
19
19
namespace FilesFullTrust
20
20
{
@@ -25,7 +25,7 @@ internal class Program
25
25
[ STAThread ]
26
26
private static void Main ( string [ ] args )
27
27
{
28
- Windows . Storage . StorageFolder storageFolder = Windows . Storage . ApplicationData . Current . LocalFolder ;
28
+ StorageFolder storageFolder = ApplicationData . Current . LocalFolder ;
29
29
NLog . LogManager . Configuration = new NLog . Config . XmlLoggingConfiguration ( Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , "NLog.config" ) ) ;
30
30
NLog . LogManager . Configuration . Variables [ "LogPath" ] = storageFolder . Path ;
31
31
@@ -45,14 +45,31 @@ private static void Main(string[] args)
45
45
try
46
46
{
47
47
// Create shell COM object and get recycle bin folder
48
- recycler = new ShellFolder ( Vanara . PInvoke . Shell32 . KNOWNFOLDERID . FOLDERID_RecycleBinFolder ) ;
49
- Windows . Storage . ApplicationData . Current . LocalSettings . Values [ "RecycleBin_Title" ] = recycler . Name ;
50
-
51
- // Create shell watcher to monitor recycle bin folder
52
- watcher = new ShellItemChangeWatcher ( recycler , false ) ;
53
- watcher . NotifyFilter = ChangeFilters . AllDiskEvents ;
54
- watcher . Changed += Watcher_Changed ;
55
- //watcher.EnableRaisingEvents = true; // TODO: uncomment this when updated library is released
48
+ recycler = new ShellFolder ( KNOWNFOLDERID . FOLDERID_RecycleBinFolder ) ;
49
+ ApplicationData . Current . LocalSettings . Values [ "RecycleBin_Title" ] = recycler . Name ;
50
+
51
+ // Create filesystem watcher to monitor recycle bin folder(s)
52
+ // SHChangeNotifyRegister only works if recycle bin is open in explorer :(
53
+ watchers = new List < FileSystemWatcher > ( ) ;
54
+ var sid = System . Security . Principal . WindowsIdentity . GetCurrent ( ) . User . ToString ( ) ;
55
+ foreach ( var drive in DriveInfo . GetDrives ( ) )
56
+ {
57
+ var recycle_path = Path . Combine ( drive . Name , "$Recycle.Bin" , sid ) ;
58
+ if ( ! Directory . Exists ( recycle_path ) )
59
+ {
60
+ continue ;
61
+ }
62
+ var watcher = new FileSystemWatcher ( ) ;
63
+ watcher . Path = recycle_path ;
64
+ watcher . Filter = "*.*" ;
65
+ watcher . NotifyFilter = NotifyFilters . LastWrite
66
+ | NotifyFilters . FileName
67
+ | NotifyFilters . DirectoryName ;
68
+ watcher . Created += Watcher_Changed ;
69
+ watcher . Deleted += Watcher_Changed ;
70
+ watcher . EnableRaisingEvents = true ;
71
+ watchers . Add ( watcher ) ;
72
+ }
56
73
57
74
// Connect to app service and wait until the connection gets closed
58
75
appServiceExit = new AutoResetEvent ( false ) ;
@@ -62,7 +79,10 @@ private static void Main(string[] args)
62
79
finally
63
80
{
64
81
connection ? . Dispose ( ) ;
65
- watcher ? . Dispose ( ) ;
82
+ foreach ( var watcher in watchers )
83
+ {
84
+ watcher . Dispose ( ) ;
85
+ }
66
86
recycler ? . Dispose ( ) ;
67
87
appServiceExit ? . Dispose ( ) ;
68
88
mutex ? . ReleaseMutex ( ) ;
@@ -75,20 +95,24 @@ private static void UnhandledExceptionTrapper(object sender, UnhandledExceptionE
75
95
Logger . Error ( exception , exception . Message ) ;
76
96
}
77
97
78
- private static async void Watcher_Changed ( object sender , ShellItemChangeWatcher . ShellItemChangeEventArgs e )
98
+ private static async void Watcher_Changed ( object sender , FileSystemEventArgs e )
79
99
{
80
- Console . WriteLine ( $ "File : { e . ChangedItems . FirstOrDefault ( ) ? . FileSystemPath } { e . ChangeType } " ) ;
100
+ Debug . WriteLine ( "Reycle bin event : {0}, {1}" , e . ChangeType , e . FullPath ) ;
81
101
if ( connection != null )
82
102
{
83
103
// Send message to UWP app to refresh items
84
- await connection . SendMessageAsync ( new ValueSet ( ) { { "FileSystem" , @"Shell:RecycleBinFolder" } , { "Path" , e . ChangedItems . FirstOrDefault ( ) ? . FileSystemPath } , { "Type" , e . ChangeType . ToString ( ) } } ) ;
104
+ await connection . SendMessageAsync ( new ValueSet ( ) {
105
+ { "FileSystem" , @"Shell:RecycleBinFolder" } ,
106
+ { "Path" , e . FullPath } ,
107
+ { "Type" , e . ChangeType . ToString ( ) }
108
+ } ) ;
85
109
}
86
110
}
87
111
88
112
private static AppServiceConnection connection ;
89
113
private static AutoResetEvent appServiceExit ;
90
114
private static ShellFolder recycler ;
91
- private static ShellItemChangeWatcher watcher ;
115
+ private static IList < FileSystemWatcher > watchers ;
92
116
93
117
private static async void InitializeAppServiceConnection ( )
94
118
{
@@ -126,7 +150,7 @@ private static async void Connection_RequestReceived(AppServiceConnection sender
126
150
// Instead a single instance of the process is running
127
151
// Requests from UWP app are sent via AppService connection
128
152
var arguments = ( string ) args . Request . Message [ "Arguments" ] ;
129
- var localSettings = Windows . Storage . ApplicationData . Current . LocalSettings ;
153
+ var localSettings = ApplicationData . Current . LocalSettings ;
130
154
Logger . Info ( $ "Argument: { arguments } ") ;
131
155
132
156
await parseArguments ( args , messageDeferral , arguments , localSettings ) ;
@@ -180,8 +204,8 @@ private static async Task parseArguments(AppServiceRequestReceivedEventArgs args
180
204
#if DEBUG
181
205
// In debug mode this kills this process too??
182
206
#else
183
- var pid = ( int ) args . Request . Message [ "pid" ] ;
184
- Process . GetProcessById ( pid ) . Kill ( ) ;
207
+ var pid = ( int ) args . Request . Message [ "pid" ] ;
208
+ Process . GetProcessById ( pid ) . Kill ( ) ;
185
209
#endif
186
210
187
211
Process process = new Process ( ) ;
@@ -201,7 +225,7 @@ private static async Task parseArguments(AppServiceRequestReceivedEventArgs args
201
225
case "ParseAguments" :
202
226
var responseArray = new ValueSet ( ) ;
203
227
var resultArgument = Win32API . CommandLineToArgs ( ( string ) args . Request . Message [ "Command" ] ) ;
204
- responseArray . Add ( "ParsedArguments" , Newtonsoft . Json . JsonConvert . SerializeObject ( resultArgument ) ) ;
228
+ responseArray . Add ( "ParsedArguments" , JsonConvert . SerializeObject ( resultArgument ) ) ;
205
229
await args . Request . SendResponseAsync ( responseArray ) ;
206
230
break ;
207
231
@@ -236,23 +260,27 @@ private static async Task parseRecycleBinAction(AppServiceRequestReceivedEventAr
236
260
{
237
261
case "Empty" :
238
262
// Shell function to empty recyclebin
239
- Vanara . PInvoke . Shell32 . SHEmptyRecycleBin ( IntPtr . Zero , null , Vanara . PInvoke . Shell32 . SHERB . SHERB_NOCONFIRMATION | Vanara . PInvoke . Shell32 . SHERB . SHERB_NOPROGRESSUI ) ;
263
+ SHEmptyRecycleBin ( IntPtr . Zero , null , SHERB . SHERB_NOCONFIRMATION | SHERB . SHERB_NOPROGRESSUI ) ;
240
264
break ;
241
265
242
266
case "Query" :
243
267
var responseQuery = new ValueSet ( ) ;
244
- Win32API . SHQUERYRBINFO queryBinInfo = new Win32API . SHQUERYRBINFO ( ) ;
245
- queryBinInfo . cbSize = ( uint ) Marshal . SizeOf ( typeof ( Win32API . SHQUERYRBINFO ) ) ;
246
- var res = Win32API . SHQueryRecycleBin ( "" , ref queryBinInfo ) ;
247
- // TODO: use this when updated library is released
248
- //Vanara.PInvoke.Shell32.SHQUERYRBINFO queryBinInfo = new Vanara.PInvoke.Shell32.SHQUERYRBINFO();
249
- //Vanara.PInvoke.Shell32.SHQueryRecycleBin(null, ref queryBinInfo);
268
+ SHQUERYRBINFO queryBinInfo = new SHQUERYRBINFO ( ) ;
269
+ queryBinInfo . cbSize = ( uint ) Marshal . SizeOf ( queryBinInfo ) ;
270
+ var res = SHQueryRecycleBin ( null , ref queryBinInfo ) ;
250
271
if ( res == Vanara . PInvoke . HRESULT . S_OK )
251
272
{
252
273
var numItems = queryBinInfo . i64NumItems ;
253
274
var binSize = queryBinInfo . i64Size ;
254
275
responseQuery . Add ( "NumItems" , numItems ) ;
255
276
responseQuery . Add ( "BinSize" , binSize ) ;
277
+ responseQuery . Add ( "FileOwner" , ( string ) recycler . Properties [ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . FileOwner ] ) ;
278
+ if ( watchers . Any ( ) )
279
+ {
280
+ var info = new DirectoryInfo ( watchers . First ( ) . Path ) ;
281
+ responseQuery . Add ( "DateAccessed" , info . LastAccessTime . ToBinary ( ) ) ;
282
+ responseQuery . Add ( "DateCreated" , info . CreationTime . ToBinary ( ) ) ;
283
+ }
256
284
await args . Request . SendResponseAsync ( responseQuery ) ;
257
285
}
258
286
break ;
@@ -265,20 +293,26 @@ private static async Task parseRecycleBinAction(AppServiceRequestReceivedEventAr
265
293
{
266
294
try
267
295
{
268
- folderItem . Properties . ReadOnly = true ;
269
- folderItem . Properties . NoInheritedProperties = false ;
270
296
string recyclePath = folderItem . FileSystemPath ; // True path on disk
271
297
string fileName = Path . GetFileName ( folderItem . Name ) ; // Original file name
272
298
string filePath = folderItem . Name ; // Original file path + name
273
- var dt = ( System . Runtime . InteropServices . ComTypes . FILETIME ) folderItem . Properties [ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . DateCreated ] ;
274
- var recycleDate = dt . ToDateTime ( ) . ToLocalTime ( ) ; // This is LocalTime
275
- string fileSize = folderItem . Properties . GetPropertyString ( Vanara . PInvoke . Ole32 . PROPERTYKEY . System . Size ) ;
276
- long fileSizeBytes = ( long ) folderItem . Properties [ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . Size ] ;
277
- string fileType = ( string ) folderItem . Properties [ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . ItemTypeText ] ;
278
299
bool isFolder = folderItem . IsFolder && Path . GetExtension ( folderItem . Name ) != ".zip" ;
279
- folderContentsList . Add ( new ShellFileItem ( isFolder , recyclePath , fileName , filePath , recycleDate , fileSize , fileSizeBytes , fileType ) ) ;
300
+ if ( folderItem . Properties == null )
301
+ {
302
+ folderContentsList . Add ( new ShellFileItem ( isFolder , recyclePath , fileName , filePath , DateTime . Now , null , 0 , null ) ) ;
303
+ continue ;
304
+ }
305
+ folderItem . Properties . TryGetValue < System . Runtime . InteropServices . ComTypes . FILETIME ? > (
306
+ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . DateCreated , out var fileTime ) ;
307
+ var recycleDate = fileTime ? . ToDateTime ( ) . ToLocalTime ( ) ?? DateTime . Now ; // This is LocalTime
308
+ string fileSize = folderItem . Properties . TryGetValue < ulong ? > (
309
+ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . Size , out var fileSizeBytes ) ?
310
+ folderItem . Properties . GetPropertyString ( Vanara . PInvoke . Ole32 . PROPERTYKEY . System . Size ) : null ;
311
+ folderItem . Properties . TryGetValue < string > (
312
+ Vanara . PInvoke . Ole32 . PROPERTYKEY . System . ItemTypeText , out var fileType ) ;
313
+ folderContentsList . Add ( new ShellFileItem ( isFolder , recyclePath , fileName , filePath , recycleDate , fileSize , fileSizeBytes ?? 0 , fileType ) ) ;
280
314
}
281
- catch ( System . IO . FileNotFoundException )
315
+ catch ( FileNotFoundException )
282
316
{
283
317
// Happens if files are being deleted
284
318
}
@@ -287,7 +321,7 @@ private static async Task parseRecycleBinAction(AppServiceRequestReceivedEventAr
287
321
folderItem . Dispose ( ) ;
288
322
}
289
323
}
290
- responseEnum . Add ( "Enumerate" , Newtonsoft . Json . JsonConvert . SerializeObject ( folderContentsList ) ) ;
324
+ responseEnum . Add ( "Enumerate" , JsonConvert . SerializeObject ( folderContentsList ) ) ;
291
325
await args . Request . SendResponseAsync ( responseEnum ) ;
292
326
break ;
293
327
@@ -377,13 +411,13 @@ private static void HandleApplicationLaunch(string application, AppServiceReques
377
411
if ( ! group . Any ( ) ) continue ;
378
412
var files = group . Select ( x => new ShellItem ( x ) ) ;
379
413
using var sf = files . First ( ) . Parent ;
380
- Vanara . PInvoke . Shell32 . IContextMenu menu = null ;
414
+ IContextMenu menu = null ;
381
415
try
382
416
{
383
- menu = sf . GetChildrenUIObjects < Vanara . PInvoke . Shell32 . IContextMenu > ( null , files . ToArray ( ) ) ;
384
- menu . QueryContextMenu ( Vanara . PInvoke . HMENU . NULL , 0 , 0 , 0 , Vanara . PInvoke . Shell32 . CMF . CMF_DEFAULTONLY ) ;
385
- var pici = new Vanara . PInvoke . Shell32 . CMINVOKECOMMANDINFOEX ( ) ;
386
- pici . lpVerb = Vanara . PInvoke . Shell32 . CMDSTR_OPEN ;
417
+ menu = sf . GetChildrenUIObjects < IContextMenu > ( null , files . ToArray ( ) ) ;
418
+ menu . QueryContextMenu ( Vanara . PInvoke . HMENU . NULL , 0 , 0 , 0 , CMF . CMF_DEFAULTONLY ) ;
419
+ var pici = new CMINVOKECOMMANDINFOEX ( ) ;
420
+ pici . lpVerb = CMDSTR_OPEN ;
387
421
pici . nShow = Vanara . PInvoke . ShowWindowCommand . SW_SHOW ;
388
422
pici . cbSize = ( uint ) Marshal . SizeOf ( pici ) ;
389
423
menu . InvokeCommand ( pici ) ;
@@ -412,7 +446,7 @@ private static void HandleApplicationLaunch(string application, AppServiceReques
412
446
413
447
private static bool HandleCommandLineArgs ( )
414
448
{
415
- var localSettings = Windows . Storage . ApplicationData . Current . LocalSettings ;
449
+ var localSettings = ApplicationData . Current . LocalSettings ;
416
450
var arguments = ( string ) localSettings . Values [ "Arguments" ] ;
417
451
if ( ! string . IsNullOrWhiteSpace ( arguments ) )
418
452
{
0 commit comments