diff --git a/starsky/starsky.foundation.database/Interfaces/IQuery.cs b/starsky/starsky.foundation.database/Interfaces/IQuery.cs index b7580d8c0..d20d2d875 100644 --- a/starsky/starsky.foundation.database/Interfaces/IQuery.cs +++ b/starsky/starsky.foundation.database/Interfaces/IQuery.cs @@ -11,8 +11,8 @@ namespace starsky.foundation.database.Interfaces; public interface IQuery { /// - /// Get a list of all files inside an folder (NOT recursive) - /// But this uses a database as source + /// Get a list of all files inside an folder (NOT recursive) + /// But this uses a database as source /// /// relative database path /// @@ -20,8 +20,8 @@ public interface IQuery Task> GetAllFilesAsync(List filePaths, int timeout = 1000); /// - /// Get a list of all files inside an folder (NOT recursive) - /// But this uses a database as source + /// Get a list of all files inside a folder (NOT recursive) + /// But this uses a database as source /// /// relative database path /// list of FileIndex-objects @@ -32,7 +32,7 @@ public interface IQuery Task> GetAllRecursiveAsync(List filePathList); /// - /// to do the query and return object + /// to do the query and return object /// /// subPath style /// filter the colorClass @@ -53,7 +53,7 @@ IEnumerable DisplayFileFolders( bool hideDeleted = true); /// - /// to do the query and return object + /// to do the query and return object /// /// /// @@ -69,7 +69,7 @@ IEnumerable DisplayFileFolders( SortType? sort = SortType.FileName); /// - /// To make an object without any query + /// To make an object without any query /// /// /// @@ -87,14 +87,14 @@ IEnumerable DisplayFileFolders( SortType? sort = SortType.FileName); /// - /// Get FirstOrDefault for that filePath + /// Get FirstOrDefault for that filePath /// /// subPath style /// item FileIndexItem? GetObjectByFilePath(string filePath); /// - /// Get FirstOrDefault for that filePath + /// Get FirstOrDefault for that filePath /// /// subPath style /// time of cache @@ -102,7 +102,6 @@ IEnumerable DisplayFileFolders( Task GetObjectByFilePathAsync(string filePath, TimeSpan? cacheTime = null); /// - /// /// /// /// @@ -111,7 +110,7 @@ Task> GetObjectsByFilePathAsync(string inputFilePath, bool collections); /// - /// Cached result that contain values + /// Cached result that contain values /// /// List of filePaths /// enable implicit raw files with the same base name @@ -120,7 +119,7 @@ Task> GetObjectsByFilePathAsync(List inputFilePaths, bool collections); /// - /// Query direct by filePaths (without cache) + /// Query direct by filePaths (without cache) /// /// List of filePaths /// @@ -133,7 +132,7 @@ Task> RemoveItemAsync( List updateStatusContentList); /// - /// Clear the directory name from the cache + /// Clear the directory name from the cache /// /// the path of the directory (there is no parent generation) bool RemoveCacheParentItem(string directoryName); @@ -154,6 +153,8 @@ Task> GetAllObjectsAsync(List filePaths, Task AddItemAsync(FileIndexItem fileIndexItem); + Task ExistsAsync(string filePath); + Task> AddRangeAsync(List fileIndexItemList); Task UpdateItemAsync(FileIndexItem updateStatusContent); @@ -163,7 +164,7 @@ Task> GetAllObjectsAsync(List filePaths, /// - /// Update parent item with all data from child items + /// Update parent item with all data from child items /// /// parent directory /// all items that are in this folder @@ -172,14 +173,14 @@ bool AddCacheParentItem(string directoryName, List items); /// - /// Cache API within Query to update cached items - /// For DisplayFileFolders and SingleItem + /// Cache API within Query to update cached items + /// For DisplayFileFolders and SingleItem /// /// items to update void CacheUpdateItem(List updateStatusContent); /// - /// And remove content from cache + /// And remove content from cache /// /// list of items void RemoveCacheItem(List updateStatusContent); @@ -188,14 +189,14 @@ bool AddCacheParentItem(string directoryName, /// - /// Add Sub Path Folder - Parent Folders - /// root(/) - /// /2017 *= index only this folder - /// /2018 - /// If you use the cmd: $ starskycli -s "/2017" - /// the folder '2017' it self is not added - /// and all parent paths are not included - /// this class does add those parent folders + /// Add Sub Path Folder - Parent Folders + /// root(/) + /// /2017 *= index only this folder + /// /2018 + /// If you use the cmd: $ starskycli -s "/2017" + /// the folder '2017' it self is not added + /// and all parent paths are not included + /// this class does add those parent folders /// /// subPath as input /// void diff --git a/starsky/starsky.foundation.database/Query/QueryExistsAsync.cs b/starsky/starsky.foundation.database/Query/QueryExistsAsync.cs new file mode 100644 index 000000000..3bfbd9c63 --- /dev/null +++ b/starsky/starsky.foundation.database/Query/QueryExistsAsync.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using starsky.foundation.database.Data; +using starsky.foundation.database.Interfaces; +using starsky.foundation.platform.Helpers; + +namespace starsky.foundation.database.Query; + +/// +/// QueryExistsAsync +/// +public partial class Query : IQuery +{ + public async Task ExistsAsync(string filePath) + { + if ( filePath != "/" ) + { + filePath = PathHelper.RemoveLatestSlash(filePath); + } + + async Task LocalQuery(ApplicationDbContext context) + { + return await context.FileIndex.AnyAsync(p => p.FilePath == filePath); + } + + try + { + return await LocalQuery(_context); + } + catch ( ObjectDisposedException e ) + { + _logger.LogInformation("[ExistsAsync] catch-ed ObjectDisposedException", e); + return await LocalQuery(new InjectServiceScope(_scopeFactory).Context()); + } + } +} diff --git a/starsky/starsky/Controllers/DiskController.cs b/starsky/starsky/Controllers/DiskController.cs index bfc749500..394b4ae7e 100644 --- a/starsky/starsky/Controllers/DiskController.cs +++ b/starsky/starsky/Controllers/DiskController.cs @@ -73,6 +73,9 @@ public async Task Mkdir(string f) FilePath = subPath, Status = FileIndexItem.ExifStatus.Ok }; + // if it exists make sure the item is added to the database + await AddIfExistItemAsync(subPath); + if ( _iStorage.ExistFolder(subPath) ) { toAddStatus.Status = FileIndexItem.ExifStatus.OperationNotSupported; @@ -80,11 +83,6 @@ public async Task Mkdir(string f) continue; } - await _query.AddItemAsync(new FileIndexItem(subPath) - { - IsDirectory = true, ImageFormat = ExtensionRolesHelper.ImageFormat.directory - }); - // add to fs _iStorage.CreateDirectory(subPath); @@ -100,6 +98,17 @@ await _query.AddItemAsync(new FileIndexItem(subPath) await SyncMessageToSocket(syncResultsList, ApiNotificationType.Mkdir); return Json(syncResultsList); + + async Task AddIfExistItemAsync(string subPath) + { + if ( !await _query.ExistsAsync(subPath) ) + { + await _query.AddItemAsync(new FileIndexItem(subPath) + { + IsDirectory = true, ImageFormat = ExtensionRolesHelper.ImageFormat.directory + }); + } + } } /// diff --git a/starsky/starskytest/FakeMocks/FakeIQuery.cs b/starsky/starskytest/FakeMocks/FakeIQuery.cs index 11ed83aa2..caa006a99 100644 --- a/starsky/starskytest/FakeMocks/FakeIQuery.cs +++ b/starsky/starskytest/FakeMocks/FakeIQuery.cs @@ -252,11 +252,6 @@ public void ResetItemByHash(string fileHash) throw new NotImplementedException(); } - public List GetAllFolders() - { - return _content.Where(p => p.IsDirectory == true).ToList(); - } - public Task> GetFoldersAsync(string subPath) { return Task.FromResult(_content @@ -294,6 +289,11 @@ public async Task AddItemAsync(FileIndexItem fileIndexItem) return fileIndexItem; } + public Task ExistsAsync(string filePath) + { + throw new NotImplementedException(); + } + public async Task> AddRangeAsync(List fileIndexItemList) { foreach ( var fileIndexItem in fileIndexItemList ) @@ -407,6 +407,11 @@ public Task CountAsync(Expression>? expression = return Task.FromResult(expression == null ? _content.Count : _content.Count(func!)); } + public List GetAllFolders() + { + return _content.Where(p => p.IsDirectory == true).ToList(); + } + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract")] public List GetAllFiles(string subPath) { diff --git a/starsky/starskytest/FakeMocks/FakeIQueryException.cs b/starsky/starskytest/FakeMocks/FakeIQueryException.cs index 4f59e177c..fafbe3ca5 100644 --- a/starsky/starskytest/FakeMocks/FakeIQueryException.cs +++ b/starsky/starskytest/FakeMocks/FakeIQueryException.cs @@ -7,260 +7,254 @@ using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; -namespace starskytest.FakeMocks +namespace starskytest.FakeMocks; + +public class FakeIQueryException : IQuery { - public class FakeIQueryException : IQuery - { - private readonly Exception _exception; - - public FakeIQueryException(Exception exception) - { - _exception = exception; - } - public List GetAllFiles(string subPath) - { - throw _exception; - } - - public Task> GetAllFilesAsync(List filePaths, int timeout = 1000) - { - throw _exception; - } - - public Task> GetAllFilesAsync(string subPath) - { - throw _exception; - } - - public List GetAllRecursive(string subPath = "/") - { - throw _exception; - } - - public Task> GetAllRecursiveAsync(string subPath = "/") - { - throw _exception; - } - - public Task> GetAllRecursiveAsync(List filePathList) - { - throw _exception; - } - - public IEnumerable DisplayFileFolders(string subPath = "/", - List? colorClassActiveList = null, bool enableCollections = true, - bool hideDeleted = true) - { - throw _exception; - } - - public IEnumerable DisplayFileFolders(List fileIndexItems, - List? colorClassActiveList = null, bool enableCollections = true, - bool hideDeleted = true) - { - throw _exception; - } - - public DetailView SingleItem(string singleItemDbPath, List? colorClassActiveList = null, - bool enableCollections = true, bool hideDeleted = true, - SortType? sort = SortType.FileName) - { - throw _exception; - } - - public DetailView SingleItem(List fileIndexItemsList, string singleItemDbPath, - List? colorClassActiveList = null, bool enableCollections = true, - bool hideDeleted = true, SortType? sort = SortType.FileName) - { - throw _exception; - } - - public FileIndexItem GetObjectByFilePath(string filePath) - { - throw _exception; - } - - public Task GetObjectByFilePathAsync(string filePath, TimeSpan? cacheTime = null) - { - throw _exception; - } - - public Task> GetObjectsByFilePathAsync(string inputFilePath, bool collections) - { - throw _exception; - } - - public Task> GetObjectsByFilePathAsync(List inputFilePaths, bool collections) - { - throw _exception; - } - - public Task> GetObjectsByFilePathQueryAsync(List filePathList) - { - throw _exception; - } - - public FileIndexItem RemoveItem(FileIndexItem updateStatusContent) - { - throw _exception; - } - - public Task RemoveItemAsync(FileIndexItem updateStatusContent) - { - throw _exception; - } - - public Task> RemoveItemAsync(List updateStatusContentList) - { - throw _exception; - } - - public bool RemoveCacheParentItem(string directoryName) - { - throw _exception; - } - - public string? GetSubPathByHash(string fileHash) - { - throw _exception; - } - - public Task GetSubPathByHashAsync(string fileHash) - { - return Task.FromResult(GetSubPathByHash(fileHash)); - } - - public Task> GetObjectsByFileHashAsync(List fileHashesList, int retryCount = 2) - { - throw _exception; - } - - public void ResetItemByHash(string fileHash) - { - throw _exception; - } - - public List GetAllFolders() - { - throw _exception; - } - - public Task> GetFoldersAsync(string subPath) - { - throw _exception; - } - - public Task> GetAllObjectsAsync(string subPath) - { - throw _exception; - } - - public Task> GetAllObjectsAsync( - List filePaths, int fallbackDelay = 5000) - { - throw _exception; - } - - public FileIndexItem AddItem(FileIndexItem updateStatusContent) - { - throw _exception; - } - - public Task AddItemAsync(FileIndexItem fileIndexItem) - { - throw _exception; - } - - public Task> AddRangeAsync(List fileIndexItemList) - { - throw _exception; - } - - public FileIndexItem UpdateItem(FileIndexItem updateStatusContent) - { - throw _exception; - } - - public List UpdateItem(List updateStatusContentList) - { - throw _exception; - } - - public Task UpdateItemAsync(FileIndexItem updateStatusContent) - { - throw _exception; - } - - public Task> UpdateItemAsync(List updateStatusContentList) - { - throw _exception; - } - - public string SubPathSlashRemove(string subPath = "/") - { - throw _exception; - } - - public RelativeObjects GetNextPrevInFolder(string currentFolder) - { - throw _exception; - } - - public bool AddCacheParentItem(string directoryName, List items) - { - throw _exception; - } - - public void CacheUpdateItem(List updateStatusContent) - { - throw _exception; - } - - public void RemoveCacheItem(List updateStatusContent) - { - throw _exception; - } - - public void RemoveCacheItem(FileIndexItem updateStatusContent) - { - throw _exception; - } - - public Tuple> CacheGetParentFolder(string subPath) - { - throw _exception; - } - - public Task> AddParentItemsAsync(string subPath) - { - throw _exception; - } - - public IQuery Clone(ApplicationDbContext applicationDbContext) - { - throw _exception; - } - - public void Invoke(ApplicationDbContext applicationDbContext) - { - throw _exception; - } - - public void SetGetObjectByFilePathCache(string filePath, FileIndexItem result, - TimeSpan? cacheTime) - { - throw _exception; - } - - public Task DisposeAsync() - { - return Task.CompletedTask; - } - - public Task CountAsync(Expression>? expression = null) - { - throw new NotImplementedException(); - } - } - + private readonly Exception _exception; + + public FakeIQueryException(Exception exception) + { + _exception = exception; + } + + public Task> GetAllFilesAsync(List filePaths, int timeout = 1000) + { + throw _exception; + } + + public Task> GetAllFilesAsync(string subPath) + { + throw _exception; + } + + public Task> GetAllRecursiveAsync(string subPath = "/") + { + throw _exception; + } + + public Task> GetAllRecursiveAsync(List filePathList) + { + throw _exception; + } + + public IEnumerable DisplayFileFolders(string subPath = "/", + List? colorClassActiveList = null, bool enableCollections = true, + bool hideDeleted = true) + { + throw _exception; + } + + public IEnumerable DisplayFileFolders(List fileIndexItems, + List? colorClassActiveList = null, bool enableCollections = true, + bool hideDeleted = true) + { + throw _exception; + } + + public DetailView SingleItem(string singleItemDbPath, + List? colorClassActiveList = null, + bool enableCollections = true, bool hideDeleted = true, + SortType? sort = SortType.FileName) + { + throw _exception; + } + + public DetailView SingleItem(List fileIndexItemsList, string singleItemDbPath, + List? colorClassActiveList = null, bool enableCollections = true, + bool hideDeleted = true, SortType? sort = SortType.FileName) + { + throw _exception; + } + + public FileIndexItem GetObjectByFilePath(string filePath) + { + throw _exception; + } + + public Task GetObjectByFilePathAsync(string filePath, + TimeSpan? cacheTime = null) + { + throw _exception; + } + + public Task> GetObjectsByFilePathAsync(string inputFilePath, + bool collections) + { + throw _exception; + } + + public Task> GetObjectsByFilePathAsync(List inputFilePaths, + bool collections) + { + throw _exception; + } + + public Task> GetObjectsByFilePathQueryAsync(List filePathList) + { + throw _exception; + } + + public Task RemoveItemAsync(FileIndexItem updateStatusContent) + { + throw _exception; + } + + public Task> RemoveItemAsync(List updateStatusContentList) + { + throw _exception; + } + + public bool RemoveCacheParentItem(string directoryName) + { + throw _exception; + } + + public Task GetSubPathByHashAsync(string fileHash) + { + return Task.FromResult(GetSubPathByHash(fileHash)); + } + + public Task> GetObjectsByFileHashAsync(List fileHashesList, + int retryCount = 2) + { + throw _exception; + } + + public void ResetItemByHash(string fileHash) + { + throw _exception; + } + + public Task> GetFoldersAsync(string subPath) + { + throw _exception; + } + + public Task> GetAllObjectsAsync(string subPath) + { + throw _exception; + } + + public Task> GetAllObjectsAsync( + List filePaths, int fallbackDelay = 5000) + { + throw _exception; + } + + public Task AddItemAsync(FileIndexItem fileIndexItem) + { + throw _exception; + } + + public Task ExistsAsync(string filePath) + { + throw new NotImplementedException(); + } + + public Task> AddRangeAsync(List fileIndexItemList) + { + throw _exception; + } + + public Task UpdateItemAsync(FileIndexItem updateStatusContent) + { + throw _exception; + } + + public Task> UpdateItemAsync(List updateStatusContentList) + { + throw _exception; + } + + public RelativeObjects GetNextPrevInFolder(string currentFolder) + { + throw _exception; + } + + public bool AddCacheParentItem(string directoryName, List items) + { + throw _exception; + } + + public void CacheUpdateItem(List updateStatusContent) + { + throw _exception; + } + + public void RemoveCacheItem(List updateStatusContent) + { + throw _exception; + } + + public Tuple> CacheGetParentFolder(string subPath) + { + throw _exception; + } + + public Task> AddParentItemsAsync(string subPath) + { + throw _exception; + } + + public void Invoke(ApplicationDbContext applicationDbContext) + { + throw _exception; + } + + public void SetGetObjectByFilePathCache(string filePath, FileIndexItem result, + TimeSpan? cacheTime) + { + throw _exception; + } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + public Task CountAsync(Expression>? expression = null) + { + throw new NotImplementedException(); + } + + public List GetAllFiles(string subPath) + { + throw _exception; + } + + public List GetAllRecursive(string subPath = "/") + { + throw _exception; + } + + public FileIndexItem RemoveItem(FileIndexItem updateStatusContent) + { + throw _exception; + } + + public string? GetSubPathByHash(string fileHash) + { + throw _exception; + } + + public List GetAllFolders() + { + throw _exception; + } + + public FileIndexItem AddItem(FileIndexItem updateStatusContent) + { + throw _exception; + } + + public FileIndexItem UpdateItem(FileIndexItem updateStatusContent) + { + throw _exception; + } + + public List UpdateItem(List updateStatusContentList) + { + throw _exception; + } } diff --git a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryExistsTests.cs b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryExistsTests.cs new file mode 100644 index 000000000..9ad6f4fe0 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryExistsTests.cs @@ -0,0 +1,87 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.database.Data; +using starsky.foundation.database.Models; +using starsky.foundation.database.Query; +using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Models; +using starskytest.FakeMocks; + +namespace starskytest.starsky.foundation.database.QueryTest; + +[TestClass] +public class QueryExistsTests +{ + private readonly IMemoryCache? _memoryCache; + + public QueryExistsTests() + { + var provider = new ServiceCollection() + .AddMemoryCache() + .BuildServiceProvider(); + _memoryCache = provider.GetService(); + } + + private static IServiceScopeFactory CreateNewScope() + { + var services = new ServiceCollection(); + services.AddDbContext(options => + options.UseInMemoryDatabase(nameof(QueryTest))); + var serviceProvider = services.BuildServiceProvider(); + return serviceProvider.GetRequiredService(); + } + + [TestMethod] + public async Task ExistsAsync_Disposed() + { + var serviceScope = CreateNewScope(); + var scope = serviceScope.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var query = new Query(dbContext, + new AppSettings { Verbose = true }, serviceScope, new FakeIWebLogger(), + _memoryCache); + const string path = "/ExistsAsync_Disposed/test.jpg"; + await query.AddItemAsync( + new FileIndexItem(path) { Tags = "hi" }); + + // important to Dispose + await dbContext.DisposeAsync(); + + var getItem = await query.ExistsAsync(path); + Assert.IsTrue(getItem); + } + + + [DataTestMethod] + [DataRow("/", true, true)] + [DataRow("/test.jpg", false, true)] + [DataRow("/test/", true, true)] + [DataRow("/notfound.jpg", false, false)] + public async Task ExistsAsync_HomePathAndNotFound(string filePath, bool isDirectory, + bool expected) + { + var serviceScope = CreateNewScope(); + var scope = serviceScope.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var query = new Query(dbContext, + new AppSettings { Verbose = true }, serviceScope, new FakeIWebLogger(), + _memoryCache); + + if ( expected ) + { + if ( filePath != "/" ) + { + filePath = PathHelper.RemoveLatestSlash(filePath); + } + + await query.AddItemAsync( + new FileIndexItem(filePath) { Tags = "item", IsDirectory = isDirectory }); + } + + var result = await query.ExistsAsync(filePath); + Assert.AreEqual(expected, result); + } +} diff --git a/starsky/starskytest/starsky.foundation.storage/Storage/SelectorStorageTest.cs b/starsky/starskytest/starsky.foundation.storage/Storage/SelectorStorageTest.cs index 4e3107369..ee1fae50f 100644 --- a/starsky/starskytest/starsky.foundation.storage/Storage/SelectorStorageTest.cs +++ b/starsky/starskytest/starsky.foundation.storage/Storage/SelectorStorageTest.cs @@ -12,13 +12,13 @@ public class SelectorStorageTest /// /// Service Provider /// - private readonly IServiceProvider _serviceProvider; + private readonly IServiceScopeFactory _scopeFactory; public SelectorStorageTest() { var serviceCollection = new ServiceCollection(); var sp = serviceCollection.BuildServiceProvider(); - _serviceProvider = sp.GetRequiredService(); + _scopeFactory = sp.GetRequiredService(); } [TestMethod] @@ -34,7 +34,7 @@ public void Get_ArgumentOutOfRangeException() // Act & Assert Assert.ThrowsException(() => { - new SelectorStorage(_serviceProvider).Get(myClass.Type); + new SelectorStorage(_scopeFactory).Get(myClass.Type); }); }