diff --git a/API.Tests/Parser/ParserTest.cs b/API.Tests/Parser/ParserTest.cs index 5b7900e86..84dee1b8b 100644 --- a/API.Tests/Parser/ParserTest.cs +++ b/API.Tests/Parser/ParserTest.cs @@ -208,6 +208,9 @@ namespace API.Tests.Parser [InlineData("MACOSX/Love Hina/", false)] [InlineData("._Love Hina/Love Hina/", true)] [InlineData("@Recently-Snapshot/Love Hina/", true)] + [InlineData("@recycle/Love Hina/", true)] + [InlineData("@recycle/Love Hina/", true)] + [InlineData("E:/Test/__MACOSX/Love Hina/", true)] public void HasBlacklistedFolderInPathTest(string inputPath, bool expected) { Assert.Equal(expected, HasBlacklistedFolderInPath(inputPath)); diff --git a/API.Tests/Services/DirectoryServiceTests.cs b/API.Tests/Services/DirectoryServiceTests.cs index 391b4eac4..c0d49820b 100644 --- a/API.Tests/Services/DirectoryServiceTests.cs +++ b/API.Tests/Services/DirectoryServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Abstractions.TestingHelpers; @@ -755,6 +755,22 @@ namespace API.Tests.Services Assert.True(fileSystem.Directory.Exists($"{testDirectory}subdir/")); } + [Fact] + public void Flatten_ShouldFlatten_WithoutMacosx() + { + const string testDirectory = "/manga/"; + var fileSystem = new MockFileSystem(); + fileSystem.AddDirectory(testDirectory); + fileSystem.AddFile($"{testDirectory}data-1.jpg", new MockFileData("abc")); + fileSystem.AddFile($"{testDirectory}subdir/data-3.webp", new MockFileData("abc")); + fileSystem.AddFile($"{testDirectory}__MACOSX/data-4.webp", new MockFileData("abc")); + + var ds = new DirectoryService(Substitute.For>(), fileSystem); + ds.Flatten($"{testDirectory}"); + Assert.Equal(2, ds.GetFiles(testDirectory).Count()); + Assert.False(fileSystem.FileExists($"{testDirectory}data-4.webp")); + } + #endregion #region CheckWriteAccess diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 1d72040d9..efe2f1a27 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -597,7 +597,7 @@ public class SeriesRepository : ISeriesRepository { //var allSeriesWithProgress = await _context.AppUserProgresses.Select(p => p.SeriesId).ToListAsync(); //var allChapters = await GetChapterIdsForSeriesAsync(allSeriesWithProgress); - + var cuttoffProgressPoint = DateTime.Now - TimeSpan.FromDays(30); var query = (await CreateFilteredSearchQueryable(userId, libraryId, filter)) .Join(_context.AppUserProgresses, s => s.Id, progress => progress.SeriesId, (s, progress) => new @@ -606,12 +606,14 @@ public class SeriesRepository : ISeriesRepository PagesRead = _context.AppUserProgresses.Where(s1 => s1.SeriesId == s.Id && s1.AppUserId == userId) .Sum(s1 => s1.PagesRead), progress.AppUserId, - LastReadingProgress = _context.AppUserProgresses.Where(p => p.Id == progress.Id && p.AppUserId == userId) + LastReadingProgress = _context.AppUserProgresses + .Where(p => p.Id == progress.Id && p.AppUserId == userId) .Max(p => p.LastModified), // This is only taking into account chapters that have progress on them, not all chapters in said series LastChapterCreated = _context.Chapter.Where(c => progress.ChapterId == c.Id).Max(c => c.Created) //LastChapterCreated = _context.Chapter.Where(c => allChapters.Contains(c.Id)).Max(c => c.Created) - }); + }) + .Where(d => d.LastReadingProgress >= cuttoffProgressPoint); // I think I need another Join statement. The problem is the chapters are still limited to progress diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index 5cbb816e1..1b8557f73 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -1003,7 +1003,7 @@ namespace API.Parser public static bool HasBlacklistedFolderInPath(string path) { - return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("._"); + return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("@recycle") || path.StartsWith("._"); } diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index da9e4698b..6e5b335d1 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -703,7 +703,7 @@ namespace API.Services } - private void FlattenDirectory(IDirectoryInfo root, IDirectoryInfo directory, ref int directoryIndex) + private static void FlattenDirectory(IFileSystemInfo root, IDirectoryInfo directory, ref int directoryIndex) { if (!root.FullName.Equals(directory.FullName)) { @@ -725,6 +725,9 @@ namespace API.Services foreach (var subDirectory in directory.EnumerateDirectories().OrderByNatural(d => d.FullName)) { + // We need to check if the directory is not a blacklisted (ie __MACOSX) + if (Parser.Parser.HasBlacklistedFolderInPath(subDirectory.FullName)) continue; + FlattenDirectory(root, subDirectory, ref directoryIndex); } } diff --git a/API/Services/EmailService.cs b/API/Services/EmailService.cs index 08d00d29d..ab2c52a2c 100644 --- a/API/Services/EmailService.cs +++ b/API/Services/EmailService.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Net; using System.Threading.Tasks; using API.Data; using API.DTOs.Email; @@ -40,14 +42,22 @@ public class EmailService : IEmailService cli.Settings.HttpClientFactory = new UntrustedCertClientFactory()); } + /// + /// Test if this instance is accessible outside the network + /// + /// This will do some basic filtering to auto return false if the emailUrl is a LAN ip + /// + /// public async Task TestConnectivity(string emailUrl) { - // FlurlHttp.ConfigureClient(emailUrl, cli => - // cli.Settings.HttpClientFactory = new UntrustedCertClientFactory()); - var result = new EmailTestResultDto(); try { + if (IsLocalIpAddress(emailUrl)) + { + result.Successful = false; + result.ErrorMessage = "This is a local IP address"; + } result.Successful = await SendEmailWithGet(emailUrl + "/api/email/test"); } catch (KavitaException ex) @@ -72,6 +82,7 @@ public class EmailService : IEmailService public async Task CheckIfAccessible(string host) { // This is the only exception for using the default because we need an external service to check if the server is accessible for emails + if (IsLocalIpAddress(host)) return false; return await SendEmailWithGet(DefaultApiUrl + "/api/email/reachable?host=" + host); } @@ -138,4 +149,34 @@ public class EmailService : IEmailService return true; } + private static bool IsLocalIpAddress(string url) + { + var host = url.Split(':')[0]; + try + { + // get host IP addresses + var hostIPs = Dns.GetHostAddresses(host); + // get local IP addresses + var localIPs = Dns.GetHostAddresses(Dns.GetHostName()); + + // test if any host IP equals to any local IP or to localhost + foreach (var hostIp in hostIPs) + { + // is localhost + if (IPAddress.IsLoopback(hostIp)) return true; + // is local address + if (localIPs.Contains(hostIp)) + { + return true; + } + } + } + catch + { + // ignored + } + + return false; + } + } diff --git a/UI/Web/src/app/library/library.component.html b/UI/Web/src/app/library/library.component.html index 0eb8f7aa9..4add40fa0 100644 --- a/UI/Web/src/app/library/library.component.html +++ b/UI/Web/src/app/library/library.component.html @@ -12,25 +12,25 @@ - + - + - + diff --git a/UI/Web/src/app/library/library.component.ts b/UI/Web/src/app/library/library.component.ts index 19462c71b..dade32f8b 100644 --- a/UI/Web/src/app/library/library.component.ts +++ b/UI/Web/src/app/library/library.component.ts @@ -138,7 +138,7 @@ export class LibraryComponent implements OnInit, OnDestroy { handleSectionClick(sectionTitle: string) { if (sectionTitle.toLowerCase() === 'collections') { this.router.navigate(['collections']); - } else if (sectionTitle.toLowerCase() === 'recently added') { + } else if (sectionTitle.toLowerCase() === 'recently updated series') { this.router.navigate(['recently-added']); } else if (sectionTitle.toLowerCase() === 'on deck') { this.router.navigate(['on-deck']); diff --git a/UI/Web/src/app/shared/confirm.service.ts b/UI/Web/src/app/shared/confirm.service.ts index 72344ca08..f1cbbb881 100644 --- a/UI/Web/src/app/shared/confirm.service.ts +++ b/UI/Web/src/app/shared/confirm.service.ts @@ -41,7 +41,7 @@ export class ConfirmService { return resolve(result); }); modalRef.dismissed.pipe(take(1)).subscribe(() => { - return reject(false); + return resolve(false); }); }); @@ -65,7 +65,7 @@ export class ConfirmService { return resolve(result); }); modalRef.dismissed.pipe(take(1)).subscribe(() => { - return reject(false); + return resolve(false); }); }) }