2023-11-10 00:29:16 -06:00
using LANCommander.SDK.Enums ;
using LANCommander.SDK.Helpers ;
using LANCommander.SDK.Models ;
using Microsoft.Extensions.Logging ;
using SharpCompress.Common ;
using SharpCompress.Readers ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2026-03-09 22:12:01 -05:00
using System.Threading ;
2024-08-05 18:02:01 -05:00
using System.Threading.Tasks ;
2025-09-22 00:29:51 -05:00
using LANCommander.SDK.Abstractions ;
2025-02-04 02:34:31 -06:00
using LANCommander.SDK.Exceptions ;
2025-09-22 00:29:51 -05:00
using LANCommander.SDK.Factories ;
2023-11-10 00:29:16 -06:00
2024-10-04 23:37:49 -05:00
namespace LANCommander.SDK.Services
2023-11-10 00:29:16 -06:00
{
2025-09-24 20:58:23 -05:00
public class RedistributableClient (
2026-03-11 19:36:24 -05:00
ILogger < RedistributableClient > logger ,
2025-10-06 20:29:34 -05:00
ISettingsProvider settingsProvider ,
2025-09-22 00:29:51 -05:00
ApiRequestFactory apiRequestFactory ,
2025-09-24 20:58:23 -05:00
ScriptClient scriptClient ,
ProfileClient profileClient )
2023-11-10 00:29:16 -06:00
{
public delegate void OnArchiveEntryExtractionProgressHandler ( object sender , ArchiveEntryExtractionProgressArgs e ) ;
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress ;
public delegate void OnArchiveExtractionProgressHandler ( long position , long length ) ;
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress ;
2025-01-31 00:34:37 -06:00
public delegate void OnInstallProgressUpdateHandler ( InstallProgress e ) ;
public event OnInstallProgressUpdateHandler OnInstallProgressUpdate ;
private InstallProgress _installProgress ;
2025-12-04 23:43:25 -06:00
public async Task < SDK . Models . Manifest . Redistributable > GetManifestAsync ( Guid id )
{
return await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
2026-03-20 01:09:29 -05:00
. UseRoute ( $"/api/Redistributables/{id}" )
2025-12-04 23:43:25 -06:00
. GetAsync < SDK . Models . Manifest . Redistributable > ( ) ;
}
2026-03-11 19:36:24 -05:00
public async Task < IEnumerable < Script > > GetScriptsAsync ( Guid id )
{
return await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
2026-03-20 01:09:29 -05:00
. UseRoute ( $"/api/Redistributables/{id}/Scripts" )
2026-03-11 19:36:24 -05:00
. GetAsync < IEnumerable < Script > > ( ) ;
}
public async Task WriteScriptsAsync ( Game game , Redistributable redistributable )
{
var scripts = await GetScriptsAsync ( redistributable . Id ) ;
if ( scripts ! = null & & scripts . Any ( ) )
{
logger ? . LogTrace ( $"Saving scripts for redistributable {redistributable.Name} ({redistributable.Id}) into {game.InstallDirectory}" ) ;
foreach ( var script in scripts )
2026-03-11 20:12:09 -05:00
await ScriptHelper . SaveScriptAsync ( game , redistributable , script ) ;
2026-03-11 19:36:24 -05:00
}
}
2025-09-22 00:29:51 -05:00
2026-06-10 21:51:12 -05:00
public async Task < bool > CheckForUpdateAsync ( Guid id , string currentVersion )
{
return await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
. UseRoute ( $"/api/Redistributables/{id}/CheckForUpdate?version={currentVersion}" )
. GetAsync < bool > ( ) ;
}
public async Task < IEnumerable < Archive > > GetUpdatesAsync ( Guid redistributableId , string version )
{
return await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
. UseRoute ( $"/api/Redistributables/{redistributableId}/Updates?version={version}" )
. GetAsync < IEnumerable < Archive > > ( ) ;
}
2025-10-09 02:16:20 -05:00
public async Task < Stream > Stream ( Guid id )
2024-01-02 02:34:58 -06:00
{
2025-09-22 00:29:51 -05:00
return await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
2026-03-20 01:09:29 -05:00
. UseRoute ( $"/api/Redistributables/{id}/Download" )
2025-09-22 00:29:51 -05:00
. StreamAsync ( ) ;
2024-01-02 02:34:58 -06:00
}
2026-06-10 21:51:12 -05:00
private async Task < TrackableStream > StreamArchiveAsync ( Guid archiveId )
{
return await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
. UseRoute ( $"/Download/Archive/{archiveId}" )
. StreamAsync ( ) ;
}
2024-08-05 18:02:01 -05:00
public async Task InstallAsync ( Game game )
2023-11-10 00:29:16 -06:00
{
foreach ( var redistributable in game . Redistributables )
{
2025-01-30 20:29:50 -06:00
await InstallAsync ( redistributable , game ) ;
2023-11-10 00:29:16 -06:00
}
}
2025-02-04 02:34:31 -06:00
public async Task InstallAsync ( Redistributable redistributable , Game game , int maxAttempts = 10 )
2023-11-10 00:29:16 -06:00
{
2025-01-31 00:34:37 -06:00
_installProgress = new InstallProgress ( ) ;
_installProgress . Status = InstallStatus . Downloading ;
2025-02-04 02:34:31 -06:00
_installProgress . Title = redistributable . Name ;
2025-01-31 00:34:37 -06:00
_installProgress . Progress = 0 ;
_installProgress . TransferSpeed = 0 ;
2025-02-04 02:34:31 -06:00
_installProgress . TotalBytes = 0 ;
2025-02-16 15:48:34 -06:00
_installProgress . BytesTransferred = 0 ;
2025-01-31 00:34:37 -06:00
OnInstallProgressUpdate ? . Invoke ( _installProgress ) ;
2023-11-10 00:29:16 -06:00
try
{
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Saving manifest" ) ;
2025-12-04 23:43:25 -06:00
var manifest = await GetManifestAsync ( redistributable . Id ) ;
2025-02-04 02:34:31 -06:00
2025-12-04 23:43:25 -06:00
await ManifestHelper . WriteAsync ( manifest , game . InstallDirectory ) ;
2025-02-04 02:34:31 -06:00
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Saving scripts" ) ;
await WriteScriptsAsync ( game , redistributable ) ;
2025-09-22 00:29:51 -05:00
2026-05-23 22:17:32 -05:00
var hasDetectInstallScript = redistributable . Scripts ! = null & &
redistributable . Scripts . Any ( s = > s . Type = = Enums . ScriptType . DetectInstall ) ;
var installed = hasDetectInstallScript & &
2026-02-10 17:34:35 -06:00
await scriptClient . Redistributable_RunDetectInstallScriptAsync ( game . InstallDirectory , game . Id , redistributable . Id ) ;
2023-11-10 00:29:16 -06:00
2026-05-23 22:17:32 -05:00
logger ? . LogTrace ( "Redistributable install detection returned {Result} (hasDetectScript={HasDetectScript})" , installed , hasDetectInstallScript ) ;
2023-11-10 00:29:16 -06:00
2024-09-20 00:28:50 -05:00
if ( ! installed )
2023-11-10 00:29:16 -06:00
{
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Redistributable {RedistributableName} not installed" , redistributable . Name ) ;
2026-05-23 22:59:56 -05:00
using ( var fileTracker = new InstallDirectoryFileTracker ( game . InstallDirectory ) )
2023-11-10 00:29:16 -06:00
{
2026-05-23 22:59:56 -05:00
if ( redistributable . Archives ? . Any ( ) ? ? false )
{
logger ? . LogTrace ( "Archives for redistributable {RedistributableName} exist. Attempting to download..." , redistributable . Name ) ;
2023-12-19 19:44:24 -06:00
2026-05-23 22:59:56 -05:00
var result = await RetryHelper . RetryOnExceptionAsync ( maxAttempts ,
TimeSpan . FromMilliseconds ( 500 ) , new ExtractionResult ( ) ,
async ( ) = >
{
logger ? . LogTrace ( "Attempting to download and extract redistributable" ) ;
2023-11-10 00:29:16 -06:00
2026-05-23 22:59:56 -05:00
return await DownloadAndExtractAsync ( redistributable , game , CancellationToken . None ) ;
} ) ;
2023-11-10 00:29:16 -06:00
2026-05-23 22:59:56 -05:00
if ( ! result . Success & & ! result . Canceled )
throw new InstallException ( "Could not extract the redistributable. Retry the install or check your connection" ) ;
2026-05-24 00:06:46 -05:00
if ( result . Canceled )
2026-05-23 22:59:56 -05:00
throw new InstallCanceledException ( "Redistributable install canceled" ) ;
2023-12-19 19:44:24 -06:00
2026-05-23 22:59:56 -05:00
logger ? . LogTrace ( "Extraction of redistributable successful. Extracted path is {Path}" , result . Directory ) ;
logger ? . LogTrace ( "Running install script for redistributable {RedistributableName}" , redistributable . Name ) ;
2023-12-19 19:44:24 -06:00
2026-05-23 22:59:56 -05:00
await RunPostInstallScripts ( game , redistributable ) ;
}
else
{
logger ? . LogTrace ( "No archives exist for redistributable {RedistributableName}. Running install script anyway..." , redistributable . Name ) ;
await RunPostInstallScripts ( game , redistributable ) ;
}
SaveTrackedFiles ( game . InstallDirectory , redistributable . Id , fileTracker ) ;
2023-11-10 00:29:16 -06:00
}
}
}
catch ( Exception ex )
{
2026-03-11 19:36:24 -05:00
logger ? . LogError ( ex , "Redistributable {Redistributable} failed to install" , redistributable . Name ) ;
2023-11-10 00:29:16 -06:00
}
}
2025-01-30 23:57:12 -06:00
2026-06-10 21:51:12 -05:00
public async Task < bool > ApplyUpdateArchiveAsync ( Guid archiveId , Guid redistributableId , Game game , CancellationToken cancellationToken = default )
{
_installProgress = new InstallProgress ( ) ;
_installProgress . Status = InstallStatus . Downloading ;
_installProgress . Title = game . Title ;
_installProgress . Progress = 0 ;
OnInstallProgressUpdate ? . Invoke ( _installProgress ) ;
var destination = Path . Combine ( GameClient . GetMetadataDirectoryPath ( game . InstallDirectory , redistributableId ) , "Files" ) ;
logger ? . LogTrace ( "Downloading archive {ArchiveId} and extracting redistributable {RedistributableId} to path {Destination}" , archiveId , redistributableId , destination ) ;
try
{
Directory . CreateDirectory ( destination ) ;
using ( var stream = await StreamArchiveAsync ( archiveId ) )
{
var monitor = new FileTransferMonitor ( stream . Length ) ;
var progress = new Progress < ProgressReport > ( report = >
{
if ( cancellationToken . IsCancellationRequested )
return ;
if ( monitor . CanUpdate ( ) )
{
monitor . Update ( stream . Position ) ;
_installProgress . BytesTransferred = monitor . GetBytesTransferred ( ) ;
_installProgress . TotalBytes = stream . Length ;
_installProgress . TransferSpeed = monitor . GetSpeed ( ) ;
_installProgress . TimeRemaining = monitor . GetTimeRemaining ( ) ;
OnInstallProgressUpdate ? . Invoke ( _installProgress ) ;
}
OnArchiveEntryExtractionProgress ? . Invoke ( this , new ArchiveEntryExtractionProgressArgs
{
Progress = report ,
} ) ;
} ) ;
await using var reader = await ReaderFactory . OpenAsyncReader ( stream , new ReaderOptions { Progress = progress } , cancellationToken ) ;
await reader . WriteAllToDirectoryAsync ( destination , new ExtractionOptions ( )
{
ExtractFullPath = true ,
Overwrite = true
} , cancellationToken ) ;
}
logger ? . LogTrace ( "Successfully applied update archive {ArchiveId} for redistributable {RedistributableId}" , archiveId , redistributableId ) ;
return true ;
}
catch ( Exception ex )
{
logger ? . LogError ( ex , "Could not apply update archive {ArchiveId} for redistributable {RedistributableId}" , archiveId , redistributableId ) ;
if ( Directory . Exists ( destination ) )
{
logger ? . LogTrace ( "Cleaning up orphaned files after bad update" ) ;
Directory . Delete ( destination , true ) ;
}
throw new InstallException ( "The redistributable update archive could not be extracted. Please try again" ) ;
}
}
public async Task RefreshManifestAndScriptsAsync ( string installDirectory , Redistributable redistributable )
{
logger ? . LogTrace ( "Refreshing manifest and scripts for redistributable {RedistributableId} in {InstallDirectory}" , redistributable . Id , installDirectory ) ;
var manifest = await GetManifestAsync ( redistributable . Id ) ;
await ManifestHelper . WriteAsync ( manifest , installDirectory ) ;
var scripts = await GetScriptsAsync ( redistributable . Id ) ;
if ( scripts ! = null & & scripts . Any ( ) )
{
var game = new Game { InstallDirectory = installDirectory } ;
foreach ( var script in scripts )
await ScriptHelper . SaveScriptAsync ( game , redistributable , script ) ;
}
}
2025-01-30 23:57:12 -06:00
private async Task RunPostInstallScripts ( Game game , Redistributable redistributable )
{
2025-02-04 02:34:31 -06:00
if ( redistributable . Scripts ! = null & & redistributable . Scripts . Any ( ) )
2025-01-30 23:57:12 -06:00
{
//GameInstallProgress.Status = GameInstallStatus.RunningScripts;
// OnGameInstallProgressUpdate?.Invoke(GameInstallProgress);
try
{
2026-02-10 17:34:35 -06:00
await scriptClient . Redistributable_RunInstallScriptAsync ( game . InstallDirectory , game . Id , redistributable . Id ) ;
await scriptClient . Redistributable_RunNameChangeScriptAsync ( game . InstallDirectory , game . Id , redistributable . Id , await profileClient . GetAliasAsync ( ) ) ;
2025-01-30 23:57:12 -06:00
}
catch ( Exception ex )
{
2026-03-11 19:36:24 -05:00
logger ? . LogError ( ex , "Scripts failed to execute for redistributable {RedistributableName} ({GameId})" , redistributable . Name , redistributable . Id ) ;
2025-01-30 23:57:12 -06:00
}
}
}
2023-11-10 00:29:16 -06:00
2026-03-09 22:12:01 -05:00
private async Task < ExtractionResult > DownloadAndExtractAsync ( Redistributable redistributable , Game game , CancellationToken cancellationToken = default )
2023-11-10 00:29:16 -06:00
{
if ( redistributable = = null )
{
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Redistributable failed to download! No redistributable was specified" ) ;
2025-08-18 03:02:49 -05:00
throw new ArgumentNullException ( nameof ( redistributable ) ) ;
2023-11-10 00:29:16 -06:00
}
2025-09-24 20:58:23 -05:00
var destination = Path . Combine ( GameClient . GetMetadataDirectoryPath ( game . InstallDirectory , redistributable . Id ) , "Files" ) ;
2025-05-18 08:31:56 +02:00
var files = new List < ExtractionResult . FileEntry > ( ) ;
2023-11-10 00:29:16 -06:00
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Downloading and extracting {Redistributable} to path {Destination}" , redistributable . Name , destination ) ;
2023-11-10 00:29:16 -06:00
try
{
Directory . CreateDirectory ( destination ) ;
2025-09-22 00:29:51 -05:00
using ( var redistributableStream = await Stream ( redistributable . Id ) )
2023-11-10 00:29:16 -06:00
{
2026-03-09 22:12:01 -05:00
var monitor = new FileTransferMonitor ( redistributableStream . Length ) ;
var seenEntries = new System . Collections . Generic . HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
var progress = new Progress < ProgressReport > ( report = >
2023-11-10 00:29:16 -06:00
{
2026-03-09 22:12:01 -05:00
if ( ! string . IsNullOrEmpty ( report . EntryPath ) & & seenEntries . Add ( report . EntryPath ) )
{
files . Add ( new ExtractionResult . FileEntry
{
EntryPath = report . EntryPath ,
LocalPath = Path . Combine ( destination , report . EntryPath ) ,
} ) ;
}
2025-02-04 02:34:31 -06:00
if ( monitor . CanUpdate ( ) )
{
2026-03-09 22:12:01 -05:00
monitor . Update ( redistributableStream . Position ) ;
2025-02-04 02:34:31 -06:00
2025-02-16 15:48:34 -06:00
_installProgress . BytesTransferred = monitor . GetBytesTransferred ( ) ;
2026-03-09 22:12:01 -05:00
_installProgress . TotalBytes = redistributableStream . Length ;
2025-02-04 02:34:31 -06:00
_installProgress . TransferSpeed = monitor . GetSpeed ( ) ;
_installProgress . TimeRemaining = monitor . GetTimeRemaining ( ) ;
2026-03-09 22:12:01 -05:00
2025-02-04 02:34:31 -06:00
OnInstallProgressUpdate ? . Invoke ( _installProgress ) ;
}
2025-05-18 08:31:56 +02:00
2023-11-10 00:29:16 -06:00
OnArchiveEntryExtractionProgress ? . Invoke ( this , new ArchiveEntryExtractionProgressArgs
{
2026-03-09 22:12:01 -05:00
Progress = report ,
2023-11-10 00:29:16 -06:00
} ) ;
2026-03-09 22:12:01 -05:00
} ) ;
2023-11-10 00:29:16 -06:00
2026-03-09 22:12:01 -05:00
await using var reader = await ReaderFactory . OpenAsyncReader ( redistributableStream , new ReaderOptions { Progress = progress } , cancellationToken ) ;
await reader . WriteAllToDirectoryAsync ( destination , new ExtractionOptions ( )
2023-11-10 00:29:16 -06:00
{
ExtractFullPath = true ,
Overwrite = true
2026-03-09 22:12:01 -05:00
} , cancellationToken ) ;
2023-11-10 00:29:16 -06:00
}
}
catch ( Exception ex )
{
2026-03-11 19:36:24 -05:00
logger ? . LogError ( ex , "Could not extract to path {Destination}" , destination ) ;
2023-11-10 00:29:16 -06:00
if ( Directory . Exists ( destination ) )
{
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Cleaning up orphaned files after bad install" ) ;
2023-11-10 00:29:16 -06:00
Directory . Delete ( destination , true ) ;
}
throw new Exception ( "The redistributable archive could not be extracted, is it corrupted? Please try again" ) ;
}
var extractionResult = new ExtractionResult
{
Canceled = false
} ;
if ( ! extractionResult . Canceled )
{
extractionResult . Success = true ;
extractionResult . Directory = destination ;
2025-05-18 08:31:56 +02:00
extractionResult . Files = files ;
2026-03-11 19:36:24 -05:00
logger ? . LogTrace ( "Redistributable {Redistributable} successfully downloaded and extracted to {Destination}" , redistributable . Name , destination ) ;
2023-11-10 00:29:16 -06:00
}
return extractionResult ;
}
2024-10-01 17:56:06 -05:00
public async Task ImportAsync ( string archivePath )
{
using ( var fs = new FileStream ( archivePath , FileMode . Open , FileAccess . Read ) )
{
2025-09-22 00:29:51 -05:00
var objectKey = await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
2025-10-06 20:29:34 -05:00
. UploadInChunksAsync ( settingsProvider . CurrentValue . Archives . UploadChunkSize , fs ) ;
2024-10-01 17:56:06 -05:00
if ( objectKey ! = Guid . Empty )
2025-09-22 00:29:51 -05:00
await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
. UseRoute ( $"/api/Redistributables/Import/{objectKey}" )
2025-10-01 18:45:30 -05:00
. PostAsync ( ) ;
2024-10-01 17:56:06 -05:00
}
}
2024-10-01 17:56:58 -05:00
2025-09-22 00:29:51 -05:00
[Obsolete("Exporter no longer provides \"full\" exports")]
2024-10-01 17:56:58 -05:00
public async Task ExportAsync ( string destinationPath , Guid redistributableId )
{
2025-09-22 00:29:51 -05:00
await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
. UseRoute ( $"/Redistributables/{redistributableId}/Export/Full" )
. DownloadAsync ( destinationPath ) ;
2024-10-01 17:56:58 -05:00
}
public async Task UploadArchiveAsync ( string archivePath , Guid redistributableId , string version , string changelog = "" )
{
using ( var fs = new FileStream ( archivePath , FileMode . Open , FileAccess . Read ) )
{
2025-09-22 00:29:51 -05:00
var objectKey = await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
2025-10-06 20:29:34 -05:00
. UploadInChunksAsync ( settingsProvider . CurrentValue . Archives . UploadChunkSize , fs ) ;
2024-10-01 17:56:58 -05:00
if ( objectKey ! = Guid . Empty )
2025-09-22 00:29:51 -05:00
await apiRequestFactory
. Create ( )
. UseAuthenticationToken ( )
. UseVersioning ( )
. UseRoute ( "/api/Redistributables/UploadArchive" )
. AddBody ( new UploadArchiveRequest
{
Id = redistributableId ,
ObjectKey = objectKey ,
Version = version ,
Changelog = changelog ,
} )
2025-10-01 18:45:30 -05:00
. PostAsync ( ) ;
2024-10-01 17:56:58 -05:00
}
}
2026-05-23 22:59:56 -05:00
private void SaveTrackedFiles ( string installDirectory , Guid redistributableId , InstallDirectoryFileTracker fileTracker )
{
try
{
var relativePaths = fileTracker . GetCreatedFiles ( )
. Select ( f = > Path . GetRelativePath ( installDirectory , f ) )
. OrderBy ( f = > f )
. ToList ( ) ;
var fileListPath = GameClient . GetMetadataFilePath ( installDirectory , redistributableId , "FileList.txt" ) ;
var directory = Path . GetDirectoryName ( fileListPath ) ;
if ( ! Directory . Exists ( directory ) )
Directory . CreateDirectory ( directory ) ;
File . WriteAllText ( fileListPath , string . Join ( Environment . NewLine , relativePaths ) ) ;
logger ? . LogTrace ( "Tracked {Count} files installed by redistributable {RedistributableId}" , relativePaths . Count , redistributableId ) ;
}
catch ( Exception ex )
{
logger ? . LogWarning ( ex , "Could not track files for redistributable {RedistributableId}" , redistributableId ) ;
}
}
private class InstallDirectoryFileTracker : IDisposable
{
private readonly FileSystemWatcher _watcher ;
private readonly HashSet < string > _createdFiles = new ( StringComparer . OrdinalIgnoreCase ) ;
private readonly string _metadataPath ;
private readonly object _lock = new ( ) ;
public InstallDirectoryFileTracker ( string installDirectory )
{
_metadataPath = Path . Combine ( installDirectory , ".lancommander" ) ;
_watcher = new FileSystemWatcher ( installDirectory )
{
IncludeSubdirectories = true ,
NotifyFilter = NotifyFilters . FileName ,
EnableRaisingEvents = true ,
} ;
_watcher . Created + = OnFileCreated ;
_watcher . Renamed + = OnFileRenamed ;
}
private void OnFileCreated ( object sender , FileSystemEventArgs e )
{
if ( e . FullPath . StartsWith ( _metadataPath , StringComparison . OrdinalIgnoreCase ) )
return ;
lock ( _lock )
_createdFiles . Add ( e . FullPath ) ;
}
private void OnFileRenamed ( object sender , RenamedEventArgs e )
{
if ( e . FullPath . StartsWith ( _metadataPath , StringComparison . OrdinalIgnoreCase ) )
return ;
lock ( _lock )
_createdFiles . Add ( e . FullPath ) ;
}
public IEnumerable < string > GetCreatedFiles ( )
{
lock ( _lock )
return _createdFiles . ToList ( ) ;
}
public void Dispose ( )
{
_watcher . EnableRaisingEvents = false ;
_watcher . Created - = OnFileCreated ;
_watcher . Renamed - = OnFileRenamed ;
_watcher . Dispose ( ) ;
}
}
2023-11-10 00:29:16 -06:00
}
}