diff --git a/.editorconfig b/.editorconfig index 64ad963b34..f2ea6175b6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -37,22 +37,37 @@ trim_trailing_whitespace = true tab_width = 4 # New line preferences csharp_new_line_before_open_brace = all +csharp_new_line_before_open_brace_for_methods = true +csharp_new_line_before_open_brace_for_control_blocks = true +csharp_preserve_single_line_statements = false +csharp_style_allow_embedded_statements_on_same_line_experimental = false csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true csharp_new_line_within_query_expression_clauses = true csharp_wrap_before_eq = false place_attribute_on_same_line = "never" # csharp_style_namespace_declarations = file_scoped:warning +# https://dotnettips.wordpress.com/2023/06/27/microsoft-net-code-analysis-always-add-braces-in-c/ +dotnet_diagnostic.IDE0011.severity = warning +csharp_prefer_braces = true:warning +dotnet_diagnostic.SA1500.severity = warning +dotnet_diagnostic.SA1503.severity = warning +dotnet_diagnostic.SA1520.severity = warning + + # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_switch_labels = true csharp_indent_labels = one_less_than_current +csharp_indent_case_contents_when_block = false +csharp_space_after_cast = true # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:suggestion diff --git a/starsky/starsky.foundation.database/Query/Query.cs b/starsky/starsky.foundation.database/Query/Query.cs index d27ab6be29..f0fe289d76 100644 --- a/starsky/starsky.foundation.database/Query/Query.cs +++ b/starsky/starsky.foundation.database/Query/Query.cs @@ -49,12 +49,19 @@ public Query(ApplicationDbContext context, /// FileIndex-objects with database data public FileIndexItem? GetObjectByFilePath(string filePath) { - if ( filePath != "/" ) filePath = PathHelper.RemoveLatestSlash(filePath); + if ( filePath != "/" ) + { + filePath = PathHelper.RemoveLatestSlash(filePath); + } FileIndexItem? LocalQuery(ApplicationDbContext context) { var item = context.FileIndex.FirstOrDefault(p => p.FilePath == filePath); - if ( item != null ) item.Status = FileIndexItem.ExifStatus.Ok; + if ( item != null ) + { + item.Status = FileIndexItem.ExifStatus.Ok; + } + return item; } @@ -85,7 +92,11 @@ public Query(ApplicationDbContext context, _cache.TryGetValue( GetObjectByFilePathAsyncCacheName(filePath), out var data) ) { - if ( !( data is FileIndexItem fileIndexItem ) ) return null; + if ( !( data is FileIndexItem fileIndexItem ) ) + { + return null; + } + fileIndexItem.Status = FileIndexItem.ExifStatus.OkAndSame; return fileIndexItem; } @@ -95,7 +106,9 @@ public Query(ApplicationDbContext context, // cache code: if ( cacheTime == null || _appSettings.AddMemoryCache != true || result == null ) + { return result; + } SetGetObjectByFilePathCache(filePath, result.Clone(), cacheTime); @@ -120,19 +133,25 @@ public void SetGetObjectByFilePathCache(string filePath, public async Task GetSubPathByHashAsync(string fileHash) { // The CLI programs uses no cache - if ( !IsCacheEnabled() || _cache == null ) return await QueryGetItemByHashAsync(fileHash); + if ( !IsCacheEnabled() || _cache == null ) + { + return await QueryGetItemByHashAsync(fileHash); + } // Return values from IMemoryCache var queryHashListCacheName = CachingDbName("hashList", fileHash); // if result is not null return cached value if ( _cache.TryGetValue(queryHashListCacheName, out var cachedSubPath) - && !string.IsNullOrEmpty(( string? )cachedSubPath) ) return ( string )cachedSubPath; + && !string.IsNullOrEmpty(( string? ) cachedSubPath) ) + { + return ( string ) cachedSubPath; + } cachedSubPath = await QueryGetItemByHashAsync(fileHash); _cache.Set(queryHashListCacheName, cachedSubPath, new TimeSpan(48, 0, 0)); - return ( string? )cachedSubPath; + return ( string? ) cachedSubPath; } /// @@ -141,11 +160,17 @@ public void SetGetObjectByFilePathCache(string filePath, /// base32 fileHash public void ResetItemByHash(string? fileHash) { - if ( _cache == null || _appSettings.AddMemoryCache == false ) return; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return; + } var queryCacheName = CachingDbName("hashList", fileHash); - if ( _cache.TryGetValue(queryCacheName, out _) ) _cache.Remove(queryCacheName); + if ( _cache.TryGetValue(queryCacheName, out _) ) + { + _cache.Remove(queryCacheName); + } } /// @@ -165,7 +190,9 @@ async Task LocalQuery(ApplicationDbContext context, FileIndexItem fileIndexItem) CacheUpdateItem(new List { updateStatusContent.Clone() }); if ( _appSettings.Verbose == true ) // Ef core changes debug + { _logger.LogDebug(context.ChangeTracker.DebugView.LongView); + } // object cache path is used to avoid updates SetGetObjectByFilePathCache(fileIndexItem.FilePath!, updateStatusContent, @@ -204,7 +231,11 @@ await RetryQueryUpdateSaveChangesAsync(updateStatusContent, e, { // Skip if Duplicate entry // MySqlConnector.MySqlException (0x80004005): Duplicate entry for key 'PRIMARY' - if ( !exception.Message.Contains("Duplicate") ) throw; + if ( !exception.Message.Contains("Duplicate") ) + { + throw; + } + _logger.LogError(exception, $"[UpdateItemAsync] Skipped MySqlException Duplicate entry for key {updateStatusContent.FilePath}"); } @@ -221,12 +252,16 @@ await RetryQueryUpdateSaveChangesAsync(updateStatusContent, e, public async Task> UpdateItemAsync( List updateStatusContentList) { - if ( updateStatusContentList.Count == 0 ) return new List(); + if ( updateStatusContentList.Count == 0 ) + { + return new List(); + } async Task> LocalQuery(DbContext context, List fileIndexItems) { foreach ( var item in fileIndexItems ) + { try { context.Attach(item).State = EntityState.Modified; @@ -236,11 +271,14 @@ async Task> LocalQuery(DbContext context, // System.InvalidOperationException: The property 'FileIndexItem.Id' has a temporary value while attempting to change the entity's state to 'Modified' // Issue #994 } + } await context.SaveChangesAsync(); foreach ( var item in fileIndexItems ) + { context.Attach(item).State = EntityState.Detached; + } CacheUpdateItem(fileIndexItems); return fileIndexItems; @@ -288,14 +326,19 @@ async Task> LocalQuery(DbContext context, /// items to update public void CacheUpdateItem(List updateStatusContent) { - if ( _cache == null || _appSettings.AddMemoryCache == false ) return; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return; + } var skippedCacheItems = new HashSet(); foreach ( var item in updateStatusContent.ToList() ) { if ( item.Status == FileIndexItem.ExifStatus.OkAndSame || item.Status == FileIndexItem.ExifStatus.Default ) + { item.Status = FileIndexItem.ExifStatus.Ok; + } // ToList() > Collection was modified; enumeration operation may not execute. var queryCacheName = CachingDbName(nameof(FileIndexItem), @@ -309,7 +352,7 @@ public void CacheUpdateItem(List updateStatusContent) } objectFileFolders ??= new List(); - var displayFileFolders = ( List )objectFileFolders; + var displayFileFolders = ( List ) objectFileFolders; // make it a list to avoid enum errors displayFileFolders = displayFileFolders.ToList(); @@ -317,12 +360,16 @@ public void CacheUpdateItem(List updateStatusContent) var obj = displayFileFolders.Find(p => p.FilePath == item.FilePath); if ( obj != null ) // remove add again + { displayFileFolders.Remove(obj); + } if ( item.Status == FileIndexItem.ExifStatus.Ok ) // ExifStatus.default is already changed // Add here item to cached index + { displayFileFolders.Add(item); + } // make it a list to avoid enum errors displayFileFolders = displayFileFolders.ToList(); @@ -334,8 +381,10 @@ public void CacheUpdateItem(List updateStatusContent) } if ( skippedCacheItems.Count >= 1 && _appSettings.Verbose == true ) + { _logger.LogInformation( $"[CacheUpdateItem] skipped: {string.Join(", ", skippedCacheItems)}"); + } } /// @@ -343,11 +392,19 @@ public void CacheUpdateItem(List updateStatusContent) /// This Does remove a SINGLE item from the cache NOT from the database /// /// + [SuppressMessage("ReSharper", + "S4136: All 'RemoveCacheItem' method overloads should be adjacent.")] public void RemoveCacheItem(List updateStatusContent) { - if ( _cache == null || _appSettings.AddMemoryCache == false ) return; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return; + } - foreach ( var item in updateStatusContent.ToList() ) RemoveCacheItem(item); + foreach ( var item in updateStatusContent.ToList() ) + { + RemoveCacheItem(item); + } } /// @@ -357,11 +414,17 @@ public void RemoveCacheItem(List updateStatusContent) public bool RemoveCacheParentItem(string directoryName) { // Add protection for disabled caching - if ( _cache == null || _appSettings.AddMemoryCache == false ) return false; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return false; + } var queryCacheName = CachingDbName(nameof(FileIndexItem), PathHelper.RemoveLatestSlash(directoryName.Clone().ToString()!)); - if ( !_cache.TryGetValue(queryCacheName, out _) ) return false; + if ( !_cache.TryGetValue(queryCacheName, out _) ) + { + return false; + } _cache.Remove(queryCacheName); return true; @@ -375,7 +438,10 @@ public bool RemoveCacheParentItem(string directoryName) public bool AddCacheParentItem(string directoryName, List items) { // Add protection for disabled caching - if ( _cache == null || _appSettings.AddMemoryCache == false ) return false; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return false; + } var queryCacheName = CachingDbName(nameof(FileIndexItem), PathHelper.RemoveLatestSlash(directoryName.Clone().ToString()!)); @@ -402,7 +468,11 @@ async Task LocalDefaultQuery() async Task LocalQuery(ApplicationDbContext context) { // only in test case fileIndex is null - if ( context.FileIndex != null ) await context.FileIndex.AddAsync(fileIndexItem); + if ( context.FileIndex != null ) + { + await context.FileIndex.AddAsync(fileIndexItem); + } + await context.SaveChangesAsync(); // Fix for: The instance of entity type 'Item' cannot be tracked because // another instance with the same key value for {'Id'} is already being tracked @@ -462,7 +532,9 @@ public async Task> AddParentItemsAsync(string subPath) var toAddList = new List(); // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator foreach ( var pathShouldExist in pathListShouldExist ) + { if ( !indexItems.Select(p => p.FilePath).Contains(pathShouldExist) ) + { toAddList.Add(new FileIndexItem(pathShouldExist) { IsDirectory = true, @@ -471,6 +543,8 @@ public async Task> AddParentItemsAsync(string subPath) Software = pathShouldExist == "/" ? "Root object" : string.Empty, Status = FileIndexItem.ExifStatus.Ok }); + } + } await AddRangeAsync(toAddList); return toAddList; @@ -492,7 +566,11 @@ internal static string GetObjectByFilePathAsyncCacheName(string subPath) private async Task GetObjectByFilePathQueryAsync( string filePath) { - if ( filePath != "/" ) filePath = PathHelper.RemoveLatestSlash(filePath); + if ( filePath != "/" ) + { + filePath = PathHelper.RemoveLatestSlash(filePath); + } + var paths = new List { filePath }; return ( await GetObjectsByFilePathQueryAsync(paths) ) .FirstOrDefault(); @@ -528,7 +606,11 @@ internal static string GetObjectByFilePathAsyncCacheName(string subPath) internal static string CachingDbName(string functionName, string? singleItemDbPath) { // when is nothing assume its the home item - if ( string.IsNullOrWhiteSpace(singleItemDbPath) ) singleItemDbPath = "/"; + if ( string.IsNullOrWhiteSpace(singleItemDbPath) ) + { + singleItemDbPath = "/"; + } + // For creating an unique name: DetailView_/2018/01/1.jpg var uniqueSingleDbCacheNameBuilder = new StringBuilder(); uniqueSingleDbCacheNameBuilder.Append(functionName + "_" + singleItemDbPath); @@ -560,7 +642,11 @@ async Task LocalRetrySaveChangesAsyncQuery() // https://go.microsoft.com/fwlink/?linkid=2097913 await Task.Delay(delay); var context = new InjectServiceScope(_scopeFactory).Context(); - if ( context == null! ) throw new AggregateException("Query Context is null"); + if ( context == null! ) + { + throw new AggregateException("Query Context is null"); + } + context.Attach(updateStatusContent).State = EntityState.Modified; await context.SaveChangesAsync(); context.Attach(updateStatusContent).State = EntityState.Unchanged; @@ -605,7 +691,11 @@ async Task LocalRetrySaveChangesAsyncQuery() /// true when enabled internal bool IsCacheEnabled() { - if ( _cache == null || _appSettings.AddMemoryCache == false ) return false; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return false; + } + return true; } @@ -619,17 +709,26 @@ internal bool IsCacheEnabled() internal void AddCacheItem(FileIndexItem updateStatusContent) { // If cache is turned of - if ( _cache == null || _appSettings.AddMemoryCache == false ) return; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return; + } var queryCacheName = CachingDbName(nameof(FileIndexItem), updateStatusContent.ParentDirectory!); - if ( !_cache.TryGetValue(queryCacheName, out var objectFileFolders) ) return; + if ( !_cache.TryGetValue(queryCacheName, out var objectFileFolders) ) + { + return; + } objectFileFolders ??= new List(); - var displayFileFolders = ( List )objectFileFolders; + var displayFileFolders = ( List ) objectFileFolders; - if ( updateStatusContent.FilePath == "/" ) return; + if ( updateStatusContent.FilePath == "/" ) + { + return; + } displayFileFolders.Add(updateStatusContent); // Order by filename @@ -647,15 +746,21 @@ internal void AddCacheItem(FileIndexItem updateStatusContent) public void RemoveCacheItem(FileIndexItem updateStatusContent) { // Add protection for disabled caching - if ( _cache == null || _appSettings.AddMemoryCache == false ) return; + if ( _cache == null || _appSettings.AddMemoryCache == false ) + { + return; + } var queryCacheName = CachingDbName(nameof(FileIndexItem), updateStatusContent.ParentDirectory!); - if ( !_cache.TryGetValue(queryCacheName, out var objectFileFolders) ) return; + if ( !_cache.TryGetValue(queryCacheName, out var objectFileFolders) ) + { + return; + } objectFileFolders ??= new List(); - var displayFileFolders = ( List )objectFileFolders; + var displayFileFolders = ( List ) objectFileFolders; // Order by filename displayFileFolders = displayFileFolders diff --git a/starsky/starsky.foundation.storage/ArchiveFormats/TarBal.cs b/starsky/starsky.foundation.storage/ArchiveFormats/TarBal.cs index ffcafb6856..288a2ce2fb 100644 --- a/starsky/starsky.foundation.storage/ArchiveFormats/TarBal.cs +++ b/starsky/starsky.foundation.storage/ArchiveFormats/TarBal.cs @@ -6,119 +6,150 @@ using System.Threading; using System.Threading.Tasks; using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Interfaces; using starsky.foundation.storage.Interfaces; -namespace starsky.foundation.storage.ArchiveFormats +namespace starsky.foundation.storage.ArchiveFormats; + +[SuppressMessage("Usage", + "CA1835:Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync")] +public sealed class TarBal { - [SuppressMessage("Usage", "CA1835:Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync")] - public sealed class TarBal + private readonly IWebLogger _logger; + private readonly char _pathSeparator; + private readonly IStorage _storage; + + public TarBal(IStorage storage, IWebLogger logger, char pathSeparator = '/') + { + _storage = storage; + _pathSeparator = pathSeparator; + _logger = logger; + } + + /// + /// Extracts a .tar.gz archive stream to the specified directory. + /// + /// The .tar.gz to decompress and extract. + /// Output directory to write the files. + /// cancellationToken + public async Task ExtractTarGz(Stream stream, string outputDir, + CancellationToken cancellationToken) { - private readonly IStorage _storage; - private readonly char _pathSeparator; + // A GZipStream is not seekable, so copy it first to a MemoryStream + using var gzip = new GZipStream(stream, CompressionMode.Decompress); + const int chunk = 4096; + using var memStr = new MemoryStream(); + int read; + var buffer = new byte[chunk]; - public TarBal(IStorage storage, char pathSeparator = '/') + while ( ( read = await gzip.ReadAsync(buffer, 0, buffer.Length, cancellationToken) ) > 0 ) { - _storage = storage; - _pathSeparator = pathSeparator; + await memStr.WriteAsync(buffer, 0, read, cancellationToken); } - /// - /// Extracts a .tar.gz archive stream to the specified directory. - /// - /// The .tar.gz to decompress and extract. - /// Output directory to write the files. - /// cancellationToken - public async Task ExtractTarGz(Stream stream, string outputDir, CancellationToken cancellationToken) + gzip.Close(); + memStr.Seek(0, SeekOrigin.Begin); + await ExtractTar(memStr, outputDir, cancellationToken); + } + + /// + /// Extracts a tar archive to the specified directory. + /// + /// The .tar to extract. + /// Output directory to write the files. + /// Cancel token + public async Task ExtractTar(Stream stream, string outputDir, + CancellationToken cancellationToken) + { + var buffer = new byte[100]; + var longFileName = string.Empty; + + while ( true ) { - // A GZipStream is not seekable, so copy it first to a MemoryStream - using var gzip = new GZipStream(stream, CompressionMode.Decompress); - const int chunk = 4096; - using var memStr = new MemoryStream(); - int read; - var buffer = new byte[chunk]; - - while ( ( read = await gzip.ReadAsync(buffer, 0, buffer.Length, cancellationToken) ) > 0 ) + // Read the first 100 bytes to get the name + var bytesRead = await stream.ReadAsync(buffer, 0, 100, cancellationToken); + if ( bytesRead != 100 ) { - await memStr.WriteAsync(buffer, 0, read, cancellationToken); + _logger.LogError("[ExtractTar] less than 100 bytes read {outputDir}", outputDir); + // If fewer bytes are read, it indicates an incomplete read or end of stream. + break; } - gzip.Close(); - memStr.Seek(0, SeekOrigin.Begin); - await ExtractTar(memStr, outputDir, cancellationToken); - } + var name = string.IsNullOrEmpty(longFileName) + ? Encoding.ASCII.GetString(buffer).Trim('\0') + : longFileName; // Use longFileName if we have one read - /// - /// Extracts a tar archive to the specified directory. - /// - /// The .tar to extract. - /// Output directory to write the files. - /// cancel token - public async Task ExtractTar(Stream stream, string outputDir, CancellationToken cancellationToken) - { - var buffer = new byte[100]; - var longFileName = string.Empty; - while ( true ) + if ( string.IsNullOrWhiteSpace(name) ) { - await stream.ReadAsync(buffer, 0, 100, cancellationToken); - var name = string.IsNullOrEmpty(longFileName) ? Encoding.ASCII.GetString(buffer).Trim('\0') : longFileName; //Use longFileName if we have one read - if ( string.IsNullOrWhiteSpace(name) ) - break; - stream.Seek(24, SeekOrigin.Current); - await stream.ReadAsync(buffer, 0, 12, cancellationToken); - var size = Convert.ToInt64(Encoding.UTF8.GetString(buffer, 0, 12).Trim('\0').Trim(), 8); - stream.Seek(20, SeekOrigin.Current); //Move head to typeTag byte - var typeTag = stream.ReadByte(); - stream.Seek(355L, SeekOrigin.Current); //Move head to beginning of data (byte 512) - - if ( typeTag == 'L' ) - { - //We have a long file name - longFileName = await CreateLongFileName(size, stream, cancellationToken); - } - else - { - //We have a normal file or directory - longFileName = string.Empty; //Reset longFileName if current entry is not indicating one - await CreateFileOrDirectory(outputDir, name, size, stream, cancellationToken); - } - - //Move head to next 512 byte block - var pos = stream.Position; - var offset = 512 - ( pos % 512 ); - if ( offset == 512 ) - offset = 0; - - stream.Seek(offset, SeekOrigin.Current); + break; } - } - private static async Task CreateLongFileName(long size, Stream stream, CancellationToken cancellationToken) - { - //If Type Tag is 'L' we have a filename that is longer than the 100 bytes reserved for it in the header. - //We read it here and save it temporarily as it will be the file name of the next block where the actual data is - var buf = new byte[size]; - await stream.ReadAsync(buf, 0, buf.Length, cancellationToken); - return Encoding.ASCII.GetString(buf).Trim('\0'); - } + stream.Seek(24, SeekOrigin.Current); - private async Task CreateFileOrDirectory(string outputDir, string name, long size, Stream stream, CancellationToken cancellationToken) - { - var output = $"{outputDir}{_pathSeparator}{name}"; + // Read the size + bytesRead = await stream.ReadAsync(buffer, 0, 12, cancellationToken); + if ( bytesRead != 12 ) + { + break; + } + + var size = Convert.ToInt64(Encoding.UTF8.GetString(buffer, 0, 12).Trim('\0').Trim(), 8); + stream.Seek(20, SeekOrigin.Current); // Move head to typeTag byte + var typeTag = stream.ReadByte(); + stream.Seek(355L, SeekOrigin.Current); // Move head to beginning of data (byte 512) - if ( !_storage.ExistFolder(FilenamesHelper.GetParentPath(output)) ) + if ( typeTag == 'L' ) { - _storage.CreateDirectory(FilenamesHelper.GetParentPath(output)); + // We have a long file name + longFileName = await CreateLongFileName(size, stream, cancellationToken); + } + else + { + // We have a normal file or directory + longFileName = string.Empty; + // Reset longFileName if current entry is not indicating one + await CreateFileOrDirectory(outputDir, name, size, stream, cancellationToken); } - if ( !name.EndsWith('/') ) // Directories are zero size and don't need anything written + // Move head to next 512 byte block + var pos = stream.Position; + var offset = 512 - pos % 512; + if ( offset == 512 ) { - var str = new MemoryStream(); - var buf = new byte[size]; - await stream.ReadAsync(buf, 0, buf.Length, cancellationToken); - str.Write(buf, 0, buf.Length); - _storage.WriteStreamOpenOrCreate(str, output); + offset = 0; } + + stream.Seek(offset, SeekOrigin.Current); + } + } + + private static async Task CreateLongFileName(long size, Stream stream, + CancellationToken cancellationToken) + { + //If Type Tag is 'L' we have a filename that is longer than the 100 bytes reserved for it in the header. + //We read it here and save it temporarily as it will be the file name of the next block where the actual data is + var buf = new byte[size]; + await stream.ReadAsync(buf, 0, buf.Length, cancellationToken); + return Encoding.ASCII.GetString(buf).Trim('\0'); + } + + private async Task CreateFileOrDirectory(string outputDir, string name, long size, + Stream stream, CancellationToken cancellationToken) + { + var output = $"{outputDir}{_pathSeparator}{name}"; + + if ( !_storage.ExistFolder(FilenamesHelper.GetParentPath(output)) ) + { + _storage.CreateDirectory(FilenamesHelper.GetParentPath(output)); } + if ( !name.EndsWith('/') ) // Directories are zero size and don't need anything written + { + var str = new MemoryStream(); + var buf = new byte[size]; + await stream.ReadAsync(buf, 0, buf.Length, cancellationToken); + await str.WriteAsync(buf, 0, buf.Length, cancellationToken); + _storage.WriteStreamOpenOrCreate(str, output); + } } } diff --git a/starsky/starsky.foundation.writemeta/Services/ExifToolDownload.cs b/starsky/starsky.foundation.writemeta/Services/ExifToolDownload.cs index 2c79bc9849..acb98196f1 100644 --- a/starsky/starsky.foundation.writemeta/Services/ExifToolDownload.cs +++ b/starsky/starsky.foundation.writemeta/Services/ExifToolDownload.cs @@ -20,384 +20,441 @@ using starsky.foundation.writemeta.Interfaces; [assembly: InternalsVisibleTo("starskytest")] -namespace starsky.foundation.writemeta.Services -{ - [Service(typeof(IExifToolDownload), InjectionLifetime = InjectionLifetime.Singleton)] - [SuppressMessage("Usage", "S1075:Refactor your code not to use hardcoded absolute paths or URIs", Justification = "Source of files")] - public sealed class ExifToolDownload : IExifToolDownload - { - private readonly IHttpClientHelper _httpClientHelper; - private readonly AppSettings _appSettings; - private readonly IStorage _hostFileSystemStorage; - private readonly IWebLogger _logger; - - private const string CheckSumLocation = "https://exiftool.org/checksums.txt"; - private const string CheckSumLocationMirror = "https://qdraw.nl/special/mirror/exiftool/checksums.txt"; - private const string ExiftoolDownloadBasePath = "https://exiftool.org/"; // with slash at the end - private const string ExiftoolDownloadBasePathMirror = "https://qdraw.nl/special/mirror/exiftool/"; // with slash at the end - public ExifToolDownload(IHttpClientHelper httpClientHelper, AppSettings appSettings, IWebLogger logger) - { - _httpClientHelper = httpClientHelper; - _appSettings = appSettings; - _hostFileSystemStorage = new StorageHostFullPathFilesystem(logger); - _logger = logger; - } +namespace starsky.foundation.writemeta.Services; - internal ExifToolDownload(IHttpClientHelper httpClientHelper, AppSettings appSettings, IWebLogger logger, IStorage storage) - { - _httpClientHelper = httpClientHelper; - _appSettings = appSettings; - _hostFileSystemStorage = storage; - _logger = logger; - } +[Service(typeof(IExifToolDownload), InjectionLifetime = InjectionLifetime.Singleton)] +[SuppressMessage("Usage", "S1075:Refactor your code not to use hardcoded absolute paths or URIs", + Justification = "Source of files")] +public sealed class ExifToolDownload : IExifToolDownload +{ + private const string CheckSumLocation = "https://exiftool.org/checksums.txt"; - /// - /// Auto Download Exiftool - /// - /// download Windows version if true - /// check for min file size in bytes (Default = 30 bytes) - /// - public async Task DownloadExifTool(bool isWindows, int minimumSize = 30) - { - if ( _appSettings.ExiftoolSkipDownloadOnStartup == true || _appSettings is { AddSwaggerExport: true, AddSwaggerExportExitAfter: true } ) - { - var name = _appSettings.ExiftoolSkipDownloadOnStartup == true - ? "ExiftoolSkipDownloadOnStartup" - : "AddSwaggerExport and AddSwaggerExportExitAfter"; - _logger.LogInformation($"[DownloadExifTool] Skipped due true of {name} setting"); - return false; - } + private const string CheckSumLocationMirror = + "https://qdraw.nl/special/mirror/exiftool/checksums.txt"; - CreateDirectoryDependenciesFolderIfNotExists(); + private const string + ExiftoolDownloadBasePath = "https://exiftool.org/"; // with slash at the end - if ( isWindows && - ( !_hostFileSystemStorage.ExistFile(ExeExifToolWindowsFullFilePath()) || - _hostFileSystemStorage.Info(ExeExifToolWindowsFullFilePath()).Size <= minimumSize ) ) - { - return await StartDownloadForWindows(); - } + private const string ExiftoolDownloadBasePathMirror = + "https://qdraw.nl/special/mirror/exiftool/"; // with slash at the end - if ( !isWindows && - ( !_hostFileSystemStorage.ExistFile(ExeExifToolUnixFullFilePath()) || - _hostFileSystemStorage.Info(ExeExifToolUnixFullFilePath()).Size <= minimumSize ) ) - { - return await StartDownloadForUnix(); - } + private readonly AppSettings _appSettings; + private readonly IStorage _hostFileSystemStorage; + private readonly IHttpClientHelper _httpClientHelper; + private readonly IWebLogger _logger; - if ( _appSettings.IsVerbose() ) - { - var debugPath = isWindows ? ExeExifToolWindowsFullFilePath() - : ExeExifToolUnixFullFilePath(); - _logger.LogInformation($"[DownloadExifTool] {debugPath}"); - } + public ExifToolDownload(IHttpClientHelper httpClientHelper, AppSettings appSettings, + IWebLogger logger) + { + _httpClientHelper = httpClientHelper; + _appSettings = appSettings; + _hostFileSystemStorage = new StorageHostFullPathFilesystem(logger); + _logger = logger; + } - // When running deploy scripts rights might reset (only for unix) - if ( isWindows ) return true; + internal ExifToolDownload(IHttpClientHelper httpClientHelper, AppSettings appSettings, + IWebLogger logger, IStorage storage) + { + _httpClientHelper = httpClientHelper; + _appSettings = appSettings; + _hostFileSystemStorage = storage; + _logger = logger; + } - return await RunChmodOnExifToolUnixExe(); + /// + /// Auto Download Exiftool + /// + /// download Windows version if true + /// check for min file size in bytes (Default = 30 bytes) + /// + public async Task DownloadExifTool(bool isWindows, int minimumSize = 30) + { + if ( _appSettings.ExiftoolSkipDownloadOnStartup == true || _appSettings is + { AddSwaggerExport: true, AddSwaggerExportExitAfter: true } ) + { + var name = _appSettings.ExiftoolSkipDownloadOnStartup == true + ? "ExiftoolSkipDownloadOnStartup" + : "AddSwaggerExport and AddSwaggerExportExitAfter"; + _logger.LogInformation($"[DownloadExifTool] Skipped due true of {name} setting"); + return false; } - private void CreateDirectoryDependenciesFolderIfNotExists() + CreateDirectoryDependenciesFolderIfNotExists(); + + if ( isWindows && + ( !_hostFileSystemStorage.ExistFile(ExeExifToolWindowsFullFilePath()) || + _hostFileSystemStorage.Info(ExeExifToolWindowsFullFilePath()).Size <= minimumSize ) ) { - if ( _hostFileSystemStorage.ExistFolder( - _appSettings.DependenciesFolder) ) - { - return; - } - _logger.LogInformation("[DownloadExifTool] Create Directory: " + _appSettings.DependenciesFolder); - _hostFileSystemStorage.CreateDirectory(_appSettings.DependenciesFolder); + return await StartDownloadForWindows(); } - internal async Task?> DownloadCheckSums() + if ( !isWindows && + ( !_hostFileSystemStorage.ExistFile(ExeExifToolUnixFullFilePath()) || + _hostFileSystemStorage.Info(ExeExifToolUnixFullFilePath()).Size <= minimumSize ) ) { - var baseLocationResult = await DownloadCheckSums(CheckSumLocation); - if ( baseLocationResult == null ) - { - return await DownloadCheckSums(CheckSumLocationMirror); - } - return baseLocationResult; + return await StartDownloadForUnix(); } - internal async Task?> DownloadCheckSums(string checkSumUrl) + if ( _appSettings.IsVerbose() ) { - var checksums = await _httpClientHelper.ReadString(checkSumUrl); - if ( checksums.Key ) - { - return checksums; - } - - _logger.LogError($"Checksum loading failed {CheckSumLocation}, next retry from mirror ~ error > " + checksums.Value); - return null; + var debugPath = isWindows + ? ExeExifToolWindowsFullFilePath() + : ExeExifToolUnixFullFilePath(); + _logger.LogInformation($"[DownloadExifTool] {debugPath}"); } - internal async Task StartDownloadForUnix() + // When running deploy scripts rights might reset (only for unix) + if ( isWindows ) { - var checksums = await DownloadCheckSums(); - if ( checksums == null ) - { - return false; - } - var matchExifToolForUnixName = GetUnixTarGzFromChecksum(checksums.Value.Value); - return await DownloadForUnix(matchExifToolForUnixName, - GetChecksumsFromTextFile(checksums.Value.Value)); + return true; } - internal static string GetUnixTarGzFromChecksum(string checksumsValue) + return await RunChmodOnExifToolUnixExe(); + } + + private void CreateDirectoryDependenciesFolderIfNotExists() + { + if ( _hostFileSystemStorage.ExistFolder( + _appSettings.DependenciesFolder) ) { - // (?<=SHA1\()Image-ExifTool-[\d\.]+\.zip - var regexExifToolForWindowsName = new Regex(@"(?<=SHA256\()Image-ExifTool-[0-9\.]+\.tar.gz", - RegexOptions.None, TimeSpan.FromMilliseconds(100)); - return regexExifToolForWindowsName.Match(checksumsValue).Value; + return; } - private string ExeExifToolUnixFullFilePath() + _logger.LogInformation("[DownloadExifTool] Create Directory: " + + _appSettings.DependenciesFolder); + _hostFileSystemStorage.CreateDirectory(_appSettings.DependenciesFolder); + } + + internal async Task?> DownloadCheckSums() + { + var baseLocationResult = await DownloadCheckSums(CheckSumLocation); + if ( baseLocationResult == null ) { - var path = Path.Combine(_appSettings.DependenciesFolder, - "exiftool-unix", - "exiftool"); - return path; + return await DownloadCheckSums(CheckSumLocationMirror); } - internal async Task DownloadForUnix(string matchExifToolForUnixName, string[] getChecksumsFromTextFile) - { - var result = await DownloadForUnix(ExiftoolDownloadBasePath, matchExifToolForUnixName, - getChecksumsFromTextFile); - - if ( result ) - { - return true; - } + return baseLocationResult; + } - return await DownloadForUnix(ExiftoolDownloadBasePathMirror, matchExifToolForUnixName, - getChecksumsFromTextFile); + internal async Task?> DownloadCheckSums(string checkSumUrl) + { + var checksums = await _httpClientHelper.ReadString(checkSumUrl); + if ( checksums.Key ) + { + return checksums; } - - private async Task DownloadForUnix(string exiftoolDownloadBasePath, string matchExifToolForUnixName, - string[] getChecksumsFromTextFile) + _logger.LogError( + $"Checksum loading failed {CheckSumLocation}, next retry from mirror ~ error > " + + checksums.Value); + return null; + } + + internal async Task StartDownloadForUnix() + { + var checksums = await DownloadCheckSums(); + if ( checksums == null ) { + return false; + } - if ( _hostFileSystemStorage.ExistFile(ExeExifToolUnixFullFilePath()) ) - { - return true; - } + var matchExifToolForUnixName = GetUnixTarGzFromChecksum(checksums.Value.Value); + return await DownloadForUnix(matchExifToolForUnixName, + GetChecksumsFromTextFile(checksums.Value.Value)); + } - var tarGzArchiveFullFilePath = Path.Combine(_appSettings.DependenciesFolder, "exiftool.tar.gz"); + internal static string GetUnixTarGzFromChecksum(string checksumsValue) + { + // (?<=SHA1\()Image-ExifTool-[\d\.]+\.zip + var regexExifToolForWindowsName = new Regex(@"(?<=SHA256\()Image-ExifTool-[0-9\.]+\.tar.gz", + RegexOptions.None, TimeSpan.FromMilliseconds(100)); + return regexExifToolForWindowsName.Match(checksumsValue).Value; + } - var url = $"{exiftoolDownloadBasePath}{matchExifToolForUnixName}"; + private string ExeExifToolUnixFullFilePath() + { + var path = Path.Combine(_appSettings.DependenciesFolder, + "exiftool-unix", + "exiftool"); + return path; + } - var unixDownloaded = await _httpClientHelper.Download(url, tarGzArchiveFullFilePath); - if ( !unixDownloaded ) - { - _logger.LogError($"file is not downloaded {matchExifToolForUnixName}"); - return false; - } - - if ( !CheckSha256(tarGzArchiveFullFilePath, getChecksumsFromTextFile) ) - { - _logger.LogError($"Checksum for {tarGzArchiveFullFilePath} is not valid"); - _hostFileSystemStorage.FileDelete(tarGzArchiveFullFilePath); - return false; - } + internal async Task DownloadForUnix(string matchExifToolForUnixName, + string[] getChecksumsFromTextFile) + { + var result = await DownloadForUnix(ExiftoolDownloadBasePath, matchExifToolForUnixName, + getChecksumsFromTextFile); - await new TarBal(_hostFileSystemStorage).ExtractTarGz( - _hostFileSystemStorage.ReadStream(tarGzArchiveFullFilePath), _appSettings.DependenciesFolder, CancellationToken.None); + if ( result ) + { + return true; + } - var imageExifToolVersionFolder = _hostFileSystemStorage.GetDirectories(_appSettings.DependenciesFolder) - .FirstOrDefault(p => p.StartsWith(Path.Combine(_appSettings.DependenciesFolder, "Image-ExifTool-"))); - if ( imageExifToolVersionFolder != null ) - { - var exifToolUnixFolderFullFilePath = Path.Combine(_appSettings.DependenciesFolder, "exiftool-unix"); - if ( _hostFileSystemStorage.ExistFolder(exifToolUnixFolderFullFilePath) ) - { - _hostFileSystemStorage.FolderDelete( - exifToolUnixFolderFullFilePath); - } - _hostFileSystemStorage.FolderMove(imageExifToolVersionFolder, exifToolUnixFolderFullFilePath); - } - else - { - _logger.LogError($"[DownloadForUnix] ExifTool folder does not exists"); - return false; - } + return await DownloadForUnix(ExiftoolDownloadBasePathMirror, matchExifToolForUnixName, + getChecksumsFromTextFile); + } - // remove tar.gz file afterwards - _hostFileSystemStorage.FileDelete(tarGzArchiveFullFilePath); - var exifToolExePath = Path.Combine(_appSettings.DependenciesFolder, "exiftool-unix", "exiftool"); - _logger.LogInformation($"[DownloadForUnix] ExifTool is just downloaded: {exifToolExePath} for {_appSettings.ApplicationType}"); - return await RunChmodOnExifToolUnixExe(); + private async Task DownloadForUnix(string exiftoolDownloadBasePath, + string matchExifToolForUnixName, + string[] getChecksumsFromTextFile) + { + if ( _hostFileSystemStorage.ExistFile(ExeExifToolUnixFullFilePath()) ) + { + return true; } - internal async Task RunChmodOnExifToolUnixExe() + var tarGzArchiveFullFilePath = + Path.Combine(_appSettings.DependenciesFolder, "exiftool.tar.gz"); + + var url = $"{exiftoolDownloadBasePath}{matchExifToolForUnixName}"; + + var unixDownloaded = await _httpClientHelper.Download(url, tarGzArchiveFullFilePath); + if ( !unixDownloaded ) { - // need to check again - // when not exist - if ( !_hostFileSystemStorage.ExistFile(ExeExifToolUnixFullFilePath()) ) return false; - if ( _appSettings.IsWindows ) return true; + _logger.LogError($"file is not downloaded {matchExifToolForUnixName}"); + return false; + } + + if ( !CheckSha256(tarGzArchiveFullFilePath, getChecksumsFromTextFile) ) + { + _logger.LogError($"Checksum for {tarGzArchiveFullFilePath} is not valid"); + _hostFileSystemStorage.FileDelete(tarGzArchiveFullFilePath); + return false; + } - if ( !_hostFileSystemStorage.ExistFile("/bin/chmod") ) + await new TarBal(_hostFileSystemStorage, _logger).ExtractTarGz( + _hostFileSystemStorage.ReadStream(tarGzArchiveFullFilePath), + _appSettings.DependenciesFolder, CancellationToken.None); + + var imageExifToolVersionFolder = _hostFileSystemStorage + .GetDirectories(_appSettings.DependenciesFolder) + .FirstOrDefault(p => + p.StartsWith(Path.Combine(_appSettings.DependenciesFolder, "Image-ExifTool-"))); + if ( imageExifToolVersionFolder != null ) + { + var exifToolUnixFolderFullFilePath = + Path.Combine(_appSettings.DependenciesFolder, "exiftool-unix"); + if ( _hostFileSystemStorage.ExistFolder(exifToolUnixFolderFullFilePath) ) { - _logger.LogError("[RunChmodOnExifToolUnixExe] WARNING: /bin/chmod does not exist"); - return true; + _hostFileSystemStorage.FolderDelete( + exifToolUnixFolderFullFilePath); } - // command.run does not care about the $PATH - var result = await Command.Run("/bin/chmod", "0755", ExeExifToolUnixFullFilePath()).Task; - if ( result.Success ) return true; - - _logger.LogError($"command failed with exit code {result.ExitCode}: {result.StandardError}"); + _hostFileSystemStorage.FolderMove(imageExifToolVersionFolder, + exifToolUnixFolderFullFilePath); + } + else + { + _logger.LogError("[DownloadForUnix] ExifTool folder does not exists"); return false; } - internal async Task StartDownloadForWindows() - { - var checksums = await DownloadCheckSums(); - if ( checksums == null ) return false; + // remove tar.gz file afterwards + _hostFileSystemStorage.FileDelete(tarGzArchiveFullFilePath); + + var exifToolExePath = + Path.Combine(_appSettings.DependenciesFolder, "exiftool-unix", "exiftool"); + _logger.LogInformation( + $"[DownloadForUnix] ExifTool is just downloaded: {exifToolExePath} for {_appSettings.ApplicationType}"); + return await RunChmodOnExifToolUnixExe(); + } - var matchExifToolForWindowsName = GetWindowsZipFromChecksum(checksums.Value.Value); - return await DownloadForWindows(matchExifToolForWindowsName, - GetChecksumsFromTextFile(checksums.Value.Value)); + internal async Task RunChmodOnExifToolUnixExe() + { + // need to check again + // when not exist + if ( !_hostFileSystemStorage.ExistFile(ExeExifToolUnixFullFilePath()) ) + { + return false; } - internal static string GetWindowsZipFromChecksum(string checksumsValue) + if ( _appSettings.IsWindows ) { - // (?<=SHA256\()exiftool-[\d\.]+_64\.zip - var regexExifToolForWindowsName = new Regex(@"(?<=SHA256\()exiftool-[0-9\.]+_64\.zip", - RegexOptions.None, TimeSpan.FromMilliseconds(100)); - return regexExifToolForWindowsName.Match(checksumsValue).Value; + return true; } - /// - /// Parse the content of checksum file - /// - /// input file: see test for example - /// max number of SHA256 results - /// - internal string[] GetChecksumsFromTextFile(string checksumsValue, int max = 20) + if ( !_hostFileSystemStorage.ExistFile("/bin/chmod") ) { - // SHA256 = 64 characters, SHA1 = 40 characters - var regexExifToolForWindowsName = new Regex("[a-z0-9]{64}", - RegexOptions.None, TimeSpan.FromMilliseconds(100)); - var results = regexExifToolForWindowsName.Matches(checksumsValue). - Select(m => m.Value). - ToArray(); - if ( results.Length < max ) - { - return results; - } + _logger.LogError("[RunChmodOnExifToolUnixExe] WARNING: /bin/chmod does not exist"); + return true; + } - _logger.LogError($"More than {max} checksums found, this is not expected, code stops now"); - return []; + // command.run does not care about the $PATH + var result = await Command.Run("/bin/chmod", "0755", ExeExifToolUnixFullFilePath()).Task; + if ( result.Success ) + { + return true; } - /// - /// Check if SHA256 hash is valid - /// Instead of SHA1CryptoServiceProvider, we use SHA256.Create - /// - /// path of exiftool.exe - /// list of SHA256 hashes - /// - internal bool CheckSha256(string fullFilePath, IEnumerable checkSumOptions) + _logger.LogError( + $"command failed with exit code {result.ExitCode}: {result.StandardError}"); + return false; + } + + internal async Task StartDownloadForWindows() + { + var checksums = await DownloadCheckSums(); + if ( checksums == null ) { - using var buffer = _hostFileSystemStorage.ReadStream(fullFilePath); - using var hashAlgorithm = SHA256.Create(); + return false; + } - var byteHash = hashAlgorithm.ComputeHash(buffer); - var hash = BitConverter.ToString(byteHash).Replace("-", string.Empty).ToLowerInvariant(); - return checkSumOptions.AsEnumerable().Any(p => p.Equals(hash, StringComparison.InvariantCultureIgnoreCase)); + var matchExifToolForWindowsName = GetWindowsZipFromChecksum(checksums.Value.Value); + return await DownloadForWindows(matchExifToolForWindowsName, + GetChecksumsFromTextFile(checksums.Value.Value)); + } + + internal static string GetWindowsZipFromChecksum(string checksumsValue) + { + // (?<=SHA256\()exiftool-[\d\.]+_64\.zip + var regexExifToolForWindowsName = new Regex(@"(?<=SHA256\()exiftool-[0-9\.]+_64\.zip", + RegexOptions.None, TimeSpan.FromMilliseconds(100)); + return regexExifToolForWindowsName.Match(checksumsValue).Value; + } + + /// + /// Parse the content of checksum file + /// + /// input file: see test for example + /// max number of SHA256 results + /// + internal string[] GetChecksumsFromTextFile(string checksumsValue, int max = 20) + { + // SHA256 = 64 characters, SHA1 = 40 characters + var regexExifToolForWindowsName = new Regex("[a-z0-9]{64}", + RegexOptions.None, TimeSpan.FromMilliseconds(100)); + var results = regexExifToolForWindowsName.Matches(checksumsValue).Select(m => m.Value) + .ToArray(); + if ( results.Length < max ) + { + return results; } - private string ExeExifToolWindowsFullFilePath() + _logger.LogError($"More than {max} checksums found, this is not expected, code stops now"); + return []; + } + + /// + /// Check if SHA256 hash is valid + /// Instead of SHA1CryptoServiceProvider, we use SHA256.Create + /// + /// path of exiftool.exe + /// list of SHA256 hashes + /// + internal bool CheckSha256(string fullFilePath, IEnumerable checkSumOptions) + { + using var buffer = _hostFileSystemStorage.ReadStream(fullFilePath); + using var hashAlgorithm = SHA256.Create(); + + var byteHash = hashAlgorithm.ComputeHash(buffer); + var hash = BitConverter.ToString(byteHash).Replace("-", string.Empty).ToLowerInvariant(); + return checkSumOptions.AsEnumerable() + .Any(p => p.Equals(hash, StringComparison.InvariantCultureIgnoreCase)); + } + + private string ExeExifToolWindowsFullFilePath() + { + return Path.Combine(Path.Combine(_appSettings.DependenciesFolder, "exiftool-windows"), + "exiftool.exe"); + } + + internal async Task DownloadForWindows(string matchExifToolForWindowsName, + string[] getChecksumsFromTextFile) + { + var result = await DownloadForWindows(ExiftoolDownloadBasePath, matchExifToolForWindowsName, + getChecksumsFromTextFile); + + if ( result ) { - return Path.Combine(Path.Combine(_appSettings.DependenciesFolder, "exiftool-windows"), "exiftool.exe"); + return true; } - internal async Task DownloadForWindows(string matchExifToolForWindowsName, string[] getChecksumsFromTextFile) + return await DownloadForWindows(ExiftoolDownloadBasePathMirror, matchExifToolForWindowsName, + getChecksumsFromTextFile); + } + + private async Task DownloadForWindows(string exiftoolDownloadBasePath, + string matchExifToolForWindowsName, + string[] getChecksumsFromTextFile) + { + if ( _hostFileSystemStorage.ExistFile( + ExeExifToolWindowsFullFilePath()) ) { - var result = await DownloadForWindows(ExiftoolDownloadBasePath, matchExifToolForWindowsName, - getChecksumsFromTextFile); - - if ( result ) - { - return true; - } + return true; + } + + var zipArchiveFullFilePath = Path.Combine(_appSettings.DependenciesFolder, "exiftool.zip"); + var windowsExifToolFolder = + Path.Combine(_appSettings.DependenciesFolder, "exiftool-windows"); - return await DownloadForWindows(ExiftoolDownloadBasePathMirror, matchExifToolForWindowsName, - getChecksumsFromTextFile); + var url = $"{exiftoolDownloadBasePath}{matchExifToolForWindowsName}"; + var windowsDownloaded = await _httpClientHelper.Download(url, zipArchiveFullFilePath); + if ( !windowsDownloaded ) + { + _logger.LogError($"file is not downloaded {matchExifToolForWindowsName}"); + return false; } - - private async Task DownloadForWindows(string exiftoolDownloadBasePath, string matchExifToolForWindowsName, - string[] getChecksumsFromTextFile) + + if ( !CheckSha256(zipArchiveFullFilePath, getChecksumsFromTextFile) ) { - if ( _hostFileSystemStorage.ExistFile( - ExeExifToolWindowsFullFilePath()) ) - { - return true; - } + _logger.LogError($"Checksum for {zipArchiveFullFilePath} is not valid"); + return false; + } - var zipArchiveFullFilePath = Path.Combine(_appSettings.DependenciesFolder, "exiftool.zip"); - var windowsExifToolFolder = Path.Combine(_appSettings.DependenciesFolder, "exiftool-windows"); + _hostFileSystemStorage.CreateDirectory(windowsExifToolFolder); - var url = $"{exiftoolDownloadBasePath}{matchExifToolForWindowsName}"; - var windowsDownloaded = await _httpClientHelper.Download(url, zipArchiveFullFilePath); - if ( !windowsDownloaded ) - { - _logger.LogError($"file is not downloaded {matchExifToolForWindowsName}"); - return false; - } + new Zipper(_logger).ExtractZip(zipArchiveFullFilePath, windowsExifToolFolder); - if ( !CheckSha256(zipArchiveFullFilePath, getChecksumsFromTextFile) ) - { - _logger.LogError($"Checksum for {zipArchiveFullFilePath} is not valid"); - return false; - } + MoveFileIfExist(windowsExifToolFolder, "exiftool(-k).exe", windowsExifToolFolder, + "exiftool.exe"); + MoveFileIfExist(windowsExifToolFolder, "exiftool_files", windowsExifToolFolder, + "exiftool_files"); - _hostFileSystemStorage.CreateDirectory(windowsExifToolFolder); + _logger.LogInformation( + $"[DownloadForWindows] ExifTool downloaded: {ExeExifToolWindowsFullFilePath()}"); + return _hostFileSystemStorage.ExistFile(Path.Combine(windowsExifToolFolder, + "exiftool.exe")); + } - new Zipper(_logger).ExtractZip(zipArchiveFullFilePath, windowsExifToolFolder); - - MoveFileIfExist(windowsExifToolFolder, "exiftool(-k).exe", windowsExifToolFolder, "exiftool.exe"); - MoveFileIfExist(windowsExifToolFolder, "exiftool_files", windowsExifToolFolder, "exiftool_files"); + internal void MoveFileIfExist(string searchFolder, string searchForFileOrFolder, + string destFolder, string destFileNameOrFolderName) + { + var files = _hostFileSystemStorage.GetAllFilesInDirectoryRecursive(searchFolder).ToList(); + var folders = _hostFileSystemStorage.GetDirectoryRecursive(searchFolder).Select(p => p.Key); + List folderAndFiles = [..files, ..folders]; - _logger.LogInformation($"[DownloadForWindows] ExifTool downloaded: {ExeExifToolWindowsFullFilePath()}"); - return _hostFileSystemStorage.ExistFile(Path.Combine(windowsExifToolFolder, - "exiftool.exe")); + var srcFullPaths = folderAndFiles.Where(p => p.EndsWith(searchForFileOrFolder)).ToList(); + if ( srcFullPaths.Count == 0 ) + { + _logger.LogError($"[MoveFileIfExist] Could not find {searchForFileOrFolder} file"); + return; } - internal void MoveFileIfExist(string searchFolder, string searchForFileOrFolder, string destFolder, string destFileNameOrFolderName) + foreach ( var srcFullPath in srcFullPaths ) { - var files = _hostFileSystemStorage.GetAllFilesInDirectoryRecursive(searchFolder).ToList(); - var folders = _hostFileSystemStorage.GetDirectoryRecursive(searchFolder).Select(p => p.Key); - List folderAndFiles = [..files, ..folders]; - - var srcFullPaths = folderAndFiles.Where(p => p.EndsWith(searchForFileOrFolder)).ToList(); - if ( srcFullPaths.Count == 0 ) - { - _logger.LogError($"[MoveFileIfExist] Could not find {searchForFileOrFolder} file"); - return; - } + var isFolderOrFile = _hostFileSystemStorage.Info(srcFullPath).IsFolderOrFile; + var destFullPath = Path.Combine(destFolder, destFileNameOrFolderName); - foreach ( var srcFullPath in srcFullPaths ) + switch ( isFolderOrFile ) { - var isFolderOrFile = _hostFileSystemStorage.Info(srcFullPath).IsFolderOrFile; - var destFullPath = Path.Combine(destFolder, destFileNameOrFolderName); - - switch ( isFolderOrFile ) - { - case FolderOrFileModel.FolderOrFileTypeList.Folder: - if ( !_hostFileSystemStorage.ExistFolder(srcFullPath) ) continue; - _hostFileSystemStorage.FolderMove(srcFullPath, destFullPath); - break; - case FolderOrFileModel.FolderOrFileTypeList.File: - if ( !_hostFileSystemStorage.ExistFile(srcFullPath) ) continue; - _hostFileSystemStorage.FileMove(srcFullPath, destFullPath); - break; - default: - break; - } - + case FolderOrFileModel.FolderOrFileTypeList.Folder: + if ( !_hostFileSystemStorage.ExistFolder(srcFullPath) ) + { + continue; + } + + _hostFileSystemStorage.FolderMove(srcFullPath, destFullPath); + break; + case FolderOrFileModel.FolderOrFileTypeList.File: + if ( !_hostFileSystemStorage.ExistFile(srcFullPath) ) + { + continue; + } + + _hostFileSystemStorage.FileMove(srcFullPath, destFullPath); + break; } } } diff --git a/starsky/starsky/Controllers/AllowedTypesController.cs b/starsky/starsky/Controllers/AllowedTypesController.cs index b60eb91e9f..0882b6ccd4 100644 --- a/starsky/starsky/Controllers/AllowedTypesController.cs +++ b/starsky/starsky/Controllers/AllowedTypesController.cs @@ -5,61 +5,69 @@ using starsky.foundation.platform.Helpers; using starsky.project.web.Helpers; -namespace starsky.Controllers +namespace starsky.Controllers; + +[Authorize] +public sealed class AllowedTypesController : Controller { - [Authorize] - public sealed class AllowedTypesController : Controller + /// + /// A (string) list of allowed MIME-types ExtensionSyncSupportedList + /// + /// Json list + /// list + /// please login first + [HttpGet("/api/allowed-types/mimetype/sync")] + [ProducesResponseType(typeof(HashSet), 200)] + [Produces("application/json")] + public IActionResult AllowedTypesMimetypeSync() { - /// - /// A (string) list of allowed MIME-types ExtensionSyncSupportedList - /// - /// Json list - /// list - /// please login first - [HttpGet("/api/allowed-types/mimetype/sync")] - [ProducesResponseType(typeof(HashSet), 200)] - [Produces("application/json")] - public IActionResult AllowedTypesMimetypeSync() - { - var mimeTypes = ExtensionRolesHelper.ExtensionSyncSupportedList - .Select(MimeHelper.GetMimeType).ToHashSet(); - return Json(mimeTypes); - } + var mimeTypes = ExtensionRolesHelper.ExtensionSyncSupportedList + .Select(MimeHelper.GetMimeType).ToHashSet(); + return Json(mimeTypes); + } - /// - /// A (string) list of allowed ExtensionThumbSupportedList MimeTypes - /// - /// Json list - /// list - /// please login first - [HttpGet("/api/allowed-types/mimetype/thumb")] - [ProducesResponseType(typeof(HashSet), 200)] - [Produces("application/json")] - public IActionResult AllowedTypesMimetypeSyncThumb() + /// + /// A (string) list of allowed ExtensionThumbSupportedList MimeTypes + /// + /// Json list + /// list + /// please login first + [HttpGet("/api/allowed-types/mimetype/thumb")] + [ProducesResponseType(typeof(HashSet), 200)] + [Produces("application/json")] + public IActionResult AllowedTypesMimetypeSyncThumb() + { + var mimeTypes = ExtensionRolesHelper.ExtensionThumbSupportedList + .Select(MimeHelper.GetMimeType).ToHashSet(); + return Json(mimeTypes); + } + + /// + /// Check if IsExtensionThumbnailSupported + /// + /// Json list + /// the name with extension and no parent path + /// is supported + /// the extenstion from the filename is not supported to generate thumbnails + /// please log in first + [HttpGet("/api/allowed-types/thumb")] + [ProducesResponseType(typeof(bool), 200)] + [ProducesResponseType(typeof(bool), 415)] + [Produces("application/json")] + public IActionResult AllowedTypesThumb(string f) + { + if ( !ModelState.IsValid ) { - var mimeTypes = ExtensionRolesHelper.ExtensionThumbSupportedList - .Select(MimeHelper.GetMimeType).ToHashSet(); - return Json(mimeTypes); + return BadRequest("ModelState is not valid"); } - /// - /// Check if IsExtensionThumbnailSupported - /// - /// Json list - /// the name with extension and no parent path - /// is supported - /// the extenstion from the filename is not supported to generate thumbnails - /// please login first - [HttpGet("/api/allowed-types/thumb")] - [ProducesResponseType(typeof(bool), 200)] - [ProducesResponseType(typeof(bool), 415)] - [Produces("application/json")] - public IActionResult AllowedTypesThumb(string f) + var result = ExtensionRolesHelper.IsExtensionThumbnailSupported(f); + if ( !result ) { - var result = ExtensionRolesHelper.IsExtensionThumbnailSupported(f); - if ( !result ) Response.StatusCode = 415; - return Json(result); + Response.StatusCode = 415; } + + return Json(result); } } diff --git a/starsky/starsky/Controllers/AppSettingsController.cs b/starsky/starsky/Controllers/AppSettingsController.cs index ad18e0b25b..d077a49ed2 100644 --- a/starsky/starsky/Controllers/AppSettingsController.cs +++ b/starsky/starsky/Controllers/AppSettingsController.cs @@ -8,69 +8,73 @@ using starsky.foundation.accountmanagement.Services; using starsky.foundation.platform.Models; -namespace starsky.Controllers +namespace starsky.Controllers; + +[Authorize] +public sealed class AppSettingsController : Controller { - [Authorize] - public sealed class AppSettingsController : Controller + private readonly AppSettings _appSettings; + private readonly IUpdateAppSettingsByPath _updateAppSettingsByPath; + + public AppSettingsController(AppSettings appSettings, + IUpdateAppSettingsByPath updateAppSettingsByPath) { - private readonly AppSettings _appSettings; - private readonly IUpdateAppSettingsByPath _updateAppSettingsByPath; + _appSettings = appSettings; + _updateAppSettingsByPath = updateAppSettingsByPath; + } - public AppSettingsController(AppSettings appSettings, - IUpdateAppSettingsByPath updateAppSettingsByPath) - { - _appSettings = appSettings; - _updateAppSettingsByPath = updateAppSettingsByPath; - } + /// + /// Show the runtime settings (dont allow AllowAnonymous) + /// + /// config data, except connection strings + /// returns the runtime settings of Starsky + [HttpHead("/api/env")] + [HttpGet("/api/env")] + [Produces("application/json")] + [ProducesResponseType(typeof(AppSettings), 200)] + [ProducesResponseType(typeof(AppSettings), 401)] + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", + Justification = "Request in tests")] + public IActionResult Env() + { + var appSettings = _appSettings.CloneToDisplay(); - /// - /// Show the runtime settings (dont allow AllowAnonymous) - /// - /// config data, except connection strings - /// returns the runtime settings of Starsky - [HttpHead("/api/env")] - [HttpGet("/api/env")] - [Produces("application/json")] - [ProducesResponseType(typeof(AppSettings), 200)] - [ProducesResponseType(typeof(AppSettings), 401)] - [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", - Justification = "Request in tests")] - public IActionResult Env() + // For end-to-end testing + if ( Request != null! && Request.Headers.Any(p => p.Key == "x-force-html") ) { - var appSettings = _appSettings.CloneToDisplay(); - - // For end-to-end testing - if ( Request != null! && Request.Headers.Any(p => p.Key == "x-force-html") ) - { - Response.Headers.ContentType = "text/html; charset=utf-8"; - } - - return Json(appSettings); + Response.Headers.ContentType = "text/html; charset=utf-8"; } - /// - /// Show the runtime settings (dont allow AllowAnonymous) - /// - /// config data, except connection strings - /// returns the runtime settings of Starsky - [HttpPost("/api/env")] - [Produces("application/json")] - [ProducesResponseType(typeof(AppSettings), 200)] - [ProducesResponseType(typeof(AppSettings), 401)] - [Permission(UserManager.AppPermissions.AppSettingsWrite)] - public async Task UpdateAppSettings( - AppSettingsTransferObject appSettingTransferObject) + return Json(appSettings); + } + + /// + /// Show the runtime settings (dont allow AllowAnonymous) + /// + /// config data, except connection strings + /// returns the runtime settings of Starsky + [HttpPost("/api/env")] + [Produces("application/json")] + [ProducesResponseType(typeof(AppSettings), 200)] + [ProducesResponseType(typeof(AppSettings), 401)] + [Permission(UserManager.AppPermissions.AppSettingsWrite)] + public async Task UpdateAppSettings( + AppSettingsTransferObject appSettingTransferObject) + { + if ( !ModelState.IsValid ) { - var result = await _updateAppSettingsByPath.UpdateAppSettingsAsync( - appSettingTransferObject); + return BadRequest("ModelState is not valid"); + } - if ( !result.IsError ) - { - return Env(); - } + var result = await _updateAppSettingsByPath.UpdateAppSettingsAsync( + appSettingTransferObject); - Response.StatusCode = result.StatusCode; - return Content(result.Message); + if ( !result.IsError ) + { + return Env(); } + + Response.StatusCode = result.StatusCode; + return Content(result.Message); } } diff --git a/starsky/starsky/Controllers/CacheIndexController.cs b/starsky/starsky/Controllers/CacheIndexController.cs index 65c0d1d79a..21e3597301 100644 --- a/starsky/starsky/Controllers/CacheIndexController.cs +++ b/starsky/starsky/Controllers/CacheIndexController.cs @@ -4,80 +4,100 @@ using starsky.foundation.database.Interfaces; using starsky.foundation.platform.Models; -namespace starsky.Controllers +namespace starsky.Controllers; + +[Authorize] +public sealed class CacheIndexController : Controller { - [Authorize] - public sealed class CacheIndexController : Controller + private readonly AppSettings _appSettings; + private readonly IQuery _query; + + public CacheIndexController( + IQuery query, AppSettings appSettings) { - private readonly IQuery _query; - private readonly AppSettings _appSettings; + _appSettings = appSettings; + _query = query; + } - public CacheIndexController( - IQuery query, AppSettings appSettings) + /// + /// Get Database Cache (only the cache) + /// + /// subPath (only direct so no dot;comma list) + /// redirect or if json enabled a status + /// when json" + /// "cache disabled in config" + /// + /// ignored, please check if the 'f' path exist or use a folder string to clear + /// the cache + /// + /// User unauthorized + [HttpGet("/api/cache/list")] + public IActionResult ListCache(string f = "/") + { + if ( !ModelState.IsValid ) { - _appSettings = appSettings; - _query = query; + return BadRequest("ModelState is not valid"); } - /// - /// Get Database Cache (only the cache) - /// - /// subPath (only direct so no dot;comma list) - /// redirect or if json enabled a status - /// when json" - /// "cache disabled in config" - /// ignored, please check if the 'f' path exist or use a folder string to clear the cache - /// User unauthorized - [HttpGet("/api/cache/list")] - public IActionResult ListCache(string f = "/") + //For folder paths only + if ( _appSettings.AddMemoryCache == false ) { - //For folder paths only - if ( _appSettings.AddMemoryCache == false ) - { - Response.StatusCode = 412; - return Json("cache disabled in config"); - } - - var (success, singleItem) = _query.CacheGetParentFolder(f); - if ( !success || singleItem == null ) - return BadRequest( - "ignored, please check if the 'f' path exist or use a folder string to get the cache"); + Response.StatusCode = 412; + return Json("cache disabled in config"); + } - return Json(singleItem); + var (success, singleItem) = _query.CacheGetParentFolder(f); + if ( !success || singleItem == null ) + { + return BadRequest( + "ignored, please check if the 'f' path exist or use a folder string to get the cache"); } - /// - /// Delete Database Cache (only the cache) - /// - /// subPath (only direct so no dot;comma list) - /// redirect or if json enabled a status - /// when json is true, "cache successful cleared" - /// "cache disabled in config" - /// ignored, please check if the 'f' path exist or use a folder string to clear the cache - /// redirect back to the url - /// User unauthorized - [HttpGet("/api/remove-cache")] - [HttpPost("/api/remove-cache")] - [ProducesResponseType(200)] // "cache successful cleared" - [ProducesResponseType(412)] // "cache disabled in config" - [ProducesResponseType(400)] // "ignored, please check if the 'f' path exist or use a folder string to clear the cache" - [ProducesResponseType(302)] // redirect back to the url - public IActionResult RemoveCache(string f = "/") + return Json(singleItem); + } + + /// + /// Delete Database Cache (only the cache) + /// + /// subPath (only direct so no dot;comma list) + /// redirect or if json enabled a status + /// when json is true, "cache successful cleared" + /// "cache disabled in config" + /// + /// ignored, please check if the 'f' path exist or use a folder string to clear + /// the cache + /// + /// redirect back to the url + /// User unauthorized + [HttpGet("/api/remove-cache")] + [HttpPost("/api/remove-cache")] + [ProducesResponseType(200)] // "cache successful cleared" + [ProducesResponseType(412)] // "cache disabled in config" + [ProducesResponseType(400)] // "ignored, please check if the 'f' path exist or use a folder string to clear the cache" + [ProducesResponseType(302)] // redirect back to the url + public IActionResult RemoveCache(string f = "/") + { + if ( !ModelState.IsValid ) { - //For folder paths only - if ( _appSettings.AddMemoryCache == false ) - { - Response.StatusCode = 412; - return Json("cache disabled in config"); - } + return BadRequest("ModelState is not valid"); + } - var singleItem = _query.SingleItem(f); - if ( singleItem == null || !singleItem.IsDirectory ) - return BadRequest( - "ignored, please check if the 'f' path exist or use a folder string to clear the cache"); + //For folder paths only + if ( _appSettings.AddMemoryCache == false ) + { + Response.StatusCode = 412; + return Json("cache disabled in config"); + } - return Json(_query.RemoveCacheParentItem(f) ? "cache successful cleared" : "cache did not exist"); + var singleItem = _query.SingleItem(f); + if ( singleItem == null || !singleItem.IsDirectory ) + { + return BadRequest( + "ignored, please check if the 'f' path exist or use a folder string to clear the cache"); } + return Json(_query.RemoveCacheParentItem(f) + ? "cache successful cleared" + : "cache did not exist"); } } diff --git a/starsky/starsky/Controllers/DeleteController.cs b/starsky/starsky/Controllers/DeleteController.cs index 9257b03242..e7b8cf45b9 100644 --- a/starsky/starsky/Controllers/DeleteController.cs +++ b/starsky/starsky/Controllers/DeleteController.cs @@ -5,44 +5,52 @@ using starsky.feature.metaupdate.Interfaces; using starsky.foundation.database.Models; -namespace starsky.Controllers +namespace starsky.Controllers; + +[Authorize] +public sealed class DeleteController : Controller { - [Authorize] - public sealed class DeleteController : Controller + private readonly IDeleteItem _deleteItem; + + public DeleteController(IDeleteItem deleteItem) { - private readonly IDeleteItem _deleteItem; + _deleteItem = deleteItem; + } - public DeleteController(IDeleteItem deleteItem) + /// + /// Remove files from the disk, but the file must contain the !delete! + /// (TrashKeyword.TrashKeywordString) tag + /// + /// subPaths, separated by dot comma + /// true is to update files with the same name before the extenstion + /// list of deleted files + /// file is gone + /// + /// item not found on disk or !delete! (TrashKeyword.TrashKeywordString) tag is + /// missing + /// + /// User unauthorized + [HttpDelete("/api/delete")] + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(List), 404)] + [Produces("application/json")] + public async Task Delete(string f, bool collections = false) + { + if ( !ModelState.IsValid ) { - _deleteItem = deleteItem; + return BadRequest("ModelState is not valid"); } - /// - /// Remove files from the disk, but the file must contain the !delete! (TrashKeyword.TrashKeywordString) tag - /// - /// subPaths, separated by dot comma - /// true is to update files with the same name before the extenstion - /// list of deleted files - /// file is gone - /// item not found on disk or !delete! (TrashKeyword.TrashKeywordString) tag is missing - /// User unauthorized - [HttpDelete("/api/delete")] - [ProducesResponseType(typeof(List), 200)] - [ProducesResponseType(typeof(List), 404)] - [Produces("application/json")] - public async Task Delete(string f, bool collections = false) + var fileIndexResultsList = await _deleteItem.DeleteAsync(f, collections); + // When all items are not found + // ok = file is deleted + if ( fileIndexResultsList.TrueForAll(p => + p.Status != FileIndexItem.ExifStatus.Ok) ) { - var fileIndexResultsList = await _deleteItem.DeleteAsync(f, collections); - // When all items are not found - // ok = file is deleted - if ( fileIndexResultsList.TrueForAll(p => - p.Status != FileIndexItem.ExifStatus.Ok) ) - { - return NotFound(fileIndexResultsList); - } + return NotFound(fileIndexResultsList); + } - return Json(fileIndexResultsList); - } + return Json(fileIndexResultsList); } } diff --git a/starsky/starsky/Controllers/DesktopEditorController.cs b/starsky/starsky/Controllers/DesktopEditorController.cs index 0cfdbbd1ce..9dc2cd584f 100644 --- a/starsky/starsky/Controllers/DesktopEditorController.cs +++ b/starsky/starsky/Controllers/DesktopEditorController.cs @@ -18,13 +18,13 @@ public DesktopEditorController(IOpenEditorDesktopService openEditorDesktopServic } /// - /// Open a file in the default editor or a specific editor on the desktop + /// Open a file in the default editor or a specific editor on the desktop /// /// single or multiple subPaths /// to combine files with the same name before the extension /// /// returns a list of items from the database - /// list with no content + /// list with no content /// subPath not found in the database /// User unauthorized [HttpPost("/api/desktop-editor/open")] @@ -37,6 +37,11 @@ public async Task OpenAsync( string f = "", bool collections = true) { + if ( !ModelState.IsValid ) + { + return BadRequest("ModelState is not valid"); + } + var (success, status, list) = await _openEditorDesktopService.OpenAsync(f, collections); @@ -54,7 +59,7 @@ public async Task OpenAsync( /// - /// Check the amount of files to open before + /// Check the amount of files to open before /// /// single or multiple subPaths /// @@ -64,8 +69,14 @@ public async Task OpenAsync( [Produces("application/json")] [ProducesResponseType(typeof(bool), 200)] [ProducesResponseType(401)] + [ProducesResponseType(400)] public IActionResult OpenAmountConfirmationChecker(string f) { + if ( !ModelState.IsValid ) + { + return BadRequest("ModelState is not valid"); + } + var result = _openEditorDesktopService.OpenAmountConfirmationChecker(f); return Json(result); } diff --git a/starsky/starsky/Controllers/DiskController.cs b/starsky/starsky/Controllers/DiskController.cs index e8c8666dc4..7f61dddfea 100644 --- a/starsky/starsky/Controllers/DiskController.cs +++ b/starsky/starsky/Controllers/DiskController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,146 +16,149 @@ using starsky.foundation.storage.Storage; using starsky.project.web.ViewModels; -namespace starsky.Controllers +namespace starsky.Controllers; + +[Authorize] +public sealed class DiskController : Controller { - [Authorize] - public sealed class DiskController : Controller + private readonly IWebSocketConnectionsService _connectionsService; + private readonly IStorage _iStorage; + private readonly INotificationQuery _notificationQuery; + private readonly IQuery _query; + + public DiskController(IQuery query, ISelectorStorage selectorStorage, + IWebSocketConnectionsService connectionsService, INotificationQuery notificationQuery) { - private readonly IQuery _query; - private readonly IStorage _iStorage; - private readonly IWebSocketConnectionsService _connectionsService; - private readonly INotificationQuery _notificationQuery; + _query = query; + _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); + _connectionsService = connectionsService; + _notificationQuery = notificationQuery; + } - public DiskController(IQuery query, ISelectorStorage selectorStorage, - IWebSocketConnectionsService connectionsService, INotificationQuery notificationQuery) + /// + /// Make a directory (-p) + /// + /// subPaths split by dot comma + /// list of changed files IActionResult Mkdir + /// create the item on disk and in db + /// A conflict, Directory already exist + /// missing path + /// User unauthorized + [HttpPost("/api/disk/mkdir")] + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(List), 409)] + [ProducesResponseType(typeof(List), 400)] + [ProducesResponseType(typeof(string), 401)] + [Produces("application/json")] + public async Task Mkdir(string f) + { + var inputFilePaths = PathHelper.SplitInputFilePaths(f).ToList(); + if ( inputFilePaths.Count == 0 ) { - _query = query; - _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); - _connectionsService = connectionsService; - _notificationQuery = notificationQuery; + Response.StatusCode = 400; + return Json(new List()); } - /// - /// Make a directory (-p) - /// - /// subPaths split by dot comma - /// list of changed files IActionResult Mkdir - /// create the item on disk and in db - /// A conflict, Directory already exist - /// missing path - /// User unauthorized - [HttpPost("/api/disk/mkdir")] - [ProducesResponseType(typeof(List), 200)] - [ProducesResponseType(typeof(List), 409)] - [ProducesResponseType(typeof(List), 400)] - [ProducesResponseType(typeof(string), 401)] - [Produces("application/json")] - public async Task Mkdir(string f) + var syncResultsList = new List(); + + foreach ( var subPath in inputFilePaths.Select(PathHelper.RemoveLatestSlash) ) { - var inputFilePaths = PathHelper.SplitInputFilePaths(f).ToList(); - if ( inputFilePaths.Count == 0 ) + var toAddStatus = new SyncViewModel { - Response.StatusCode = 400; - return Json(new List()); - } + FilePath = subPath, Status = FileIndexItem.ExifStatus.Ok + }; - var syncResultsList = new List(); - - foreach ( var subPath in inputFilePaths.Select(PathHelper.RemoveLatestSlash) ) + if ( _iStorage.ExistFolder(subPath) ) { - var toAddStatus = new SyncViewModel - { - FilePath = subPath, Status = FileIndexItem.ExifStatus.Ok - }; - - if ( _iStorage.ExistFolder(subPath) ) - { - toAddStatus.Status = FileIndexItem.ExifStatus.OperationNotSupported; - syncResultsList.Add(toAddStatus); - continue; - } - - await _query.AddItemAsync(new FileIndexItem(subPath) - { - IsDirectory = true, ImageFormat = ExtensionRolesHelper.ImageFormat.directory - }); - - // add to fs - _iStorage.CreateDirectory(subPath); - + toAddStatus.Status = FileIndexItem.ExifStatus.OperationNotSupported; syncResultsList.Add(toAddStatus); + continue; } - // When all items are not found - if ( syncResultsList.TrueForAll(p => p.Status != FileIndexItem.ExifStatus.Ok) ) - Response.StatusCode = 409; // A conflict, Directory already exist + await _query.AddItemAsync(new FileIndexItem(subPath) + { + IsDirectory = true, ImageFormat = ExtensionRolesHelper.ImageFormat.directory + }); - await SyncMessageToSocket(syncResultsList, ApiNotificationType.Mkdir); + // add to fs + _iStorage.CreateDirectory(subPath); - return Json(syncResultsList); + syncResultsList.Add(toAddStatus); } - /// - /// Update other users with a message from SyncViewModel - /// - /// SyncViewModel - /// optional debug name - /// Completed send of Socket SendToAllAsync - private async Task SyncMessageToSocket(IEnumerable syncResultsList, - ApiNotificationType type = ApiNotificationType.Unknown) + // When all items are not found + if ( syncResultsList.TrueForAll(p => p.Status != FileIndexItem.ExifStatus.Ok) ) { - var list = syncResultsList.Select(t => new FileIndexItem(t.FilePath) - { - Status = t.Status, IsDirectory = true - }).ToList(); + Response.StatusCode = 409; // A conflict, Directory already exist + } - var webSocketResponse = new ApiNotificationResponseModel< - List>(list, type); + await SyncMessageToSocket(syncResultsList, ApiNotificationType.Mkdir); - await _notificationQuery.AddNotification(webSocketResponse); - await _connectionsService.SendToAllAsync(webSocketResponse, CancellationToken.None); - } + return Json(syncResultsList); + } - /// - /// Rename file/folder and update it in the database - /// - /// from subPath - /// to subPath - /// is collections bool - /// default is to not included files that are removed in result - /// list of details form changed files (IActionResult Rename) - /// the item including the updated content - /// item not found in the database or on disk - /// User unauthorized - [ProducesResponseType(typeof(List), 200)] - [ProducesResponseType(typeof(List), 404)] - [HttpPost("/api/disk/rename")] - [Produces("application/json")] - public async Task Rename(string f, string to, bool collections = true, - bool currentStatus = true) + /// + /// Update other users with a message from SyncViewModel + /// + /// SyncViewModel + /// optional debug name + /// Completed send of Socket SendToAllAsync + private async Task SyncMessageToSocket(IEnumerable syncResultsList, + ApiNotificationType type = ApiNotificationType.Unknown) + { + var list = syncResultsList.Select(t => new FileIndexItem(t.FilePath) { - if ( string.IsNullOrEmpty(f) ) - { - return BadRequest("No input files"); - } + Status = t.Status, IsDirectory = true + }).ToList(); - var rename = await new RenameService(_query, _iStorage).Rename(f, to, collections); + var webSocketResponse = new ApiNotificationResponseModel< + List>(list, type); - // When all items are not found - if ( rename.TrueForAll(p => p.Status != FileIndexItem.ExifStatus.Ok) ) - return NotFound(rename); + await _notificationQuery.AddNotification(webSocketResponse); + await _connectionsService.SendToAllAsync(webSocketResponse, CancellationToken.None); + } - var webSocketResponse = - new ApiNotificationResponseModel>(rename, - ApiNotificationType.Rename); + /// + /// Rename file/folder and update it in the database + /// + /// from subPath + /// to subPath + /// is collections bool + /// default is to not included files that are removed in result + /// list of details form changed files (IActionResult Rename) + /// the item including the updated content + /// item not found in the database or on disk + /// User unauthorized + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(List), 404)] + [HttpPost("/api/disk/rename")] + [Produces("application/json")] + public async Task Rename([Required] string f, string to, bool collections = true, + bool currentStatus = true) + { + if ( string.IsNullOrEmpty(f) || !ModelState.IsValid ) + { + return BadRequest("No input files"); + } - await _notificationQuery.AddNotification(webSocketResponse); - await _connectionsService.SendToAllAsync(webSocketResponse, CancellationToken.None); + var rename = await new RenameService(_query, _iStorage).Rename(f, to, collections); - return Json(currentStatus - ? rename.Where(p => p.Status - != FileIndexItem.ExifStatus.NotFoundSourceMissing).ToList() - : rename); + // When all items are not found + if ( rename.TrueForAll(p => p.Status != FileIndexItem.ExifStatus.Ok) ) + { + return NotFound(rename); } + + var webSocketResponse = + new ApiNotificationResponseModel>(rename, + ApiNotificationType.Rename); + + await _notificationQuery.AddNotification(webSocketResponse); + await _connectionsService.SendToAllAsync(webSocketResponse, CancellationToken.None); + + return Json(currentStatus + ? rename.Where(p => p.Status + != FileIndexItem.ExifStatus.NotFoundSourceMissing).ToList() + : rename); } } diff --git a/starsky/starsky/Controllers/DownloadPhotoController.cs b/starsky/starsky/Controllers/DownloadPhotoController.cs index 6593541b9f..57bf7b5af9 100644 --- a/starsky/starsky/Controllers/DownloadPhotoController.cs +++ b/starsky/starsky/Controllers/DownloadPhotoController.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,129 +13,147 @@ using starsky.Helpers; using starsky.project.web.Helpers; -namespace starsky.Controllers +namespace starsky.Controllers; + +[Authorize] +public sealed class DownloadPhotoController : Controller { - [Authorize] - public sealed class DownloadPhotoController : Controller + private readonly IStorage _iStorage; + private readonly IWebLogger _logger; + private readonly IQuery _query; + private readonly IThumbnailService _thumbnailService; + private readonly IStorage _thumbnailStorage; + + public DownloadPhotoController(IQuery query, ISelectorStorage selectorStorage, + IWebLogger logger, IThumbnailService thumbnailService) + { + _query = query; + _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); + _thumbnailStorage = selectorStorage.Get(SelectorStorage.StorageServices.Thumbnail); + _thumbnailService = thumbnailService; + _logger = logger; + } + + /// + /// Download sidecar file for example image.xmp + /// + /// string, subPath to find the file + /// FileStream with image + /// returns content of the file + /// source image missing + /// User unauthorized + [HttpGet("/api/download-sidecar")] + [ProducesResponseType(200)] // file + [ProducesResponseType(404)] // not found + [ProducesResponseType(400)] + public IActionResult DownloadSidecar([Required] string f) { - private readonly IQuery _query; - private readonly IStorage _iStorage; - private readonly IStorage _thumbnailStorage; - private readonly IWebLogger _logger; - private readonly IThumbnailService _thumbnailService; - - public DownloadPhotoController(IQuery query, ISelectorStorage selectorStorage, - IWebLogger logger, IThumbnailService thumbnailService) + if ( !ModelState.IsValid ) { - _query = query; - _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); - _thumbnailStorage = selectorStorage.Get(SelectorStorage.StorageServices.Thumbnail); - _thumbnailService = thumbnailService; - _logger = logger; + return BadRequest("ModelState is not valid"); } - /// - /// Download sidecar file for example image.xmp - /// - /// string, subPath to find the file - /// FileStream with image - /// returns content of the file - /// source image missing - /// User unauthorized - [HttpGet("/api/download-sidecar")] - [ProducesResponseType(200)] // file - [ProducesResponseType(404)] // not found - public IActionResult DownloadSidecar(string f) + if ( !ExtensionRolesHelper.IsExtensionSidecar(f) ) { - if ( !ExtensionRolesHelper.IsExtensionSidecar(f) ) - { - return NotFound("FileName is not a sidecar"); - } + return NotFound("FileName is not a sidecar"); + } - if ( !_iStorage.ExistFile(f) ) - return NotFound($"source image missing {f}"); + if ( !_iStorage.ExistFile(f) ) + { + return NotFound($"source image missing {f}"); + } - var fs = _iStorage.ReadStream(f); - return File(fs, MimeHelper.GetMimeTypeByFileName(f)); + var fs = _iStorage.ReadStream(f); + return File(fs, MimeHelper.GetMimeTypeByFileName(f)); + } + + /// + /// Select manually the original or thumbnail + /// + /// string, 'sub path' to find the file + /// true = 1000px thumb (if supported) + /// true = send client headers to cache + /// FileStream with image + /// returns content of the file + /// source image missing + /// "Thumbnail generation failed" + /// User unauthorized + [HttpGet("/api/download-photo")] + [ProducesResponseType(200)] // file + [ProducesResponseType(404)] // not found + [ProducesResponseType(500)] // "Thumbnail generation failed" + public async Task DownloadPhoto(string f, bool isThumbnail = true, + bool cache = true) + { + if ( !ModelState.IsValid ) + { + return BadRequest("ModelState is not valid"); } - /// - /// Select manually the original or thumbnail - /// - /// string, 'sub path' to find the file - /// true = 1000px thumb (if supported) - /// true = send client headers to cache - /// FileStream with image - /// returns content of the file - /// source image missing - /// "Thumbnail generation failed" - /// User unauthorized - [HttpGet("/api/download-photo")] - [ProducesResponseType(200)] // file - [ProducesResponseType(404)] // not found - [ProducesResponseType(500)] // "Thumbnail generation failed" - public async Task DownloadPhoto(string f, bool isThumbnail = true, - bool cache = true) + if ( f.Contains("?isthumbnail") ) { // f = subpath/filepath - if ( f.Contains("?isthumbnail") ) - { - return NotFound("please use &isthumbnail = instead of ?isthumbnail= "); - } + return NotFound("please use &isthumbnail = instead of ?isthumbnail= "); + } - var fileIndexItem = await _query.GetObjectByFilePathAsync(f); - if ( fileIndexItem == null ) + var fileIndexItem = await _query.GetObjectByFilePathAsync(f); + if ( fileIndexItem == null ) + { + return NotFound("not in index " + f); + } + + if ( !_iStorage.ExistFile(fileIndexItem.FilePath!) ) + { + return NotFound($"source image missing {fileIndexItem.FilePath}"); + } + + // Return full image + if ( !isThumbnail ) + { + if ( cache ) { - return NotFound("not in index " + f); + CacheControlOverwrite.SetExpiresResponseHeaders(Request); } - if ( !_iStorage.ExistFile(fileIndexItem.FilePath!) ) - return NotFound($"source image missing {fileIndexItem.FilePath}"); + var fileStream = _iStorage.ReadStream(fileIndexItem.FilePath!); - // Return full image - if ( !isThumbnail ) - { - if ( cache ) CacheControlOverwrite.SetExpiresResponseHeaders(Request); - var fileStream = _iStorage.ReadStream(fileIndexItem.FilePath!); + // Return the right mime type (enableRangeProcessing = needed for safari and mp4) + return File(fileStream, MimeHelper.GetMimeTypeByFileName(fileIndexItem.FilePath!), + true); + } - // Return the right mime type (enableRangeProcessing = needed for safari and mp4) - return File(fileStream, MimeHelper.GetMimeTypeByFileName(fileIndexItem.FilePath!), - true); - } + if ( !_thumbnailStorage.ExistFolder("/") ) + { + return NotFound("ThumbnailTempFolder not found"); + } - if ( !_thumbnailStorage.ExistFolder("/") ) - { - return NotFound("ThumbnailTempFolder not found"); - } + var data = new ThumbnailSizesExistStatusModel + { + Small = _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.Small)), + Large = _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.Large)), + ExtraLarge = _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.ExtraLarge)) + }; - var data = new ThumbnailSizesExistStatusModel - { - Small = _thumbnailStorage.ExistFile( - ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.Small)), - Large = _thumbnailStorage.ExistFile( - ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.Large)), - ExtraLarge = _thumbnailStorage.ExistFile( - ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.ExtraLarge)) - }; - - if ( !data.Small || !data.Large || !data.ExtraLarge ) + if ( !data.Small || !data.Large || !data.ExtraLarge ) + { + _logger.LogDebug("Thumbnail generation started"); + await _thumbnailService.CreateThumbAsync(fileIndexItem.FilePath!, + fileIndexItem.FileHash!); + + if ( !_thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, + ThumbnailSize.Large)) ) { - _logger.LogDebug("Thumbnail generation started"); - await _thumbnailService.CreateThumbAsync(fileIndexItem.FilePath!, - fileIndexItem.FileHash!); - - if ( !_thumbnailStorage.ExistFile( - ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, - ThumbnailSize.Large)) ) - { - Response.StatusCode = 500; - return Json("Thumbnail generation failed"); - } + Response.StatusCode = 500; + return Json("Thumbnail generation failed"); } - - var thumbnailFileStream = _thumbnailStorage.ReadStream( - ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.Large)); - return File(thumbnailFileStream, "image/jpeg"); } + + var thumbnailFileStream = _thumbnailStorage.ReadStream( + ThumbnailNameHelper.Combine(fileIndexItem.FileHash!, ThumbnailSize.Large)); + return File(thumbnailFileStream, "image/jpeg"); } } diff --git a/starsky/starsky/Controllers/ErrorController.cs b/starsky/starsky/Controllers/ErrorController.cs index 5ad82af93d..57689595a5 100644 --- a/starsky/starsky/Controllers/ErrorController.cs +++ b/starsky/starsky/Controllers/ErrorController.cs @@ -3,37 +3,40 @@ using Microsoft.AspNetCore.Mvc; using starsky.foundation.platform.Models; -namespace starsky.Controllers +namespace starsky.Controllers; + +[AllowAnonymous] +public sealed class ErrorController : Controller { - [AllowAnonymous] - public sealed class ErrorController : Controller + private readonly string _clientApp; + + public ErrorController(AppSettings appSettings) { - private readonly string _clientApp; + _clientApp = Path.Combine(appSettings.BaseDirectoryProject, + "clientapp", "build", "index.html"); + } - public ErrorController(AppSettings appSettings) + /// + /// Return Error page (HTML) + /// + /// to add the status code to the response + /// Any Error html page + [HttpGet("/error")] + [Produces("text/html")] + public IActionResult Error(int? statusCode = null) + { + if ( !ModelState.IsValid ) { - _clientApp = Path.Combine(appSettings.BaseDirectoryProject, - "clientapp", "build", "index.html"); + return BadRequest("Model is invalid"); } - /// - /// Return Error page (HTML) - /// - /// to add the status code to the response - /// Any Error html page - [HttpGet("/error")] - [Produces("text/html")] - public IActionResult Error(int? statusCode = null) + if ( statusCode.HasValue ) { - if ( statusCode.HasValue ) - { - // here is the trick - HttpContext.Response.StatusCode = statusCode.Value; - } - - // or "~/error/${statusCode}.html" - return PhysicalFile(_clientApp, "text/html"); + // here is the trick + HttpContext.Response.StatusCode = statusCode.Value; } - } + // or "~/error/${statusCode}.html" + return PhysicalFile(_clientApp, "text/html"); + } } diff --git a/starsky/starsky/Controllers/UploadController.cs b/starsky/starsky/Controllers/UploadController.cs index 61840c98a3..e5ec1643ba 100644 --- a/starsky/starsky/Controllers/UploadController.cs +++ b/starsky/starsky/Controllers/UploadController.cs @@ -15,7 +15,6 @@ using starsky.foundation.database.Interfaces; using starsky.foundation.database.Models; using starsky.foundation.http.Streaming; -using starsky.foundation.thumbnailmeta.Interfaces; using starsky.foundation.platform.Enums; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Interfaces; @@ -24,301 +23,324 @@ using starsky.foundation.storage.Interfaces; using starsky.foundation.storage.Storage; using starsky.foundation.sync.SyncServices; +using starsky.foundation.thumbnailmeta.Interfaces; + +namespace starsky.Controllers; -namespace starsky.Controllers +[Authorize] // <- should be logged in! +[SuppressMessage("Usage", "S5693:Make sure the content " + + "length limit is safe here", Justification = "Is checked")] +public sealed class UploadController : Controller { - [Authorize] // <- should be logged in! - [SuppressMessage("Usage", "S5693:Make sure the content " + - "length limit is safe here", Justification = "Is checked")] - public sealed class UploadController : Controller + private readonly AppSettings _appSettings; + private readonly IStorage _iHostStorage; + private readonly IImport _import; + private readonly IStorage _iStorage; + private readonly IWebLogger _logger; + private readonly IMetaExifThumbnailService _metaExifThumbnailService; + private readonly IMetaUpdateStatusThumbnailService _metaUpdateStatusThumbnailService; + private readonly IQuery _query; + private readonly IRealtimeConnectionsService _realtimeService; + private readonly ISelectorStorage _selectorStorage; + + [SuppressMessage("Usage", + "S107: Constructor has 8 parameters, which is greater than the 7 authorized")] + public UploadController(IImport import, AppSettings appSettings, + ISelectorStorage selectorStorage, IQuery query, + IRealtimeConnectionsService realtimeService, IWebLogger logger, + IMetaExifThumbnailService metaExifThumbnailService, + IMetaUpdateStatusThumbnailService metaUpdateStatusThumbnailService) { - private readonly AppSettings _appSettings; - private readonly IImport _import; - private readonly IStorage _iStorage; - private readonly IStorage _iHostStorage; - private readonly IQuery _query; - private readonly ISelectorStorage _selectorStorage; - private readonly IRealtimeConnectionsService _realtimeService; - private readonly IWebLogger _logger; - private readonly IMetaExifThumbnailService _metaExifThumbnailService; - private readonly IMetaUpdateStatusThumbnailService _metaUpdateStatusThumbnailService; - - [SuppressMessage("Usage", - "S107: Constructor has 8 parameters, which is greater than the 7 authorized")] - public UploadController(IImport import, AppSettings appSettings, - ISelectorStorage selectorStorage, IQuery query, - IRealtimeConnectionsService realtimeService, IWebLogger logger, - IMetaExifThumbnailService metaExifThumbnailService, - IMetaUpdateStatusThumbnailService metaUpdateStatusThumbnailService) + _appSettings = appSettings; + _import = import; + _query = query; + _selectorStorage = selectorStorage; + _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); + _iHostStorage = selectorStorage.Get(SelectorStorage.StorageServices.HostFilesystem); + _realtimeService = realtimeService; + _logger = logger; + _metaExifThumbnailService = metaExifThumbnailService; + _metaUpdateStatusThumbnailService = + metaUpdateStatusThumbnailService; + } + + /// + /// Upload to specific folder (does not check if already has been imported) + /// Use the header 'to' to determine the location to where to upload + /// Add header 'filename' when uploading direct without form + /// (ActionResult UploadToFolder) + /// + /// done + /// folder not found + /// Wrong input (e.g. wrong extenstion type) + /// missing 'to' header + /// the ImportIndexItem of the imported files + [HttpPost("/api/upload")] + [DisableFormValueModelBinding] + [RequestFormLimits(MultipartBodyLengthLimit = 320_000_000)] + [RequestSizeLimit(320_000_000)] // in bytes, 305MB + [ProducesResponseType(typeof(List), 200)] // yes + [ProducesResponseType(typeof(string), 400)] + [ProducesResponseType(typeof(List), 404)] + [ProducesResponseType(typeof(List), + 415)] // Wrong input (e.g. wrong extenstion type) + [Produces("application/json")] + [SuppressMessage("Usage", "S6932: Use model binding instead of accessing the raw request data")] + public async Task UploadToFolder() + { + var to = Request.Headers["to"].ToString(); + if ( string.IsNullOrWhiteSpace(to) ) { - _appSettings = appSettings; - _import = import; - _query = query; - _selectorStorage = selectorStorage; - _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); - _iHostStorage = selectorStorage.Get(SelectorStorage.StorageServices.HostFilesystem); - _realtimeService = realtimeService; - _logger = logger; - _metaExifThumbnailService = metaExifThumbnailService; - _metaUpdateStatusThumbnailService = - metaUpdateStatusThumbnailService; + return BadRequest("missing 'to' header"); } - /// - /// Upload to specific folder (does not check if already has been imported) - /// Use the header 'to' to determine the location to where to upload - /// Add header 'filename' when uploading direct without form - /// (ActionResult UploadToFolder) - /// - /// done - /// folder not found - /// Wrong input (e.g. wrong extenstion type) - /// missing 'to' header - /// the ImportIndexItem of the imported files - [HttpPost("/api/upload")] - [DisableFormValueModelBinding] - [RequestFormLimits(MultipartBodyLengthLimit = 320_000_000)] - [RequestSizeLimit(320_000_000)] // in bytes, 305MB - [ProducesResponseType(typeof(List), 200)] // yes - [ProducesResponseType(typeof(string), 400)] - [ProducesResponseType(typeof(List), 404)] - [ProducesResponseType(typeof(List), - 415)] // Wrong input (e.g. wrong extenstion type) - [Produces("application/json")] - public async Task UploadToFolder() + var parentDirectory = GetParentDirectoryFromRequestHeader(); + if ( parentDirectory == null ) { - var to = Request.Headers["to"].ToString(); - if ( string.IsNullOrWhiteSpace(to) ) return BadRequest("missing 'to' header"); + return NotFound(new ImportIndexItem { Status = ImportStatus.ParentDirectoryNotFound }); + } + + var tempImportPaths = await Request.StreamFile(_appSettings, _selectorStorage); + + var fileIndexResultsList = await _import.Preflight(tempImportPaths, + new ImportSettingsModel { IndexMode = false }); + // fail/pass, right type, string=subPath, string?2= error reason + var metaResultsList = new List<(bool, bool, string, string?)>(); - var parentDirectory = GetParentDirectoryFromRequestHeader(); - if ( parentDirectory == null ) + for ( var i = 0; i < fileIndexResultsList.Count; i++ ) + { + if ( fileIndexResultsList[i].Status != ImportStatus.Ok ) { - return NotFound(new ImportIndexItem - { - Status = ImportStatus.ParentDirectoryNotFound - }); + continue; } - var tempImportPaths = await Request.StreamFile(_appSettings, _selectorStorage); + var tempFileStream = _iHostStorage.ReadStream(tempImportPaths[i]); - var fileIndexResultsList = await _import.Preflight(tempImportPaths, - new ImportSettingsModel { IndexMode = false }); - // fail/pass, right type, string=subPath, string?2= error reason - var metaResultsList = new List<(bool, bool, string, string?)>(); + var fileName = Path.GetFileName(tempImportPaths[i]); - for ( var i = 0; i < fileIndexResultsList.Count; i++ ) + // subPath is always unix style + var subPath = PathHelper.AddSlash(parentDirectory) + fileName; + if ( parentDirectory == "/" ) { - if ( fileIndexResultsList[i].Status != ImportStatus.Ok ) - { - continue; - } - - var tempFileStream = _iHostStorage.ReadStream(tempImportPaths[i]); - - var fileName = Path.GetFileName(tempImportPaths[i]); - - // subPath is always unix style - var subPath = PathHelper.AddSlash(parentDirectory) + fileName; - if ( parentDirectory == "/" ) subPath = parentDirectory + fileName; - - // to get the output in the result right - fileIndexResultsList[i].FileIndexItem!.FileName = fileName; - fileIndexResultsList[i].FileIndexItem!.ParentDirectory = parentDirectory; - fileIndexResultsList[i].FilePath = subPath; - // Do sync action before writing it down - fileIndexResultsList[i].FileIndexItem = - await SyncItem(fileIndexResultsList[i].FileIndexItem!); - - var writeStatus = - await _iStorage.WriteStreamAsync(tempFileStream, subPath + ".tmp"); - // Is already flushed / disposed when the stream is written - - // to avoid partly written stream to be read by an other application - _iStorage.FileDelete(subPath); - _iStorage.FileMove(subPath + ".tmp", subPath); - _logger.LogInformation($"[UploadController] write {subPath} is {writeStatus}"); - - // clear directory cache - _query.RemoveCacheParentItem(subPath); - - var deleteStatus = _iHostStorage.FileDelete(tempImportPaths[i]); - _logger.LogInformation( - $"[UploadController] delete {tempImportPaths[i]} is {deleteStatus}"); - - var parentPath = Directory.GetParent(tempImportPaths[i])?.FullName; - if ( !string.IsNullOrEmpty(parentPath) && parentPath != _appSettings.TempFolder ) - { - _iHostStorage.FolderDelete(parentPath); - } - - metaResultsList.Add(( await _metaExifThumbnailService.AddMetaThumbnail(subPath, - fileIndexResultsList[i].FileIndexItem!.FileHash!) )); + subPath = parentDirectory + fileName; } - // send all uploads as list - var socketResult = fileIndexResultsList - .Where(p => p.Status == ImportStatus.Ok) - .Select(item => item.FileIndexItem).Cast().ToList(); + // to get the output in the result right + fileIndexResultsList[i].FileIndexItem!.FileName = fileName; + fileIndexResultsList[i].FileIndexItem!.ParentDirectory = parentDirectory; + fileIndexResultsList[i].FilePath = subPath; + // Do sync action before writing it down + fileIndexResultsList[i].FileIndexItem = + await SyncItem(fileIndexResultsList[i].FileIndexItem!); + + var writeStatus = + await _iStorage.WriteStreamAsync(tempFileStream, subPath + ".tmp"); + // Is already flushed / disposed when the stream is written - var webSocketResponse = new ApiNotificationResponseModel>( - socketResult, ApiNotificationType.UploadFile); - await _realtimeService.NotificationToAllAsync(webSocketResponse, - CancellationToken.None); + // to avoid partly written stream to be read by an other application + _iStorage.FileDelete(subPath); + _iStorage.FileMove(subPath + ".tmp", subPath); + _logger.LogInformation($"[UploadController] write {subPath} is {writeStatus}"); - await _metaUpdateStatusThumbnailService.UpdateStatusThumbnail(metaResultsList); + // clear directory cache + _query.RemoveCacheParentItem(subPath); - // Wrong input (extension is not allowed) - if ( fileIndexResultsList.TrueForAll(p => p.Status == ImportStatus.FileError) ) + var deleteStatus = _iHostStorage.FileDelete(tempImportPaths[i]); + _logger.LogInformation( + $"[UploadController] delete {tempImportPaths[i]} is {deleteStatus}"); + + var parentPath = Directory.GetParent(tempImportPaths[i])?.FullName; + if ( !string.IsNullOrEmpty(parentPath) && parentPath != _appSettings.TempFolder ) { - _logger.LogInformation($"Wrong input extension is not allowed" + - $" {string.Join(",", fileIndexResultsList.Select(p => p.FilePath))}"); - Response.StatusCode = 415; + _iHostStorage.FolderDelete(parentPath); } - return Json(fileIndexResultsList); + metaResultsList.Add(await _metaExifThumbnailService.AddMetaThumbnail(subPath, + fileIndexResultsList[i].FileIndexItem!.FileHash!)); } - /// - /// Perform database updates - /// - /// to update to - /// updated item - private async Task SyncItem(FileIndexItem metaDataItem) + // send all uploads as list + var socketResult = fileIndexResultsList + .Where(p => p.Status == ImportStatus.Ok) + .Select(item => item.FileIndexItem).Cast().ToList(); + + var webSocketResponse = new ApiNotificationResponseModel>( + socketResult, ApiNotificationType.UploadFile); + await _realtimeService.NotificationToAllAsync(webSocketResponse, + CancellationToken.None); + + await _metaUpdateStatusThumbnailService.UpdateStatusThumbnail(metaResultsList); + + // Wrong input (extension is not allowed) + if ( fileIndexResultsList.TrueForAll(p => p.Status == ImportStatus.FileError) ) { - var itemFromDatabase = await _query.GetObjectByFilePathAsync(metaDataItem.FilePath!); - if ( itemFromDatabase == null ) - { - AddOrRemoveXmpSidecarFileToDatabase(metaDataItem); - await _query.AddItemAsync(metaDataItem); - return metaDataItem; - } + _logger.LogInformation($"Wrong input extension is not allowed" + + $" {string.Join(",", fileIndexResultsList.Select(p => p.FilePath))}"); + Response.StatusCode = 415; + } - FileIndexCompareHelper.Compare(itemFromDatabase, metaDataItem); - AddOrRemoveXmpSidecarFileToDatabase(metaDataItem); + return Json(fileIndexResultsList); + } - await _query.UpdateItemAsync(itemFromDatabase); - return itemFromDatabase; + /// + /// Perform database updates + /// + /// to update to + /// updated item + private async Task SyncItem(FileIndexItem metaDataItem) + { + var itemFromDatabase = await _query.GetObjectByFilePathAsync(metaDataItem.FilePath!); + if ( itemFromDatabase == null ) + { + AddOrRemoveXmpSidecarFileToDatabase(metaDataItem); + await _query.AddItemAsync(metaDataItem); + return metaDataItem; } - private void AddOrRemoveXmpSidecarFileToDatabase(FileIndexItem metaDataItem) + FileIndexCompareHelper.Compare(itemFromDatabase, metaDataItem); + AddOrRemoveXmpSidecarFileToDatabase(metaDataItem); + + await _query.UpdateItemAsync(itemFromDatabase); + return itemFromDatabase; + } + + private void AddOrRemoveXmpSidecarFileToDatabase(FileIndexItem metaDataItem) + { + if ( _iStorage.ExistFile(ExtensionRolesHelper.ReplaceExtensionWithXmp(metaDataItem + .FilePath)) ) { - if ( _iStorage.ExistFile(ExtensionRolesHelper.ReplaceExtensionWithXmp(metaDataItem - .FilePath)) ) - { - metaDataItem.AddSidecarExtension("xmp"); - return; - } + metaDataItem.AddSidecarExtension("xmp"); + return; + } + + metaDataItem.RemoveSidecarExtension("xmp"); + } - metaDataItem.RemoveSidecarExtension("xmp"); + /// + /// Check if xml can be parsed + /// Used by sidecar upload + /// + /// string with xml + /// true when parsed + private bool IsValidXml(string xml) + { + try + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + XDocument.Parse(xml); + return true; + } + catch + { + _logger.LogInformation("[IsValidXml] non valid xml"); + return false; } + } - /// - /// Check if xml can be parsed - /// Used by sidecar upload - /// - /// string with xml - /// true when parsed - private bool IsValidXml(string xml) + /// + /// Upload sidecar file to specific folder (does not check if already has been imported) + /// Use the header 'to' to determine the location to where to upload + /// Add header 'filename' when uploading direct without form + /// (ActionResult UploadToFolderSidecarFile) + /// + /// done + /// parent folder not found + /// Wrong input (e.g. wrong extenstion type) + /// missing 'to' header + /// the ImportIndexItem of the imported files + [HttpPost("/api/upload-sidecar")] + [DisableFormValueModelBinding] + [RequestFormLimits(MultipartBodyLengthLimit = 3_000_000)] + [RequestSizeLimit(3_000_000)] // in bytes, 3 MB + [ProducesResponseType(typeof(List), 200)] // yes + [ProducesResponseType(typeof(string), 400)] + [ProducesResponseType(typeof(List), 404)] // parent dir not found + [ProducesResponseType(typeof(List), + 415)] // Wrong input (e.g. wrong extenstion type) + [Produces("application/json")] + [SuppressMessage("Usage", "S6932: Use model binding instead of accessing the raw request data")] + public async Task UploadToFolderSidecarFile() + { + var to = Request.Headers["to"].ToString(); + if ( string.IsNullOrWhiteSpace(to) ) { - try - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - XDocument.Parse(xml); - return true; - } - catch - { - _logger.LogInformation("[IsValidXml] non valid xml"); - return false; - } + return BadRequest("missing 'to' header"); } - /// - /// Upload sidecar file to specific folder (does not check if already has been imported) - /// Use the header 'to' to determine the location to where to upload - /// Add header 'filename' when uploading direct without form - /// (ActionResult UploadToFolderSidecarFile) - /// - /// done - /// parent folder not found - /// Wrong input (e.g. wrong extenstion type) - /// missing 'to' header - /// the ImportIndexItem of the imported files - [HttpPost("/api/upload-sidecar")] - [DisableFormValueModelBinding] - [RequestFormLimits(MultipartBodyLengthLimit = 3_000_000)] - [RequestSizeLimit(3_000_000)] // in bytes, 3 MB - [ProducesResponseType(typeof(List), 200)] // yes - [ProducesResponseType(typeof(string), 400)] - [ProducesResponseType(typeof(List), 404)] // parent dir not found - [ProducesResponseType(typeof(List), - 415)] // Wrong input (e.g. wrong extenstion type) - [Produces("application/json")] - public async Task UploadToFolderSidecarFile() + _logger.LogInformation($"[UploadToFolderSidecarFile] to:{to}"); + + var parentDirectory = GetParentDirectoryFromRequestHeader(); + if ( parentDirectory == null ) { - var to = Request.Headers["to"].ToString(); - if ( string.IsNullOrWhiteSpace(to) ) return BadRequest("missing 'to' header"); - _logger.LogInformation($"[UploadToFolderSidecarFile] to:{to}"); + return NotFound(new ImportIndexItem()); + } + + var tempImportPaths = await Request.StreamFile(_appSettings, _selectorStorage); - var parentDirectory = GetParentDirectoryFromRequestHeader(); - if ( parentDirectory == null ) + var importedList = new List(); + foreach ( var tempImportSinglePath in tempImportPaths ) + { + var data = await StreamToStringHelper.StreamToStringAsync( + _iHostStorage.ReadStream(tempImportSinglePath)); + if ( !IsValidXml(data) ) { - return NotFound(new ImportIndexItem()); + continue; } - var tempImportPaths = await Request.StreamFile(_appSettings, _selectorStorage); + var tempFileStream = _iHostStorage.ReadStream(tempImportSinglePath); + var fileName = Path.GetFileName(tempImportSinglePath); - var importedList = new List(); - foreach ( var tempImportSinglePath in tempImportPaths ) + var subPath = PathHelper.AddSlash(parentDirectory) + fileName; + if ( parentDirectory == "/" ) { - var data = await StreamToStringHelper.StreamToStringAsync( - _iHostStorage.ReadStream(tempImportSinglePath)); - if ( !IsValidXml(data) ) continue; - - var tempFileStream = _iHostStorage.ReadStream(tempImportSinglePath); - var fileName = Path.GetFileName(tempImportSinglePath); - - var subPath = PathHelper.AddSlash(parentDirectory) + fileName; - if ( parentDirectory == "/" ) subPath = parentDirectory + fileName; - - if ( _appSettings.UseDiskWatcher == false ) - { - await new SyncSingleFile(_appSettings, _query, - _iStorage, null!, _logger).UpdateSidecarFile(subPath); - } - - await _iStorage.WriteStreamAsync(tempFileStream, subPath); - await tempFileStream.DisposeAsync(); - importedList.Add(subPath); - - var deleteStatus = _iHostStorage.FileDelete(tempImportSinglePath); - _logger.LogInformation($"delete {tempImportSinglePath} is {deleteStatus}"); + subPath = parentDirectory + fileName; } - if ( importedList.Count == 0 ) + if ( _appSettings.UseDiskWatcher == false ) { - Response.StatusCode = 415; + await new SyncSingleFile(_appSettings, _query, + _iStorage, null!, _logger).UpdateSidecarFile(subPath); } - return Json(importedList); + await _iStorage.WriteStreamAsync(tempFileStream, subPath); + await tempFileStream.DisposeAsync(); + importedList.Add(subPath); + + var deleteStatus = _iHostStorage.FileDelete(tempImportSinglePath); + _logger.LogInformation($"delete {tempImportSinglePath} is {deleteStatus}"); } - internal string? GetParentDirectoryFromRequestHeader() + if ( importedList.Count == 0 ) { - var to = Request.Headers["to"].ToString(); - if ( to == "/" ) return "/"; + Response.StatusCode = 415; + } - // only used for direct import - if ( _iStorage.ExistFolder(FilenamesHelper.GetParentPath(to)) && - FilenamesHelper.IsValidFileName(FilenamesHelper.GetFileName(to)) ) - { - Request.Headers["filename"] = FilenamesHelper.GetFileName(to); - return FilenamesHelper.GetParentPath(PathHelper.RemoveLatestSlash(to)); - } + return Json(importedList); + } + + [SuppressMessage("Usage", "S6932: Use model binding instead of accessing the raw request data")] + internal string? GetParentDirectoryFromRequestHeader() + { + var to = Request.Headers["to"].ToString(); + if ( to == "/" ) + { + return "/"; + } + + // only used for direct import + if ( _iStorage.ExistFolder(FilenamesHelper.GetParentPath(to)) && + FilenamesHelper.IsValidFileName(FilenamesHelper.GetFileName(to)) ) + { + Request.Headers["filename"] = FilenamesHelper.GetFileName(to); + return FilenamesHelper.GetParentPath(PathHelper.RemoveLatestSlash(to)); + } - // ReSharper disable once ConvertIfStatementToReturnStatement - if ( !_iStorage.ExistFolder(PathHelper.RemoveLatestSlash(to)) ) return null; - return PathHelper.RemoveLatestSlash(to); + // ReSharper disable once ConvertIfStatementToReturnStatement + if ( !_iStorage.ExistFolder(PathHelper.RemoveLatestSlash(to)) ) + { + return null; } + + return PathHelper.RemoveLatestSlash(to); } } diff --git a/starsky/starskytest/Controllers/AllowedTypesControllerTest.cs b/starsky/starskytest/Controllers/AllowedTypesControllerTest.cs index 20540ca6a1..5fd86c048e 100644 --- a/starsky/starskytest/Controllers/AllowedTypesControllerTest.cs +++ b/starsky/starskytest/Controllers/AllowedTypesControllerTest.cs @@ -4,43 +4,62 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.Controllers; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class AllowedTypesControllerTest { - [TestClass] - public sealed class AllowedTypesControllerTest + private readonly HttpContext _httpContext = new DefaultHttpContext(); + + [TestMethod] + public void AllowedTypesController_MimetypeSync() + { + var jsonResult = new AllowedTypesController().AllowedTypesMimetypeSync() as JsonResult; + var allowedApiResult = jsonResult?.Value as HashSet; + Assert.IsTrue(allowedApiResult?.Contains("image/jpeg")); + } + + [TestMethod] + public void AllowedTypesController_MimetypeSyncThumb() { - private readonly HttpContext _httpContext = new DefaultHttpContext(); - - [TestMethod] - public void AllowedTypesController_MimetypeSync() - { - var jsonResult = new AllowedTypesController().AllowedTypesMimetypeSync() as JsonResult; - var allowedApiResult = jsonResult?.Value as HashSet; - Assert.IsTrue(allowedApiResult?.Contains("image/jpeg")); - } - - [TestMethod] - public void AllowedTypesController_MimetypeSyncThumb() - { - var jsonResult = new AllowedTypesController().AllowedTypesMimetypeSyncThumb() as JsonResult; - var allowedApiResult = jsonResult?.Value as HashSet; - Assert.IsTrue(allowedApiResult?.Contains("image/jpeg")); - } - - [TestMethod] - public void AllowedTypesController_AllowedTypesThumb_NoInput() - { - var jsonResult = new AllowedTypesController{ ControllerContext = {HttpContext = _httpContext}}.AllowedTypesThumb("") as JsonResult; - var allowedApiResult = bool.Parse(jsonResult?.Value?.ToString()!); - Assert.IsFalse(allowedApiResult); - } - - [TestMethod] - public void AllowedTypesController_AllowedTypesThumb_Example() - { - var jsonResult = new AllowedTypesController{ ControllerContext = {HttpContext = _httpContext}}.AllowedTypesThumb("test.jpg") as JsonResult; - var allowedApiResult = bool.Parse(jsonResult?.Value?.ToString()!); - Assert.IsTrue(allowedApiResult); - } + var jsonResult = new AllowedTypesController().AllowedTypesMimetypeSyncThumb() as JsonResult; + var allowedApiResult = jsonResult?.Value as HashSet; + Assert.IsTrue(allowedApiResult?.Contains("image/jpeg")); + } + + [TestMethod] + public void AllowedTypesController_AllowedTypesThumb_NoInput() + { + var jsonResult = + new AllowedTypesController { ControllerContext = { HttpContext = _httpContext } } + .AllowedTypesThumb("") as JsonResult; + var allowedApiResult = bool.Parse(jsonResult?.Value?.ToString()!); + Assert.IsFalse(allowedApiResult); + } + + [TestMethod] + public void AllowedTypesController_AllowedTypesThumb_Example() + { + var jsonResult = + new AllowedTypesController { ControllerContext = { HttpContext = _httpContext } } + .AllowedTypesThumb("test.jpg") as JsonResult; + var allowedApiResult = bool.Parse(jsonResult?.Value?.ToString()!); + Assert.IsTrue(allowedApiResult); + } + + [TestMethod] + public void AllowedTypesController_AllowedTypesThumb_ReturnsBadRequest() + { + // Arrange + var controller = + new AllowedTypesController { ControllerContext = { HttpContext = _httpContext } }; + + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var result = controller.AllowedTypesThumb("test.jpg"); + + // Assert + Assert.IsInstanceOfType(result); } } diff --git a/starsky/starskytest/Controllers/AppSettingsControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsControllerTest.cs index d16a7bc7a4..7f6b488097 100644 --- a/starsky/starskytest/Controllers/AppSettingsControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsControllerTest.cs @@ -13,173 +13,189 @@ using starsky.foundation.storage.Helpers; using starskytest.FakeMocks; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class AppSettingsControllerTest { - [TestClass] - public sealed class AppSettingsControllerTest + [TestMethod] + public void ENV_StarskyTestEnv() { - [TestMethod] - public void ENV_StarskyTestEnv() - { - var controller = - new AppSettingsController(new AppSettings(), new FakeIUpdateAppSettingsByPath()); - var actionResult = controller.Env() as JsonResult; - var resultAppSettings = actionResult?.Value as AppSettings; - Assert.AreEqual("Starsky", resultAppSettings?.Name); - } - - [TestMethod] - public void ENV_StarskyTestEnv_ForceHtml() - { - var controller = new AppSettingsController(new AppSettings(), - new FakeIUpdateAppSettingsByPath()); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - controller.ControllerContext.HttpContext.Request.Headers.Append("x-force-html", "true"); - var actionResult = controller.Env() as JsonResult; - var resultAppSettings = actionResult?.Value as AppSettings; - Assert.AreEqual("Starsky", resultAppSettings?.Name); - Assert.AreEqual("text/html; charset=utf-8", - controller.ControllerContext.HttpContext.Response.Headers.ContentType.ToString()); - } - - [TestMethod] - public async Task UpdateAppSettings_Verbose() - { - var appSettings = new AppSettings(); - var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, - new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - - var actionResult = - await controller.UpdateAppSettings(new AppSettingsTransferObject { Verbose = true }) - as JsonResult; - var result = actionResult?.Value as AppSettings; - Assert.IsTrue(result?.Verbose); - } - - [TestMethod] - public async Task UpdateAppSettings_StorageFolder() - { - var appSettings = new AppSettings(); - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath( - appSettings, - new FakeSelectorStorage( - new FakeIStorage(new List { $"{Path.DirectorySeparatorChar}test" })))); - - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - - var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject - { - Verbose = true, StorageFolder = $"{Path.DirectorySeparatorChar}test" - }) as JsonResult; - - var result = actionResult?.Value as AppSettings; - Assert.IsTrue(result?.Verbose); - Assert.AreEqual(Path.DirectorySeparatorChar + PathHelper.AddBackslash("test"), - result?.StorageFolder); - } - - [TestMethod] - public async Task UpdateAppSettingsTest_IgnoreWhenEnvIsSet() + var controller = + new AppSettingsController(new AppSettings(), new FakeIUpdateAppSettingsByPath()); + var actionResult = controller.Env() as JsonResult; + var resultAppSettings = actionResult?.Value as AppSettings; + Assert.AreEqual("Starsky", resultAppSettings?.Name); + } + + [TestMethod] + public void ENV_StarskyTestEnv_ForceHtml() + { + var controller = new AppSettingsController(new AppSettings(), + new FakeIUpdateAppSettingsByPath()); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + controller.ControllerContext.HttpContext.Request.Headers.Append("x-force-html", "true"); + var actionResult = controller.Env() as JsonResult; + var resultAppSettings = actionResult?.Value as AppSettings; + Assert.AreEqual("Starsky", resultAppSettings?.Name); + Assert.AreEqual("text/html; charset=utf-8", + controller.ControllerContext.HttpContext.Response.Headers.ContentType.ToString()); + } + + [TestMethod] + public async Task UpdateAppSettings_Verbose() + { + var appSettings = new AppSettings(); + var storage = new FakeIStorage(new List { "/" }); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + + var actionResult = + await controller.UpdateAppSettings(new AppSettingsTransferObject { Verbose = true }) + as JsonResult; + var result = actionResult?.Value as AppSettings; + Assert.IsTrue(result?.Verbose); + } + + [TestMethod] + public async Task UpdateAppSettings_StorageFolder() + { + var appSettings = new AppSettings(); + var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath( + appSettings, + new FakeSelectorStorage( + new FakeIStorage(new List { $"{Path.DirectorySeparatorChar}test" })))); + + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + + var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject { - Environment.SetEnvironmentVariable("app__storageFolder", - "any_value"); - - var appSettings = new AppSettings(); - var controller = new AppSettingsController(appSettings, - new FakeIUpdateAppSettingsByPath( - new UpdateAppSettingsStatusModel { StatusCode = 403 })); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - await controller.UpdateAppSettings( - new AppSettingsTransferObject { StorageFolder = "test" }); + Verbose = true, StorageFolder = $"{Path.DirectorySeparatorChar}test" + }) as JsonResult; + + var result = actionResult?.Value as AppSettings; + Assert.IsTrue(result?.Verbose); + Assert.AreEqual(Path.DirectorySeparatorChar + PathHelper.AddBackslash("test"), + result?.StorageFolder); + } + + [TestMethod] + public async Task UpdateAppSettingsTest_IgnoreWhenEnvIsSet() + { + Environment.SetEnvironmentVariable("app__storageFolder", + "any_value"); + + var appSettings = new AppSettings(); + var controller = new AppSettingsController(appSettings, + new FakeIUpdateAppSettingsByPath( + new UpdateAppSettingsStatusModel { StatusCode = 403 })); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + await controller.UpdateAppSettings( + new AppSettingsTransferObject { StorageFolder = "test" }); + + Assert.AreEqual(403, controller.Response.StatusCode); + } + + [TestMethod] + public async Task UpdateAppSettingsTest_DirNotFound() + { + var appSettings = new AppSettings(); + var controller = new AppSettingsController(appSettings, + new FakeIUpdateAppSettingsByPath( + new UpdateAppSettingsStatusModel { StatusCode = 404 })); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); - Assert.AreEqual(403, controller.Response.StatusCode); - } + await controller.UpdateAppSettings( + new AppSettingsTransferObject { StorageFolder = "not_found" }); - [TestMethod] - public async Task UpdateAppSettingsTest_DirNotFound() + Assert.AreEqual(404, controller.Response.StatusCode); + } + + [TestMethod] + public async Task UpdateAppSettingsTest_StorageFolder_JsonCheck() + { + var storage = new FakeIStorage(new List { "test" }); + Environment.SetEnvironmentVariable("app__storageFolder", string.Empty); + + var appSettings = new AppSettings { - var appSettings = new AppSettings(); - var controller = new AppSettingsController(appSettings, - new FakeIUpdateAppSettingsByPath( - new UpdateAppSettingsStatusModel { StatusCode = 404 })); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + AppSettingsPath = + $"{Path.DirectorySeparatorChar}temp{Path.DirectorySeparatorChar}appsettings.json" + }; + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + await controller.UpdateAppSettings( + new AppSettingsTransferObject { Verbose = true, StorageFolder = "test" }); + + Assert.IsTrue(storage.ExistFile(appSettings.AppSettingsPath)); + + var jsonContent = await StreamToStringHelper.StreamToStringAsync( + storage.ReadStream(appSettings.AppSettingsPath)); + Assert.IsTrue(jsonContent.Contains("app\": {")); + Assert.IsTrue(jsonContent.Contains("\"StorageFolder\": \"")); + } + + [TestMethod] + public async Task UpdateAppSettings_UseLocalDesktop() + { + var appSettings = new AppSettings(); + var storage = new FakeIStorage(new List { "/" }); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + + var actionResult = await controller.UpdateAppSettings( - new AppSettingsTransferObject { StorageFolder = "not_found" }); + new AppSettingsTransferObject { UseLocalDesktop = true }) as JsonResult; + var result = actionResult?.Value as AppSettings; + Assert.IsTrue(result?.UseLocalDesktop); + } - Assert.AreEqual(404, controller.Response.StatusCode); - } + [TestMethod] + public async Task UpdateAppSettings_UseSystemTrash() + { + var appSettings = new AppSettings(); + var storage = new FakeIStorage(new List { "/" }); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - [TestMethod] - public async Task UpdateAppSettingsTest_StorageFolder_JsonCheck() - { - var storage = new FakeIStorage(new List { "test" }); - Environment.SetEnvironmentVariable("app__storageFolder", string.Empty); - - var appSettings = new AppSettings - { - AppSettingsPath = - $"{Path.DirectorySeparatorChar}temp{Path.DirectorySeparatorChar}appsettings.json" - }; - var controller = new AppSettingsController(appSettings, - new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + var actionResult = await controller.UpdateAppSettings( - new AppSettingsTransferObject { Verbose = true, StorageFolder = "test" }); + new AppSettingsTransferObject { UseSystemTrash = true }) as JsonResult; + var result = actionResult?.Value as AppSettings; + Assert.IsTrue(result?.UseSystemTrash); + } - Assert.IsTrue(storage.ExistFile(appSettings.AppSettingsPath)); + [TestMethod] + public async Task UpdateAppSettings_Verbose_IgnoreSystemTrashValue() + { + var appSettings = new AppSettings(); + var storage = new FakeIStorage(new List { "/" }); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - var jsonContent = await StreamToStringHelper.StreamToStringAsync( - storage.ReadStream(appSettings.AppSettingsPath)); + var actionResult = + await controller.UpdateAppSettings(new AppSettingsTransferObject { Verbose = true }) + as JsonResult; + var result = actionResult?.Value as AppSettings; - Assert.IsTrue(jsonContent.Contains("app\": {")); - Assert.IsTrue(jsonContent.Contains("\"StorageFolder\": \"")); - } + Assert.AreEqual(appSettings.UseSystemTrash, result?.UseSystemTrash); + } - [TestMethod] - public async Task UpdateAppSettings_UseLocalDesktop() - { - var appSettings = new AppSettings(); - var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, - new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - - var actionResult = - await controller.UpdateAppSettings( - new AppSettingsTransferObject { UseLocalDesktop = true }) as JsonResult; - var result = actionResult?.Value as AppSettings; - Assert.IsTrue(result?.UseLocalDesktop); - } - - [TestMethod] - public async Task UpdateAppSettings_UseSystemTrash() - { - var appSettings = new AppSettings(); - var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, - new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - - var actionResult = - await controller.UpdateAppSettings( - new AppSettingsTransferObject { UseSystemTrash = true }) as JsonResult; - var result = actionResult?.Value as AppSettings; - Assert.IsTrue(result?.UseSystemTrash); - } - - [TestMethod] - public async Task UpdateAppSettings_Verbose_IgnoreSystemTrashValue() - { - var appSettings = new AppSettings(); - var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, - new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - - var actionResult = - await controller.UpdateAppSettings(new AppSettingsTransferObject { Verbose = true }) - as JsonResult; - var result = actionResult?.Value as AppSettings; - - Assert.AreEqual(appSettings.UseSystemTrash, result?.UseSystemTrash); - } + [TestMethod] + public async Task UpdateAppSettings_AllowedTypesThumb_ReturnsBadRequest() + { + // Arrange + var appSettings = new AppSettings(); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(new FakeIStorage()))); + + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var result = await controller.UpdateAppSettings(null!); + + // Assert + Assert.IsInstanceOfType(result); } } diff --git a/starsky/starskytest/Controllers/CacheIndexControllerTest.cs b/starsky/starskytest/Controllers/CacheIndexControllerTest.cs index 841639ca04..b303ebcf1d 100644 --- a/starsky/starskytest/Controllers/CacheIndexControllerTest.cs +++ b/starsky/starskytest/Controllers/CacheIndexControllerTest.cs @@ -21,197 +21,225 @@ using starskytest.FakeCreateAn; using starskytest.FakeMocks; +namespace starskytest.Controllers; -namespace starskytest.Controllers +[TestClass] +public sealed class CacheIndexControllerTest { - [TestClass] - public sealed class CacheIndexControllerTest + private readonly AppSettings _appSettings; + private readonly ApplicationDbContext _context; + private readonly Query _query; + + public CacheIndexControllerTest() + { + var provider = new ServiceCollection() + .AddMemoryCache() + .BuildServiceProvider(); + var memoryCache = provider.GetService(); + + var builderDb = new DbContextOptionsBuilder(); + builderDb.UseInMemoryDatabase("test1234"); + var options = builderDb.Options; + _context = new ApplicationDbContext(options); + _query = new Query(_context, new AppSettings(), null!, new FakeIWebLogger(), + memoryCache); + + // Inject Fake ExifTool; dependency injection + var services = new ServiceCollection(); + + // Fake the readMeta output + services.AddSingleton(); + + // Inject Config helper + services.AddSingleton(new ConfigurationBuilder().Build()); + // random config + var createAnImage = new CreateAnImage(); + var dict = new Dictionary + { + { "App:StorageFolder", createAnImage.BasePath }, + { "App:ThumbnailTempFolder", createAnImage.BasePath }, + { "App:Verbose", "true" } + }; + // Start using dependency injection + var builder = new ConfigurationBuilder(); + // Add random config to dependency injection + builder.AddInMemoryCollection(dict); + // build config + var configuration = builder.Build(); + // inject config as object to a service + services.ConfigurePoCo(configuration.GetSection("App")); + + // Add Background services + services.AddSingleton(); + services.AddSingleton(); + + // build the service + var serviceProvider = services.BuildServiceProvider(); + // get the service + _appSettings = serviceProvider.GetRequiredService(); + } + + [TestMethod] + public async Task CacheIndexController_CheckIfCacheIsRemoved_CleanCache() { - private readonly Query _query; - private readonly AppSettings _appSettings; - private readonly ApplicationDbContext _context; + // Act + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); - public CacheIndexControllerTest() + await _query.AddItemAsync(new FileIndexItem { - var provider = new ServiceCollection() - .AddMemoryCache() - .BuildServiceProvider(); - var memoryCache = provider.GetService(); - - var builderDb = new DbContextOptionsBuilder(); - builderDb.UseInMemoryDatabase("test1234"); - var options = builderDb.Options; - _context = new ApplicationDbContext(options); - _query = new Query(_context, new AppSettings(), null!, new FakeIWebLogger(), - memoryCache); - - // Inject Fake ExifTool; dependency injection - var services = new ServiceCollection(); - - // Fake the readMeta output - services.AddSingleton(); - - // Inject Config helper - services.AddSingleton(new ConfigurationBuilder().Build()); - // random config - var createAnImage = new CreateAnImage(); - var dict = new Dictionary - { - { "App:StorageFolder", createAnImage.BasePath }, - { "App:ThumbnailTempFolder", createAnImage.BasePath }, - { "App:Verbose", "true" } - }; - // Start using dependency injection - var builder = new ConfigurationBuilder(); - // Add random config to dependency injection - builder.AddInMemoryCollection(dict); - // build config - var configuration = builder.Build(); - // inject config as object to a service - services.ConfigurePoCo(configuration.GetSection("App")); - - // Add Background services - services.AddSingleton(); - services.AddSingleton(); - - // build the service - var serviceProvider = services.BuildServiceProvider(); - // get the service - _appSettings = serviceProvider.GetRequiredService(); - } - - [TestMethod] - public async Task CacheIndexController_CheckIfCacheIsRemoved_CleanCache() + FileName = "cacheDeleteTest", ParentDirectory = "/", IsDirectory = true + }); + + await _query.AddItemAsync(new FileIndexItem { - // Act - var controller = new CacheIndexController(_query, _appSettings); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + FileName = "file.jpg", ParentDirectory = "/cacheDeleteTest", IsDirectory = false + }); - await _query.AddItemAsync(new FileIndexItem - { - FileName = "cacheDeleteTest", ParentDirectory = "/", IsDirectory = true - }); + Assert.IsTrue(_query.DisplayFileFolders("/cacheDeleteTest").Any()); - await _query.AddItemAsync(new FileIndexItem - { - FileName = "file.jpg", ParentDirectory = "/cacheDeleteTest", IsDirectory = false - }); + // Ask the cache + _query.DisplayFileFolders("/cacheDeleteTest"); - Assert.IsTrue(_query.DisplayFileFolders("/cacheDeleteTest").Any()); + // Don't notify the cache that there is an update + var newItem = new FileIndexItem + { + FileName = "file2.jpg", ParentDirectory = "/cacheDeleteTest", IsDirectory = false + }; + _context.FileIndex.Add(newItem); + _context.SaveChanges(); + // Write changes to database + + // Check if there is one item in the cache + var beforeQuery = _query.DisplayFileFolders("/cacheDeleteTest"); + Assert.AreEqual(1, beforeQuery.Count()); + + // Act, remove content from cache + var actionResult = controller.RemoveCache("/cacheDeleteTest") as JsonResult; + Assert.AreEqual("cache successful cleared", actionResult?.Value); + + // Check if there are now two items in the cache + var newQuery = _query.DisplayFileFolders("/cacheDeleteTest"); + Assert.AreEqual(2, newQuery.Count()); + } - // Ask the cache - _query.DisplayFileFolders("/cacheDeleteTest"); + [TestMethod] + public async Task RemoveCache_CacheDidNotExist() + { + // Act + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); - // Don't notify the cache that there is an update - var newItem = new FileIndexItem - { - FileName = "file2.jpg", - ParentDirectory = "/cacheDeleteTest", - IsDirectory = false - }; - _context.FileIndex.Add(newItem); - _context.SaveChanges(); - // Write changes to database - - // Check if there is one item in the cache - var beforeQuery = _query.DisplayFileFolders("/cacheDeleteTest"); - Assert.AreEqual(1, beforeQuery.Count()); - - // Act, remove content from cache - var actionResult = controller.RemoveCache("/cacheDeleteTest") as JsonResult; - Assert.AreEqual("cache successful cleared", actionResult?.Value); - - // Check if there are now two items in the cache - var newQuery = _query.DisplayFileFolders("/cacheDeleteTest"); - Assert.AreEqual(2, newQuery.Count()); - } - - [TestMethod] - public async Task RemoveCache_CacheDidNotExist() + await _query.AddItemAsync(new FileIndexItem { - // Act - var controller = new CacheIndexController(_query, _appSettings); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + FileName = "cacheDeleteTest2", ParentDirectory = "/", IsDirectory = true + }); - await _query.AddItemAsync(new FileIndexItem - { - FileName = "cacheDeleteTest2", ParentDirectory = "/", IsDirectory = true - }); + // Act, remove content from cache + var actionResult = controller.RemoveCache("/cacheDeleteTest2") as JsonResult; + Assert.AreEqual("cache did not exist", actionResult?.Value); + } - // Act, remove content from cache - var actionResult = controller.RemoveCache("/cacheDeleteTest2") as JsonResult; - Assert.AreEqual("cache did not exist", actionResult?.Value); - } + [TestMethod] + public void RemoveCache_ReturnsBadRequest() + { + // Arrange + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); - [TestMethod] - public void CacheIndexController_NonExistingCacheRemove() - { - // Act - var controller = new CacheIndexController(_query, _appSettings); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + controller.ModelState.AddModelError("Key", "ErrorMessage"); - var actionResult = controller.RemoveCache("/404page") as BadRequestObjectResult; - Assert.AreEqual(400, actionResult?.StatusCode); - } + // Act + var result = controller.RemoveCache(null!); - [TestMethod] - public void CacheIndexController_CacheDisabled() - { - var controller = - new CacheIndexController(_query, new AppSettings { AddMemoryCache = false }); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + // Assert + Assert.IsInstanceOfType(result); + } - var actionResult = controller.RemoveCache("/404page") as JsonResult; - Assert.AreEqual("cache disabled in config", actionResult?.Value); - } + [TestMethod] + public void CacheIndexController_NonExistingCacheRemove() + { + // Act + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); - [TestMethod] - public void ListCache_CacheDidNotExist() - { - // Act - var controller = new CacheIndexController(_query, _appSettings); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - - // Act, remove content from cache - var actionResult = controller.ListCache("/cacheDeleteTest2") as BadRequestObjectResult; - Assert.AreEqual("ignored, please check if the 'f' path " + - "exist or use a folder string to get the cache", actionResult?.Value); - } - - [TestMethod] - public void ListCache_CacheDisabled() - { - var controller = - new CacheIndexController(_query, new AppSettings { AddMemoryCache = false }); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + var actionResult = controller.RemoveCache("/404page") as BadRequestObjectResult; + Assert.AreEqual(400, actionResult?.StatusCode); + } + + [TestMethod] + public void CacheIndexController_CacheDisabled() + { + var controller = + new CacheIndexController(_query, new AppSettings { AddMemoryCache = false }); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); - var actionResult = controller.ListCache("/404page") as JsonResult; - Assert.AreEqual("cache disabled in config", actionResult?.Value); - } + var actionResult = controller.RemoveCache("/404page") as JsonResult; + Assert.AreEqual("cache disabled in config", actionResult?.Value); + } - [TestMethod] - public void ListCache_GetCache() - { - // Act - var controller = new CacheIndexController(_query, _appSettings); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + [TestMethod] + public void ListCache_ReturnsBadRequest() + { + // Arrange + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var result = controller.ListCache(null!); + + // Assert + Assert.IsInstanceOfType(result); + } + + [TestMethod] + public void ListCache_CacheDidNotExist() + { + // Act + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + + // Act, remove content from cache + var actionResult = controller.ListCache("/cacheDeleteTest2") as BadRequestObjectResult; + Assert.AreEqual("ignored, please check if the 'f' path " + + "exist or use a folder string to get the cache", actionResult?.Value); + } + + [TestMethod] + public void ListCache_CacheDisabled() + { + var controller = + new CacheIndexController(_query, new AppSettings { AddMemoryCache = false }); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + + var actionResult = controller.ListCache("/404page") as JsonResult; + Assert.AreEqual("cache disabled in config", actionResult?.Value); + } - _query.AddCacheParentItem("/list-cache", - new List + [TestMethod] + public void ListCache_GetCache() + { + // Act + var controller = new CacheIndexController(_query, _appSettings); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + + _query.AddCacheParentItem("/list-cache", + new List + { + new() { - new FileIndexItem - { - FileName = "cacheDeleteTest2", - ParentDirectory = "/list-cache", - IsDirectory = true - } - }); - - // Act, remove content from cache - var actionResult = controller.ListCache("/list-cache") as JsonResult; - - Assert.IsNotNull(actionResult); - Assert.IsNotNull(actionResult.Value); - } + FileName = "cacheDeleteTest2", + ParentDirectory = "/list-cache", + IsDirectory = true + } + }); + + // Act, remove content from cache + var actionResult = controller.ListCache("/list-cache") as JsonResult; + + Assert.IsNotNull(actionResult); + Assert.IsNotNull(actionResult.Value); } } diff --git a/starsky/starskytest/Controllers/DeleteControllerTest.cs b/starsky/starskytest/Controllers/DeleteControllerTest.cs index e855bfd09a..8627599d4f 100644 --- a/starsky/starskytest/Controllers/DeleteControllerTest.cs +++ b/starsky/starskytest/Controllers/DeleteControllerTest.cs @@ -26,169 +26,195 @@ using starskytest.FakeCreateAn; using starskytest.FakeMocks; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class DeleteControllerTest { - [TestClass] - public sealed class DeleteControllerTest + private readonly AppSettings _appSettings; + private readonly CreateAnImage _createAnImage; + private readonly IStorage _iStorage; + + private readonly Query _query; + + public DeleteControllerTest() { - - private readonly Query _query; - private readonly AppSettings _appSettings; - private readonly CreateAnImage _createAnImage; - private readonly IStorage _iStorage; + var provider = new ServiceCollection() + .AddMemoryCache() + .BuildServiceProvider(); + var memoryCache = provider.GetService(); + + var builderDb = new DbContextOptionsBuilder(); + builderDb.UseInMemoryDatabase("test1234"); + var options = builderDb.Options; + var context = new ApplicationDbContext(options); + _query = new Query(context, + new AppSettings(), null, new FakeIWebLogger(), memoryCache); - public DeleteControllerTest() + // Inject Fake ExifTool; dependency injection + var services = new ServiceCollection(); + + // Fake the readMeta output + services.AddSingleton(); + + // Inject Config helper + services.AddSingleton(new ConfigurationBuilder().Build()); + // random config + _createAnImage = new CreateAnImage(); + var dict = new Dictionary { - var provider = new ServiceCollection() - .AddMemoryCache() - .BuildServiceProvider(); - var memoryCache = provider.GetService(); - - var builderDb = new DbContextOptionsBuilder(); - builderDb.UseInMemoryDatabase("test1234"); - var options = builderDb.Options; - var context = new ApplicationDbContext(options); - _query = new Query(context, - new AppSettings(), null, new FakeIWebLogger(),memoryCache); - - // Inject Fake ExifTool; dependency injection - var services = new ServiceCollection(); - - // Fake the readMeta output - services.AddSingleton(); - - // Inject Config helper - services.AddSingleton(new ConfigurationBuilder().Build()); - // random config - _createAnImage = new CreateAnImage(); - var dict = new Dictionary - { - { "App:StorageFolder", _createAnImage.BasePath }, - { "App:ThumbnailTempFolder",_createAnImage.BasePath }, - { "App:Verbose", "true" } - }; - // Start using dependency injection - var builder = new ConfigurationBuilder(); - // Add random config to dependency injection - builder.AddInMemoryCollection(dict); - // build config - var configuration = builder.Build(); - // inject config as object to a service - services.ConfigurePoCo(configuration.GetSection("App")); - - // Add Background services - services.AddSingleton(); - services.AddSingleton(); - - // build the service - var serviceProvider = services.BuildServiceProvider(); - // get the service - _appSettings = serviceProvider.GetRequiredService(); - - _iStorage = new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger()); - } - - private async Task InsertSearchData(bool delete = false) + { "App:StorageFolder", _createAnImage.BasePath }, + { "App:ThumbnailTempFolder", _createAnImage.BasePath }, + { "App:Verbose", "true" } + }; + // Start using dependency injection + var builder = new ConfigurationBuilder(); + // Add random config to dependency injection + builder.AddInMemoryCollection(dict); + // build config + var configuration = builder.Build(); + // inject config as object to a service + services.ConfigurePoCo(configuration.GetSection("App")); + + // Add Background services + services.AddSingleton(); + services.AddSingleton(); + + // build the service + var serviceProvider = services.BuildServiceProvider(); + // get the service + _appSettings = serviceProvider.GetRequiredService(); + + _iStorage = new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger()); + } + + private async Task InsertSearchData(bool delete = false) + { + var fileHashCode = new FileHash(_iStorage).GetHashCode(_createAnImage.DbPath).Key; + + if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync(fileHashCode)) ) { - var fileHashCode = new FileHash(_iStorage).GetHashCode(_createAnImage.DbPath).Key; - - if (string.IsNullOrEmpty(await _query.GetSubPathByHashAsync(fileHashCode))) + var isDelete = string.Empty; + if ( delete ) { - var isDelete = string.Empty; - if (delete) isDelete = TrashKeyword.TrashKeywordString; - await _query.AddItemAsync(new FileIndexItem - { - FileName = _createAnImage.FileName, - ParentDirectory = "/", - FileHash = fileHashCode, - ColorClass = ColorClassParser.Color.Winner, // 1 - Tags = isDelete - }); + isDelete = TrashKeyword.TrashKeywordString; } - return _query.GetObjectByFilePath(_createAnImage.DbPath); - } - - [TestMethod] - public async Task ApiController_Delete_API_HappyFlow_Test() - { - var createAnImage = await InsertSearchData(true); - _appSettings.DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase; - - // RealFs Storage - var selectorStorage = new FakeSelectorStorage(new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger())); - - var deleteItem = new DeleteItem(_query,_appSettings,selectorStorage); - var controller = new DeleteController(deleteItem); - - Console.WriteLine("createAnImage.FilePath"); - Console.WriteLine("@#~ "+ createAnImage?.FilePath); - - // create an image - var createAnImage1 = new CreateAnImage(); - Assert.IsNotNull(createAnImage1); - - var actionResult = await controller.Delete(createAnImage?.FilePath!) as JsonResult; - Assert.AreNotEqual(null,actionResult); - var jsonCollection = actionResult?.Value as List; - Assert.AreEqual(createAnImage?.FilePath,jsonCollection?.FirstOrDefault()?.FilePath); - - var createAnImage2 = new CreateAnImage(); //restore afterwards - Assert.IsNotNull(createAnImage2); - } - - [TestMethod] - public async Task ApiController_Delete_API_RemoveNotAllowedFile_Test() - { - // re add data - var createAnImage = await InsertSearchData(); - Assert.IsNotNull(createAnImage?.FilePath); - - // Clean existing items to avoid errors - var itemByHash = _query.SingleItem(createAnImage.FilePath); - Assert.IsNotNull(itemByHash); - Assert.IsNotNull(itemByHash.FileIndexItem); - - itemByHash.FileIndexItem.Tags = string.Empty; - await _query.UpdateItemAsync(itemByHash.FileIndexItem); - - _appSettings.DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase; - - var selectorStorage = - new FakeSelectorStorage(new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger())); - - var deleteItem = new DeleteItem(_query,_appSettings,selectorStorage); - var controller = new DeleteController(deleteItem); - - var notFoundResult = await controller.Delete(createAnImage.FilePath) as NotFoundObjectResult; - Assert.AreEqual(404,notFoundResult?.StatusCode); - var jsonCollection = notFoundResult?.Value as List; - - Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, - jsonCollection?.FirstOrDefault()?.Status); - - await _query.RemoveItemAsync(_query.SingleItem(createAnImage.FilePath)?.FileIndexItem!); - } - - - [TestMethod] - public async Task ApiController_Delete_SourceImageMissingOnDisk_WithFakeExiftool() - { await _query.AddItemAsync(new FileIndexItem { - FileName = "345678765434567.jpg", + FileName = _createAnImage.FileName, ParentDirectory = "/", - FileHash = "345678765434567" + FileHash = fileHashCode, + ColorClass = ColorClassParser.Color.Winner, // 1 + Tags = isDelete }); - - var selectorStorage = new FakeSelectorStorage(new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger())); - var deleteItem = new DeleteItem(_query,_appSettings,selectorStorage); - var controller = new DeleteController(deleteItem); - var notFoundResult = await controller.Delete("/345678765434567.jpg") as NotFoundObjectResult; - Assert.AreEqual(404,notFoundResult?.StatusCode); - - await _query.RemoveItemAsync(_query.SingleItem("/345678765434567.jpg")?.FileIndexItem!); } + return _query.GetObjectByFilePath(_createAnImage.DbPath); + } + + + [TestMethod] + public async Task ApiController_Delete_API_HappyFlow_Test() + { + var createAnImage = await InsertSearchData(true); + _appSettings.DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase; + + // RealFs Storage + var selectorStorage = + new FakeSelectorStorage( + new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger())); + + var deleteItem = new DeleteItem(_query, _appSettings, selectorStorage); + var controller = new DeleteController(deleteItem); + + Console.WriteLine("createAnImage.FilePath"); + Console.WriteLine("@#~ " + createAnImage?.FilePath); + + // create an image + var createAnImage1 = new CreateAnImage(); + Assert.IsNotNull(createAnImage1); + + var actionResult = await controller.Delete(createAnImage?.FilePath!) as JsonResult; + Assert.AreNotEqual(null, actionResult); + var jsonCollection = actionResult?.Value as List; + Assert.AreEqual(createAnImage?.FilePath, jsonCollection?.FirstOrDefault()?.FilePath); + + var createAnImage2 = new CreateAnImage(); //restore afterwards + Assert.IsNotNull(createAnImage2); + } + + [TestMethod] + public async Task Delete_ReturnsBadRequest() + { + // Arrange + var deleteItem = new DeleteItem(_query, _appSettings, new FakeSelectorStorage()); + var controller = new DeleteController(deleteItem); + + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var result = await controller.Delete(null!); + + // Assert + Assert.IsInstanceOfType(result); + } + + [TestMethod] + public async Task ApiController_Delete_API_RemoveNotAllowedFile_Test() + { + // re add data + var createAnImage = await InsertSearchData(); + Assert.IsNotNull(createAnImage?.FilePath); + + // Clean existing items to avoid errors + var itemByHash = _query.SingleItem(createAnImage.FilePath); + Assert.IsNotNull(itemByHash); + Assert.IsNotNull(itemByHash.FileIndexItem); + + itemByHash.FileIndexItem.Tags = string.Empty; + await _query.UpdateItemAsync(itemByHash.FileIndexItem); + + _appSettings.DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase; + + var selectorStorage = + new FakeSelectorStorage( + new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger())); + + var deleteItem = new DeleteItem(_query, _appSettings, selectorStorage); + var controller = new DeleteController(deleteItem); + + var notFoundResult = + await controller.Delete(createAnImage.FilePath) as NotFoundObjectResult; + Assert.AreEqual(404, notFoundResult?.StatusCode); + var jsonCollection = notFoundResult?.Value as List; + + Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, + jsonCollection?.FirstOrDefault()?.Status); + + await _query.RemoveItemAsync(_query.SingleItem(createAnImage.FilePath)?.FileIndexItem!); + } + + + [TestMethod] + public async Task ApiController_Delete_SourceImageMissingOnDisk_WithFakeExiftool() + { + await _query.AddItemAsync(new FileIndexItem + { + FileName = "345678765434567.jpg", + ParentDirectory = "/", + FileHash = "345678765434567" + }); + + var selectorStorage = + new FakeSelectorStorage( + new StorageSubPathFilesystem(_appSettings, new FakeIWebLogger())); + var deleteItem = new DeleteItem(_query, _appSettings, selectorStorage); + var controller = new DeleteController(deleteItem); + var notFoundResult = + await controller.Delete("/345678765434567.jpg") as NotFoundObjectResult; + Assert.AreEqual(404, notFoundResult?.StatusCode); + + await _query.RemoveItemAsync(_query.SingleItem("/345678765434567.jpg")?.FileIndexItem!); } } diff --git a/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs b/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs index ef09835d47..12468292ed 100644 --- a/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs +++ b/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs @@ -31,13 +31,30 @@ public void OpenAmountConfirmationChecker_FeatureToggleEnabled() var result = controller.OpenAmountConfirmationChecker("/test.jpg;/test2.jpg"); - var castedResult = ( JsonResult )result; - var boolValue = ( bool? )castedResult.Value; + var castedResult = ( JsonResult ) result; + var boolValue = ( bool? ) castedResult.Value; // mock is always true Assert.IsTrue(boolValue); } + [TestMethod] + public void OpenAmountConfirmationChecker_ReturnsBadRequest() + { + // Arrange + var controller = new DesktopEditorController(new OpenEditorDesktopService(new AppSettings(), + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List()))); + + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var result = controller.OpenAmountConfirmationChecker(null!); + + // Assert + Assert.IsInstanceOfType(result); + } + [TestMethod] public async Task OpenAsync_FeatureToggleDisabled() { @@ -51,11 +68,28 @@ public async Task OpenAsync_FeatureToggleDisabled() }; var result = await controller.OpenAsync("/test.jpg;/test2.jpg"); - var castedResult = ( BadRequestObjectResult )result; + var castedResult = ( BadRequestObjectResult ) result; Assert.AreEqual(400, castedResult.StatusCode); } + [TestMethod] + public async Task OpenAsync_ReturnsBadRequest() + { + // Arrange + var controller = new DesktopEditorController(new OpenEditorDesktopService(new AppSettings(), + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List()))); + + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var result = await controller.OpenAsync(null!); + + // Assert + Assert.IsInstanceOfType(result); + } + [TestMethod] public async Task OpenAsync_NoResultsBack() { @@ -72,8 +106,8 @@ public async Task OpenAsync_NoResultsBack() var result = await controller.OpenAsync("/test.jpg;/test2.jpg"); Assert.AreEqual(206, controller.HttpContext.Response.StatusCode); - var castedResult = ( JsonResult )result; - var arrayValues = ( List? )castedResult.Value; + var castedResult = ( JsonResult ) result; + var arrayValues = ( List? ) castedResult.Value; Assert.AreEqual(0, arrayValues?.Count); } @@ -83,7 +117,7 @@ public async Task OpenAsync_HappyFlow() { var preflight = new FakeIOpenEditorPreflight(new List { - new PathImageFormatExistsAppPathModel + new() { AppPath = "test", Status = FileIndexItem.ExifStatus.Ok, @@ -105,8 +139,8 @@ public async Task OpenAsync_HappyFlow() var result = await controller.OpenAsync("/test.jpg;/test2.jpg"); Assert.AreEqual(200, controller.HttpContext.Response.StatusCode); - var castedResult = ( JsonResult )result; - var arrayValues = ( List? )castedResult.Value; + var castedResult = ( JsonResult ) result; + var arrayValues = ( List? ) castedResult.Value; Assert.AreEqual(1, arrayValues?.Count); } diff --git a/starsky/starskytest/Controllers/DiskControllerTest.cs b/starsky/starskytest/Controllers/DiskControllerTest.cs index 72e2471710..b6c34d5593 100644 --- a/starsky/starskytest/Controllers/DiskControllerTest.cs +++ b/starsky/starskytest/Controllers/DiskControllerTest.cs @@ -26,297 +26,315 @@ using starskytest.FakeCreateAn; using starskytest.FakeMocks; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class DiskControllerTest { - [TestClass] - public sealed class DiskControllerTest + private readonly CreateAnImage _createAnImage; + private readonly Query _query; + private IStorage? _iStorage; + + public DiskControllerTest() { - private readonly Query _query; - private readonly CreateAnImage _createAnImage; - private IStorage? _iStorage; + var provider = new ServiceCollection() + .AddMemoryCache() + .BuildServiceProvider(); + var memoryCache = provider.GetService(); - public DiskControllerTest() - { - var provider = new ServiceCollection() - .AddMemoryCache() - .BuildServiceProvider(); - var memoryCache = provider.GetService(); + var builderDb = new DbContextOptionsBuilder(); + builderDb.UseInMemoryDatabase("SyncControllerTest"); + var options = builderDb.Options; + var context = new ApplicationDbContext(options); - var builderDb = new DbContextOptionsBuilder(); - builderDb.UseInMemoryDatabase("SyncControllerTest"); - var options = builderDb.Options; - var context = new ApplicationDbContext(options); + // Inject Fake Exiftool; dependency injection + var services = new ServiceCollection(); + services.AddSingleton(); - // Inject Fake Exiftool; dependency injection - var services = new ServiceCollection(); - services.AddSingleton(); + // Fake the readmeta output + services.AddSingleton(); - // Fake the readmeta output - services.AddSingleton(); + // Inject Config helper + services.AddSingleton(new ConfigurationBuilder().Build()); + // random config + _createAnImage = new CreateAnImage(); + var dict = new Dictionary + { + { "App:StorageFolder", _createAnImage.BasePath }, + { "App:ThumbnailTempFolder", _createAnImage.BasePath }, + { "App:Verbose", "true" } + }; + // Start using dependency injection + var builder = new ConfigurationBuilder(); + // Add random config to dependency injection + builder.AddInMemoryCollection(dict); + // build config + var configuration = builder.Build(); + // inject config as object to a service + services.ConfigurePoCo(configuration.GetSection("App")); + + // Add Background services + services.AddSingleton(); + services.AddSingleton(); + + // build the service + var serviceProvider = services.BuildServiceProvider(); + // get the service + var appSettings = serviceProvider.GetRequiredService(); + + var scopeFactory = serviceProvider.GetRequiredService(); + _query = new Query(context, appSettings, scopeFactory, new FakeIWebLogger(), + memoryCache); + } - // Inject Config helper - services.AddSingleton(new ConfigurationBuilder().Build()); - // random config - _createAnImage = new CreateAnImage(); - var dict = new Dictionary - { - { "App:StorageFolder", _createAnImage.BasePath }, - { "App:ThumbnailTempFolder", _createAnImage.BasePath }, - { "App:Verbose", "true" } - }; - // Start using dependency injection - var builder = new ConfigurationBuilder(); - // Add random config to dependency injection - builder.AddInMemoryCollection(dict!); - // build config - var configuration = builder.Build(); - // inject config as object to a service - services.ConfigurePoCo(configuration.GetSection("App")); - - // Add Background services - services.AddSingleton(); - services.AddSingleton(); - - // build the service - var serviceProvider = services.BuildServiceProvider(); - // get the service - var appSettings = serviceProvider.GetRequiredService(); - - var scopeFactory = serviceProvider.GetRequiredService(); - _query = new Query(context, appSettings, scopeFactory, new FakeIWebLogger(), - memoryCache); - } + private async Task InsertSearchData() + { + _iStorage = new FakeIStorage(new List { "/" }, + new List { _createAnImage.DbPath }); + var fileHashCode = + ( await new FileHash(_iStorage).GetHashCodeAsync(_createAnImage.DbPath) ).Key; - private async Task InsertSearchData() + if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync(fileHashCode)) ) { - _iStorage = new FakeIStorage(new List { "/" }, - new List { _createAnImage.DbPath }); - var fileHashCode = - ( await new FileHash(_iStorage).GetHashCodeAsync(_createAnImage.DbPath) ).Key; + await _query.AddItemAsync(new FileIndexItem + { + FileName = "/", ParentDirectory = "/", IsDirectory = true + }); - if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync(fileHashCode)) ) + await _query.AddItemAsync(new FileIndexItem { - await _query.AddItemAsync(new FileIndexItem - { - FileName = "/", ParentDirectory = "/", IsDirectory = true - }); - - await _query.AddItemAsync(new FileIndexItem - { - FileName = _createAnImage.FileName, - ParentDirectory = "/", - FileHash = fileHashCode, - ColorClass = ColorClassParser.Color.Winner, // 1 - }); - } - - _query.GetObjectByFilePath(_createAnImage.DbPath); + FileName = _createAnImage.FileName, + ParentDirectory = "/", + FileHash = fileHashCode, + ColorClass = ColorClassParser.Color.Winner // 1 + }); } + await _query.GetObjectByFilePathAsync(_createAnImage.DbPath); + } - [TestMethod] - public async Task SyncControllerTest_Rename_NotFoundInIndex() - { - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - var fakeStorage = new FakeIStorage(); - var storageSelector = new FakeSelectorStorage(fakeStorage); - var controller = new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); - controller.ControllerContext = context; + [TestMethod] + public async Task SyncControllerTest_Rename_NotFoundInIndex() + { + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + var fakeStorage = new FakeIStorage(); + var storageSelector = new FakeSelectorStorage(fakeStorage); - var result = - await controller.Rename("/notfound-image.jpg", "/test.jpg") as NotFoundObjectResult; + var controller = new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); + controller.ControllerContext = context; - Assert.AreEqual(404, result?.StatusCode); - } + var result = + await controller.Rename("/notfound-image.jpg", "/test.jpg") as NotFoundObjectResult; - [TestMethod] - public async Task SyncControllerTest_BadRequest() - { - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - var fakeStorage = new FakeIStorage(); - var storageSelector = new FakeSelectorStorage(fakeStorage); + Assert.AreEqual(404, result?.StatusCode); + } - var controller = new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); - controller.ControllerContext = context; + [TestMethod] + public async Task Rename_BadRequest() + { + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + var fakeStorage = new FakeIStorage(); + var storageSelector = new FakeSelectorStorage(fakeStorage); - var result = - await controller.Rename(string.Empty, "/test.jpg") as BadRequestObjectResult; + var controller = new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); + controller.ControllerContext = context; - Assert.AreEqual(400, result?.StatusCode); - } + var result = + await controller.Rename(string.Empty, "/test.jpg") as BadRequestObjectResult; - [TestMethod] - public async Task SyncControllerTest_Rename_Good() - { - await InsertSearchData(); + Assert.AreEqual(400, result?.StatusCode); + } - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + [TestMethod] + public async Task Rename_ReturnsBadRequest() + { + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + var fakeStorage = new FakeIStorage(); + var storageSelector = new FakeSelectorStorage(fakeStorage); - var fakeStorage = new FakeIStorage(new List { "/" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); + var controller = new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); + controller.ControllerContext = context; + controller.ModelState.AddModelError("Key", "ErrorMessage"); - var controller = - new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) - { - ControllerContext = context - }; + var result = + await controller.Rename(string.Empty, "/test.jpg") as BadRequestObjectResult; - var result = await controller.Rename(_createAnImage.DbPath, "/test.jpg") as JsonResult; - var list = result?.Value as List; + Assert.AreEqual(400, result?.StatusCode); + Assert.IsInstanceOfType(result); + } - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?.FirstOrDefault()?.Status); + [TestMethod] + public async Task SyncControllerTest_Rename_Good() + { + await InsertSearchData(); - await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); - } + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - [TestMethod] - public async Task SyncControllerTest_Rename_WithCurrentStatusDisabled() - { - await InsertSearchData(); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + var controller = + new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) + { + ControllerContext = context + }; - var fakeStorage = new FakeIStorage(new List { "/" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); + var result = await controller.Rename(_createAnImage.DbPath, "/test.jpg") as JsonResult; + var list = result?.Value as List; - var controller = - new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) - { - ControllerContext = context - }; + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?.FirstOrDefault()?.Status); - var result = - await controller.Rename(_createAnImage.DbPath, "/test.jpg", true, false) as - JsonResult; - var list = result?.Value as List; + await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); + } - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?[0].Status); - Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, list?[1].Status); + [TestMethod] + public async Task SyncControllerTest_Rename_WithCurrentStatusDisabled() + { + await InsertSearchData(); - await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); - } + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - [TestMethod] - public async Task SyncControllerTest_Rename_Good_SocketUpdate() - { - await InsertSearchData(); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - var socket = new FakeIWebSocketConnectionsService(); + var controller = + new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) + { + ControllerContext = context + }; - var fakeStorage = new FakeIStorage(new List { "/" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); + var result = + await controller.Rename(_createAnImage.DbPath, "/test.jpg", true, false) as + JsonResult; + var list = result?.Value as List; - var controller = - new DiskController(_query, storageSelector, - socket, new FakeINotificationQuery()) { ControllerContext = context }; + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?[0].Status); + Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, list?[1].Status); - await controller.Rename(_createAnImage.DbPath, "/test.jpg"); + await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); + } - Assert.AreEqual(1, socket.FakeSendToAllAsync.Count(p => !p.Contains("[system]"))); - Assert.IsTrue(socket.FakeSendToAllAsync[0].Contains("/test.jpg")); + [TestMethod] + public async Task SyncControllerTest_Rename_Good_SocketUpdate() + { + await InsertSearchData(); - await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); - } + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + var socket = new FakeIWebSocketConnectionsService(); - [TestMethod] - public async Task SyncControllerTest_Mkdir_Good() - { - await InsertSearchData(); - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - - var fakeStorage = new FakeIStorage(new List { "/" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); - - var controller = - new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) - { - ControllerContext = context - }; - - var result = await controller.Mkdir("/test_dir") as JsonResult; - var list = result?.Value as List; - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?.FirstOrDefault()?.Status); - } + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); - [TestMethod] - public async Task SyncControllerTest_Mkdir_Good_SocketUpdate() - { - await InsertSearchData(); - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + var controller = + new DiskController(_query, storageSelector, + socket, new FakeINotificationQuery()) { ControllerContext = context }; - var socket = new FakeIWebSocketConnectionsService(); - var fakeStorage = new FakeIStorage(new List { "/" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); + await controller.Rename(_createAnImage.DbPath, "/test.jpg"); - var controller = - new DiskController(_query, storageSelector, - socket, new FakeINotificationQuery()) { ControllerContext = context }; + Assert.AreEqual(1, socket.FakeSendToAllAsync.Count(p => !p.Contains("[system]"))); + Assert.IsTrue(socket.FakeSendToAllAsync[0].Contains("/test.jpg")); - await controller.Mkdir("/test_dir"); + await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); + } - var value = socket.FakeSendToAllAsync.Find(p => - !p.StartsWith("[system]")); + [TestMethod] + public async Task SyncControllerTest_Mkdir_Good() + { + await InsertSearchData(); + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - Assert.IsNotNull(value); - Assert.IsTrue(value.Contains("/test_dir")); - } + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); - [TestMethod] - public async Task SyncControllerTest_Mkdir_Exist() - { - await InsertSearchData(); - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - - var fakeStorage = new FakeIStorage(new List { "/", "/test_dir" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); - - var controller = - new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) - { - ControllerContext = context - }; - - var result = await controller.Mkdir("/test_dir") as JsonResult; - var list = result?.Value as List; - Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, - list?.FirstOrDefault()?.Status); - } + var controller = + new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) + { + ControllerContext = context + }; + var result = await controller.Mkdir("/test_dir") as JsonResult; + var list = result?.Value as List; + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?.FirstOrDefault()?.Status); + } - [TestMethod] - public async Task Mkdir_BadRequest() - { - var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + [TestMethod] + public async Task SyncControllerTest_Mkdir_Good_SocketUpdate() + { + await InsertSearchData(); + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; - var fakeStorage = new FakeIStorage(new List { "/", "/test_dir" }, - new List { _createAnImage.DbPath }); - var storageSelector = new FakeSelectorStorage(fakeStorage); + var socket = new FakeIWebSocketConnectionsService(); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); - var controller = - new DiskController(_query, storageSelector, - new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) - { - ControllerContext = context - }; + var controller = + new DiskController(_query, storageSelector, + socket, new FakeINotificationQuery()) { ControllerContext = context }; - await controller.Mkdir(string.Empty); + await controller.Mkdir("/test_dir"); - Assert.AreEqual(400, context.HttpContext.Response.StatusCode); - } + var value = socket.FakeSendToAllAsync.Find(p => + !p.StartsWith("[system]")); + + Assert.IsNotNull(value); + Assert.IsTrue(value.Contains("/test_dir")); + } + + [TestMethod] + public async Task SyncControllerTest_Mkdir_Exist() + { + await InsertSearchData(); + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/", "/test_dir" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); + + var controller = + new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) + { + ControllerContext = context + }; + + var result = await controller.Mkdir("/test_dir") as JsonResult; + var list = result?.Value as List; + Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, + list?.FirstOrDefault()?.Status); + } + + + [TestMethod] + public async Task Mkdir_BadRequest() + { + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/", "/test_dir" }, + new List { _createAnImage.DbPath }); + var storageSelector = new FakeSelectorStorage(fakeStorage); + + var controller = + new DiskController(_query, storageSelector, + new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) + { + ControllerContext = context + }; + + await controller.Mkdir(string.Empty); + + Assert.AreEqual(400, context.HttpContext.Response.StatusCode); } } diff --git a/starsky/starskytest/Controllers/DownloadPhotoControllerTest.cs b/starsky/starskytest/Controllers/DownloadPhotoControllerTest.cs index f11005c120..a0eddcfdf7 100644 --- a/starsky/starskytest/Controllers/DownloadPhotoControllerTest.cs +++ b/starsky/starskytest/Controllers/DownloadPhotoControllerTest.cs @@ -14,261 +14,322 @@ using starsky.foundation.database.Query; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; +using starskytest.FakeCreateAn; using starskytest.FakeMocks; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class DownloadPhotoControllerTest { - [TestClass] - public sealed class DownloadPhotoControllerTest + private readonly Query _query; + + public DownloadPhotoControllerTest() { - private readonly Query _query; + var provider = new ServiceCollection() + .AddMemoryCache() + .BuildServiceProvider(); + var memoryCache = provider.GetService(); + + var builderDb = new DbContextOptionsBuilder(); + builderDb.UseInMemoryDatabase(nameof(DownloadPhotoControllerTest)); + var options = builderDb.Options; + var context = new ApplicationDbContext(options); + var scopeFactory = provider.GetService(); + _query = new Query(context, new AppSettings(), scopeFactory, new FakeIWebLogger(), + memoryCache); + } - public DownloadPhotoControllerTest() + private async Task InsertSearchData() + { + var item = new FileIndexItem { - var provider = new ServiceCollection() - .AddMemoryCache() - .BuildServiceProvider(); - var memoryCache = provider.GetService(); - - var builderDb = new DbContextOptionsBuilder(); - builderDb.UseInMemoryDatabase(nameof(DownloadPhotoControllerTest)); - var options = builderDb.Options; - var context = new ApplicationDbContext(options); - var scopeFactory = provider.GetService(); - _query = new Query(context, new AppSettings(), scopeFactory, new FakeIWebLogger(), memoryCache); - } + FileName = "test.jpg", + ParentDirectory = "/", + FileHash = "/home0012304590.jpg", + ColorClass = ColorClassParser.Color.Winner // 1 + }; - private async Task InsertSearchData() + if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync("home0012304590")) ) { - var item = new FileIndexItem + await _query.AddItemAsync(item); + } + + return item; + } + + private static FakeIStorage ArrangeStorage() + { + var folderPaths = new List { "/" }; + var inputSubPaths = new List { "/test.jpg", "/test.xmp", "/corrupt.jpg" }; + var storage = + new FakeIStorage(folderPaths, inputSubPaths, + new List + { + CreateAnImage.Bytes.ToArray(), + CreateAnXmp.Bytes.ToArray(), + Array.Empty() + }); + return storage; + } + + [TestMethod] + public void DownloadSidecar_Ok() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + var actionResult = controller.DownloadSidecar("/test.xmp") as FileStreamResult; + Assert.AreNotEqual(null, actionResult); + + actionResult?.FileStream.Dispose(); + } + + [TestMethod] + public void DownloadSidecar_ReturnsBadRequest() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + var actionResult = controller.DownloadSidecar(null!); + + // Assert + Assert.IsInstanceOfType(actionResult, typeof(BadRequestObjectResult)); + } + + [TestMethod] + public void DownloadSidecar_TryToGetJpeg() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + // Act + var controller = + new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()) { - FileName = "test.jpg", - ParentDirectory = "/", - FileHash = "/home0012304590.jpg", - ColorClass = ColorClassParser.Color.Winner // 1 + ControllerContext = { HttpContext = new DefaultHttpContext() } }; + var actionResult = controller.DownloadSidecar("/test.jpg") as NotFoundObjectResult; - if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync("home0012304590")) ) - { - await _query.AddItemAsync(item); - } - return item; - } + Assert.AreNotEqual(null, actionResult); + Assert.AreEqual(404, actionResult?.StatusCode); + } - private static FakeIStorage ArrangeStorage() - { - var folderPaths = new List{"/"}; - var inputSubPaths = new List{"/test.jpg","/test.xmp", "/corrupt.jpg"}; - var storage = - new FakeIStorage(folderPaths, inputSubPaths, - new List{FakeCreateAn.CreateAnImage.Bytes.ToArray(), - FakeCreateAn.CreateAnXmp.Bytes.ToArray(), Array.Empty()}); - return storage; - } + [TestMethod] + public void DownloadSidecar_NotFound() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - [TestMethod] - public void DownloadSidecar_Ok() - { - // Arrange - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query,selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - var actionResult = controller.DownloadSidecar("/test.xmp") as FileStreamResult; - Assert.AreNotEqual(null,actionResult); - - actionResult?.FileStream.Dispose(); - } - - [TestMethod] - public void DownloadSidecar_TryToGetJpeg() - { - // Arrange - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()) + // Act + var controller = + new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - var actionResult = controller.DownloadSidecar("/test.jpg") as NotFoundObjectResult; - - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(404,actionResult?.StatusCode); - } - - [TestMethod] - public void DownloadSidecar_NotFound() - { - // Arrange - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()) + var actionResult = controller.DownloadSidecar("/not-found.xmp") as NotFoundObjectResult; + + Assert.AreNotEqual(null, actionResult); + Assert.AreEqual(404, actionResult?.StatusCode); + } + + [TestMethod] + public async Task DownloadPhoto_isThumbnailTrue_CreateThumb_ReturnFileStream_Test() + { + // Arrange + var fileIndexItem = await InsertSearchData(); + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService(selectorStorage)); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + var actionResult = + await controller.DownloadPhoto(fileIndexItem.FilePath!) as FileStreamResult; + Assert.AreNotEqual(null, actionResult); + + await actionResult!.FileStream.DisposeAsync(); + } + + [TestMethod] + public async Task DownloadPhoto_WrongInputNotFound() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + // Act + var controller = + new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - var actionResult = controller.DownloadSidecar("/not-found.xmp") as NotFoundObjectResult; - - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(404,actionResult?.StatusCode); - } - - [TestMethod] - public async Task DownloadPhoto_isThumbnailTrue_CreateThumb_ReturnFileStream_Test() - { - // Arrange - var fileIndexItem = await InsertSearchData(); - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query,selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService(selectorStorage)); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - var actionResult = await controller.DownloadPhoto(fileIndexItem.FilePath!) as FileStreamResult; - Assert.AreNotEqual(null,actionResult); - - await actionResult!.FileStream.DisposeAsync(); - } - - [TestMethod] - public async Task DownloadPhoto_WrongInputNotFound() - { - // Arrange - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()) + var actionResult = await controller.DownloadPhoto("?isthumbnail") as NotFoundObjectResult; + + Assert.AreNotEqual(null, actionResult); + Assert.AreEqual(404, actionResult?.StatusCode); + } + + [TestMethod] + public async Task DownloadPhoto_ReturnsBadRequest() + { + // Arrange + var controller = + new DownloadPhotoController(_query, new FakeSelectorStorage(), new FakeIWebLogger(), + new FakeIThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - var actionResult = await controller.DownloadPhoto("?isthumbnail") as NotFoundObjectResult; - - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(404,actionResult?.StatusCode); - } - - [TestMethod] - public async Task DownloadPhotoCorrupt() + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + var actionResult = await controller.DownloadPhoto(null!); + + // Assert + Assert.IsInstanceOfType(actionResult, typeof(BadRequestObjectResult)); + } + + [TestMethod] + public async Task DownloadPhotoCorrupt() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + var item = await _query.AddItemAsync(new FileIndexItem { - // Arrange - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - var item = await _query.AddItemAsync(new FileIndexItem - { - FileName = "corrupt.jpg", - ParentDirectory = "/", - FileHash = "hash" - }); - - // Act - var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()) + FileName = "corrupt.jpg", ParentDirectory = "/", FileHash = "hash" + }); + + // Act + var controller = + new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - - var actionResult = await controller.DownloadPhoto("/corrupt.jpg") as JsonResult; - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(500,controller.Response.StatusCode); + var actionResult = await controller.DownloadPhoto("/corrupt.jpg") as JsonResult; + Assert.AreNotEqual(null, actionResult); - await _query.RemoveItemAsync(item); - } - - - [TestMethod] - public async Task DownloadPhoto_NotFound() - { - // Arrange - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()) + Assert.AreEqual(500, controller.Response.StatusCode); + + await _query.RemoveItemAsync(item); + } + + + [TestMethod] + public async Task DownloadPhoto_NotFound() + { + // Arrange + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + + // Act + var controller = + new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - var actionResult = await controller.DownloadPhoto("/not-found.jpg") as NotFoundObjectResult; - - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(404,actionResult?.StatusCode); - } - - [TestMethod] - public async Task DownloadPhoto_isThumbnailFalse_ReturnFileStream_Test() - { - // Arrange - var fileIndexItem = await InsertSearchData(); - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - - // Act - var controller = new DownloadPhotoController(_query,selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - var actionResult = await controller.DownloadPhoto(fileIndexItem.FilePath!,false) as FileStreamResult; - Assert.AreNotEqual(null,actionResult); - - await actionResult!.FileStream.DisposeAsync(); - } + var actionResult = await controller.DownloadPhoto("/not-found.jpg") as NotFoundObjectResult; - [TestMethod] - public async Task DownloadPhoto_isThumbnailTrue_ReturnAThumb_ReturnFileStream_Test() - { - // Arrange - var fileIndexItem = await InsertSearchData(); - var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); + Assert.AreNotEqual(null, actionResult); + Assert.AreEqual(404, actionResult?.StatusCode); + } - // Act - var controller = new DownloadPhotoController(_query,selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService(selectorStorage)); - controller.ControllerContext.HttpContext = new DefaultHttpContext(); + [TestMethod] + public async Task DownloadPhoto_isThumbnailFalse_ReturnFileStream_Test() + { + // Arrange + var fileIndexItem = await InsertSearchData(); + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - // Run once - var actionResult1 = await controller.DownloadPhoto(fileIndexItem.FilePath!) as FileStreamResult; - await actionResult1!.FileStream.DisposeAsync(); + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + var actionResult = + await controller.DownloadPhoto(fileIndexItem.FilePath!, false) as FileStreamResult; + Assert.AreNotEqual(null, actionResult); - // Run twice - var actionResult2 = await controller.DownloadPhoto(fileIndexItem.FilePath!) as FileStreamResult; - Assert.AreNotEqual(null,actionResult2); + await actionResult!.FileStream.DisposeAsync(); + } - await actionResult2!.FileStream.DisposeAsync(); - } - - [TestMethod] - public async Task ApiController_DownloadPhoto_SourceImageIsMissing_Test() - { - // Arrange - var fileIndexItem = await InsertSearchData(); - - // so the item does not exist on disk - var selectorStorage = new FakeSelectorStorage(); - - // Act - var controller = new DownloadPhotoController(_query,selectorStorage, new FakeIWebLogger(), new FakeIThumbnailService()); - var actionResult = await controller.DownloadPhoto(fileIndexItem.FilePath!) as NotFoundObjectResult; - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(404,actionResult?.StatusCode); - Assert.AreEqual("source image missing /test.jpg",actionResult?.Value); - } + [TestMethod] + public async Task DownloadPhoto_isThumbnailTrue_ReturnAThumb_ReturnFileStream_Test() + { + // Arrange + var fileIndexItem = await InsertSearchData(); + var selectorStorage = new FakeSelectorStorage(ArrangeStorage()); - [TestMethod] - public async Task DownloadPhoto_Thumb_base_folder_not_found_Test() - { - // Arrange - var fileIndexItem = await InsertSearchData(); - var storage = - new FakeIStorage(null!, new List{"/test.jpg"}, - new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()}); - var selectorStorage = new FakeSelectorStorage(storage); - - - // Act - var controller = new DownloadPhotoController(_query,selectorStorage, - new FakeIWebLogger(), new FakeIThumbnailService()); - - controller.ControllerContext.HttpContext = new DefaultHttpContext(); - var actionResult = await controller.DownloadPhoto(fileIndexItem.FilePath!) as NotFoundObjectResult; - - Assert.AreNotEqual(null,actionResult); - Assert.AreEqual(404,actionResult?.StatusCode); - Assert.AreEqual("ThumbnailTempFolder not found",actionResult?.Value); - } + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService(selectorStorage)); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + + // Run once + var actionResult1 = + await controller.DownloadPhoto(fileIndexItem.FilePath!) as FileStreamResult; + await actionResult1!.FileStream.DisposeAsync(); + + // Run twice + var actionResult2 = + await controller.DownloadPhoto(fileIndexItem.FilePath!) as FileStreamResult; + Assert.AreNotEqual(null, actionResult2); + + await actionResult2!.FileStream.DisposeAsync(); + } + + [TestMethod] + public async Task ApiController_DownloadPhoto_SourceImageIsMissing_Test() + { + // Arrange + var fileIndexItem = await InsertSearchData(); + + // so the item does not exist on disk + var selectorStorage = new FakeSelectorStorage(); + + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, new FakeIWebLogger(), + new FakeIThumbnailService()); + var actionResult = + await controller.DownloadPhoto(fileIndexItem.FilePath!) as NotFoundObjectResult; + Assert.AreNotEqual(null, actionResult); + Assert.AreEqual(404, actionResult?.StatusCode); + Assert.AreEqual("source image missing /test.jpg", actionResult?.Value); + } + + [TestMethod] + public async Task DownloadPhoto_Thumb_base_folder_not_found_Test() + { + // Arrange + var fileIndexItem = await InsertSearchData(); + var storage = + new FakeIStorage(null!, new List { "/test.jpg" }, + new List { CreateAnImage.Bytes.ToArray() }); + var selectorStorage = new FakeSelectorStorage(storage); + + + // Act + var controller = new DownloadPhotoController(_query, selectorStorage, + new FakeIWebLogger(), new FakeIThumbnailService()); + + controller.ControllerContext.HttpContext = new DefaultHttpContext(); + var actionResult = + await controller.DownloadPhoto(fileIndexItem.FilePath!) as NotFoundObjectResult; + + Assert.AreNotEqual(null, actionResult); + Assert.AreEqual(404, actionResult?.StatusCode); + Assert.AreEqual("ThumbnailTempFolder not found", actionResult?.Value); } } diff --git a/starsky/starskytest/Controllers/ErrorControllerTest.cs b/starsky/starskytest/Controllers/ErrorControllerTest.cs index 8bf9595612..d58c434114 100644 --- a/starsky/starskytest/Controllers/ErrorControllerTest.cs +++ b/starsky/starskytest/Controllers/ErrorControllerTest.cs @@ -4,22 +4,37 @@ using starsky.Controllers; using starsky.foundation.platform.Models; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class ErrorControllerTest { - [TestClass] - public sealed class ErrorControllerTest + [TestMethod] + public void ErrorControllerTest_Error() { - - [TestMethod] - public void ErrorControllerTest_Error() + var controller = new ErrorController(new AppSettings()) { - var controller = new ErrorController(new AppSettings()) - { - ControllerContext = {HttpContext = new DefaultHttpContext()} - }; + ControllerContext = { HttpContext = new DefaultHttpContext() } + }; + + var actionResult = controller.Error(404) as PhysicalFileResult; + Assert.AreEqual("text/html", actionResult?.ContentType); + } + + [TestMethod] + public void Error_ReturnsBadRequest() + { + // Arrange + var controller = new ErrorController(new AppSettings()) + { + ControllerContext = { HttpContext = new DefaultHttpContext() } + }; + controller.ModelState.AddModelError("Key", "ErrorMessage"); + + // Act + var actionResult = controller.Error(); - var actionResult = controller.Error(404) as PhysicalFileResult; - Assert.AreEqual("text/html",actionResult?.ContentType); - } + // Assert + Assert.IsInstanceOfType(actionResult, typeof(BadRequestObjectResult)); } } diff --git a/starsky/starskytest/Controllers/UploadControllerTest.cs b/starsky/starskytest/Controllers/UploadControllerTest.cs index 782b803c55..a62a10b751 100644 --- a/starsky/starskytest/Controllers/UploadControllerTest.cs +++ b/starsky/starskytest/Controllers/UploadControllerTest.cs @@ -27,481 +27,480 @@ using starskytest.FakeCreateAn; using starskytest.FakeMocks; -namespace starskytest.Controllers +namespace starskytest.Controllers; + +[TestClass] +public sealed class UploadControllerTest { - [TestClass] - public sealed class UploadControllerTest + private readonly AppSettings _appSettings; + private readonly Import _import; + private readonly FakeIStorage _iStorage; + private readonly Query _query; + + public UploadControllerTest() { - private readonly Query _query; - private readonly FakeIStorage _iStorage; - private readonly AppSettings _appSettings; - private readonly Import _import; + var provider = new ServiceCollection() + .AddMemoryCache() + .BuildServiceProvider(); + var memoryCache = provider.GetRequiredService(); + + var builderDb = new DbContextOptionsBuilder(); + builderDb.UseInMemoryDatabase(nameof(ExportControllerTest)); + var options = builderDb.Options; + var context = new ApplicationDbContext(options); + var scopeFactory = provider.GetService(); + var services = new ServiceCollection(); + + // Fake the readMeta output + services.AddSingleton(); + + // Inject Config helper + services.AddSingleton(new ConfigurationBuilder().Build()); + // random config + var createAnImage = new CreateAnImage(); + _appSettings = new AppSettings { TempFolder = createAnImage.BasePath }; + _query = new Query(context, _appSettings, scopeFactory, new FakeIWebLogger(), + memoryCache); + + _iStorage = new FakeIStorage(["/", "/test"], + [createAnImage.DbPath], + new List { CreateAnImage.Bytes.ToArray() }); + + var selectorStorage = new FakeSelectorStorage(_iStorage); + + _import = new Import(selectorStorage, _appSettings, new FakeIImportQuery(), + new FakeExifTool(_iStorage, _appSettings), _query, new ConsoleWrapper(), + new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), + new FakeIThumbnailQuery(), memoryCache); + + // Start using dependency injection + // Add random config to dependency injection + // build config + // inject config as object to a service + + // Add Background services + services.AddSingleton(); + services.AddSingleton(); + + // build the service + var serviceProvider = services.BuildServiceProvider(); + // get the service + + serviceProvider.GetRequiredService(); + } - public UploadControllerTest() - { - var provider = new ServiceCollection() - .AddMemoryCache() - .BuildServiceProvider(); - var memoryCache = provider.GetRequiredService(); - - var builderDb = new DbContextOptionsBuilder(); - builderDb.UseInMemoryDatabase(nameof(ExportControllerTest)); - var options = builderDb.Options; - var context = new ApplicationDbContext(options); - var scopeFactory = provider.GetService(); - var services = new ServiceCollection(); - - // Fake the readMeta output - services.AddSingleton(); - - // Inject Config helper - services.AddSingleton(new ConfigurationBuilder().Build()); - // random config - var createAnImage = new CreateAnImage(); - _appSettings = new AppSettings { TempFolder = createAnImage.BasePath }; - _query = new Query(context, _appSettings, scopeFactory, new FakeIWebLogger(), - memoryCache); - - _iStorage = new FakeIStorage(new List { "/", "/test" }, - new List { createAnImage.DbPath }, - new List { CreateAnImage.Bytes.ToArray() }); - - var selectorStorage = new FakeSelectorStorage(_iStorage); - - _import = new Import(selectorStorage, _appSettings, new FakeIImportQuery(), - new FakeExifTool(_iStorage, _appSettings), _query, new ConsoleWrapper(), - new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), - new FakeIThumbnailQuery(), memoryCache); - - // Start using dependency injection - // Add random config to dependency injection - // build config - // inject config as object to a service - - // Add Background services - services.AddSingleton(); - services.AddSingleton(); - - // build the service - var serviceProvider = services.BuildServiceProvider(); - // get the service - - serviceProvider.GetRequiredService(); - } + /// + /// Add the file in the underlying request object. + /// + /// Controller Context with file + private static ControllerContext RequestWithFile(byte[]? bytes = null) + { + bytes ??= [.. CreateAnImage.Bytes]; - /// - /// Add the file in the underlying request object. - /// - /// Controller Context with file - private static ControllerContext RequestWithFile(byte[]? bytes = null) - { - // ReSharper disable once ConvertIfStatementToNullCoalescingAssignment - if ( bytes == null ) bytes = CreateAnImage.Bytes.ToArray(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Append("Content-Type", "application/octet-stream"); - httpContext.Request.Body = new MemoryStream(bytes); - - var actionContext = new ActionContext(httpContext, new RouteData(), - new ControllerActionDescriptor()); - return new ControllerContext(actionContext); - } + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Append("Content-Type", "application/octet-stream"); + httpContext.Request.Body = new MemoryStream(bytes); - [TestMethod] - public async Task UploadToFolder_NoToHeader_BadRequest() - { - var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(new FakeIStorage()), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), - new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = { HttpContext = new DefaultHttpContext() } - }; - - var actionResult = await controller.UploadToFolder() as BadRequestObjectResult; - - Assert.AreEqual(400, actionResult?.StatusCode); - } + var actionContext = new ActionContext(httpContext, new RouteData(), + new ControllerActionDescriptor()); + return new ControllerContext(actionContext); + } - [TestMethod] - public async Task UploadToFolder_DefaultFlow() - { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + [TestMethod] + public async Task UploadToFolder_NoToHeader_BadRequest() + { + var controller = + new UploadController(_import, _appSettings, + new FakeSelectorStorage(new FakeIStorage()), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { - ControllerContext = RequestWithFile(), + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - const string toPlaceSubPath = "/yes01.jpg"; + var actionResult = await controller.UploadToFolder() as BadRequestObjectResult; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + Assert.AreEqual(400, actionResult?.StatusCode); + } - var actionResult = await controller.UploadToFolder() as JsonResult; - var list = actionResult?.Value as List; + [TestMethod] + public async Task UploadToFolder_DefaultFlow() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + { + ControllerContext = RequestWithFile() + }; - Assert.AreEqual(ImportStatus.Ok, list?.FirstOrDefault()?.Status); + const string toPlaceSubPath = "/yes01.jpg"; - var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); - Assert.IsTrue(fileSystemResult); + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - var queryResult = _query.SingleItem(toPlaceSubPath); - Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); + var actionResult = await controller.UploadToFolder() as JsonResult; + var list = actionResult?.Value as List; - await _query.RemoveItemAsync(queryResult?.FileIndexItem!); - } + Assert.AreEqual(ImportStatus.Ok, list?.FirstOrDefault()?.Status); + + var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); + Assert.IsTrue(fileSystemResult); + + var queryResult = _query.SingleItem(toPlaceSubPath); + Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); - [TestMethod] - public async Task UploadToFolder_DefaultFlow_ColorClass() + await _query.RemoveItemAsync(queryResult?.FileIndexItem!); + } + + [TestMethod] + public async Task UploadToFolder_DefaultFlow_ColorClass() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithFile(CreateAnImageColorClass.Bytes.ToArray()), - }; + ControllerContext = RequestWithFile(CreateAnImageColorClass.Bytes.ToArray()) + }; - const string toPlaceSubPath = "/color-class01.jpg"; + const string toPlaceSubPath = "/color-class01.jpg"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolder() as JsonResult; - var list = actionResult?.Value as List; + var actionResult = await controller.UploadToFolder() as JsonResult; + var list = actionResult?.Value as List; - Assert.AreEqual(ImportStatus.Ok, list?.FirstOrDefault()?.Status); + Assert.AreEqual(ImportStatus.Ok, list?.FirstOrDefault()?.Status); - var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); - Assert.IsTrue(fileSystemResult); + var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); + Assert.IsTrue(fileSystemResult); - var queryResult = _query.SingleItem(toPlaceSubPath); + var queryResult = _query.SingleItem(toPlaceSubPath); - Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); - Assert.AreEqual(ColorClassParser.Color.Winner, queryResult?.FileIndexItem?.ColorClass); + Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); + Assert.AreEqual(ColorClassParser.Color.Winner, queryResult?.FileIndexItem?.ColorClass); - await _query.RemoveItemAsync(queryResult?.FileIndexItem!); - } + await _query.RemoveItemAsync(queryResult?.FileIndexItem!); + } - [TestMethod] - public async Task UploadToFolder_DefaultFlow_ShouldNotOverWriteDatabase() + [TestMethod] + public async Task UploadToFolder_DefaultFlow_ShouldNotOverWriteDatabase() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithFile(), - }; + ControllerContext = RequestWithFile() + }; - const string toPlaceSubPath = "/duplicate_upload/yes01.jpg"; - const string toPlaceFolder = "/duplicate_upload"; + const string toPlaceSubPath = "/duplicate_upload/yes01.jpg"; + const string toPlaceFolder = "/duplicate_upload"; - // add to db - await _query.AddItemAsync(new FileIndexItem(toPlaceSubPath)); + // add to db + await _query.AddItemAsync(new FileIndexItem(toPlaceSubPath)); - _iStorage.CreateDirectory(toPlaceFolder); + _iStorage.CreateDirectory(toPlaceFolder); - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolder() as JsonResult; - if ( actionResult == null ) - { - throw new WebException("actionResult should not be null"); - } + var actionResult = await controller.UploadToFolder() as JsonResult; + if ( actionResult == null ) + { + throw new WebException("actionResult should not be null"); + } - var list = actionResult.Value as List; - if ( list == null ) - { - throw new WebException("list should not be null"); - } + var list = actionResult.Value as List; + if ( list == null ) + { + throw new WebException("list should not be null"); + } - Assert.AreEqual(ImportStatus.Ok, list[0].Status); + Assert.AreEqual(ImportStatus.Ok, list[0].Status); - var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); - Assert.IsTrue(fileSystemResult); + var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); + Assert.IsTrue(fileSystemResult); - var getAllFiles = await _query.GetAllFilesAsync(toPlaceFolder); + var getAllFiles = await _query.GetAllFilesAsync(toPlaceFolder); - // Should not duplicate - Assert.AreEqual(1, getAllFiles.Count); + // Should not duplicate + Assert.AreEqual(1, getAllFiles.Count); - var queryResult = _query.SingleItem(toPlaceSubPath); - Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); + var queryResult = _query.SingleItem(toPlaceSubPath); + Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); - await _query.RemoveItemAsync(queryResult?.FileIndexItem!); - } + await _query.RemoveItemAsync(queryResult?.FileIndexItem!); + } - [TestMethod] - public async Task UploadToFolder_SidecarListShouldBeUpdated() + [TestMethod] + public async Task UploadToFolder_SidecarListShouldBeUpdated() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithFile(), - }; + ControllerContext = RequestWithFile() + }; - var toPlaceSubPath = "/test_sidecar.dng"; - var toPlaceXmp = "/test_sidecar.xmp"; + var toPlaceSubPath = "/test_sidecar.dng"; + var toPlaceXmp = "/test_sidecar.xmp"; - await _iStorage.WriteStreamAsync(new MemoryStream(new byte[1]), toPlaceXmp); + await _iStorage.WriteStreamAsync(new MemoryStream(new byte[1]), toPlaceXmp); - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - await controller.UploadToFolder(); + await controller.UploadToFolder(); - var queryResult = _query.SingleItem(toPlaceSubPath); + var queryResult = _query.SingleItem(toPlaceSubPath); - var sidecarExtList = queryResult?.FileIndexItem?.SidecarExtensionsList.ToList(); - Assert.AreEqual(1, sidecarExtList?.Count); - Assert.AreEqual("xmp", sidecarExtList?[0]); + var sidecarExtList = queryResult?.FileIndexItem?.SidecarExtensionsList.ToList(); + Assert.AreEqual(1, sidecarExtList?.Count); + Assert.AreEqual("xmp", sidecarExtList?[0]); - await _query.RemoveItemAsync(queryResult?.FileIndexItem!); - } - - [TestMethod] - public async Task UploadToFolder_NotFound() - { - var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), - new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithFile(), - }; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - "/not-found"; //Set header - - var actionResult = await controller.UploadToFolder() as NotFoundObjectResult; - - Assert.AreEqual(404, actionResult?.StatusCode); - } + await _query.RemoveItemAsync(queryResult?.FileIndexItem!); + } - [TestMethod] - public async Task UploadToFolder_UnknownFailFlow() - { - var controller = new UploadController(_import, _appSettings, + [TestMethod] + public async Task UploadToFolder_NotFound() + { + var controller = + new UploadController(_import, _appSettings, new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { - ControllerContext = RequestWithFile(), + ControllerContext = RequestWithFile() }; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + "/not-found"; //Set header - controller.ControllerContext.HttpContext.Request.Headers["to"] = "/"; //Set header + var actionResult = await controller.UploadToFolder() as NotFoundObjectResult; - var actionResult = await controller.UploadToFolder() as JsonResult; - var list = actionResult?.Value as List; + Assert.AreEqual(404, actionResult?.StatusCode); + } - Assert.AreEqual(ImportStatus.FileError, list?.FirstOrDefault()?.Status); - } + [TestMethod] + public async Task UploadToFolder_UnknownFailFlow() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + { + ControllerContext = RequestWithFile() + }; + + controller.ControllerContext.HttpContext.Request.Headers["to"] = "/"; //Set header + + var actionResult = await controller.UploadToFolder() as JsonResult; + var list = actionResult?.Value as List; + + Assert.AreEqual(ImportStatus.FileError, list?.FirstOrDefault()?.Status); + } - [TestMethod] - public void GetParentDirectoryFromRequestHeader_InputToAsSubPath() + [TestMethod] + public void GetParentDirectoryFromRequestHeader_InputToAsSubPath() + { + var controllerContext = RequestWithFile(); + controllerContext.HttpContext.Request.Headers.Append("to", "/test.jpg"); + + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controllerContext = RequestWithFile(); - controllerContext.HttpContext.Request.Headers.Append("to", "/test.jpg"); + ControllerContext = controllerContext + }; - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = controllerContext - }; + var result = controller.GetParentDirectoryFromRequestHeader(); + Assert.AreEqual("/", result); + } - var result = controller.GetParentDirectoryFromRequestHeader(); - Assert.AreEqual("/", result); - } + [TestMethod] + public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestFolder() + { + var controllerContext = RequestWithFile(); + controllerContext.HttpContext.Request.Headers.Append("to", "/test/test.jpg"); - [TestMethod] - public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestFolder() + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controllerContext = RequestWithFile(); - controllerContext.HttpContext.Request.Headers.Append("to", "/test/test.jpg"); + ControllerContext = controllerContext + }; - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = controllerContext - }; + var result = controller.GetParentDirectoryFromRequestHeader(); + Assert.AreEqual("/test", result); + } - var result = controller.GetParentDirectoryFromRequestHeader(); - Assert.AreEqual("/test", result); - } + [TestMethod] + public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestDirectFolder() + { + var controllerContext = RequestWithFile(); + controllerContext.HttpContext.Request.Headers.Append("to", "/test/"); - [TestMethod] - public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestDirectFolder() + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controllerContext = RequestWithFile(); - controllerContext.HttpContext.Request.Headers.Append("to", "/test/"); + ControllerContext = controllerContext + }; + + var result = controller.GetParentDirectoryFromRequestHeader(); + Assert.AreEqual("/test", result); + } - var controller = new UploadController(_import, _appSettings, + [TestMethod] + public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_NonExistFolder() + { + var controllerContext = RequestWithFile(); + controllerContext.HttpContext.Request.Headers.Append("to", "/non-exist/test.jpg"); + + var controller = + new UploadController(_import, _appSettings, new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { ControllerContext = controllerContext }; - var result = controller.GetParentDirectoryFromRequestHeader(); - Assert.AreEqual("/test", result); - } - - [TestMethod] - public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_NonExistFolder() - { - var controllerContext = RequestWithFile(); - controllerContext.HttpContext.Request.Headers.Append("to", "/non-exist/test.jpg"); - - var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), - new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = controllerContext - }; - - var result = controller.GetParentDirectoryFromRequestHeader(); - Assert.IsNull(result); - } + var result = controller.GetParentDirectoryFromRequestHeader(); + Assert.IsNull(result); + } - /// - /// Add the file in the underlying request object. - /// - /// Controller Context with file - private static ControllerContext RequestWithSidecar() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Append("Content-Type", "application/octet-stream"); - httpContext.Request.Body = new MemoryStream(CreateAnXmp.Bytes.ToArray()); + /// + /// Add the file in the underlying request object. + /// + /// Controller Context with file + private static ControllerContext RequestWithSidecar() + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Append("Content-Type", "application/octet-stream"); + httpContext.Request.Body = new MemoryStream(CreateAnXmp.Bytes.ToArray()); - var actionContext = new ActionContext(httpContext, new RouteData(), - new ControllerActionDescriptor()); - return new ControllerContext(actionContext); - } + var actionContext = new ActionContext(httpContext, new RouteData(), + new ControllerActionDescriptor()); + return new ControllerContext(actionContext); + } - [TestMethod] - public async Task UploadToFolderSidecarFile_DefaultFlow() + [TestMethod] + public async Task UploadToFolderSidecarFile_DefaultFlow() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithSidecar(), - }; + ControllerContext = RequestWithSidecar() + }; - var toPlaceSubPath = "/yes01.xmp"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + var toPlaceSubPath = "/yes01.xmp"; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; - var list = actionResult?.Value as List; + var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; + var list = actionResult?.Value as List; - Assert.AreEqual(toPlaceSubPath, list?.FirstOrDefault()); - } + Assert.AreEqual(toPlaceSubPath, list?.FirstOrDefault()); + } - [TestMethod] - public async Task UploadToFolderSidecarFile_UpdateMainItemWithSidecarRef() + [TestMethod] + public async Task UploadToFolderSidecarFile_UpdateMainItemWithSidecarRef() + { + // it should add a reference to the main item + var controller = new UploadController(_import, + new AppSettings { UseDiskWatcher = false }, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - // it should add a reference to the main item - var controller = new UploadController(_import, - new AppSettings { UseDiskWatcher = false }, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithSidecar(), - }; + ControllerContext = RequestWithSidecar() + }; - var dngSubPath = "/UploadToFolderSidecarFile.dng"; - await _query.AddItemAsync( - new FileIndexItem(dngSubPath)); + var dngSubPath = "/UploadToFolderSidecarFile.dng"; + await _query.AddItemAsync( + new FileIndexItem(dngSubPath)); - var toPlaceSubPath = "/UploadToFolderSidecarFile.xmp"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + var toPlaceSubPath = "/UploadToFolderSidecarFile.xmp"; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - await controller.UploadToFolderSidecarFile(); + await controller.UploadToFolderSidecarFile(); - var queryResult = await _query.GetObjectByFilePathAsync(dngSubPath); - var sidecarExtList = queryResult?.SidecarExtensionsList.ToList(); - Assert.AreEqual(1, sidecarExtList?.Count); - Assert.AreEqual("xmp", sidecarExtList?[0]); - } + var queryResult = await _query.GetObjectByFilePathAsync(dngSubPath); + var sidecarExtList = queryResult?.SidecarExtensionsList.ToList(); + Assert.AreEqual(1, sidecarExtList?.Count); + Assert.AreEqual("xmp", sidecarExtList?[0]); + } - [TestMethod] - public async Task UploadToFolderSidecarFile_NoXml_SoIgnore() + [TestMethod] + public async Task UploadToFolderSidecarFile_NoXml_SoIgnore() + { + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { - var controller = new UploadController(_import, _appSettings, + ControllerContext = RequestWithFile() // < - - - - - - this is not a xml + }; + + var toPlaceSubPath = "/yes01.xmp"; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header + + var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; + var list = actionResult?.Value as List; + + Assert.AreEqual(0, list?.Count); + } + + [TestMethod] + public async Task UploadToFolderSidecarFile_NotFound() + { + var controller = + new UploadController(_import, _appSettings, new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { - ControllerContext = RequestWithFile() // < - - - - - - this is not an xml + ControllerContext = RequestWithFile() }; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + "/not-found"; //Set header - var toPlaceSubPath = "/yes01.xmp"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - toPlaceSubPath; //Set header + var actionResult = await controller.UploadToFolderSidecarFile() as NotFoundObjectResult; - var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; - var list = actionResult?.Value as List; + Assert.AreEqual(404, actionResult?.StatusCode); + } - Assert.AreEqual(0, list?.Count); - } + [TestMethod] + public async Task UploadToFolderSidecarFile_NoToHeader_BadRequest() + { + var controller = + new UploadController(_import, _appSettings, + new FakeSelectorStorage(new FakeIStorage()), _query, + new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) + { + ControllerContext = { HttpContext = new DefaultHttpContext() } + }; - [TestMethod] - public async Task UploadToFolderSidecarFile_NotFound() - { - var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), - new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = RequestWithFile(), - }; - controller.ControllerContext.HttpContext.Request.Headers["to"] = - "/not-found"; //Set header - - var actionResult = await controller.UploadToFolderSidecarFile() as NotFoundObjectResult; - - Assert.AreEqual(404, actionResult?.StatusCode); - } + var actionResult = + await controller.UploadToFolderSidecarFile() as BadRequestObjectResult; - [TestMethod] - public async Task UploadToFolderSidecarFile_NoToHeader_BadRequest() - { - var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(new FakeIStorage()), _query, - new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), - new FakeIMetaUpdateStatusThumbnailService()) - { - ControllerContext = { HttpContext = new DefaultHttpContext() } - }; - - var actionResult = - await controller.UploadToFolderSidecarFile() as BadRequestObjectResult; - - Assert.AreEqual(400, actionResult?.StatusCode); - } + Assert.AreEqual(400, actionResult?.StatusCode); } } diff --git a/starsky/starskytest/FakeCreateAn/CreateAnImage.cs b/starsky/starskytest/FakeCreateAn/CreateAnImage.cs index f5946bf881..06365ccea8 100644 --- a/starsky/starskytest/FakeCreateAn/CreateAnImage.cs +++ b/starsky/starskytest/FakeCreateAn/CreateAnImage.cs @@ -5,273 +5,279 @@ using System.Reflection; using starsky.foundation.platform.Helpers; -namespace starskytest.FakeCreateAn +namespace starskytest.FakeCreateAn; + +public class CreateAnImage { - public class CreateAnImage - { - private const string FileNamePrivate = "0000000000aaaaa__exifreadingtest00.jpg"; - - /// - /// The filename *.jpg - /// There is an unit test for using directory thumbnails that uses the first image; - /// starskytest.SyncServiceTest.SyncServiceFirstItemDirectoryTest - /// - [SuppressMessage("Performance", "CA1822:Mark members as static")] - [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] - public string FileName => FileNamePrivate; - /// - /// Database Path/subpath of the iamge - /// - public readonly string DbPath = "/" + FileNamePrivate; - /// - /// Full path of the image - /// - public readonly string FullFilePath = - (Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + - Path.DirectorySeparatorChar).Replace("./",string.Empty) + FileNamePrivate; - /// - /// The FullFile Path of the Directory of the Assemblies - /// - public readonly string BasePath = - (Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + - Path.DirectorySeparatorChar).Replace("./",string.Empty); + private const string FileNamePrivate = "0000000000aaaaa__exifreadingtest00.jpg"; + + /// + /// Split line + /// @see: https://superuser.com/a/1467266 and 80 chars + /// + [SuppressMessage("ReSharper", "StringLiteralTypo")] + private static readonly string Base64JpgString = "/9j/4AAQSkZJRgABAQABXgF" + + "eAAD/4QQgRXhpZgAATU0AKgAAAAgACwEOAAIAAAAg" + + "AAAAkgEPAAIAAAAFAAAAsgEQAAIAAAAIAAAAuAESAAMAAAABAAEAAAEaAAUAAAAB" + + "AAAAwAEbAAUAAAABAAAAyAEoAAMAAAABAAIAAAExAAIAAAAOAAAA0AEyAAIAAAAU" + + "AAAA3odpAAQAAAABAAAA8oglAAQAAAABAAADNgAAAAAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgAFNPTlkAAFNMVC1BNTgAAAABXgAAAAEAAAFeAAAAAVNM" + + "VC1BNTggdjEuMDAAMjAxODowNDoyMiAxNzo0MzowMAAAI4KaAAUAAAABAAACnIKd" + + "AAUAAAABAAACpIgiAAMAAAABAAMAAIgnAAMAAAABAMgAAIgwAAMAAAABAAIAAIgy" + + "AAQAAAABAAAAyJAAAAcAAAAEMDIzMJADAAIAAAAUAAACrJAEAAIAAAAUAAACwJEB" + + "AAcAAAAEAQIDAJECAAUAAAABAAAC1JIDAAoAAAABAAAC3JIEAAoAAAABAAAC5JIF" + + "AAUAAAABAAAC7JIHAAMAAAABAAUAAJIIAAMAAAABAAAAAJIJAAMAAAABABAAAJIK" + + "AAUAAAABAAAC9KAAAAcAAAAEMDEwMKABAAMAAAABAAEAAKACAAQAAAABAAAAA6AD" + + "AAQAAAABAAAAAqMAAAcAAAABAwAAAKMBAAcAAAABAQAAAKQBAAMAAAABAAAAAKQC" + + "AAMAAAABAAAAAKQDAAMAAAABAAAAAKQEAAUAAAABAAAC/KQFAAMAAAABAJYAAKQG" + + "AAMAAAABAAAAAKQIAAMAAAABAAAAAKQJAAMAAAABAAAAAKQKAAMAAAABAAAAAKQy" + + "AAUAAAAEAAADBKQ0AAIAAAASAAADJAAAAAAAAAABAAAADwAAAA0AAAABMjAxODow" + + "NDoyMiAxNjoxNDo1NAAyMDE4OjA0OjIyIDE2OjE0OjU0AAAAAAMAAAABAAAH5wAA" + + "AUAAAAADAAAACgAAAJ8AAAAgAAAAZAAAAAEAAAABAAAAAQAAABgAAAABAAAAaQAA" + + "AAEAAAAHAAAAAgAAAAkAAAACMjQtMTA1bW0gRjMuNS00LjUAAAoAAAABAAAABAIC" + + "AAAAAQACAAAAAk4AAAAAAgAFAAAAAwAAA7QAAwACAAAAAkUAAAAABAAFAAAAAwAA" + + "A8wABQABAAAAAQAAAAAABgAFAAAAAQAAA+QABwAFAAAAAwAAA+wAEgACAAAABwAA" + + "BAQAHQACAAAACwAABAwAAAAAAAAANAAAAAEAAAASAAAAAQAAC4oAAABkAAAABgAA" + + "AAEAAAALAAAAAQAADmAAAABkAAAYxQAAA+gAAAAOAAAAAQAAAA4AAAABAAAAIgAA" + + "AAFXR1MtODQAADIwMTg6MDQ6MjIAAP/hIFdodHRwOi8vbnMuYWRvYmUuY29tL3hh" + + "cC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVT" + + "ek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8i" + + "IHg6eG1wdGs9IlhNUCBDb3JlIDUuMS4yIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0i" + + "aHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAg" + + "PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOmV4aWY9Imh0" + + "dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgeG1sbnM6ZXhpZkVYPSJo" + + "dHRwOi8vY2lwYS5qcC9leGlmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9u" + + "cy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMu" + + "YWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOmF1eD0iaHR0cDovL25zLmFk" + + "b2JlLmNvbS9leGlmLzEuMC9hdXgvIgogICAgeG1sbnM6cGhvdG9zaG9wPSJodHRw" + + "Oi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZGM9Imh0" + + "dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6SXB0YzR4" + + "bXBFeHQ9Imh0dHA6Ly9pcHRjLm9yZy9zdGQvSXB0YzR4bXBFeHQvMjAwOC0wMi0y" + + "OS8iCiAgICB4bWxuczpwaG90b21lY2hhbmljPSJodHRwOi8vbnMuY2FtZXJhYml0" + + "cy5jb20vcGhvdG9tZWNoYW5pYy8xLjAvIgogICBleGlmOlNjZW5lVHlwZT0iMSIK" + + "ICAgZXhpZjpHUFNBbHRpdHVkZVJlZj0iMCIKICAgZXhpZjpDb250cmFzdD0iMCIK" + + "ICAgZXhpZjpDb21wcmVzc2VkQml0c1BlclBpeGVsPSIzLzEiCiAgIGV4aWY6R1BT" + + "TGF0aXR1ZGU9IjUyLDE4LjQ5Mk4iCiAgIGV4aWY6R1BTVGltZVN0YW1wPSIyMDE4" + + "LTA0LTIyVDE0OjE0OjM0KzAwMDAiCiAgIGV4aWY6RGlnaXRhbFpvb21SYXRpbz0i" + + "MS8xIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMiIKICAgZXhpZjpDdXN0b21S" + + "ZW5kZXJlZD0iMCIKICAgZXhpZjpNZXRlcmluZ01vZGU9IjUiCiAgIGV4aWY6UGl4" + + "ZWxYRGltZW5zaW9uPSIzIgogICBleGlmOlNjZW5lQ2FwdHVyZVR5cGU9IjAiCiAg" + + "IGV4aWY6Rm9jYWxMZW5JbjM1bW1GaWxtPSIxNTAiCiAgIGV4aWY6RXhwb3N1cmVN" + + "b2RlPSIwIgogICBleGlmOkdQU0FsdGl0dWRlPSI2My8xMCIKICAgZXhpZjpTYXR1" + + "cmF0aW9uPSIwIgogICBleGlmOkV4cG9zdXJlVGltZT0iMS8xNSIKICAgZXhpZjpT" + + "aGFycG5lc3M9IjAiCiAgIGV4aWY6QnJpZ2h0bmVzc1ZhbHVlPSIyMDIzLzMyMCIK" + + "ICAgZXhpZjpHUFNMb25naXR1ZGU9IjYsMTEuNjEzRSIKICAgZXhpZjpHUFNWZXJz" + + "aW9uSUQ9IjIuMi4wLjAiCiAgIGV4aWY6RXhpZlZlcnNpb249IjAyMzAiCiAgIGV4" + + "aWY6RmlsZVNvdXJjZT0iMyIKICAgZXhpZjpGbGFzaFBpeFZlcnNpb249IjAxMDAi" + + "CiAgIGV4aWY6V2hpdGVCYWxhbmNlPSIwIgogICBleGlmOkNvbG9yU3BhY2U9IjEi" + + "CiAgIGV4aWY6Rm9jYWxMZW5ndGg9IjEwMC8xIgogICBleGlmOkV4cG9zdXJlUHJv" + + "Z3JhbT0iMyIKICAgZXhpZjpGTnVtYmVyPSIxMy8xIgogICBleGlmOk1heEFwZXJ0" + + "dXJlVmFsdWU9IjE1OS8zMiIKICAgZXhpZjpHUFNNYXBEYXR1bT0iV0dTLTg0Igog" + + "ICBleGlmOkxpZ2h0U291cmNlPSIwIgogICBleGlmOkV4cG9zdXJlQmlhc1ZhbHVl" + + "PSIzLzEwIgogICBleGlmOkdQU0RhdGVUaW1lPSIyMDE4LTA0LTIyVDE0OjE0OjM0" + + "WiIKICAgZXhpZkVYOlJlY29tbWVuZGVkRXhwb3N1cmVJbmRleD0iMjAwIgogICBl" + + "eGlmRVg6UGhvdG9ncmFwaGljU2Vuc2l0aXZpdHk9IjIwMCIKICAgZXhpZkVYOkxl" + + "bnNNb2RlbD0iMjQtMTA1bW0gRjMuNS00LjUiCiAgIGV4aWZFWDpTZW5zaXRpdml0" + + "eVR5cGU9IjIiCiAgIHhtcDpDcmVhdG9yVG9vbD0iU0xULUE1OCB2MS4wMCIKICAg" + + "eG1wOkNyZWF0ZURhdGU9IjIwMTgtMDQtMjJUMTY6MTQ6NTQiCiAgIHhtcDpNZXRh" + + "ZGF0YURhdGU9IjIwMTgtMDQtMjJUMTc6NDM6MDArMDI6MDAiCiAgIHhtcDpNb2Rp" + + "ZnlEYXRlPSIyMDE4LTA0LTIyVDE3OjQzOjAwKzAyOjAwIgogICB4bXA6TGFiZWw9" + + "IiIKICAgeG1wOlJhdGluZz0iMCIKICAgdGlmZjpSZXNvbHV0aW9uVW5pdD0iMiIK" + + "ICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgdGlmZjpYUmVzb2x1dGlvbj0iMzUw" + + "LzEiCiAgIHRpZmY6WVJlc29sdXRpb249IjM1MC8xIgogICB0aWZmOk1vZGVsPSJT" + + "TFQtQTU4IgogICB0aWZmOk1ha2U9IlNPTlkiCiAgIGF1eDpMZW5zPSJTaWdtYSAx" + + "OC0yMDBtbSBGMy41LTYuMyBEQyIKICAgYXV4OkZsYXNoQ29tcGVuc2F0aW9uPSIw" + + "LzEiCiAgIGF1eDpMZW5zSUQ9IjI0IgogICBwaG90b3Nob3A6Q2l0eT0iRGllcGVu" + + "dmVlbiIKICAgcGhvdG9zaG9wOlN0YXRlPSJPdmVyaWpzc2VsIgogICBwaG90b3No" + + "b3A6Q291bnRyeT0iTmVkZXJsYW5kIgogICBwaG90b3Nob3A6RGF0ZUNyZWF0ZWQ9" + + "IjIwMTgtMDQtMjJUMTY6MTQ6NTQrMDE6MDAiCiAgIHBob3RvbWVjaGFuaWM6Q29s" + + "b3JDbGFzcz0iMCIKICAgcGhvdG9tZWNoYW5pYzpUYWdnZWQ9IkZhbHNlIgogICBw" + + "aG90b21lY2hhbmljOlByZWZzPSIwOjA6MDotMDAwMDEiCiAgIHBob3RvbWVjaGFu" + + "aWM6UE1WZXJzaW9uPSJQTTUiPgogICA8ZXhpZjpGbGFzaAogICAgZXhpZjpGdW5j" + + "dGlvbj0iRmFsc2UiCiAgICBleGlmOkZpcmVkPSJGYWxzZSIKICAgIGV4aWY6UmV0" + + "dXJuPSIwIgogICAgZXhpZjpNb2RlPSIyIgogICAgZXhpZjpSZWRFeWVNb2RlPSJG" + + "YWxzZSIvPgogICA8ZXhpZjpJU09TcGVlZFJhdGluZ3M+CiAgICA8cmRmOlNlcT4K" + + "ICAgICA8cmRmOmxpPjIwMDwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8L2V4" + + "aWY6SVNPU3BlZWRSYXRpbmdzPgogICA8ZXhpZjpDb21wb25lbnRzQ29uZmlndXJh" + + "dGlvbj4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGk+MTwvcmRmOmxpPgogICAg" + + "IDxyZGY6bGk+MjwvcmRmOmxpPgogICAgIDxyZGY6bGk+MzwvcmRmOmxpPgogICAg" + + "IDxyZGY6bGk+MDwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8L2V4aWY6Q29t" + + "cG9uZW50c0NvbmZpZ3VyYXRpb24+CiAgIDxleGlmRVg6TGVuc1NwZWNpZmljYXRp" + + "b24+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpPjI0LzE8L3JkZjpsaT4KICAg" + + "ICA8cmRmOmxpPjEwNS8xPC9yZGY6bGk+CiAgICAgPHJkZjpsaT43LzI8L3JkZjps" + + "aT4KICAgICA8cmRmOmxpPjkvMjwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8" + + "L2V4aWZFWDpMZW5zU3BlY2lmaWNhdGlvbj4KICAgPGRjOnN1YmplY3Q+CiAgICA8" + + "cmRmOkJhZz4KICAgICA8cmRmOmxpPnRlc3Q8L3JkZjpsaT4KICAgICA8cmRmOmxp" + + "PnNpb248L3JkZjpsaT4KICAgIDwvcmRmOkJhZz4KICAgPC9kYzpzdWJqZWN0Pgog" + + "ICA8ZGM6ZGVzY3JpcHRpb24+CiAgICA8cmRmOkFsdD4KICAgICA8cmRmOmxpIHht" + + "bDpsYW5nPSJ4LWRlZmF1bHQiPmNhcHRpb248L3JkZjpsaT4KICAgIDwvcmRmOkFs" + + "dD4KICAgPC9kYzpkZXNjcmlwdGlvbj4KICAgPGRjOnRpdGxlPgogICAgPHJkZjpB" + + "bHQ+CiAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij50aXRsZTwvcmRm" + + "OmxpPgogICAgPC9yZGY6QWx0PgogICA8L2RjOnRpdGxlPgogICA8SXB0YzR4bXBF" + + "eHQ6TG9jYXRpb25DcmVhdGVkPgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQog" + + "ICAgICBJcHRjNHhtcEV4dDpTdWJsb2NhdGlvbj0iIgogICAgICBJcHRjNHhtcEV4" + + "dDpDaXR5PSJEaWVwZW52ZWVuIgogICAgICBJcHRjNHhtcEV4dDpQcm92aW5jZVN0" + + "YXRlPSJPdmVyaWpzc2VsIgogICAgICBJcHRjNHhtcEV4dDpDb3VudHJ5TmFtZT0i" + + "TmVkZXJsYW5kIgogICAgICBJcHRjNHhtcEV4dDpDb3VudHJ5Q29kZT0iIgogICAg" + + "ICBJcHRjNHhtcEV4dDpXb3JsZFJlZ2lvbj0iIi8+CiAgICA8L3JkZjpCYWc+CiAg" + + "IDwvSXB0YzR4bXBFeHQ6TG9jYXRpb25DcmVhdGVkPgogICA8SXB0YzR4bXBFeHQ6" + + "TG9jYXRpb25TaG93bj4KICAgIDxyZGY6QmFnPgogICAgIDxyZGY6bGkKICAgICAg" + + "SXB0YzR4bXBFeHQ6U3VibG9jYXRpb249IiIKICAgICAgSXB0YzR4bXBFeHQ6Q2l0" + + "eT0iRGllcGVudmVlbiIKICAgICAgSXB0YzR4bXBFeHQ6UHJvdmluY2VTdGF0ZT0i" + + "T3Zlcmlqc3NlbCIKICAgICAgSXB0YzR4bXBFeHQ6Q291bnRyeU5hbWU9Ik5lZGVy" + + "bGFuZCIKICAgICAgSXB0YzR4bXBFeHQ6Q291bnRyeUNvZGU9IiIKICAgICAgSXB0" + + "YzR4bXBFeHQ6V29ybGRSZWdpb249IiIvPgogICAgPC9yZGY6QmFnPgogICA8L0lw" + + "dGM0eG1wRXh0OkxvY2F0aW9uU2hvd24+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8" + + "L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7/" + + "7QDiUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAKkcAVoAAxslRxwCAAACAAMcAjcA" + + "CDIwMTgwNDIyHAI8AAsxNjE0NTQrMDEwMBwCWgAKRGllcGVudmVlbhwCXwAKT3Zl" + + "cmlqc3NlbBwCZQAJTmVkZXJsYW5kHAIFAAV0aXRsZRwCGQAEdGVzdBwCGQAEc2lv" + + "bhwCeAAHY2FwdGlvbhwC3QAMMDowOjA6LTAwMDAxHAI+AAgyMDE4MDQyMhwCPwAG" + + "MTYxNDU0ADhCSU0EJQAAAAAAEOXrWd9hKhRBbyqe22Ymbij/2wCEAAEBAQEBAQIC" + + "AgICAgICAgQDAgIDBAUEAwMDBAUHBQQDAwQFBwcGBQQFBgcIBgUFBggIBwcHCAkI" + + "CAkKCgoMDA4BAgICAgICAwICAwYDAgMGDAYEBAYMDwwHBQcMDw8PDQkJDQ8PDw8P" + + "Dg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD//CABEIAAIAAwMBIQACEQEDEQH/xAAU" + + "AAEAAAAAAAAAAAAAAAAAAAAJ/9oACAEBAAAAAEV//8QAFAEBAAAAAAAAAAAAAAAA" + + "AAAABf/aAAgBAhAAAAAH/8QAFAEBAAAAAAAAAAAAAAAAAAAAA//aAAgBAxAAAAAf" + + "/8QAIBAAAQMCBwAAAAAAAAAAAAAAAQIGIQQRAAMFEiMxUf/aAAgBAQABPwBhNVrp" + + "aFBbTaAXydx4USpRJJMdkyT7j//EABsRAAIBBQAAAAAAAAAAAAAAAAECBQADBBFy" + + "/9oACAECAQE/AFjo8qCcW3vkV//EABkRAQACAwAAAAAAAAAAAAAAAAEAAhESIf/a" + + "AAgBAwEBPwDaxwXE/9k="; + + public static readonly ImmutableArray Bytes = + Base64Helper.TryParse(Base64JpgString).ToImmutableArray(); + + /// + /// The FullFile Path of the Directory of the Assemblies + /// + public readonly string BasePath = + ( Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + + Path.DirectorySeparatorChar ).Replace("./", string.Empty); + + /// + /// Database Path/subpath of the iamge + /// + public readonly string DbPath = "/" + FileNamePrivate; - /// - /// Split line - /// @see: https://superuser.com/a/1467266 and 80 chars - /// - [SuppressMessage("ReSharper", "StringLiteralTypo")] - private static readonly string Base64JpgString = "/9j/4AAQSkZJRgABAQABXgF" + - "eAAD/4QQgRXhpZgAATU0AKgAAAAgACwEOAAIAAAAg" + - "AAAAkgEPAAIAAAAFAAAAsgEQAAIAAAAIAAAAuAESAAMAAAABAAEAAAEaAAUAAAAB" + - "AAAAwAEbAAUAAAABAAAAyAEoAAMAAAABAAIAAAExAAIAAAAOAAAA0AEyAAIAAAAU" + - "AAAA3odpAAQAAAABAAAA8oglAAQAAAABAAADNgAAAAAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgAFNPTlkAAFNMVC1BNTgAAAABXgAAAAEAAAFeAAAAAVNM" + - "VC1BNTggdjEuMDAAMjAxODowNDoyMiAxNzo0MzowMAAAI4KaAAUAAAABAAACnIKd" + - "AAUAAAABAAACpIgiAAMAAAABAAMAAIgnAAMAAAABAMgAAIgwAAMAAAABAAIAAIgy" + - "AAQAAAABAAAAyJAAAAcAAAAEMDIzMJADAAIAAAAUAAACrJAEAAIAAAAUAAACwJEB" + - "AAcAAAAEAQIDAJECAAUAAAABAAAC1JIDAAoAAAABAAAC3JIEAAoAAAABAAAC5JIF" + - "AAUAAAABAAAC7JIHAAMAAAABAAUAAJIIAAMAAAABAAAAAJIJAAMAAAABABAAAJIK" + - "AAUAAAABAAAC9KAAAAcAAAAEMDEwMKABAAMAAAABAAEAAKACAAQAAAABAAAAA6AD" + - "AAQAAAABAAAAAqMAAAcAAAABAwAAAKMBAAcAAAABAQAAAKQBAAMAAAABAAAAAKQC" + - "AAMAAAABAAAAAKQDAAMAAAABAAAAAKQEAAUAAAABAAAC/KQFAAMAAAABAJYAAKQG" + - "AAMAAAABAAAAAKQIAAMAAAABAAAAAKQJAAMAAAABAAAAAKQKAAMAAAABAAAAAKQy" + - "AAUAAAAEAAADBKQ0AAIAAAASAAADJAAAAAAAAAABAAAADwAAAA0AAAABMjAxODow" + - "NDoyMiAxNjoxNDo1NAAyMDE4OjA0OjIyIDE2OjE0OjU0AAAAAAMAAAABAAAH5wAA" + - "AUAAAAADAAAACgAAAJ8AAAAgAAAAZAAAAAEAAAABAAAAAQAAABgAAAABAAAAaQAA" + - "AAEAAAAHAAAAAgAAAAkAAAACMjQtMTA1bW0gRjMuNS00LjUAAAoAAAABAAAABAIC" + - "AAAAAQACAAAAAk4AAAAAAgAFAAAAAwAAA7QAAwACAAAAAkUAAAAABAAFAAAAAwAA" + - "A8wABQABAAAAAQAAAAAABgAFAAAAAQAAA+QABwAFAAAAAwAAA+wAEgACAAAABwAA" + - "BAQAHQACAAAACwAABAwAAAAAAAAANAAAAAEAAAASAAAAAQAAC4oAAABkAAAABgAA" + - "AAEAAAALAAAAAQAADmAAAABkAAAYxQAAA+gAAAAOAAAAAQAAAA4AAAABAAAAIgAA" + - "AAFXR1MtODQAADIwMTg6MDQ6MjIAAP/hIFdodHRwOi8vbnMuYWRvYmUuY29tL3hh" + - "cC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVT" + - "ek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8i" + - "IHg6eG1wdGs9IlhNUCBDb3JlIDUuMS4yIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0i" + - "aHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAg" + - "PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOmV4aWY9Imh0" + - "dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgeG1sbnM6ZXhpZkVYPSJo" + - "dHRwOi8vY2lwYS5qcC9leGlmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9u" + - "cy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMu" + - "YWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOmF1eD0iaHR0cDovL25zLmFk" + - "b2JlLmNvbS9leGlmLzEuMC9hdXgvIgogICAgeG1sbnM6cGhvdG9zaG9wPSJodHRw" + - "Oi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZGM9Imh0" + - "dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6SXB0YzR4" + - "bXBFeHQ9Imh0dHA6Ly9pcHRjLm9yZy9zdGQvSXB0YzR4bXBFeHQvMjAwOC0wMi0y" + - "OS8iCiAgICB4bWxuczpwaG90b21lY2hhbmljPSJodHRwOi8vbnMuY2FtZXJhYml0" + - "cy5jb20vcGhvdG9tZWNoYW5pYy8xLjAvIgogICBleGlmOlNjZW5lVHlwZT0iMSIK" + - "ICAgZXhpZjpHUFNBbHRpdHVkZVJlZj0iMCIKICAgZXhpZjpDb250cmFzdD0iMCIK" + - "ICAgZXhpZjpDb21wcmVzc2VkQml0c1BlclBpeGVsPSIzLzEiCiAgIGV4aWY6R1BT" + - "TGF0aXR1ZGU9IjUyLDE4LjQ5Mk4iCiAgIGV4aWY6R1BTVGltZVN0YW1wPSIyMDE4" + - "LTA0LTIyVDE0OjE0OjM0KzAwMDAiCiAgIGV4aWY6RGlnaXRhbFpvb21SYXRpbz0i" + - "MS8xIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMiIKICAgZXhpZjpDdXN0b21S" + - "ZW5kZXJlZD0iMCIKICAgZXhpZjpNZXRlcmluZ01vZGU9IjUiCiAgIGV4aWY6UGl4" + - "ZWxYRGltZW5zaW9uPSIzIgogICBleGlmOlNjZW5lQ2FwdHVyZVR5cGU9IjAiCiAg" + - "IGV4aWY6Rm9jYWxMZW5JbjM1bW1GaWxtPSIxNTAiCiAgIGV4aWY6RXhwb3N1cmVN" + - "b2RlPSIwIgogICBleGlmOkdQU0FsdGl0dWRlPSI2My8xMCIKICAgZXhpZjpTYXR1" + - "cmF0aW9uPSIwIgogICBleGlmOkV4cG9zdXJlVGltZT0iMS8xNSIKICAgZXhpZjpT" + - "aGFycG5lc3M9IjAiCiAgIGV4aWY6QnJpZ2h0bmVzc1ZhbHVlPSIyMDIzLzMyMCIK" + - "ICAgZXhpZjpHUFNMb25naXR1ZGU9IjYsMTEuNjEzRSIKICAgZXhpZjpHUFNWZXJz" + - "aW9uSUQ9IjIuMi4wLjAiCiAgIGV4aWY6RXhpZlZlcnNpb249IjAyMzAiCiAgIGV4" + - "aWY6RmlsZVNvdXJjZT0iMyIKICAgZXhpZjpGbGFzaFBpeFZlcnNpb249IjAxMDAi" + - "CiAgIGV4aWY6V2hpdGVCYWxhbmNlPSIwIgogICBleGlmOkNvbG9yU3BhY2U9IjEi" + - "CiAgIGV4aWY6Rm9jYWxMZW5ndGg9IjEwMC8xIgogICBleGlmOkV4cG9zdXJlUHJv" + - "Z3JhbT0iMyIKICAgZXhpZjpGTnVtYmVyPSIxMy8xIgogICBleGlmOk1heEFwZXJ0" + - "dXJlVmFsdWU9IjE1OS8zMiIKICAgZXhpZjpHUFNNYXBEYXR1bT0iV0dTLTg0Igog" + - "ICBleGlmOkxpZ2h0U291cmNlPSIwIgogICBleGlmOkV4cG9zdXJlQmlhc1ZhbHVl" + - "PSIzLzEwIgogICBleGlmOkdQU0RhdGVUaW1lPSIyMDE4LTA0LTIyVDE0OjE0OjM0" + - "WiIKICAgZXhpZkVYOlJlY29tbWVuZGVkRXhwb3N1cmVJbmRleD0iMjAwIgogICBl" + - "eGlmRVg6UGhvdG9ncmFwaGljU2Vuc2l0aXZpdHk9IjIwMCIKICAgZXhpZkVYOkxl" + - "bnNNb2RlbD0iMjQtMTA1bW0gRjMuNS00LjUiCiAgIGV4aWZFWDpTZW5zaXRpdml0" + - "eVR5cGU9IjIiCiAgIHhtcDpDcmVhdG9yVG9vbD0iU0xULUE1OCB2MS4wMCIKICAg" + - "eG1wOkNyZWF0ZURhdGU9IjIwMTgtMDQtMjJUMTY6MTQ6NTQiCiAgIHhtcDpNZXRh" + - "ZGF0YURhdGU9IjIwMTgtMDQtMjJUMTc6NDM6MDArMDI6MDAiCiAgIHhtcDpNb2Rp" + - "ZnlEYXRlPSIyMDE4LTA0LTIyVDE3OjQzOjAwKzAyOjAwIgogICB4bXA6TGFiZWw9" + - "IiIKICAgeG1wOlJhdGluZz0iMCIKICAgdGlmZjpSZXNvbHV0aW9uVW5pdD0iMiIK" + - "ICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgdGlmZjpYUmVzb2x1dGlvbj0iMzUw" + - "LzEiCiAgIHRpZmY6WVJlc29sdXRpb249IjM1MC8xIgogICB0aWZmOk1vZGVsPSJT" + - "TFQtQTU4IgogICB0aWZmOk1ha2U9IlNPTlkiCiAgIGF1eDpMZW5zPSJTaWdtYSAx" + - "OC0yMDBtbSBGMy41LTYuMyBEQyIKICAgYXV4OkZsYXNoQ29tcGVuc2F0aW9uPSIw" + - "LzEiCiAgIGF1eDpMZW5zSUQ9IjI0IgogICBwaG90b3Nob3A6Q2l0eT0iRGllcGVu" + - "dmVlbiIKICAgcGhvdG9zaG9wOlN0YXRlPSJPdmVyaWpzc2VsIgogICBwaG90b3No" + - "b3A6Q291bnRyeT0iTmVkZXJsYW5kIgogICBwaG90b3Nob3A6RGF0ZUNyZWF0ZWQ9" + - "IjIwMTgtMDQtMjJUMTY6MTQ6NTQrMDE6MDAiCiAgIHBob3RvbWVjaGFuaWM6Q29s" + - "b3JDbGFzcz0iMCIKICAgcGhvdG9tZWNoYW5pYzpUYWdnZWQ9IkZhbHNlIgogICBw" + - "aG90b21lY2hhbmljOlByZWZzPSIwOjA6MDotMDAwMDEiCiAgIHBob3RvbWVjaGFu" + - "aWM6UE1WZXJzaW9uPSJQTTUiPgogICA8ZXhpZjpGbGFzaAogICAgZXhpZjpGdW5j" + - "dGlvbj0iRmFsc2UiCiAgICBleGlmOkZpcmVkPSJGYWxzZSIKICAgIGV4aWY6UmV0" + - "dXJuPSIwIgogICAgZXhpZjpNb2RlPSIyIgogICAgZXhpZjpSZWRFeWVNb2RlPSJG" + - "YWxzZSIvPgogICA8ZXhpZjpJU09TcGVlZFJhdGluZ3M+CiAgICA8cmRmOlNlcT4K" + - "ICAgICA8cmRmOmxpPjIwMDwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8L2V4" + - "aWY6SVNPU3BlZWRSYXRpbmdzPgogICA8ZXhpZjpDb21wb25lbnRzQ29uZmlndXJh" + - "dGlvbj4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGk+MTwvcmRmOmxpPgogICAg" + - "IDxyZGY6bGk+MjwvcmRmOmxpPgogICAgIDxyZGY6bGk+MzwvcmRmOmxpPgogICAg" + - "IDxyZGY6bGk+MDwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8L2V4aWY6Q29t" + - "cG9uZW50c0NvbmZpZ3VyYXRpb24+CiAgIDxleGlmRVg6TGVuc1NwZWNpZmljYXRp" + - "b24+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpPjI0LzE8L3JkZjpsaT4KICAg" + - "ICA8cmRmOmxpPjEwNS8xPC9yZGY6bGk+CiAgICAgPHJkZjpsaT43LzI8L3JkZjps" + - "aT4KICAgICA8cmRmOmxpPjkvMjwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8" + - "L2V4aWZFWDpMZW5zU3BlY2lmaWNhdGlvbj4KICAgPGRjOnN1YmplY3Q+CiAgICA8" + - "cmRmOkJhZz4KICAgICA8cmRmOmxpPnRlc3Q8L3JkZjpsaT4KICAgICA8cmRmOmxp" + - "PnNpb248L3JkZjpsaT4KICAgIDwvcmRmOkJhZz4KICAgPC9kYzpzdWJqZWN0Pgog" + - "ICA8ZGM6ZGVzY3JpcHRpb24+CiAgICA8cmRmOkFsdD4KICAgICA8cmRmOmxpIHht" + - "bDpsYW5nPSJ4LWRlZmF1bHQiPmNhcHRpb248L3JkZjpsaT4KICAgIDwvcmRmOkFs" + - "dD4KICAgPC9kYzpkZXNjcmlwdGlvbj4KICAgPGRjOnRpdGxlPgogICAgPHJkZjpB" + - "bHQ+CiAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij50aXRsZTwvcmRm" + - "OmxpPgogICAgPC9yZGY6QWx0PgogICA8L2RjOnRpdGxlPgogICA8SXB0YzR4bXBF" + - "eHQ6TG9jYXRpb25DcmVhdGVkPgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQog" + - "ICAgICBJcHRjNHhtcEV4dDpTdWJsb2NhdGlvbj0iIgogICAgICBJcHRjNHhtcEV4" + - "dDpDaXR5PSJEaWVwZW52ZWVuIgogICAgICBJcHRjNHhtcEV4dDpQcm92aW5jZVN0" + - "YXRlPSJPdmVyaWpzc2VsIgogICAgICBJcHRjNHhtcEV4dDpDb3VudHJ5TmFtZT0i" + - "TmVkZXJsYW5kIgogICAgICBJcHRjNHhtcEV4dDpDb3VudHJ5Q29kZT0iIgogICAg" + - "ICBJcHRjNHhtcEV4dDpXb3JsZFJlZ2lvbj0iIi8+CiAgICA8L3JkZjpCYWc+CiAg" + - "IDwvSXB0YzR4bXBFeHQ6TG9jYXRpb25DcmVhdGVkPgogICA8SXB0YzR4bXBFeHQ6" + - "TG9jYXRpb25TaG93bj4KICAgIDxyZGY6QmFnPgogICAgIDxyZGY6bGkKICAgICAg" + - "SXB0YzR4bXBFeHQ6U3VibG9jYXRpb249IiIKICAgICAgSXB0YzR4bXBFeHQ6Q2l0" + - "eT0iRGllcGVudmVlbiIKICAgICAgSXB0YzR4bXBFeHQ6UHJvdmluY2VTdGF0ZT0i" + - "T3Zlcmlqc3NlbCIKICAgICAgSXB0YzR4bXBFeHQ6Q291bnRyeU5hbWU9Ik5lZGVy" + - "bGFuZCIKICAgICAgSXB0YzR4bXBFeHQ6Q291bnRyeUNvZGU9IiIKICAgICAgSXB0" + - "YzR4bXBFeHQ6V29ybGRSZWdpb249IiIvPgogICAgPC9yZGY6QmFnPgogICA8L0lw" + - "dGM0eG1wRXh0OkxvY2F0aW9uU2hvd24+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8" + - "L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7/" + - "7QDiUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAKkcAVoAAxslRxwCAAACAAMcAjcA" + - "CDIwMTgwNDIyHAI8AAsxNjE0NTQrMDEwMBwCWgAKRGllcGVudmVlbhwCXwAKT3Zl" + - "cmlqc3NlbBwCZQAJTmVkZXJsYW5kHAIFAAV0aXRsZRwCGQAEdGVzdBwCGQAEc2lv" + - "bhwCeAAHY2FwdGlvbhwC3QAMMDowOjA6LTAwMDAxHAI+AAgyMDE4MDQyMhwCPwAG" + - "MTYxNDU0ADhCSU0EJQAAAAAAEOXrWd9hKhRBbyqe22Ymbij/2wCEAAEBAQEBAQIC" + - "AgICAgICAgQDAgIDBAUEAwMDBAUHBQQDAwQFBwcGBQQFBgcIBgUFBggIBwcHCAkI" + - "CAkKCgoMDA4BAgICAgICAwICAwYDAgMGDAYEBAYMDwwHBQcMDw8PDQkJDQ8PDw8P" + - "Dg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD//CABEIAAIAAwMBIQACEQEDEQH/xAAU" + - "AAEAAAAAAAAAAAAAAAAAAAAJ/9oACAEBAAAAAEV//8QAFAEBAAAAAAAAAAAAAAAA" + - "AAAABf/aAAgBAhAAAAAH/8QAFAEBAAAAAAAAAAAAAAAAAAAAA//aAAgBAxAAAAAf" + - "/8QAIBAAAQMCBwAAAAAAAAAAAAAAAQIGIQQRAAMFEiMxUf/aAAgBAQABPwBhNVrp" + - "aFBbTaAXydx4USpRJJMdkyT7j//EABsRAAIBBQAAAAAAAAAAAAAAAAECBQADBBFy" + - "/9oACAECAQE/AFjo8qCcW3vkV//EABkRAQACAwAAAAAAAAAAAAAAAAEAAhESIf/a" + - "AAgBAwEBPwDaxwXE/9k="; + /// + /// Full path of the image + /// + public readonly string FullFilePath = + ( Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + + Path.DirectorySeparatorChar ).Replace("./", string.Empty) + FileNamePrivate; - public static readonly ImmutableArray Bytes = Base64Helper.TryParse(Base64JpgString).ToImmutableArray(); + /// + /// Use abstractions instead of a System.IO dependency + /// + public CreateAnImage() + { + if ( File.Exists(FullFilePath) ) + { + return; + } - /// - /// Use abstractions instead of a System.IO dependency - /// - public CreateAnImage() + try { - if ( File.Exists(FullFilePath) ) return; - - try - { - File.WriteAllBytes(FullFilePath, Convert.FromBase64String(Base64JpgString)); - } - catch ( IOException exceptionMessage ) - { - // to avoid: The process cannot access the file - // 'D:\a\..\0000000000aaaaa__exifreadingtest00.jpg' - // because it is being used by another process. - Console.WriteLine(exceptionMessage); - } + File.WriteAllBytes(FullFilePath, Convert.FromBase64String(Base64JpgString)); + } + catch ( IOException exceptionMessage ) + { + // to avoid: The process cannot access the file + // 'D:\a\..\0000000000aaaaa__exifreadingtest00.jpg' + // because it is being used by another process. + Console.WriteLine(exceptionMessage); } } + + /// + /// The filename *.jpg + /// There is an unit test for using directory thumbnails that uses the first image; + /// starskytest.SyncServiceTest.SyncServiceFirstItemDirectoryTest + /// + [SuppressMessage("Performance", "CA1822:Mark members as static")] + [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] + public string FileName => FileNamePrivate; } diff --git a/starsky/starskytest/FakeCreateAn/CreateAnImageCorrupt/CreateAnImageCorrupt.cs b/starsky/starskytest/FakeCreateAn/CreateAnImageCorrupt/CreateAnImageCorrupt.cs index 7073b76444..126d958705 100644 --- a/starsky/starskytest/FakeCreateAn/CreateAnImageCorrupt/CreateAnImageCorrupt.cs +++ b/starsky/starskytest/FakeCreateAn/CreateAnImageCorrupt/CreateAnImageCorrupt.cs @@ -5,31 +5,32 @@ using starsky.foundation.storage.Storage; using starskytest.FakeMocks; -namespace starskytest.FakeCreateAn.CreateAnImageCorrupt +namespace starskytest.FakeCreateAn.CreateAnImageCorrupt; + +public class CreateAnImageCorrupt { - public class CreateAnImageCorrupt + public readonly ImmutableArray Bytes = Array.Empty().ToImmutableArray(); + + public CreateAnImageCorrupt() { - public CreateAnImageCorrupt() + var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if ( string.IsNullOrEmpty(dirName) ) { - var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if ( string.IsNullOrEmpty(dirName) ) return; - var path = Path.Combine(dirName, "FakeCreateAn", - "CreateAnImageCorrupt", "corrupt.jpg"); - - Bytes = StreamToBytes(path).ToImmutableArray(); + return; } - private static byte[] StreamToBytes(string path) - { - var input = new StorageHostFullPathFilesystem(new FakeIWebLogger()).ReadStream(path); - using var ms = new MemoryStream(); - input.CopyTo(ms); - input.Dispose(); - return ms.ToArray(); - } + var path = Path.Combine(dirName, "FakeCreateAn", + "CreateAnImageCorrupt", "corrupt.jpg"); - public readonly ImmutableArray Bytes = Array.Empty().ToImmutableArray(); + Bytes = StreamToBytes(path).ToImmutableArray(); + } + private static byte[] StreamToBytes(string path) + { + var input = new StorageHostFullPathFilesystem(new FakeIWebLogger()).ReadStream(path); + using var ms = new MemoryStream(); + input.CopyTo(ms); + input.Dispose(); + return ms.ToArray(); } } - diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs index fe541fcf62..fa48937da4 100644 --- a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs @@ -9,7 +9,11 @@ public class CreateFakeStarskyWindowsExe public CreateFakeStarskyWindowsExe() { var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if ( string.IsNullOrEmpty(dirName) ) return; + if ( string.IsNullOrEmpty(dirName) ) + { + return; + } + var parentFolder = Path.Combine(dirName, "FakeCreateAn", "CreateFakeStarskyExe"); var path = Path.Combine(parentFolder, "starsky.exe"); diff --git a/starsky/starskytest/FakeMocks/AppSettingsReflection.cs b/starsky/starskytest/FakeMocks/AppSettingsReflection.cs index 4911ad33bd..6697a54c23 100644 --- a/starsky/starskytest/FakeMocks/AppSettingsReflection.cs +++ b/starsky/starskytest/FakeMocks/AppSettingsReflection.cs @@ -1,20 +1,30 @@ using System.Reflection; using starsky.foundation.platform.Models; -namespace starskytest.FakeMocks +namespace starskytest.FakeMocks; + +public static class AppSettingsReflection { - public static class AppSettingsReflection + public static void Modify(AppSettings inputObject, string methodGetName = "get_DatabaseType", + object? value = null) { - public static void Modify(AppSettings inputObject, string methodGetName = "get_DatabaseType", object? value = null) + var type = typeof(AppSettings); + foreach ( var property in type.GetProperties(BindingFlags.Public + | BindingFlags.Instance | + BindingFlags.DeclaredOnly) ) { - var type = typeof(AppSettings); - foreach ( var property in type.GetProperties(BindingFlags.Public - | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { - var getMethod = property.GetGetMethod(false); - if ( getMethod?.GetBaseDefinition() != getMethod ) continue; - if ( methodGetName != getMethod?.Name ) continue; - property.SetValue(inputObject, value, null); + var getMethod = property.GetGetMethod(false); + if ( getMethod?.GetBaseDefinition() != getMethod ) + { + continue; + } + + if ( methodGetName != getMethod?.Name ) + { + continue; } + + property.SetValue(inputObject, value, null); } } } diff --git a/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs b/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs index 3ef833c73c..9ff231bf60 100644 --- a/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs +++ b/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs @@ -128,7 +128,9 @@ public int GetCurrentUserId(HttpContext httpContext) public User? GetUser(string credentialTypeCode, string identifier) { if ( CurrentUser != null && !Users.Contains(CurrentUser!) ) + { Users.Add(CurrentUser!); + } return Users.Find(p => p.Credentials?.Any(credential => credential.Identifier == identifier) == true); @@ -147,7 +149,11 @@ public Task RemoveUser(string credentialTypeCode, public User? Exist(string identifier) { - if ( Credentials.Identifier == identifier ) return CurrentUser; + if ( Credentials.Identifier == identifier ) + { + return CurrentUser; + } + return null; } @@ -171,13 +177,8 @@ public bool PreflightValidate(string userName, string password, string confirmPa return password != "false"; } - public CredentialType? GetCachedCredentialType(string email) + public CredentialType GetCachedCredentialType(string? credentialTypeCode) { return new CredentialType { Code = "email" }; } - - public void AddUserToCache(User user) - { - throw new NotImplementedException(); - } } diff --git a/starsky/starskytest/FakeMocks/FakeWebSocket.cs b/starsky/starskytest/FakeMocks/FakeWebSocket.cs index 4a1abbeea9..090c6fe866 100644 --- a/starsky/starskytest/FakeMocks/FakeWebSocket.cs +++ b/starsky/starskytest/FakeMocks/FakeWebSocket.cs @@ -5,72 +5,78 @@ using System.Threading; using System.Threading.Tasks; -namespace starskytest.FakeMocks +namespace starskytest.FakeMocks; + +public class FakeWebSocket : WebSocket { - public class FakeWebSocket : WebSocket - { - public List FakeSendItems { get; set; } = new List(); - - public override void Abort() - { - throw new NotImplementedException(); - } + public List FakeSendItems { get; set; } = new(); - public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, - CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public List FakeCloseOutputAsync { get; set; } = new(); - public List FakeCloseOutputAsync { get; set; } = new List(); - -#pragma warning disable 1998 - public async override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, -#pragma warning restore 1998 - CancellationToken cancellationToken) - { - FakeCloseOutputAsync.Add(closeStatus); - } + private int FakeReceiveAsyncCounter { get; set; } - public override void Dispose() - { - GC.SuppressFinalize(this); - } + public string ReceiveAsyncMessage { get; set; } = "message"; + + public WebSocketError ReceiveAsyncErrorType { get; set; } = WebSocketError.InvalidState; - private int FakeReceiveAsyncCounter { get; set; } + public override WebSocketCloseStatus? CloseStatus { get; } + public override string? CloseStatusDescription { get; } - public string ReceiveAsyncMessage { get; set; } = "message"; + public override WebSocketState State { get; } = WebSocketState.None; + public override string? SubProtocol { get; } - public WebSocketError ReceiveAsyncErrorType { get; set; } = WebSocketError.InvalidState; + public override void Abort() + { + throw new NotImplementedException(); + } + + public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, + CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } #pragma warning disable 1998 - public override async Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) + public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, + string? statusDescription, #pragma warning restore 1998 - { - FakeReceiveAsyncCounter++; - if ( FakeReceiveAsyncCounter <= 2 ) - { - new ArraySegment(Encoding.ASCII.GetBytes(ReceiveAsyncMessage)).CopyTo(buffer); - return new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Text, true, - WebSocketCloseStatus.Empty, ""); - } - if ( FakeReceiveAsyncCounter == 3 ) return new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Close, true); - - throw new WebSocketException(ReceiveAsyncErrorType); - } + CancellationToken cancellationToken) + { + FakeCloseOutputAsync.Add(closeStatus); + } + + public override void Dispose() + { + GC.SuppressFinalize(this); + } #pragma warning disable 1998 - public override async Task SendAsync(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, + public override async Task ReceiveAsync(ArraySegment buffer, + CancellationToken cancellationToken) #pragma warning restore 1998 - CancellationToken cancellationToken) + { + FakeReceiveAsyncCounter++; + if ( FakeReceiveAsyncCounter <= 2 ) + { + new ArraySegment(Encoding.ASCII.GetBytes(ReceiveAsyncMessage)).CopyTo(buffer); + return new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Text, true, + WebSocketCloseStatus.Empty, ""); + } + + if ( FakeReceiveAsyncCounter == 3 ) { - FakeSendItems.Add(System.Text.Encoding.Default.GetString(buffer)); + return new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Close, true); } - public override WebSocketCloseStatus? CloseStatus { get; } - public override string? CloseStatusDescription { get; } + throw new WebSocketException(ReceiveAsyncErrorType); + } - public override WebSocketState State { get; } = WebSocketState.None; - public override string? SubProtocol { get; } +#pragma warning disable 1998 + public override async Task SendAsync(ArraySegment buffer, + WebSocketMessageType messageType, bool endOfMessage, +#pragma warning restore 1998 + CancellationToken cancellationToken) + { + FakeSendItems.Add(Encoding.Default.GetString(buffer)); } } diff --git a/starsky/starskytest/Helpers/AntiForgeryCookieTest.cs b/starsky/starskytest/Helpers/AntiForgeryCookieTest.cs index 3054d7e914..124a2bbeb2 100644 --- a/starsky/starskytest/Helpers/AntiForgeryCookieTest.cs +++ b/starsky/starskytest/Helpers/AntiForgeryCookieTest.cs @@ -3,36 +3,39 @@ using starsky.Helpers; using starskytest.FakeMocks; -namespace starskytest.Helpers +namespace starskytest.Helpers; + +[TestClass] +public sealed class AntiForgeryCookieTest { - [TestClass] - public sealed class AntiForgeryCookieTest + private static string? GetCookieValueFromResponse(HttpResponse response, string cookieName) { - private static string? GetCookieValueFromResponse(HttpResponse response, string cookieName) + foreach ( var headers in response.Headers ) { - foreach (var headers in response.Headers) + if ( headers.Key != "Set-Cookie" ) { - if (headers.Key != "Set-Cookie") - continue; - string? header = headers.Value; - if (header?.StartsWith($"{cookieName}=") == true) - { - var p1 = header.IndexOf('='); - var p2 = header.IndexOf(';'); - return header.Substring(p1 + 1, p2 - p1 - 1); - } + continue; } - return null; - } - - [TestMethod] - public void AntiForgeryCookie_SetRequestToken() - { - var httpContext = new DefaultHttpContext(); - new AntiForgeryCookie(new FakeAntiforgery()).SetAntiForgeryCookie(httpContext); - var requestToken = GetCookieValueFromResponse(httpContext.Response, "X-XSRF-TOKEN"); - Assert.AreEqual("requestToken",requestToken); + string? header = headers.Value; + if ( header?.StartsWith($"{cookieName}=") == true ) + { + var p1 = header.IndexOf('='); + var p2 = header.IndexOf(';'); + return header.Substring(p1 + 1, p2 - p1 - 1); + } } + + return null; + } + + + [TestMethod] + public void AntiForgeryCookie_SetRequestToken() + { + var httpContext = new DefaultHttpContext(); + new AntiForgeryCookie(new FakeAntiforgery()).SetAntiForgeryCookie(httpContext); + var requestToken = GetCookieValueFromResponse(httpContext.Response, "X-XSRF-TOKEN"); + Assert.AreEqual("requestToken", requestToken); } } diff --git a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs index 6826f64931..fec7059ab6 100644 --- a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs +++ b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -153,12 +154,12 @@ public async Task QueryAddSingleItemHiJpgOutputTest() Assert.AreEqual(_insertSearchDatahiJpgInput.FileHash, hiJpgOutput?.FileHash); // other api Get Object By FilePath - hiJpgOutput = _query.GetObjectByFilePath(_insertSearchDatahiJpgInput.FilePath!); + hiJpgOutput = await _query.GetObjectByFilePathAsync(_insertSearchDatahiJpgInput.FilePath!); Assert.AreEqual(_insertSearchDatahiJpgInput.FilePath, hiJpgOutput?.FilePath); } /// - /// Item exist but not in folder cache, it now add this item to cache #228 + /// Item exist but not in folder cache, it now adds this item to cache #228 /// [TestMethod] public async Task SingleItem_ItemExistInDbButNotInFolderCache() @@ -166,7 +167,7 @@ public async Task SingleItem_ItemExistInDbButNotInFolderCache() await _query.AddItemAsync(new FileIndexItem("/cache_test") { IsDirectory = true }); var existingItem = new FileIndexItem("/cache_test/test.jpg"); await _query.AddItemAsync(existingItem); - _query.AddCacheParentItem("/cache_test", new List { existingItem }); + _query.AddCacheParentItem("/cache_test", [existingItem]); const string newItem = "/cache_test/test2.jpg"; await _query.AddItemAsync(new FileIndexItem(newItem)); @@ -195,7 +196,7 @@ public async Task GetAllFilesAsync_Disposed() // And dispose await dbContext.DisposeAsync(); - var items = await query.GetAllFilesAsync(new List { "/" }, 0); + var items = await query.GetAllFilesAsync(["/"], 0); Assert.AreEqual("/test.jpg", items[0].FilePath); Assert.AreEqual(FileIndexItem.ExifStatus.Ok, items[0].Status); @@ -699,7 +700,7 @@ public async Task Query_UpdateItem_1_DisposedItem() item.Tags = "test"; await query.UpdateItemAsync(item); - var getItem = query.GetObjectByFilePath("/test/010101.jpg"); + var getItem = await _query.GetObjectByFilePathAsync("/test/010101.jpg"); Assert.IsNotNull(getItem); Assert.AreEqual("test", getItem.Tags); @@ -707,6 +708,7 @@ public async Task Query_UpdateItem_1_DisposedItem() } [TestMethod] + [SuppressMessage("Usage", "S6966: GetObjectByFilePath")] public async Task Query_GetObjectByFilePath_home() { var serviceScope = CreateNewScope(); @@ -810,7 +812,7 @@ public async Task Query_UpdateItem_Multiple_DisposedItem() item.Tags = "test"; await query.UpdateItemAsync(new List { item }); - var getItem = query.GetObjectByFilePath("/test/010101.jpg"); + var getItem = await query.GetObjectByFilePathAsync("/test/010101.jpg"); Assert.IsNotNull(getItem); Assert.AreEqual("test", getItem.Tags); @@ -1090,7 +1092,7 @@ await _query.AddItemAsync(new FileIndexItem .FileIndexItem; Assert.AreEqual("#", cachingDeleted001Update!.Tags); Assert.AreNotEqual(string.Empty, cachingDeleted001Update.Tags); - // AreNotEqual: When it item used cache it will return string.empty + // AreNotEqual: When its item used cache it will return string.empty } [TestMethod] @@ -1111,7 +1113,7 @@ public void QueryFolder_Add_And_UpdateFolderCache_UpdateCacheItemTest() _query.CacheUpdateItem(new List { item1 }); _memoryCache.TryGetValue(name, out var objectFileFolders); - var displayFileFolders = ( List? )objectFileFolders; + var displayFileFolders = ( List? ) objectFileFolders; Assert.IsNotNull(displayFileFolders); Assert.AreEqual("hi", displayFileFolders.Find(p => p.FileName == "cache")?.Tags); @@ -1285,19 +1287,22 @@ public async Task Query_updateStatusContentList() }; await _query.AddItemAsync(toUpdate.FirstOrDefault()!); - foreach ( var item in toUpdate ) item.Tags = "updated"; + foreach ( var item in toUpdate ) + { + item.Tags = "updated"; + } await _query.UpdateItemAsync(toUpdate); var fileObjectByFilePath = - _query.GetObjectByFilePath("/3456784567890987654/3456784567890987654.jpg"); + await _query.GetObjectByFilePathAsync("/3456784567890987654/3456784567890987654.jpg"); Assert.AreEqual("updated", fileObjectByFilePath?.Tags); } [TestMethod] public async Task Query_updateStatusContentList_Async() { - // for updateing multiple items + // for update-ing multiple items var toUpdate = new List { new() @@ -1310,7 +1315,10 @@ public async Task Query_updateStatusContentList_Async() }; await _query.AddItemAsync(toUpdate.FirstOrDefault()!); - foreach ( var item in toUpdate ) item.Tags = "updated"; + foreach ( var item in toUpdate ) + { + item.Tags = "updated"; + } await _query.UpdateItemAsync(toUpdate); diff --git a/starsky/starskytest/starsky.foundation.storage/ArchiveFormats/TarBalTest.cs b/starsky/starskytest/starsky.foundation.storage/ArchiveFormats/TarBalTest.cs index aa81f7dc11..98e7c4c4e8 100644 --- a/starsky/starskytest/starsky.foundation.storage/ArchiveFormats/TarBalTest.cs +++ b/starsky/starskytest/starsky.foundation.storage/ArchiveFormats/TarBalTest.cs @@ -10,49 +10,53 @@ using starskytest.FakeCreateAn.CreateAnTagGzLongerThan100CharsFileName; using starskytest.FakeMocks; -namespace starskytest.starsky.foundation.storage.ArchiveFormats +namespace starskytest.starsky.foundation.storage.ArchiveFormats; + +[TestClass] +public sealed class TarBalTest { - [TestClass] - public sealed class TarBalTest + [TestMethod] + public async Task ExtractTar() + { + // Non Gz Tar + var storage = new FakeIStorage(new List { "/" }, + new List()); + + var memoryStream = new MemoryStream(CreateAnExifToolTar.Bytes.ToArray()); + await new TarBal(storage, new FakeIWebLogger()).ExtractTar(memoryStream, "/test", + CancellationToken.None); + Assert.IsTrue(storage.ExistFile("/test/Image-ExifTool-11.99/exiftool")); + } + + [TestMethod] + public async Task ExtractTarGz() { - [TestMethod] - public async Task ExtractTar() - { - // Non Gz Tar - var storage = new FakeIStorage(new List {"/"}, - new List()); - - var memoryStream = new MemoryStream(CreateAnExifToolTar.Bytes.ToArray()); - await new TarBal(storage).ExtractTar(memoryStream,"/test", CancellationToken.None); - Assert.IsTrue(storage.ExistFile("/test/Image-ExifTool-11.99/exiftool")); - } - - [TestMethod] - public async Task ExtractTarGz() - { - // Gz Tar! - var storage = new FakeIStorage(new List {"/"}, - new List()); - - var memoryStream = new MemoryStream(CreateAnExifToolTarGz.Bytes.ToArray()); - await new TarBal(storage).ExtractTarGz(memoryStream,"/test", CancellationToken.None); - Assert.IsTrue(storage.ExistFile("/test/Image-ExifTool-11.99/exiftool")); - } - - - [TestMethod] - public async Task ExtractTarGz_LongerThan100Chars() - { - // Gz Tar! - var storage = new FakeIStorage(new List {"/"}, - new List()); - - var memoryStream = new MemoryStream(new CreateAnTagGzLongerThan100CharsFileName().Bytes); - await new TarBal(storage).ExtractTarGz(memoryStream,"/test", CancellationToken.None); - Assert.IsTrue(storage.ExistFile($"/test/{CreateAnTagGzLongerThan100CharsFileName.FileName}")); - var file = storage.ReadStream($"/test/{CreateAnTagGzLongerThan100CharsFileName.FileName}"); - // the filename is written as content in the file - Assert.AreEqual(CreateAnTagGzLongerThan100CharsFileName.FileName,(await StreamToStringHelper.StreamToStringAsync(file)).Trim()); - } + // Gz Tar! + var storage = new FakeIStorage(new List { "/" }, + new List()); + + var memoryStream = new MemoryStream(CreateAnExifToolTarGz.Bytes.ToArray()); + await new TarBal(storage, new FakeIWebLogger()).ExtractTarGz(memoryStream, "/test", + CancellationToken.None); + Assert.IsTrue(storage.ExistFile("/test/Image-ExifTool-11.99/exiftool")); + } + + + [TestMethod] + public async Task ExtractTarGz_LongerThan100Chars() + { + // Gz Tar! + var storage = new FakeIStorage(new List { "/" }, + new List()); + + var memoryStream = new MemoryStream(new CreateAnTagGzLongerThan100CharsFileName().Bytes); + await new TarBal(storage, new FakeIWebLogger()).ExtractTarGz(memoryStream, "/test", + CancellationToken.None); + Assert.IsTrue( + storage.ExistFile($"/test/{CreateAnTagGzLongerThan100CharsFileName.FileName}")); + var file = storage.ReadStream($"/test/{CreateAnTagGzLongerThan100CharsFileName.FileName}"); + // the filename is written as content in the file + Assert.AreEqual(CreateAnTagGzLongerThan100CharsFileName.FileName, + ( await StreamToStringHelper.StreamToStringAsync(file) ).Trim()); } } diff --git a/starsky/starskytest/starsky.foundation.writemeta/Services/ExifToolHostStorageServiceTest.cs b/starsky/starskytest/starsky.foundation.writemeta/Services/ExifToolHostStorageServiceTest.cs index 1c735b0736..a382280b88 100644 --- a/starsky/starskytest/starsky.foundation.writemeta/Services/ExifToolHostStorageServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.writemeta/Services/ExifToolHostStorageServiceTest.cs @@ -77,7 +77,7 @@ public async Task WriteTagsAndRenameThumbnailAsync_FakeExifToolBashTest_UnixOnly hostFileSystemStorage.FolderDelete(outputPath); } - await new TarBal(hostFileSystemStorage).ExtractTarGz(memoryStream, outputPath, + await new TarBal(hostFileSystemStorage, new FakeIWebLogger()).ExtractTarGz(memoryStream, outputPath, CancellationToken.None); var imageExifToolVersionFolder = hostFileSystemStorage.GetDirectories(outputPath) .FirstOrDefault(p => p.StartsWith(Path.Combine(outputPath, "Image-ExifTool-")))?