Misc Bugfixes (#1015)

* Fixed some security issues in dev env

* When deleting folders in bookmark cleanup, delete empty folders correctly.

* When a new library is created and cards are added, cards can have a blank library name. Card library name code is reworked to be much lighter on memory.

* Added a config for github issues to disable blank issues.

* Skip any sort of directory iteration code if we haven't deleted any bookmarks.

* Fixed a bug where some style overrides were duplicating. Now logic is much more targetted, only applying to the correct tags.

* Applied sorting to the filtering apis.

* Reverted one of my changes for a better version Robbie did.
This commit is contained in:
Joseph Milazzo 2022-01-31 08:50:13 -08:00 committed by GitHub
parent c631395aae
commit c6d1311560
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 186 additions and 47 deletions

View File

@ -400,6 +400,78 @@ public class CleanupServiceTests
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_context.AppUser.Add(new AppUser()
{
Bookmarks = new List<AppUserBookmark>()
{
new AppUserBookmark()
{
AppUserId = 1,
ChapterId = 1,
Page = 1,
FileName = "1/1/1/0001.jpg",
SeriesId = 1,
VolumeId = 1
},
new AppUserBookmark()
{
AppUserId = 1,
ChapterId = 1,
Page = 2,
FileName = "1/1/1/0002.jpg",
SeriesId = 1,
VolumeId = 1
}
}
});
await _context.SaveChangesAsync();
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub,
ds);
await cleanupService.CleanupBookmarks();
Assert.Equal(2, ds.GetFiles(BookmarkDirectory, searchOption:SearchOption.AllDirectories).Count());
}
[Fact]
public async Task CleanupBookmarks_LeavesOneFiles()
{
var filesystem = CreateFileSystem();
filesystem.AddFile($"{BookmarkDirectory}1/1/1/0001.jpg", new MockFileData(""));
filesystem.AddFile($"{BookmarkDirectory}1/1/2/0002.jpg", new MockFileData(""));
// Delete all Series to reset state
await ResetDB();
_context.Series.Add(new Series()
{
Name = "Test",
Library = new Library() {
Name = "Test LIb",
Type = LibraryType.Manga,
},
Volumes = new List<Volume>()
{
new Volume()
{
Chapters = new List<Chapter>()
{
new Chapter()
{
}
}
}
}
});
await _context.SaveChangesAsync();
_context.AppUser.Add(new AppUser() _context.AppUser.Add(new AppUser()
{ {
Bookmarks = new List<AppUserBookmark>() Bookmarks = new List<AppUserBookmark>()
@ -426,7 +498,7 @@ public class CleanupServiceTests
await cleanupService.CleanupBookmarks(); await cleanupService.CleanupBookmarks();
Assert.Equal(1, ds.GetFiles(BookmarkDirectory, searchOption:SearchOption.AllDirectories).Count()); Assert.Equal(1, ds.GetFiles(BookmarkDirectory, searchOption:SearchOption.AllDirectories).Count());
Assert.Equal(1, ds.FileSystem.Directory.GetDirectories($"{BookmarkDirectory}1/1/").Length);
} }
#endregion #endregion

View File

@ -111,7 +111,7 @@ public class MetadataController : BaseApiController
{ {
Title = t.ToDescription(), Title = t.ToDescription(),
Value = t Value = t
})); }).OrderBy(t => t.Title));
} }
/// <summary> /// <summary>

View File

@ -67,6 +67,7 @@ public class GenreRepository : IGenreRepository
.Where(s => libraryIds.Contains(s.LibraryId)) .Where(s => libraryIds.Contains(s.LibraryId))
.SelectMany(s => s.Metadata.Genres) .SelectMany(s => s.Metadata.Genres)
.Distinct() .Distinct()
.OrderBy(p => p.Title)
.ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider) .ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider)
.ToListAsync(); .ToListAsync();
} }

View File

@ -66,6 +66,8 @@ public class PersonRepository : IPersonRepository
.Where(s => libraryIds.Contains(s.LibraryId)) .Where(s => libraryIds.Contains(s.LibraryId))
.SelectMany(s => s.Metadata.People) .SelectMany(s => s.Metadata.People)
.Distinct() .Distinct()
.OrderBy(p => p.Name)
.AsNoTracking()
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider) .ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
.ToListAsync(); .ToListAsync();
} }
@ -74,6 +76,7 @@ public class PersonRepository : IPersonRepository
public async Task<IList<Person>> GetAllPeople() public async Task<IList<Person>> GetAllPeople()
{ {
return await _context.Person return await _context.Person
.OrderBy(p => p.Name)
.ToListAsync(); .ToListAsync();
} }
} }

