This commit is contained in:
2026-07-17 16:04:57 +02:00
parent 77872ed1ea
commit b2ff1dc624
+91 -68
View File
@@ -8,7 +8,7 @@ import UIKit
import ZIPFoundation import ZIPFoundation
let preloadCount = 2 let preloadCount = 2
// With a whopping 2 cores, and 2 threads because no hyperthreading, it makes sense to only have a queue as an extra thread to do work with to allow the main thread for ui. /// With a whopping 2 cores, and 2 threads because no hyperthreading, it makes sense to only have a queue as an extra thread to do work with to allow the main thread for ui.
let queue = DispatchQueue(label: "queue", qos: .utility) let queue = DispatchQueue(label: "queue", qos: .utility)
enum PageTurn { enum PageTurn {
@@ -170,7 +170,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
} }
func loadComics() { func loadComics() {
let alert = UIAlertController(title: "Failed to load comics.", message: "This is an alert.", preferredStyle: .alert) let alert = UIAlertController(title: "Failed to load comics.", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
do { do {
var directories: [URL] = [] var directories: [URL] = []
@@ -259,7 +259,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
func saveGlobalState() { func saveGlobalState() {
do { do {
try JSONEncoder().encode(globalState).write( try JSONEncoder().encode(globalState).write(
to: documentsURL.appendingPathComponent("state.json")) to: documentsURL.appendingPathComponent("state.json")
)
} catch { } catch {
print("failed to save global state") print("failed to save global state")
} }
@@ -298,7 +299,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any], guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any],
let width = properties[kCGImagePropertyPixelWidth] as? CGFloat, let width = properties[kCGImagePropertyPixelWidth] as? CGFloat,
let height = properties[kCGImagePropertyPixelHeight] as? CGFloat else { let height = properties[kCGImagePropertyPixelHeight] as? CGFloat
else {
return nil return nil
} }
@@ -322,8 +324,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var currentChapter: (UInt32, UInt32?)! var currentChapter: (UInt32, UInt32?)!
if fileManager.fileExists( if fileManager.fileExists(
atPath: path.appendingPathComponent(metadataFilename).absoluteString) atPath: path.appendingPathComponent(metadataFilename).absoluteString
{ ) {
print("skipped metadata file creation") print("skipped metadata file creation")
return return
} }
@@ -335,9 +337,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var currentImages: [Offset] = [] var currentImages: [Offset] = []
var previousVolumeURL = "" var previousVolumeURL = ""
var titleValue: String = "" var titleValue = ""
let alert = UIAlertController(title: "Failed to get metadata from filename.", message: "This is an alert.", preferredStyle: .alert) let alert = UIAlertController(title: "Failed to get metadata from filename.", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
let volumeDir = path.appendingPathComponent("volumes") let volumeDir = path.appendingPathComponent("volumes")
@@ -352,8 +354,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
includingPropertiesForKeys: [.isRegularFileKey], includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles] options: [.skipsHiddenFiles]
).filter { $0.pathExtension == "cbz" } ).filter { $0.pathExtension == "cbz" }
.sorted { $0.lastPathComponent < $1.lastPathComponent } .sorted { $0.lastPathComponent < $1.lastPathComponent }
if volumeURLs == nil { if volumeURLs == nil {
alert.message = String(format: "failed to read dir: \(volumeDir)") alert.message = String(format: "failed to read dir: \(volumeDir)")
pendingAlert = alert pendingAlert = alert
return return
@@ -478,14 +480,12 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
nChapter0, chapterParts.count > 1 ? UInt32(chapterParts[1]) : nil nChapter0, chapterParts.count > 1 ? UInt32(chapterParts[1]) : nil
) )
guard let volume = UInt32((filename as NSString).substring(with: match.range(at: volumeRegexPosition))) else {
guard let volume: UInt32 = UInt32((filename as NSString).substring(with: match.range(at: volumeRegexPosition))) else {
alert.message = String(format: "failed to get match for volume in filename: \(filename)") alert.message = String(format: "failed to get match for volume in filename: \(filename)")
pendingAlert = alert pendingAlert = alert
return return
} }
let pageStr = (filename as NSString).substring(with: match.range(at: pageRegexPosition)) let pageStr = (filename as NSString).substring(with: match.range(at: pageRegexPosition))
let pageParts = pageStr.split(separator: "-", maxSplits: 1) let pageParts = pageStr.split(separator: "-", maxSplits: 1)
guard let nPage = UInt32(pageParts[0]) else { guard let nPage = UInt32(pageParts[0]) else {
@@ -512,7 +512,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return return
} }
let m_filename = builder.create(string: filename) let m_filename = builder.create(string: filename)
let imageMetadata = ImageMetadata.createImageMetadata(&builder, let imageMetadata = ImageMetadata.createImageMetadata(
&builder,
doublePage: doublePage, doublePage: doublePage,
filenameOffset: m_filename, filenameOffset: m_filename,
firstPage: page, firstPage: page,
@@ -525,12 +526,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
let chapterName = builder.create(string: "") let chapterName = builder.create(string: "")
let imagesOffset = builder.createVector(ofOffsets: currentImages) let imagesOffset = builder.createVector(ofOffsets: currentImages)
let chapterMetadata = ChapterMetadata.createChapterMetadata(&builder, let chapterMetadata = ChapterMetadata.createChapterMetadata(
&builder,
chapter: MetaValue( chapter: MetaValue(
main: currentChapter.0, bonus: currentChapter.1 ?? 0 main: currentChapter.0, bonus: currentChapter.1 ?? 0
), ),
nameOffset: chapterName, nameOffset: chapterName,
imagesVectorOffset: imagesOffset, imagesVectorOffset: imagesOffset
) )
currentChapters.append(chapterMetadata) currentChapters.append(chapterMetadata)
@@ -543,7 +545,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
volume: MetaValue(main: currentVolume, bonus: 0), volume: MetaValue(main: currentVolume, bonus: 0),
titleOffset: volumeTitle, titleOffset: volumeTitle,
archiveOffset: volumeURLOffset, archiveOffset: volumeURLOffset,
chaptersVectorOffset: chaptersOffset, chaptersVectorOffset: chaptersOffset
) )
volumes.append(volumeMetadata) volumes.append(volumeMetadata)
@@ -574,12 +576,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
let chapterName = builder.create(string: "") let chapterName = builder.create(string: "")
let imagesOffset = builder.createVector(ofOffsets: currentImages) let imagesOffset = builder.createVector(ofOffsets: currentImages)
let chapterMetadata = ChapterMetadata.createChapterMetadata(&builder, let chapterMetadata = ChapterMetadata.createChapterMetadata(
&builder,
chapter: MetaValue( chapter: MetaValue(
main: currentChapter.0, bonus: currentChapter.1 ?? 0 main: currentChapter.0, bonus: currentChapter.1 ?? 0
), ),
nameOffset: chapterName, nameOffset: chapterName,
imagesVectorOffset: imagesOffset, imagesVectorOffset: imagesOffset
) )
currentChapters.append(chapterMetadata) currentChapters.append(chapterMetadata)
@@ -592,15 +595,16 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
} else { } else {
currentImages.append(imageMetadata) currentImages.append(imageMetadata)
} }
if (i == filenames.count - 1) { if i == filenames.count - 1 {
let chapterName = builder.create(string: "") let chapterName = builder.create(string: "")
let imagesOffset = builder.createVector(ofOffsets: currentImages) let imagesOffset = builder.createVector(ofOffsets: currentImages)
let chapterMetadata = ChapterMetadata.createChapterMetadata(&builder, let chapterMetadata = ChapterMetadata.createChapterMetadata(
&builder,
chapter: MetaValue( chapter: MetaValue(
main: chapter.0, bonus: chapter.1 ?? 0 main: chapter.0, bonus: chapter.1 ?? 0
), ),
nameOffset: chapterName, nameOffset: chapterName,
imagesVectorOffset: imagesOffset, imagesVectorOffset: imagesOffset
) )
currentChapters.append(chapterMetadata) currentChapters.append(chapterMetadata)
@@ -658,7 +662,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
endReleaseDate: Date(year: 2000, month: 1, day: 1), endReleaseDate: Date(year: 2000, month: 1, day: 1),
tagsVectorOffset: tags, tagsVectorOffset: tags,
descriptionOffset: description, descriptionOffset: description,
volumesVectorOffset: volumeOffsets, volumesVectorOffset: volumeOffsets
) )
builder.finish(offset: metadata) builder.finish(offset: metadata)
@@ -702,7 +706,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
progress: newProgress, backgroundColor: self.convertColorToString(color: color) progress: newProgress, backgroundColor: self.convertColorToString(color: color)
) )
).write( ).write(
to: self.currentPath.appendingPathComponent("state.json")) to: self.currentPath.appendingPathComponent("state.json")
)
} catch { } catch {
print("failed to save local state") print("failed to save local state")
} }
@@ -1037,25 +1042,33 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
equalTo: topBarView.leadingAnchor, constant: 32 equalTo: topBarView.leadingAnchor, constant: 32
), ),
backgroundColorDropdownButton.centerYAnchor.constraint( backgroundColorDropdownButton.centerYAnchor.constraint(
equalTo: topBarView.centerYAnchor), equalTo: topBarView.centerYAnchor
),
backgroundColorDropdownButton.topAnchor.constraint( backgroundColorDropdownButton.topAnchor.constraint(
equalTo: topBarView.topAnchor), equalTo: topBarView.topAnchor
),
backgroundColorDropdownButton.bottomAnchor.constraint( backgroundColorDropdownButton.bottomAnchor.constraint(
equalTo: topBarView.bottomAnchor), equalTo: topBarView.bottomAnchor
),
backgroundColorDropdownView.topAnchor.constraint( backgroundColorDropdownView.topAnchor.constraint(
equalTo: backgroundColorDropdownButton.bottomAnchor), equalTo: backgroundColorDropdownButton.bottomAnchor
),
backgroundColorDropdownView.leadingAnchor.constraint( backgroundColorDropdownView.leadingAnchor.constraint(
equalTo: backgroundColorDropdownButton.leadingAnchor), equalTo: backgroundColorDropdownButton.leadingAnchor
),
backgroundColorDropdownView.trailingAnchor.constraint( backgroundColorDropdownView.trailingAnchor.constraint(
equalTo: backgroundColorDropdownButton.trailingAnchor), equalTo: backgroundColorDropdownButton.trailingAnchor
),
stackView.topAnchor.constraint( stackView.topAnchor.constraint(
equalTo: backgroundColorDropdownView.topAnchor), equalTo: backgroundColorDropdownView.topAnchor
),
stackView.bottomAnchor.constraint(equalTo: backgroundColorDropdownView.bottomAnchor), stackView.bottomAnchor.constraint(equalTo: backgroundColorDropdownView.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: backgroundColorDropdownView.leadingAnchor), stackView.leadingAnchor.constraint(equalTo: backgroundColorDropdownView.leadingAnchor),
stackView.trailingAnchor.constraint( stackView.trailingAnchor.constraint(
equalTo: backgroundColorDropdownView.trailingAnchor), equalTo: backgroundColorDropdownView.trailingAnchor
),
]) ])
} }
@@ -1103,25 +1116,33 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
equalTo: backgroundColorDropdownButton.trailingAnchor, constant: 32 equalTo: backgroundColorDropdownButton.trailingAnchor, constant: 32
), ),
pageTurnDropdownButton.centerYAnchor.constraint( pageTurnDropdownButton.centerYAnchor.constraint(
equalTo: topBarView.centerYAnchor), equalTo: topBarView.centerYAnchor
),
pageTurnDropdownButton.topAnchor.constraint( pageTurnDropdownButton.topAnchor.constraint(
equalTo: topBarView.topAnchor), equalTo: topBarView.topAnchor
),
pageTurnDropdownButton.bottomAnchor.constraint( pageTurnDropdownButton.bottomAnchor.constraint(
equalTo: topBarView.bottomAnchor), equalTo: topBarView.bottomAnchor
),
pageTurnDropdownView.topAnchor.constraint( pageTurnDropdownView.topAnchor.constraint(
equalTo: pageTurnDropdownButton.bottomAnchor), equalTo: pageTurnDropdownButton.bottomAnchor
),
pageTurnDropdownView.leadingAnchor.constraint( pageTurnDropdownView.leadingAnchor.constraint(
equalTo: pageTurnDropdownButton.leadingAnchor), equalTo: pageTurnDropdownButton.leadingAnchor
),
pageTurnDropdownView.trailingAnchor.constraint( pageTurnDropdownView.trailingAnchor.constraint(
equalTo: pageTurnDropdownButton.trailingAnchor), equalTo: pageTurnDropdownButton.trailingAnchor
),
stackView.topAnchor.constraint( stackView.topAnchor.constraint(
equalTo: pageTurnDropdownView.topAnchor), equalTo: pageTurnDropdownView.topAnchor
),
stackView.bottomAnchor.constraint(equalTo: pageTurnDropdownView.bottomAnchor), stackView.bottomAnchor.constraint(equalTo: pageTurnDropdownView.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: pageTurnDropdownView.leadingAnchor), stackView.leadingAnchor.constraint(equalTo: pageTurnDropdownView.leadingAnchor),
stackView.trailingAnchor.constraint( stackView.trailingAnchor.constraint(
equalTo: pageTurnDropdownView.trailingAnchor), equalTo: pageTurnDropdownView.trailingAnchor
),
]) ])
} }
@@ -1378,9 +1399,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
func getImagePath(_ progress: ProgressIndices) -> String { func getImagePath(_ progress: ProgressIndices) -> String {
let (v, c, i) = (progress.v, progress.c, progress.i) let (v, c, i) = (progress.v, progress.c, progress.i)
let modernPath = metadata.volumes[v].chapters[c].images[i].filename! return metadata.volumes[v].chapters[c].images[i].filename!
// if fileManager.fileExists(atPath: modernPath.path) { // if fileManager.fileExists(atPath: modernPath.path) {
return modernPath
// } // }
// return nil // return nil
} }
@@ -1445,7 +1465,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
archiveURL: getArchiveURL(newProgress.v), archiveURL: getArchiveURL(newProgress.v),
filename: getImagePath(newProgress), filename: getImagePath(newProgress),
scaling: scaling, scaling: scaling,
screenSize: UIScreen.main.bounds.size, screenSize: UIScreen.main.bounds.size
) )
} }
} }
@@ -1471,7 +1491,8 @@ func getGlobalState() -> GlobalState {
let json = try Data( let json = try Data(
String( String(
contentsOfFile: path contentsOfFile: path
).utf8) ).utf8
)
return try JSONDecoder().decode(GlobalState.self, from: json) return try JSONDecoder().decode(GlobalState.self, from: json)
} catch { } catch {
print("Error reading directory contents: \(error)") print("Error reading directory contents: \(error)")
@@ -1538,14 +1559,14 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
} }
var newProgress = ProgressIndices(v: 0, c: 0, i: 0) var newProgress = ProgressIndices(v: 0, c: 0, i: 0)
if indexPath.item != 0 { if indexPath.item != 0 {
for _ in 0 ..< indexPath.item { for _ in 0 ..< indexPath.item {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
} }
} }
imageLoader.loadImage( imageLoader.loadImage(
archiveURL: getArchiveURL(progress.v), archiveURL: getArchiveURL(progress.v),
filename: getImagePath(newProgress), filename: getImagePath(newProgress),
scaling: scaling, screenSize: UIScreen.main.bounds.size scaling: scaling, screenSize: UIScreen.main.bounds.size
) { image in ) { image in
cell.imageView.image = image cell.imageView.image = image
} }
@@ -1579,7 +1600,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
} }
var newProgress = ProgressIndices(v: 0, c: 0, i: 0) var newProgress = ProgressIndices(v: 0, c: 0, i: 0)
for _ in 0 ..< indexPath.item { for _ in 0 ..< indexPath.item {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
} }
let imageMetadata = metadata.volumes[newProgress.v].chapters[newProgress.c].images[newProgress.i] let imageMetadata = metadata.volumes[newProgress.v].chapters[newProgress.c].images[newProgress.i]
return CGSize( return CGSize(
@@ -1767,7 +1788,7 @@ class ImageLoader {
orientation: rotation, orientation: rotation,
screenSize: screenSize, screenSize: screenSize,
scaling: scaling, scaling: scaling,
filename: key, filename: key
) )
if token.isCancelled { finish(); return } if token.isCancelled { finish(); return }
@@ -1781,14 +1802,14 @@ class ImageLoader {
orientation: otherOrientation, orientation: otherOrientation,
screenSize: screenSize, screenSize: screenSize,
scaling: scaling, scaling: scaling,
filename: key, filename: key
) )
finish() finish()
} }
} }
func scaleAndCacheImage(image: UIImage, orientation: Rotation, screenSize: CGSize, scaling: UIView.ContentMode, filename: String) -> UIImage { func scaleAndCacheImage(image: UIImage, orientation: Rotation, screenSize: CGSize, scaling: UIView.ContentMode, filename: String) -> UIImage {
let resizeTo: CGSize = if orientation == .vertical { let resizeTo = if orientation == .vertical {
CGSize( CGSize(
width: min(screenSize.width, screenSize.height), width: min(screenSize.width, screenSize.height),
height: max(screenSize.height, screenSize.width) height: max(screenSize.height, screenSize.width)
@@ -1802,17 +1823,17 @@ class ImageLoader {
let scaledSize = aspectSize(for: image.size, in: resizeTo, scaling: scaling) let scaledSize = aspectSize(for: image.size, in: resizeTo, scaling: scaling)
let scaledImage = resizeImage(image, to: scaledSize) let scaledImage = resizeImage(image, to: scaledSize)
let cost = imageByteSize(scaledImage)! let cost = imageByteSize(scaledImage)!
switch ((orientation, scaling)) { switch (orientation, scaling) {
case (.vertical, .scaleAspectFit): case (.vertical, .scaleAspectFit):
self.portraitFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost) portraitFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
case (.vertical, .scaleAspectFill): case (.vertical, .scaleAspectFill):
self.portraitFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost) portraitFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
case (.horizontal, .scaleAspectFit): case (.horizontal, .scaleAspectFit):
self.landscapeFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost) landscapeFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
case (.horizontal, .scaleAspectFill): case (.horizontal, .scaleAspectFill):
self.landscapeFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost) landscapeFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
default: default:
assertionFailure() assertionFailure()
} }
return scaledImage return scaledImage
} }
@@ -1897,12 +1918,13 @@ func imageDimensions(of entry: Entry, in archive: Archive) throws -> ImageDimens
if let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any], if let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
let width = properties[kCGImagePropertyPixelWidth as String] as? Int, let width = properties[kCGImagePropertyPixelWidth as String] as? Int,
let height = properties[kCGImagePropertyPixelHeight as String] as? Int { let height = properties[kCGImagePropertyPixelHeight as String] as? Int
{
// We have what we need abort the rest of the decompression // We have what we need abort the rest of the decompression
throw ImageSizeResult.found(ImageDimensions(width: width, height: height)) throw ImageSizeResult.found(ImageDimensions(width: width, height: height))
} }
} }
} catch ImageSizeResult.found(let dimensions) { } catch let ImageSizeResult.found(dimensions) {
return dimensions return dimensions
} }
@@ -1910,7 +1932,8 @@ func imageDimensions(of entry: Entry, in archive: Archive) throws -> ImageDimens
CGImageSourceUpdateData(imageSource, accumulatedData as CFData, true) CGImageSourceUpdateData(imageSource, accumulatedData as CFData, true)
if let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any], if let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
let width = properties[kCGImagePropertyPixelWidth as String] as? Int, let width = properties[kCGImagePropertyPixelWidth as String] as? Int,
let height = properties[kCGImagePropertyPixelHeight as String] as? Int { let height = properties[kCGImagePropertyPixelHeight as String] as? Int
{
print("This fell back to loading the complete image for size metadata") print("This fell back to loading the complete image for size metadata")
return ImageDimensions(width: width, height: height) return ImageDimensions(width: width, height: height)
} }