using System; using System.Linq; using System.Threading.Tasks; using API.Comparators; using API.Entities.Enums; using API.Services; using Microsoft.Extensions.Logging; namespace API.Data; /// /// Responsible to migrate existing bookmarks to files /// public static class MigrateBookmarks { private static readonly Version VersionBookmarksChanged = new Version(0, 4, 9, 27); /// /// This will migrate existing bookmarks to bookmark folder based /// /// Bookmark directory is configurable. This will always use the default bookmark directory. /// /// public static async Task Migrate(IDirectoryService directoryService, IUnitOfWork unitOfWork, ILogger logger, ICacheService cacheService) { var bookmarkDirectory = (await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)) .Value; if (string.IsNullOrEmpty(bookmarkDirectory)) { bookmarkDirectory = directoryService.BookmarkDirectory; } if (directoryService.Exists(bookmarkDirectory)) return; logger.LogInformation("Bookmark migration is needed....This may take some time"); var allBookmarks = (await unitOfWork.UserRepository.GetAllBookmarksAsync()).ToList(); var uniqueChapterIds = allBookmarks.Select(b => b.ChapterId).Distinct().ToList(); var uniqueUserIds = allBookmarks.Select(b => b.AppUserId).Distinct().ToList(); foreach (var userId in uniqueUserIds) { foreach (var chapterId in uniqueChapterIds) { var chapterBookmarks = allBookmarks.Where(b => b.ChapterId == chapterId).ToList(); var chapterPages = chapterBookmarks .Select(b => b.Page).ToList(); var seriesId = chapterBookmarks .Select(b => b.SeriesId).First(); var mangaFiles = await unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId); var chapterExtractPath = directoryService.FileSystem.Path.Join(directoryService.TempDirectory, $"bookmark_c{chapterId}_u{userId}_s{seriesId}"); var numericComparer = new NumericComparer(); if (!mangaFiles.Any()) continue; switch (mangaFiles.First().Format) { case MangaFormat.Image: directoryService.ExistOrCreate(chapterExtractPath); directoryService.CopyFilesToDirectory(mangaFiles.Select(f => f.FilePath), chapterExtractPath); break; case MangaFormat.Archive: case MangaFormat.Pdf: cacheService.ExtractChapterFiles(chapterExtractPath, mangaFiles.ToList()); break; case MangaFormat.Epub: continue; default: continue; } var files = directoryService.GetFilesWithExtension(chapterExtractPath, Parser.Parser.ImageFileExtensions); // Filter out images that aren't in bookmarks Array.Sort(files, numericComparer); foreach (var chapterPage in chapterPages) { var file = files.ElementAt(chapterPage); var bookmark = allBookmarks.FirstOrDefault(b => b.ChapterId == chapterId && b.SeriesId == seriesId && b.AppUserId == userId && b.Page == chapterPage); if (bookmark == null) continue; var filename = directoryService.FileSystem.Path.GetFileName(file); var newLocation = directoryService.FileSystem.Path.Join( ReaderService.FormatBookmarkFolderPath(String.Empty, userId, seriesId, chapterId), filename); bookmark.FileName = newLocation; directoryService.CopyFileToDirectory(file, ReaderService.FormatBookmarkFolderPath(bookmarkDirectory, userId, seriesId, chapterId)); unitOfWork.UserRepository.Update(bookmark); } } // Clear temp after each user to avoid too much space being eaten directoryService.ClearDirectory(directoryService.TempDirectory); } await unitOfWork.CommitAsync(); // Run CleanupService as we cache a ton of files directoryService.ClearDirectory(directoryService.TempDirectory); } }