2020-07-17 22:32:21 -07:00
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using Microsoft.Plugin.Program.Programs ;
using Wox.Infrastructure.Logger ;
using Wox.Infrastructure.Storage ;
using System.IO ;
using System.Linq ;
2020-08-11 09:08:44 -07:00
using System.Globalization ;
2020-07-17 22:32:21 -07:00
namespace Microsoft.Plugin.Program.Storage
{
2020-08-11 09:08:44 -07:00
using Win32Program = Programs . Win32Program ;
internal class Win32ProgramRepository : ListRepository < Programs . Win32Program > , IProgramRepository
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
private IStorage < IList < Programs . Win32Program > > _storage ;
private ProgramPluginSettings _settings ;
2020-07-17 22:32:21 -07:00
private IList < IFileSystemWatcherWrapper > _fileSystemWatcherHelpers ;
private string [ ] _pathsToWatch ;
private int _numberOfPathsToWatch ;
2020-07-22 13:27:17 -07:00
private Collection < string > extensionsToWatch = new Collection < string > { "*.exe" , "*.lnk" , "*.appref-ms" , "*.url" } ;
2020-07-17 22:32:21 -07:00
private readonly string lnkExtension = ".lnk" ;
private readonly string urlExtension = ".url" ;
2020-08-11 09:08:44 -07:00
public Win32ProgramRepository ( IList < IFileSystemWatcherWrapper > fileSystemWatcherHelpers , IStorage < IList < Win32Program > > storage , ProgramPluginSettings settings , string [ ] pathsToWatch )
2020-07-17 22:32:21 -07:00
{
this . _fileSystemWatcherHelpers = fileSystemWatcherHelpers ;
2020-08-11 09:08:44 -07:00
this . _storage = storage ? ? throw new ArgumentNullException ( nameof ( storage ) , "Win32ProgramRepository requires an initialized storage interface" ) ;
this . _settings = settings ? ? throw new ArgumentNullException ( nameof ( settings ) , "Win32ProgramRepository requires an initialized settings object" ) ;
2020-07-17 22:32:21 -07:00
this . _pathsToWatch = pathsToWatch ;
2020-08-11 09:08:44 -07:00
this . _numberOfPathsToWatch = pathsToWatch . Length ;
2020-07-17 22:32:21 -07:00
InitializeFileSystemWatchers ( ) ;
}
private void InitializeFileSystemWatchers ( )
{
2020-07-22 13:27:17 -07:00
for ( int index = 0 ; index < _numberOfPathsToWatch ; index + + )
2020-07-17 22:32:21 -07:00
{
// To set the paths to monitor
_fileSystemWatcherHelpers [ index ] . Path = _pathsToWatch [ index ] ;
// to be notified when there is a change to a file
2020-07-22 10:58:01 -07:00
_fileSystemWatcherHelpers [ index ] . NotifyFilter = NotifyFilters . FileName | NotifyFilters . LastWrite ;
2020-07-17 22:32:21 -07:00
// filtering the app types that we want to monitor
_fileSystemWatcherHelpers [ index ] . Filters = extensionsToWatch ;
// Registering the event handlers
_fileSystemWatcherHelpers [ index ] . Created + = OnAppCreated ;
_fileSystemWatcherHelpers [ index ] . Deleted + = OnAppDeleted ;
_fileSystemWatcherHelpers [ index ] . Renamed + = OnAppRenamed ;
2020-07-22 10:58:01 -07:00
_fileSystemWatcherHelpers [ index ] . Changed + = OnAppChanged ;
2020-07-17 22:32:21 -07:00
// Enable the file system watcher
_fileSystemWatcherHelpers [ index ] . EnableRaisingEvents = true ;
// Enable it to search in sub folders as well
_fileSystemWatcherHelpers [ index ] . IncludeSubdirectories = true ;
}
2020-08-11 09:08:44 -07:00
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive>")]
2020-07-17 22:32:21 -07:00
private void OnAppRenamed ( object sender , RenamedEventArgs e )
{
string oldPath = e . OldFullPath ;
string newPath = e . FullPath ;
string extension = Path . GetExtension ( newPath ) ;
2020-08-11 09:08:44 -07:00
Programs . Win32Program newApp = Programs . Win32Program . GetAppFromPath ( newPath ) ;
Programs . Win32Program oldApp = null ;
2020-07-17 22:32:21 -07:00
// Once the shortcut application is renamed, the old app does not exist and therefore when we try to get the FullPath we get the lnk path instead of the exe path
// This changes the hashCode() of the old application.
// Therefore, instead of retrieving the old app using the GetAppFromPath(), we construct the application ourself
// This situation is not encountered for other application types because the fullPath is the path itself, instead of being computed by using the path to the app.
try
{
if ( extension . Equals ( lnkExtension , StringComparison . OrdinalIgnoreCase ) )
{
2020-08-11 09:08:44 -07:00
oldApp = new Win32Program ( ) { Name = Path . GetFileNameWithoutExtension ( e . OldName ) , ExecutableName = newApp . ExecutableName , FullPath = newApp . FullPath } ;
2020-07-17 22:32:21 -07:00
}
2020-07-22 13:27:17 -07:00
else if ( extension . Equals ( urlExtension , StringComparison . OrdinalIgnoreCase ) )
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
oldApp = new Win32Program ( ) { Name = Path . GetFileNameWithoutExtension ( e . OldName ) , ExecutableName = Path . GetFileName ( e . OldName ) , FullPath = newApp . FullPath } ;
2020-07-17 22:32:21 -07:00
}
else
{
2020-08-11 09:08:44 -07:00
oldApp = Win32Program . GetAppFromPath ( oldPath ) ;
2020-07-17 22:32:21 -07:00
}
}
catch ( Exception ex )
{
Log . Info ( $"|Win32ProgramRepository|OnAppRenamed-{extension}Program|{oldPath}|Unable to create program from {oldPath}| {ex.Message}" ) ;
}
// To remove the old app which has been renamed and to add the new application.
if ( oldApp ! = null )
{
Remove ( oldApp ) ;
}
if ( newApp ! = null )
{
Add ( newApp ) ;
}
2020-08-11 09:08:44 -07:00
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive")]
2020-07-17 22:32:21 -07:00
private void OnAppDeleted ( object sender , FileSystemEventArgs e )
{
string path = e . FullPath ;
string extension = Path . GetExtension ( path ) ;
2020-08-11 09:08:44 -07:00
Programs . Win32Program app = null ;
2020-07-17 22:32:21 -07:00
try
{
// To mitigate the issue of not having a FullPath for a shortcut app, we iterate through the items and find the app with the same hashcode.
if ( extension . Equals ( lnkExtension , StringComparison . OrdinalIgnoreCase ) )
{
app = GetAppWithSameLnkResolvedPath ( path ) ;
}
else if ( extension . Equals ( urlExtension , StringComparison . OrdinalIgnoreCase ) )
{
app = GetAppWithSameNameAndExecutable ( Path . GetFileNameWithoutExtension ( path ) , Path . GetFileName ( path ) ) ;
}
else
{
2020-08-11 09:08:44 -07:00
app = Programs . Win32Program . GetAppFromPath ( path ) ;
2020-07-17 22:32:21 -07:00
}
}
2020-07-22 13:27:17 -07:00
catch ( Exception ex )
2020-07-17 22:32:21 -07:00
{
Log . Info ( $"|Win32ProgramRepository|OnAppDeleted-{extension}Program|{path}|Unable to create program from {path}| {ex.Message}" ) ;
}
if ( app ! = null )
{
Remove ( app ) ;
}
}
// When a URL application is deleted, we can no longer get the HashCode directly from the path because the FullPath a Url app is the URL obtained from reading the file
2020-08-11 09:08:44 -07:00
private Win32Program GetAppWithSameNameAndExecutable ( string name , string executableName )
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
foreach ( Win32Program app in Items )
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
if ( name . Equals ( app . Name , StringComparison . CurrentCultureIgnoreCase ) & & executableName . Equals ( app . ExecutableName , StringComparison . CurrentCultureIgnoreCase ) )
2020-07-17 22:32:21 -07:00
{
return app ;
}
}
return null ;
}
// To mitigate the issue faced (as stated above) when a shortcut application is renamed, the Exe FullPath and executable name must be obtained.
// Unlike the rename event args, since we do not have a newPath, we iterate through all the programs and find the one with the same LnkResolved path.
2020-08-11 09:08:44 -07:00
private Programs . Win32Program GetAppWithSameLnkResolvedPath ( string lnkResolvedPath )
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
foreach ( Programs . Win32Program app in Items )
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
if ( lnkResolvedPath . ToLower ( CultureInfo . CurrentCulture ) . Equals ( app . LnkResolvedPath , StringComparison . CurrentCultureIgnoreCase ) )
2020-07-17 22:32:21 -07:00
{
return app ;
}
}
return null ;
}
private void OnAppCreated ( object sender , FileSystemEventArgs e )
{
string path = e . FullPath ;
2020-08-11 09:08:44 -07:00
if ( ! Path . GetExtension ( path ) . Equals ( urlExtension , StringComparison . CurrentCultureIgnoreCase ) )
2020-07-17 22:32:21 -07:00
{
2020-08-11 09:08:44 -07:00
Programs . Win32Program app = Programs . Win32Program . GetAppFromPath ( path ) ;
2020-07-22 10:58:01 -07:00
if ( app ! = null )
{
Add ( app ) ;
}
}
}
private void OnAppChanged ( object sender , FileSystemEventArgs e )
{
string path = e . FullPath ;
2020-08-11 09:08:44 -07:00
if ( Path . GetExtension ( path ) . Equals ( urlExtension , StringComparison . CurrentCultureIgnoreCase ) )
2020-07-22 10:58:01 -07:00
{
2020-08-11 09:08:44 -07:00
Programs . Win32Program app = Programs . Win32Program . GetAppFromPath ( path ) ;
2020-07-22 10:58:01 -07:00
if ( app ! = null )
{
Add ( app ) ;
}
2020-07-17 22:32:21 -07:00
}
}
public void IndexPrograms ( )
{
2020-08-11 09:08:44 -07:00
var applications = Programs . Win32Program . All ( _settings ) ;
2020-07-17 22:32:21 -07:00
Set ( applications ) ;
}
public void Save ( )
{
_storage . Save ( Items ) ;
}
public void Load ( )
{
2020-08-11 09:08:44 -07:00
var items = _storage . TryLoad ( Array . Empty < Win32Program > ( ) ) ;
2020-07-17 22:32:21 -07:00
Set ( items ) ;
}
}
}