11using Files . Common ;
22using Newtonsoft . Json ;
3- using NLog ;
43using System ;
54using System . Collections . Generic ;
65using System . ComponentModel ;
1514using Windows . ApplicationModel . AppService ;
1615using Windows . Foundation . Collections ;
1716using Windows . Storage ;
17+ using static Vanara . PInvoke . Shell32 ;
1818
1919namespace FilesFullTrust
2020{
@@ -25,7 +25,7 @@ internal class Program
2525 [ STAThread ]
2626 private static void Main ( string [ ] args )
2727 {
28- Windows . Storage . StorageFolder storageFolder = Windows . Storage . ApplicationData . Current . LocalFolder ;
28+ StorageFolder storageFolder = ApplicationData . Current . LocalFolder ;
2929 NLog . LogManager . Configuration = new NLog . Config . XmlLoggingConfiguration ( Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , "NLog.config" ) ) ;
3030 NLog . LogManager . Configuration . Variables [ "LogPath" ] = storageFolder . Path ;
3131
@@ -45,14 +45,31 @@ private static void Main(string[] args)
4545 try
4646 {
4747 // 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+ }
5673
5774 // Connect to app service and wait until the connection gets closed
5875 appServiceExit = new AutoResetEvent ( false ) ;
@@ -62,7 +79,10 @@ private static void Main(string[] args)
6279 finally
6380 {
6481 connection ? . Dispose ( ) ;
65- watcher ? . Dispose ( ) ;
82+ foreach ( var watcher in watchers )
83+ {
84+ watcher . Dispose ( ) ;
85+ }
6686 recycler ? . Dispose ( ) ;
6787 appServiceExit ? . Dispose ( ) ;
6888 mutex ? . ReleaseMutex ( ) ;
@@ -75,20 +95,24 @@ private static void UnhandledExceptionTrapper(object sender, UnhandledExceptionE
7595 Logger . Error ( exception , exception . Message ) ;
7696 }
7797
78- private static async void Watcher_Changed ( object sender , ShellItemChangeWatcher . ShellItemChangeEventArgs e )
98+ private static async void Watcher_Changed ( object sender , FileSystemEventArgs e )
7999 {
80- Console . WriteLine ( $ "File : { e . ChangedItems . FirstOrDefault ( ) ? . FileSystemPath } { e . ChangeType } " ) ;
100+ Debug . WriteLine ( "Reycle bin event : {0}, {1}" , e . ChangeType , e . FullPath ) ;
81101 if ( connection != null )
82102 {
83103 // 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+ } ) ;
85109 }
86110 }
87111
88112 private static AppServiceConnection connection ;
89113 private static AutoResetEvent appServiceExit ;
90114 private static ShellFolder recycler ;
91- private static ShellItemChangeWatcher watcher ;
115+ private static IList < FileSystemWatcher > watchers ;
92116
93117 private static async void InitializeAppServiceConnection ( )
94118 {
@@ -126,7 +150,7 @@ private static async void Connection_RequestReceived(AppServiceConnection sender
126150 // Instead a single instance of the process is running
127151 // Requests from UWP app are sent via AppService connection
128152 var arguments = ( string ) args . Request . Message [ "Arguments" ] ;
129- var localSettings = Windows . Storage . ApplicationData . Current . LocalSettings ;
153+ var localSettings = ApplicationData . Current . LocalSettings ;
130154 Logger . Info ( $ "Argument: { arguments } ") ;
131155
132156 await parseArguments ( args , messageDeferral , arguments , localSettings ) ;
@@ -180,8 +204,8 @@ private static async Task parseArguments(AppServiceRequestReceivedEventArgs args
180204#if DEBUG
181205 // In debug mode this kills this process too??
182206#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 ( ) ;
185209#endif
186210
187211 Process process = new Process ( ) ;
@@ -201,7 +225,7 @@ private static async Task parseArguments(AppServiceRequestReceivedEventArgs args
201225 case "ParseAguments" :
202226 var responseArray = new ValueSet ( ) ;
203227 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 ) ) ;
205229 await args . Request . SendResponseAsync ( responseArray ) ;
206230 break ;
207231
@@ -236,23 +260,27 @@ private static async Task parseRecycleBinAction(AppServiceRequestReceivedEventAr
236260 {
237261 case "Empty" :
238262 // 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 ) ;
240264 break ;
241265
242266 case "Query" :
243267 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 ) ;
250271 if ( res == Vanara . PInvoke . HRESULT . S_OK )
251272 {
252273 var numItems = queryBinInfo . i64NumItems ;
253274 var binSize = queryBinInfo . i64Size ;
254275 responseQuery . Add ( "NumItems" , numItems ) ;
255276 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+ }
256284 await args . Request . SendResponseAsync ( responseQuery ) ;
257285 }
258286 break ;
@@ -265,20 +293,26 @@ private static async Task parseRecycleBinAction(AppServiceRequestReceivedEventAr
265293 {
266294 try
267295 {
268- folderItem . Properties . ReadOnly = true ;
269- folderItem . Properties . NoInheritedProperties = false ;
270296 string recyclePath = folderItem . FileSystemPath ; // True path on disk
271297 string fileName = Path . GetFileName ( folderItem . Name ) ; // Original file name
272298 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 ] ;
278299 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 ) ) ;
280314 }
281- catch ( System . IO . FileNotFoundException )
315+ catch ( FileNotFoundException )
282316 {
283317 // Happens if files are being deleted
284318 }
@@ -287,7 +321,7 @@ private static async Task parseRecycleBinAction(AppServiceRequestReceivedEventAr
287321 folderItem . Dispose ( ) ;
288322 }
289323 }
290- responseEnum . Add ( "Enumerate" , Newtonsoft . Json . JsonConvert . SerializeObject ( folderContentsList ) ) ;
324+ responseEnum . Add ( "Enumerate" , JsonConvert . SerializeObject ( folderContentsList ) ) ;
291325 await args . Request . SendResponseAsync ( responseEnum ) ;
292326 break ;
293327
@@ -377,13 +411,13 @@ private static void HandleApplicationLaunch(string application, AppServiceReques
377411 if ( ! group . Any ( ) ) continue ;
378412 var files = group . Select ( x => new ShellItem ( x ) ) ;
379413 using var sf = files . First ( ) . Parent ;
380- Vanara . PInvoke . Shell32 . IContextMenu menu = null ;
414+ IContextMenu menu = null ;
381415 try
382416 {
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 ;
387421 pici . nShow = Vanara . PInvoke . ShowWindowCommand . SW_SHOW ;
388422 pici . cbSize = ( uint ) Marshal . SizeOf ( pici ) ;
389423 menu . InvokeCommand ( pici ) ;
@@ -412,7 +446,7 @@ private static void HandleApplicationLaunch(string application, AppServiceReques
412446
413447 private static bool HandleCommandLineArgs ( )
414448 {
415- var localSettings = Windows . Storage . ApplicationData . Current . LocalSettings ;
449+ var localSettings = ApplicationData . Current . LocalSettings ;
416450 var arguments = ( string ) localSettings . Values [ "Arguments" ] ;
417451 if ( ! string . IsNullOrWhiteSpace ( arguments ) )
418452 {
0 commit comments