View File

@ -778,6 +778,7 @@ public class SeriesRepository : ISeriesRepository
var ret = await _context.Series var ret = await _context.Series
.Where(s => libraryIds.Contains(s.LibraryId)) .Where(s => libraryIds.Contains(s.LibraryId))
.Select(s => s.Metadata.Language) .Select(s => s.Metadata.Language)
.AsNoTracking()
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();
@ -787,7 +788,9 @@ public class SeriesRepository : ISeriesRepository
{ {
Title = CultureInfo.GetCultureInfo(s).DisplayName, Title = CultureInfo.GetCultureInfo(s).DisplayName,
IsoCode = s IsoCode = s
}).ToList(); })
.OrderBy(s => s.Title)
.ToList();
} }
public async Task<IList<PublicationStatusDto>> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds) public async Task<IList<PublicationStatusDto>> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds)
@ -801,6 +804,7 @@ public class SeriesRepository : ISeriesRepository
Value = s, Value = s,
Title = s.ToDescription() Title = s.ToDescription()
}) })
.OrderBy(s => s.Title)
.ToListAsync(); .ToListAsync();
} }

View File

@ -67,6 +67,8 @@ public class TagRepository : ITagRepository
.Where(s => libraryIds.Contains(s.LibraryId)) .Where(s => libraryIds.Contains(s.LibraryId))
.SelectMany(s => s.Metadata.Tags) .SelectMany(s => s.Metadata.Tags)
.Distinct() .Distinct()
.OrderBy(t => t.Title)
.AsNoTracking()
.ProjectTo<TagDto>(_mapper.ConfigurationProvider) .ProjectTo<TagDto>(_mapper.ConfigurationProvider)
.ToListAsync(); .ToListAsync();
} }
@ -80,6 +82,7 @@ public class TagRepository : ITagRepository
{ {
return await _context.Tag return await _context.Tag
.AsNoTracking() .AsNoTracking()
.OrderBy(t => t.Title)
.ProjectTo<TagDto>(_mapper.ConfigurationProvider) .ProjectTo<TagDto>(_mapper.ConfigurationProvider)
.ToListAsync(); .ToListAsync();
} }

View File

@ -184,19 +184,23 @@ namespace API.Services.Tasks
var filesToDelete = allBookmarkFiles.ToList().Except(bookmarks).ToList(); var filesToDelete = allBookmarkFiles.ToList().Except(bookmarks).ToList();
_logger.LogDebug("[Bookmarks] Bookmark cleanup wants to delete {Count} files", filesToDelete.Count()); _logger.LogDebug("[Bookmarks] Bookmark cleanup wants to delete {Count} files", filesToDelete.Count);
if (filesToDelete.Count == 0) return;
_directoryService.DeleteFiles(filesToDelete); _directoryService.DeleteFiles(filesToDelete);
// Clear all empty directories // Clear all empty directories
foreach (var directory in _directoryService.FileSystem.Directory.GetDirectories(bookmarkDirectory)) foreach (var directory in _directoryService.FileSystem.Directory.GetDirectories(bookmarkDirectory, "", SearchOption.AllDirectories))
{ {
if (_directoryService.FileSystem.Directory.GetFiles(directory).Length == 0 && if (_directoryService.FileSystem.Directory.GetFiles(directory, "", SearchOption.AllDirectories).Length == 0 &&
_directoryService.FileSystem.Directory.GetDirectories(directory).Length == 0) _directoryService.FileSystem.Directory.GetDirectories(directory).Length == 0)
{ {
_directoryService.FileSystem.Directory.Delete(directory, false); _directoryService.FileSystem.Directory.Delete(directory, false);
} }
} }
} }
} }
} }

View File

@ -10645,9 +10645,33 @@
"dev": true "dev": true
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.1", "version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
},
"dependencies": {
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
}, },
"node-forge": { "node-forge": {
"version": "0.10.0", "version": "0.10.0",
@ -12384,8 +12408,7 @@
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true "dev": true
}, },
"strip-ansi": { "strip-ansi": {
@ -12590,8 +12613,7 @@
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true "dev": true
}, },
"ansi-styles": { "ansi-styles": {

View File

@ -34,6 +34,21 @@ export class LibraryService {
})); }));
} }
getLibraryName(libraryId: number) {
if (this.libraryNames != undefined && this.libraryNames.hasOwnProperty(libraryId)) {
return of(this.libraryNames[libraryId]);
}
return this.httpClient.get<Library[]>(this.baseUrl + 'library').pipe(map(l => {
this.libraryNames = {};
l.forEach(lib => {
if (this.libraryNames !== undefined) {
this.libraryNames[lib.id] = lib.name;
}
});
return this.libraryNames[libraryId];
}));
}
listDirectories(rootPath: string) { listDirectories(rootPath: string) {
let query = ''; let query = '';
if (rootPath !== undefined && rootPath.length > 0) { if (rootPath !== undefined && rootPath.length > 0) {

View File

@ -43,6 +43,15 @@ const TOP_OFFSET = -50 * 1.5; // px the sticky header takes up
const CHAPTER_ID_NOT_FETCHED = -2; const CHAPTER_ID_NOT_FETCHED = -2;
const CHAPTER_ID_DOESNT_EXIST = -1; const CHAPTER_ID_DOESNT_EXIST = -1;
/**
* Styles that should be applied on the top level book-content tag
*/
const pageLevelStyles = ['margin-left', 'margin-right', 'font-size'];
/**
* Styles that should be applied on every element within book-content tag
*/
const elementLevelStyles = ['line-height', 'font-family'];
@Component({ @Component({
selector: 'app-book-reader', selector: 'app-book-reader',
templateUrl: './book-reader.component.html', templateUrl: './book-reader.component.html',
@ -680,17 +689,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
page = 0; page = 0;
} }
// BUG: Last page is not counting as read
if (!(page === 0 || page === this.maxPages - 1)) { if (!(page === 0 || page === this.maxPages - 1)) {
page -= 1; page -= 1;
} }
// // Due to the fact that we start at image 0, but page 1, we need the last page to have progress as page + 1 to be completed
// let tempPageNum = this.pageNum;
// if (this.pageNum == this.maxPages - 1) {
// tempPageNum = this.pageNum + 1;
// }
this.pageNum = page; this.pageNum = page;
this.loadPage(); this.loadPage();
@ -903,31 +905,41 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateReaderStyles(); this.updateReaderStyles();
} }
/**
* Applies styles onto the html of the book page
*/
updateReaderStyles() { updateReaderStyles() {
if (this.readingHtml != undefined && this.readingHtml.nativeElement) { if (this.readingHtml === undefined || !this.readingHtml.nativeElement) return;
Object.entries(this.pageStyles).forEach(item => {
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
// Remove the style or skip
this.renderer.removeStyle(this.readingHtml.nativeElement, item[0]);
return;
}
this.renderer.setStyle(this.readingHtml.nativeElement, item[0], item[1], RendererStyleFlags2.Important);
});
for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) { // Line Height must be placed on each element in the page
const elem = this.readingHtml.nativeElement.children.item(i);
if (elem?.tagName === 'STYLE') continue; // Apply page level overrides
Object.entries(this.pageStyles).forEach(item => { Object.entries(this.pageStyles).forEach(item => {
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') { if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
// Remove the style or skip // Remove the style or skip
this.renderer.removeStyle(elem, item[0]); this.renderer.removeStyle(this.readingHtml.nativeElement, item[0]);
return; return;
}
this.renderer.setStyle(elem, item[0], item[1], RendererStyleFlags2.Important);
});
} }
if (pageLevelStyles.includes(item[0])) {
this.renderer.setStyle(this.readingHtml.nativeElement, item[0], item[1], RendererStyleFlags2.Important);
}
});
const individualElementStyles = Object.entries(this.pageStyles).filter(item => elementLevelStyles.includes(item[0]));
for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) {
const elem = this.readingHtml.nativeElement.children.item(i);
if (elem?.tagName === 'STYLE') continue;
individualElementStyles.forEach(item => {
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
// Remove the style or skip
this.renderer.removeStyle(elem, item[0]);
return;
}
this.renderer.setStyle(elem, item[0], item[1], RendererStyleFlags2.Important);
});
} }
} }

View File

@ -115,12 +115,15 @@ export class CardItemComponent implements OnInit, OnDestroy {
} }
if (this.supressLibraryLink === false) { if (this.supressLibraryLink === false) {
this.libraryService.getLibraryNames().pipe(takeUntil(this.onDestroy)).subscribe(names => { if (this.entity !== undefined && this.entity.hasOwnProperty('libraryId')) {
if (this.entity !== undefined && this.entity.hasOwnProperty('libraryId')) { this.libraryId = (this.entity as Series).libraryId;
this.libraryId = (this.entity as Series).libraryId; }
this.libraryName = names[this.libraryId];
} if (this.libraryId !== undefined && this.libraryId > 0) {
}); this.libraryService.getLibraryName(this.libraryId).pipe(takeUntil(this.onDestroy)).subscribe(name => {
this.libraryName = name;
});
}
} }
this.format = (this.entity as Series).format; this.format = (this.entity as Series).format;