diff --git a/API.Tests/Services/DirectoryServiceTests.cs b/API.Tests/Services/DirectoryServiceTests.cs
index 94cc8fc06..9844e7766 100644
--- a/API.Tests/Services/DirectoryServiceTests.cs
+++ b/API.Tests/Services/DirectoryServiceTests.cs
@@ -742,6 +742,9 @@ public class DirectoryServiceTests
[InlineData(new [] {"C:/Manga/"},
new [] {"C:/Manga/Love Hina/Vol. 01.cbz", "C:/Manga/Love Hina/Specials/Sp01.cbz"},
"C:/Manga/Love Hina")]
+ [InlineData(new [] {"/manga"},
+ new [] {"/manga/Love Hina/Vol. 01.cbz", "/manga/Love Hina/Specials/Sp01.cbz"},
+ "/manga/Love Hina")]
public void FindLowestDirectoriesFromFilesTest(string[] rootDirectories, string[] files, string expectedDirectory)
{
var fileSystem = new MockFileSystem();
diff --git a/API/Data/ManualMigrations/MigrateSeriesLowestFolderPath.cs b/API/Data/ManualMigrations/MigrateSeriesLowestFolderPath.cs
new file mode 100644
index 000000000..ca68392cd
--- /dev/null
+++ b/API/Data/ManualMigrations/MigrateSeriesLowestFolderPath.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using API.Entities;
+using API.Services;
+using API.Services.Tasks.Scanner.Parser;
+using Kavita.Common.EnvironmentInfo;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+namespace API.Data.ManualMigrations;
+#nullable enable
+
+///
+/// Some linux-based users are having non-rooted LowestFolderPaths. This will attempt to fix it or null them.
+/// Fixed in v0.8.2
+///
+public static class MigrateSeriesLowestFolderPath
+{
+ public static async Task Migrate(DataContext dataContext, ILogger logger, IDirectoryService directoryService)
+ {
+ if (await dataContext.ManualMigrationHistory.AnyAsync(m => m.Name == "MigrateSeriesLowestFolderPath"))
+ {
+ return;
+ }
+
+ logger.LogCritical("Running MigrateSeriesLowestFolderPath migration - Please be patient, this may take some time. This is not an error");
+
+ var seriesWithFolderPath =
+ await dataContext.Series.Where(s => !string.IsNullOrEmpty(s.LowestFolderPath))
+ .Include(s => s.Library)
+ .ThenInclude(l => l.Folders)
+ .ToListAsync();
+
+ foreach (var series in seriesWithFolderPath)
+ {
+ var isValidPath = series.Library.Folders
+ .Any(folder => Parser.NormalizePath(series.LowestFolderPath!).StartsWith(Parser.NormalizePath(folder.Path), StringComparison.OrdinalIgnoreCase));
+
+ if (isValidPath) continue;
+ series.LowestFolderPath = null;
+ dataContext.Entry(series).State = EntityState.Modified;
+ }
+
+ await dataContext.SaveChangesAsync();
+
+
+
+ dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
+ {
+ Name = "MigrateSeriesLowestFolderPath",
+ ProductVersion = BuildInfo.Version.ToString(),
+ RanAt = DateTime.UtcNow
+ });
+ await dataContext.SaveChangesAsync();
+
+ logger.LogCritical("Running MigrateSeriesLowestFolderPath migration - Completed. This is not an error");
+ }
+}
diff --git a/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs b/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs
index 6f97dbaad..e684ef6a0 100644
--- a/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs
+++ b/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs
@@ -3,7 +3,9 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using API.DTOs.Filtering.v2;
+using API.Entities;
using API.Helpers;
+using Kavita.Common.EnvironmentInfo;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -29,7 +31,7 @@ public static class MigrateSmartFilterEncoding
logger.LogCritical("Running MigrateSmartFilterEncoding migration - Please be patient, this may take some time. This is not an error");
- var smartFilters = dataContext.AppUserSmartFilter.ToList();
+ var smartFilters = await dataContext.AppUserSmartFilter.ToListAsync();
foreach (var filter in smartFilters)
{
if (!ShouldMigrateFilter(filter.Filter)) continue;
@@ -43,6 +45,14 @@ public static class MigrateSmartFilterEncoding
await unitOfWork.CommitAsync();
}
+ dataContext.ManualMigrationHistory.Add(new ManualMigrationHistory()
+ {
+ Name = "MigrateSmartFilterEncoding",
+ ProductVersion = BuildInfo.Version.ToString(),
+ RanAt = DateTime.UtcNow
+ });
+ await dataContext.SaveChangesAsync();
+
logger.LogCritical("Running MigrateSmartFilterEncoding migration - Completed. This is not an error");
}
diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs
index 3c7150883..5a38c5903 100644
--- a/API/Services/DirectoryService.cs
+++ b/API/Services/DirectoryService.cs
@@ -745,12 +745,12 @@ public class DirectoryService : IDirectoryService
///
/// Recursively scans a folder and returns the max last write time on any folders and files
///
- /// If the folder is empty, this will return MaxValue for a DateTime
+ /// If the folder is empty or non-existant, this will return MaxValue for a DateTime
///
/// Max Last Write Time
public DateTime GetLastWriteTime(string folderPath)
{
- if (!FileSystem.Directory.Exists(folderPath)) throw new IOException($"{folderPath} does not exist");
+ if (!FileSystem.Directory.Exists(folderPath)) return DateTime.MaxValue;
var fileEntries = FileSystem.Directory.GetFileSystemEntries(folderPath, "*.*", SearchOption.AllDirectories);
if (fileEntries.Length == 0) return DateTime.MaxValue;
return fileEntries.Max(path => FileSystem.File.GetLastWriteTime(path));
diff --git a/API/Services/Tasks/Scanner/ParseScannedFiles.cs b/API/Services/Tasks/Scanner/ParseScannedFiles.cs
index 742b37410..6c4946307 100644
--- a/API/Services/Tasks/Scanner/ParseScannedFiles.cs
+++ b/API/Services/Tasks/Scanner/ParseScannedFiles.cs
@@ -143,7 +143,8 @@ public class ParseScannedFiles
_logger.LogDebug("[ProcessFiles] Dirty check passed, series list: {@SeriesModified}", series2);
foreach (var s in series2)
{
- _logger.LogDebug("[ProcessFiles] Last Scanned: {LastScanned} vs Directory Check: {DirectoryLastScanned}", s.LastScanned, _directoryService
+ _logger.LogDebug("[ProcessFiles] Last Scanned: {LastScanned} vs Directory Check: {DirectoryLastScanned}",
+ s.LastScanned, _directoryService
.GetLastWriteTime(s.LowestFolderPath!)
.Truncate(TimeSpan.TicksPerSecond));
}
diff --git a/API/Startup.cs b/API/Startup.cs
index c6b1e852d..30c663a4b 100644
--- a/API/Startup.cs
+++ b/API/Startup.cs
@@ -269,6 +269,7 @@ public class Startup
// v0.8.2
await ManualMigrateThemeDescription.Migrate(dataContext, logger);
await MigrateInitialInstallData.Migrate(dataContext, logger, directoryService);
+ await MigrateSeriesLowestFolderPath.Migrate(dataContext, logger, directoryService);
// Update the version in the DB after all migrations are run
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);