Fixed Delete Series + Issue Covers from Kavita+ (#3784)
This commit is contained in:
parent
3a0d33ca13
commit
bc41b0256e
38 changed files with 2189 additions and 1596 deletions
|
@ -29,11 +29,11 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
|
||||
public CleanupServiceTests() : base()
|
||||
{
|
||||
_context.Library.Add(new LibraryBuilder("Manga")
|
||||
Context.Library.Add(new LibraryBuilder("Manga")
|
||||
.WithFolderPath(new FolderPathBuilder(Root + "data/").Build())
|
||||
.Build());
|
||||
|
||||
_readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>(), Substitute.For<IEventHub>(),
|
||||
_readerService = new ReaderService(UnitOfWork, Substitute.For<ILogger<ReaderService>>(), Substitute.For<IEventHub>(),
|
||||
Substitute.For<IImageService>(),
|
||||
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()), Substitute.For<IScrobblingService>());
|
||||
}
|
||||
|
@ -43,11 +43,11 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
|
||||
protected override async Task ResetDb()
|
||||
{
|
||||
_context.Series.RemoveRange(_context.Series.ToList());
|
||||
_context.Users.RemoveRange(_context.Users.ToList());
|
||||
_context.AppUserBookmark.RemoveRange(_context.AppUserBookmark.ToList());
|
||||
Context.Series.RemoveRange(Context.Series.ToList());
|
||||
Context.Users.RemoveRange(Context.Users.ToList());
|
||||
Context.AppUserBookmark.RemoveRange(Context.AppUserBookmark.ToList());
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -68,18 +68,18 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
var s = new SeriesBuilder("Test 1").Build();
|
||||
s.CoverImage = $"{ImageService.GetSeriesFormat(1)}.jpg";
|
||||
s.LibraryId = 1;
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
s = new SeriesBuilder("Test 2").Build();
|
||||
s.CoverImage = $"{ImageService.GetSeriesFormat(3)}.jpg";
|
||||
s.LibraryId = 1;
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
s = new SeriesBuilder("Test 3").Build();
|
||||
s.CoverImage = $"{ImageService.GetSeriesFormat(1000)}.jpg";
|
||||
s.LibraryId = 1;
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
|
||||
await cleanupService.DeleteSeriesCoverImages();
|
||||
|
@ -102,16 +102,16 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
var s = new SeriesBuilder("Test 1").Build();
|
||||
s.CoverImage = $"{ImageService.GetSeriesFormat(1)}.jpg";
|
||||
s.LibraryId = 1;
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
s = new SeriesBuilder("Test 2").Build();
|
||||
s.CoverImage = $"{ImageService.GetSeriesFormat(3)}.jpg";
|
||||
s.LibraryId = 1;
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
|
||||
await cleanupService.DeleteSeriesCoverImages();
|
||||
|
@ -133,7 +133,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
await ResetDb();
|
||||
|
||||
// Add 2 series with cover images
|
||||
_context.Series.Add(new SeriesBuilder("Test 1")
|
||||
Context.Series.Add(new SeriesBuilder("Test 1")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithCoverImage("v01_c01.jpg").Build())
|
||||
.WithCoverImage("v01_c01.jpg")
|
||||
|
@ -142,7 +142,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
.WithLibraryId(1)
|
||||
.Build());
|
||||
|
||||
_context.Series.Add(new SeriesBuilder("Test 2")
|
||||
Context.Series.Add(new SeriesBuilder("Test 2")
|
||||
.WithVolume(new VolumeBuilder("1")
|
||||
.WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithCoverImage("v01_c03.jpg").Build())
|
||||
.WithCoverImage("v01_c03.jpg")
|
||||
|
@ -152,9 +152,9 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
.Build());
|
||||
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
|
||||
await cleanupService.DeleteChapterCoverImages();
|
||||
|
@ -223,7 +223,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
// Delete all Series to reset state
|
||||
await ResetDb();
|
||||
|
||||
_context.Users.Add(new AppUser()
|
||||
Context.Users.Add(new AppUser()
|
||||
{
|
||||
UserName = "Joe",
|
||||
ReadingLists = new List<ReadingList>()
|
||||
|
@ -239,9 +239,9 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
}
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
|
||||
await cleanupService.DeleteReadingListCoverImages();
|
||||
|
@ -260,7 +260,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
filesystem.AddFile($"{CacheDirectory}02.jpg", new MockFileData(""));
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
cleanupService.CleanupCacheAndTempDirectories();
|
||||
Assert.Empty(ds.GetFiles(CacheDirectory, searchOption: SearchOption.AllDirectories));
|
||||
|
@ -274,7 +274,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
filesystem.AddFile($"{CacheDirectory}subdir/02.jpg", new MockFileData(""));
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
cleanupService.CleanupCacheAndTempDirectories();
|
||||
Assert.Empty(ds.GetFiles(CacheDirectory, searchOption: SearchOption.AllDirectories));
|
||||
|
@ -297,7 +297,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
filesystem.AddFile($"{BackupDirectory}randomfile.zip", filesystemFile);
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
await cleanupService.CleanupBackups();
|
||||
Assert.Single(ds.GetFiles(BackupDirectory, searchOption: SearchOption.AllDirectories));
|
||||
|
@ -319,7 +319,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
});
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
await cleanupService.CleanupBackups();
|
||||
Assert.True(filesystem.File.Exists($"{BackupDirectory}randomfile.zip"));
|
||||
|
@ -343,7 +343,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
}
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
await cleanupService.CleanupLogs();
|
||||
Assert.Single(ds.GetFiles(LogDirectory, searchOption: SearchOption.AllDirectories));
|
||||
|
@ -372,7 +372,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
|
||||
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
|
||||
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
|
||||
var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub,
|
||||
ds);
|
||||
await cleanupService.CleanupLogs();
|
||||
Assert.True(filesystem.File.Exists($"{LogDirectory}kavita20200911.log"));
|
||||
|
@ -396,36 +396,36 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
.Build();
|
||||
series.Library = new LibraryBuilder("Test LIb").Build();
|
||||
|
||||
_context.Series.Add(series);
|
||||
Context.Series.Add(series);
|
||||
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
Context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress);
|
||||
var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress);
|
||||
await _readerService.MarkChaptersUntilAsRead(user, 1, 5);
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
// Validate correct chapters have read status
|
||||
Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead);
|
||||
Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead);
|
||||
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), _unitOfWork,
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), UnitOfWork,
|
||||
Substitute.For<IEventHub>(),
|
||||
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()));
|
||||
|
||||
// Delete the Chapter
|
||||
_context.Chapter.Remove(c);
|
||||
await _unitOfWork.CommitAsync();
|
||||
Assert.Empty(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
Context.Chapter.Remove(c);
|
||||
await UnitOfWork.CommitAsync();
|
||||
Assert.Empty(await UnitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
|
||||
// NOTE: This may not be needed, the underlying DB structure seems fixed as of v0.7
|
||||
await cleanupService.CleanupDbEntries();
|
||||
|
||||
Assert.Empty(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
Assert.Empty(await UnitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -436,7 +436,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
.WithMetadata(new SeriesMetadataBuilder().Build())
|
||||
.Build();
|
||||
s.Library = new LibraryBuilder("Test LIb").Build();
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
|
||||
var c = new AppUserCollection()
|
||||
{
|
||||
|
@ -446,24 +446,24 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
Items = new List<Series>() {s}
|
||||
};
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
Context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007",
|
||||
Collections = new List<AppUserCollection>() {c}
|
||||
});
|
||||
await _context.SaveChangesAsync();
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), _unitOfWork,
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), UnitOfWork,
|
||||
Substitute.For<IEventHub>(),
|
||||
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()));
|
||||
|
||||
// Delete the Chapter
|
||||
_context.Series.Remove(s);
|
||||
await _unitOfWork.CommitAsync();
|
||||
Context.Series.Remove(s);
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
await cleanupService.CleanupDbEntries();
|
||||
|
||||
Assert.Empty(await _unitOfWork.CollectionTagRepository.GetAllCollectionsAsync());
|
||||
Assert.Empty(await UnitOfWork.CollectionTagRepository.GetAllCollectionsAsync());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -480,15 +480,15 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
.Build();
|
||||
|
||||
s.Library = new LibraryBuilder("Test LIb").Build();
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
|
||||
var user = new AppUser()
|
||||
{
|
||||
UserName = "CleanupWantToRead_ShouldRemoveFullyReadSeries",
|
||||
};
|
||||
_context.AppUser.Add(user);
|
||||
Context.AppUser.Add(user);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
// Add want to read
|
||||
user.WantToRead = new List<AppUserWantToRead>()
|
||||
|
@ -498,12 +498,12 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
SeriesId = s.Id
|
||||
}
|
||||
};
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
await _readerService.MarkSeriesAsRead(user, s.Id);
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), _unitOfWork,
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), UnitOfWork,
|
||||
Substitute.For<IEventHub>(),
|
||||
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()));
|
||||
|
||||
|
@ -511,7 +511,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
await cleanupService.CleanupWantToRead();
|
||||
|
||||
var wantToRead =
|
||||
await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(user.Id, new UserParams(), new FilterDto());
|
||||
await UnitOfWork.SeriesRepository.GetWantToReadForUserAsync(user.Id, new UserParams(), new FilterDto());
|
||||
|
||||
Assert.Equal(0, wantToRead.TotalCount);
|
||||
}
|
||||
|
@ -533,15 +533,15 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
.Build();
|
||||
|
||||
s.Library = new LibraryBuilder("Test Lib").Build();
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
|
||||
var user = new AppUser()
|
||||
{
|
||||
UserName = "ConsolidateProgress_ShouldRemoveDuplicates",
|
||||
};
|
||||
_context.AppUser.Add(user);
|
||||
Context.AppUser.Add(user);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
// Add 2 progress events
|
||||
user.Progresses ??= [];
|
||||
|
@ -553,7 +553,7 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
LibraryId = s.LibraryId,
|
||||
PagesRead = 1,
|
||||
});
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
// Add a duplicate with higher page number
|
||||
user.Progresses.Add(new AppUserProgress()
|
||||
|
@ -564,18 +564,18 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
LibraryId = s.LibraryId,
|
||||
PagesRead = 3,
|
||||
});
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
Assert.Equal(2, (await _unitOfWork.AppUserProgressRepository.GetAllProgress()).Count());
|
||||
Assert.Equal(2, (await UnitOfWork.AppUserProgressRepository.GetAllProgress()).Count());
|
||||
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), _unitOfWork,
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), UnitOfWork,
|
||||
Substitute.For<IEventHub>(),
|
||||
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()));
|
||||
|
||||
|
||||
await cleanupService.ConsolidateProgress();
|
||||
|
||||
var progress = await _unitOfWork.AppUserProgressRepository.GetAllProgress();
|
||||
var progress = await UnitOfWork.AppUserProgressRepository.GetAllProgress();
|
||||
|
||||
Assert.Single(progress);
|
||||
Assert.True(progress.First().PagesRead == 3);
|
||||
|
@ -601,50 +601,50 @@ public class CleanupServiceTests : AbstractDbTest
|
|||
{
|
||||
new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume).WithChapter(c).Build()
|
||||
};
|
||||
_context.Series.Add(s);
|
||||
Context.Series.Add(s);
|
||||
|
||||
var user = new AppUser()
|
||||
{
|
||||
UserName = "EnsureChapterProgressIsCapped",
|
||||
Progresses = new List<AppUserProgress>()
|
||||
};
|
||||
_context.AppUser.Add(user);
|
||||
Context.AppUser.Add(user);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
await _readerService.MarkChaptersAsRead(user, s.Id, new List<Chapter>() {c});
|
||||
await _unitOfWork.CommitAsync();
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||
await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||
var chapter = await UnitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||
await UnitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||
|
||||
Assert.NotNull(chapter);
|
||||
Assert.Equal(2, chapter.PagesRead);
|
||||
|
||||
// Update chapter to have 1 page
|
||||
c.Pages = 1;
|
||||
_unitOfWork.ChapterRepository.Update(c);
|
||||
await _unitOfWork.CommitAsync();
|
||||
UnitOfWork.ChapterRepository.Update(c);
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||
await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||
chapter = await UnitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||
await UnitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||
Assert.NotNull(chapter);
|
||||
Assert.Equal(2, chapter.PagesRead);
|
||||
Assert.Equal(1, chapter.Pages);
|
||||
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), _unitOfWork,
|
||||
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), UnitOfWork,
|
||||
Substitute.For<IEventHub>(),
|
||||
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()));
|
||||
|
||||
await cleanupService.EnsureChapterProgressIsCapped();
|
||||
chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||
await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||
chapter = await UnitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||
await UnitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||
|
||||
Assert.NotNull(chapter);
|
||||
Assert.Equal(1, chapter.PagesRead);
|
||||
|
||||
_context.AppUser.Remove(user);
|
||||
await _unitOfWork.CommitAsync();
|
||||
Context.AppUser.Remove(user);
|
||||
await UnitOfWork.CommitAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue