Compare commits

..

2 Commits

Author SHA1 Message Date
vegardbm c1bb06a1b4 feat: reintroduce scrolling 2026-07-09 22:56:57 +02:00
vegardbm a5c3a6d96e add flatbuffers as submodule dependency 2026-07-08 01:32:13 +02:00
6 changed files with 206 additions and 415 deletions
+2 -33
View File
@@ -16,9 +16,6 @@
840F62692E7B65B700C8A64A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840F62682E7B65B700C8A64A /* AppDelegate.swift */; }; 840F62692E7B65B700C8A64A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840F62682E7B65B700C8A64A /* AppDelegate.swift */; };
840F626D2E7B65B700C8A64A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840F626C2E7B65B700C8A64A /* ViewController.swift */; }; 840F626D2E7B65B700C8A64A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840F626C2E7B65B700C8A64A /* ViewController.swift */; };
840F62722E7B65B900C8A64A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 840F62712E7B65B900C8A64A /* Assets.xcassets */; }; 840F62722E7B65B900C8A64A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 840F62712E7B65B900C8A64A /* Assets.xcassets */; };
9A77032C300317E4004A0222 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 9A77032B300317E4004A0222 /* ZIPFoundation */; };
9A77032F30031A32004A0222 /* FlatBuffers in Frameworks */ = {isa = PBXBuildFile; productRef = 9A77032E30031A32004A0222 /* FlatBuffers */; };
9A77033130031A32004A0222 /* FlexBuffers in Frameworks */ = {isa = PBXBuildFile; productRef = 9A77033030031A32004A0222 /* FlexBuffers */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@@ -39,10 +36,7 @@
22EF4C5B2FFDC39600852A4F /* FlatBuffers in Frameworks */, 22EF4C5B2FFDC39600852A4F /* FlatBuffers in Frameworks */,
22B015A72F82A2DC0088F94F /* FlatBuffers in Frameworks */, 22B015A72F82A2DC0088F94F /* FlatBuffers in Frameworks */,
22B015A92F82A2DC0088F94F /* FlexBuffers in Frameworks */, 22B015A92F82A2DC0088F94F /* FlexBuffers in Frameworks */,
9A77032F30031A32004A0222 /* FlatBuffers in Frameworks */,
22EF4C5D2FFDC39600852A4F /* FlexBuffers in Frameworks */, 22EF4C5D2FFDC39600852A4F /* FlexBuffers in Frameworks */,
9A77033130031A32004A0222 /* FlexBuffers in Frameworks */,
9A77032C300317E4004A0222 /* ZIPFoundation in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -123,8 +117,7 @@
); );
mainGroup = 840F625C2E7B65B700C8A64A; mainGroup = 840F625C2E7B65B700C8A64A;
packageReferences = ( packageReferences = (
9A77032A300317E4004A0222 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, 22EF4C592FFDC39600852A4F /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */,
9A77032D30031A32004A0222 /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */,
); );
productRefGroup = 840F62662E7B65B700C8A64A /* Products */; productRefGroup = 840F62662E7B65B700C8A64A /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -359,23 +352,12 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */ /* Begin XCLocalSwiftPackageReference section */
9A77032D30031A32004A0222 /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */ = { 22EF4C592FFDC39600852A4F /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */ = {
isa = XCLocalSwiftPackageReference; isa = XCLocalSwiftPackageReference;
relativePath = dependencies/flatbuffers; relativePath = dependencies/flatbuffers;
}; };
/* End XCLocalSwiftPackageReference section */ /* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
9A77032A300317E4004A0222 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/weichsel/ZIPFoundation.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.9.20;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
22B015A62F82A2DC0088F94F /* FlatBuffers */ = { 22B015A62F82A2DC0088F94F /* FlatBuffers */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
@@ -393,19 +375,6 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = FlexBuffers; productName = FlexBuffers;
}; };
9A77032B300317E4004A0222 /* ZIPFoundation */ = {
isa = XCSwiftPackageProductDependency;
package = 9A77032A300317E4004A0222 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
productName = ZIPFoundation;
};
9A77032E30031A32004A0222 /* FlatBuffers */ = {
isa = XCSwiftPackageProductDependency;
productName = FlatBuffers;
};
9A77033030031A32004A0222 /* FlexBuffers */ = {
isa = XCSwiftPackageProductDependency;
productName = FlexBuffers;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 840F625D2E7B65B700C8A64A /* Project object */; rootObject = 840F625D2E7B65B700C8A64A /* Project object */;
@@ -1,15 +0,0 @@
{
"originHash" : "1d2ea13999643e07c5cb84a07c2cd10221ddf2714932691ba7147378d56f57ac",
"pins" : [
{
"identity" : "zipfoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/weichsel/ZIPFoundation.git",
"state" : {
"revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d",
"version" : "0.9.20"
}
}
],
"version" : 3
}
+183 -338
View File
@@ -1,11 +1,11 @@
// TODO: Anilist support? // TODO: Anilist support?
// TODO: Properly avoid swallowing of input from UICollectionView used for scrolling // TODO: Properly avoid swallowing of input from UICollectionView used for scrolling
// TODO: Convert between state for normal and scrolling page turn // TODO: Convert between state for normal and scrolling page turn
// TODO: Support reading with scrolling and landscape mode
import FlatBuffers
import Foundation import Foundation
import FlatBuffers
import UIKit import UIKit
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.
@@ -182,7 +182,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
for dir in directories { for dir in directories {
if !fileManager.fileExists(atPath: dir.appendingPathComponent(metadataFilename).path) { if !fileManager.fileExists(atPath: dir.appendingPathComponent(metadataFilename).path) {
getMetadataFromFilename(path: dir) getMetadataFromFileName(path: dir)
} }
currentPath = dir currentPath = dir
@@ -192,22 +192,15 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
metadata = try! getCheckedRoot(byteBuffer: &byteBuffer) metadata = try! getCheckedRoot(byteBuffer: &byteBuffer)
metadataList[dir] = metadata metadataList[dir] = metadata
loadLocalState() loadLocalState()
let volCovP = ProgressIndices(v: progress.v, c: 0, i: 0)
let archivePath = getArchiveURL(volCovP.v)
let archive = try! Archive(url: archivePath, accessMode: .read)
let filename = metadata.volumes[volCovP.v].chapters[volCovP.c].images[volCovP.i].filename!
let targetEntry = archive[filename]!
var extractedData = Data()
_ = try archive.extract(targetEntry) { data in
extractedData.append(data)
}
comics.append( comics.append(
Comic( Comic(
cover: UIImage(data: extractedData)!, cover: UIImage(contentsOfFile:
getImagePath(progress: ProgressIndices(v: progress.v, c: 0, i: 0)).path
)!,
metadata: metadata, metadata: metadata,
path: dir path: dir
) ))
)
} }
} catch { } catch {
print("Failed to read directories") print("Failed to read directories")
@@ -263,10 +256,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return CGSize(width: width, height: height) return CGSize(width: width, height: height)
} }
func getMetadataFromFilename(path: URL) { func getMetadataFromFileName(path: URL) {
// Beautiful, is it not? // Beautiful, is it not?
let patterns: [(NSRegularExpression, NSRegularExpression, Int, Int, Int)] = [ let patterns: [(NSRegularExpression, NSRegularExpression, Int, Int, Int)] = [
(try! NSRegularExpression(pattern: #"c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-p?[0-9]+)?)"#), try! NSRegularExpression(pattern: #"^(.*) - c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-p?[0-9]+)?)"#), 3, 1, 2), (try! NSRegularExpression(pattern: #"c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-[0-9]+)?)"#), try! NSRegularExpression(pattern: #"^(.*) - c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-[0-9]+)?)"#), 3, 1, 2),
(try! NSRegularExpression(pattern: "v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)"), try! NSRegularExpression(pattern: "^(.*) - v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)"), 2, 3, 1), (try! NSRegularExpression(pattern: "v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)"), try! NSRegularExpression(pattern: "^(.*) - v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)"), 2, 3, 1),
] ]
var pattern: NSRegularExpression! = nil var pattern: NSRegularExpression! = nil
@@ -291,48 +284,31 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var volumes: [Offset] = [] var volumes: [Offset] = []
var currentChapters: [Offset] = [] var currentChapters: [Offset] = []
var currentImages: [Offset] = [] var currentImages: [Offset] = []
var previousVolumeURL = ""
var titleValue: String = "" var titleValue: String = ""
let volumeDir = path.appendingPathComponent("volumes")
var isDir: ObjCBool = false
if !fileManager.fileExists(atPath: volumeDir.path, isDirectory: &isDir) || !isDir.boolValue {
print("Subdirectory volumes not found in \"\(path)\", returning")
return
}
let volumeURLs = try! fileManager.contentsOfDirectory(
at: volumeDir,
includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles]
).filter { $0.pathExtension == "cbz" }
.sorted { $0.lastPathComponent < $1.lastPathComponent }
if volumeURLs.isEmpty {
print("found no volumes in volume dir: \(volumeDir)")
return
}
var filenames: [(String, String)] = []
for volumeURL in volumeURLs {
do {
let volumeArchive = try Archive(url: volumeURL, accessMode: .read)
volumeArchive.map { URL(string: $0.path)!.lastPathComponent }.sorted().forEach{ filenames.append((volumeURL.lastPathComponent, $0)) }
} catch {
print("Failed to read archive \(volumeURL)")
continue
}
}
if filenames.isEmpty {
print("Found no files in archives")
return
}
do { do {
let dir = path.appendingPathComponent("images")
var isDir: ObjCBool = false
if !fileManager.fileExists(atPath: dir.path, isDirectory: &isDir) || !isDir.boolValue {
print("Subdirectory images not found in \"\(path)\", returning")
return
}
let testFilename = try fileManager.contentsOfDirectory(
at: dir,
includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles]
).first
if testFilename == nil {
print("Empty images dir, returning")
return
}
for (currentPattern, currentTitlePattern, currentPageRegexPosition, currentChapterRegexPosition, currentVolumeRegexPosition) in patterns { for (currentPattern, currentTitlePattern, currentPageRegexPosition, currentChapterRegexPosition, currentVolumeRegexPosition) in patterns {
let filename = filenames[0].1 let filename = testFilename!.lastPathComponent
let range = NSRange(filename.startIndex..., in: filename) let range = NSRange(filename.startIndex..., in: filename)
if let match = currentTitlePattern.firstMatch(in: filename, range: range) { if let match = currentTitlePattern.firstMatch(in: filename, range: range) {
if (filename as NSString).substring(with: match.range(at: 1)) == "" { if (filename as NSString).substring(with: match.range(at: 1)) == "" {
@@ -376,10 +352,17 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return return
} }
let regex = pattern! let regex = pattern!
let titleRegex = titlePattern! let titleRegex = titlePattern!
for (i, (volumeURL, filename)) in filenames.enumerated() { let fileURLs = try fileManager.contentsOfDirectory(
at: dir,
includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles]
)
let filenames = fileURLs.map { $0.lastPathComponent }.sorted()
for (i, filename) in filenames.enumerated() {
let range = NSRange(filename.startIndex..., in: filename) let range = NSRange(filename.startIndex..., in: filename)
if i == 0 { if i == 0 {
if let match = titleRegex.firstMatch(in: filename, range: range) { if let match = titleRegex.firstMatch(in: filename, range: range) {
@@ -403,21 +386,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
let (page, doublePage) = ( let (page, doublePage) = (
UInt32(pageParts[0])!, pageParts.count > 1 UInt32(pageParts[0])!, pageParts.count > 1
) )
let archive = try Archive(url: volumeDir.appendingPathComponent(volumeURL), accessMode: .read) let size = imageSize(at: dir.appendingPathComponent(filename))!
let entry = archive[filename]!
var size = ImageDimensions(width: 0, height: 0)
do {
size = try imageDimensions(of: entry, in: archive)
} catch {
print("Failed to get image dimensions of filename: \(filename)")
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,
size: Size(width: UInt32(size.width), height: UInt32(size.height)) size: Size(width: UInt32(size.width), height: UInt32(size.height))
) )
@@ -427,7 +402,7 @@ 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
), ),
@@ -438,14 +413,12 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
currentChapters.append(chapterMetadata) currentChapters.append(chapterMetadata)
let volumeTitle = builder.create(string: "") let volumeTitle = builder.create(string: "")
let volumeURLOffset = builder.create(string: previousVolumeURL)
let chaptersOffset = builder.createVector(ofOffsets: currentChapters) let chaptersOffset = builder.createVector(ofOffsets: currentChapters)
let volumeMetadata = VolumeMetadata.createVolumeMetadata( let volumeMetadata = VolumeMetadata.createVolumeMetadata(
&builder, &builder,
volume: MetaValue(main: currentVolume, bonus: 0), volume: MetaValue(main: currentVolume, bonus: 0),
titleOffset: volumeTitle, titleOffset: volumeTitle,
archiveOffset: volumeURLOffset, chaptersVectorOffset: chaptersOffset
chaptersVectorOffset: chaptersOffset,
) )
volumes.append(volumeMetadata) volumes.append(volumeMetadata)
@@ -461,7 +434,7 @@ 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
), ),
@@ -482,7 +455,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
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
), ),
@@ -493,13 +466,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
currentChapters.append(chapterMetadata) currentChapters.append(chapterMetadata)
let volumeTitle = builder.create(string: "") let volumeTitle = builder.create(string: "")
let volumeURLOffset = builder.create(string: volumeURL)
let chaptersOffset = builder.createVector(ofOffsets: currentChapters) let chaptersOffset = builder.createVector(ofOffsets: currentChapters)
let volumeMetadata = VolumeMetadata.createVolumeMetadata( let volumeMetadata = VolumeMetadata.createVolumeMetadata(
&builder, &builder,
volume: MetaValue(main: volume, bonus: 0), volume: MetaValue(main: volume, bonus: 0),
titleOffset: volumeTitle, titleOffset: volumeTitle,
archiveOffset: volumeURLOffset,
chaptersVectorOffset: chaptersOffset chaptersVectorOffset: chaptersOffset
) )
@@ -510,7 +481,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
} }
currentVolume = volume currentVolume = volume
currentChapter = chapter currentChapter = chapter
previousVolumeURL = volumeURL
} else { } else {
print("no regex match on \(filename), returning") print("no regex match on \(filename), returning")
return return
@@ -696,31 +666,67 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
} }
func readComic(name: String) { func readComic(name: String) {
imageView.image = nil
readerView.isHidden = false readerView.isHidden = false
homeView.isHidden = true homeView.isHidden = true
setNeedsStatusBarAppearanceUpdate() setNeedsStatusBarAppearanceUpdate()
if let path = getPathFromComicName(name: name) { if let path = getPathFromComicName(name: name) {
currentPath = path currentPath = path
metadata = metadataList[path] metadata = metadataList[path]
globalState.comicName = metadata.title globalState.comicName = metadata.title
saveGlobalState() saveGlobalState()
loadLocalState() loadLocalState()
scrollingCollectionView.isHidden = mode != .scroll if mode != .scroll {
leftView.isHidden = mode == .scroll scrollingCollectionView.isHidden = true
rightView.isHidden = mode == .scroll leftView.isHidden = false
imageView.isHidden = mode == .scroll rightView.isHidden = false
if mode == .scroll { setImages(path: path)
scrollingCollectionView.reloadData()
} else { } else {
setImages() setupScrolling()
} }
} else { } else {
print("path not found") print("path not found")
} }
} }
func setupScrolling() {
leftView.isHidden = true
rightView.isHidden = true
scrollingCollectionView.isHidden = false
scrollingCollectionView.reloadData()
}
func countFiles() -> Int {
var count = 0
if let enumerator = fileManager.enumerator(
at: currentPath, includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsHiddenFiles]
) {
for case let dir as URL in enumerator {
do {
if let enumerator = fileManager.enumerator(
at: dir, includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles]
) {
for case let file as URL in enumerator {
let resourceValues = try file.resourceValues(forKeys: [
.isRegularFileKey,
])
if resourceValues.isRegularFile == true {
count += 1
}
}
}
} catch {
print("Error reading file attributes for \(dir):", error)
}
}
}
return count
}
func setupScrollingCollectionView() { func setupScrollingCollectionView() {
let layout = UICollectionViewFlowLayout() let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0 layout.minimumInteritemSpacing = 0
@@ -1007,11 +1013,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
default: break default: break
} }
if prev == mode { return } if prev == mode { return }
if mode != .scroll { if mode == .scroll {
let archiveURL = getArchiveURL(progress.v) setupScrolling()
} else {
imageLoader.loadImage( imageLoader.loadImage(
archiveURL: archiveURL, at: getImagePath(progress: progress),
filename: getImagePath(progress),
scaling: .scaleAspectFit, scaling: .scaleAspectFit,
screenSize: UIScreen.main.bounds.size screenSize: UIScreen.main.bounds.size
) { [weak self] image in ) { [weak self] image in
@@ -1023,10 +1029,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
leftView.isHidden = mode == .scroll leftView.isHidden = mode == .scroll
rightView.isHidden = mode == .scroll rightView.isHidden = mode == .scroll
imageView.isHidden = mode == .scroll imageView.isHidden = mode == .scroll
// This does not optmize for the case where you switch between scrolling and non-scrolling in the same comic. However this badly supported because of the lac of state conversion.
if mode == .scroll {
scrollingCollectionView.reloadData()
}
togglePageTurnDropdown() togglePageTurnDropdown()
saveLocalState() saveLocalState()
} }
@@ -1154,28 +1156,16 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return return
} }
progress = newProgress progress = newProgress
updateInfo() let path = getImagePath(progress: progress)
let path = getImagePath(progress) imageLoader.loadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size) { [weak self] image in
self?.imageView.image = image
imageLoader.setActiveWindow([path]) self?.updateInfo()
}
imageLoader.loadImage(
archiveURL: getArchiveURL(progress.v),
filename: getImagePath(progress),
scaling: scaling,
screenSize: UIScreen.main.bounds.size
) { [weak self] image in
self?.imageView.image = image
}
for _ in 0 ..< preloadCount { for _ in 0 ..< preloadCount {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
imageLoader.preloadImage( let path = getImagePath(progress: newProgress)
archiveURL: getArchiveURL(newProgress.v), imageLoader.preloadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size)
filename: getImagePath(newProgress),
scaling: scaling,
screenSize: UIScreen.main.bounds.size
)
} }
saveLocalState() saveLocalState()
@@ -1240,17 +1230,16 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
Chapter \(chapterString) of \(lastChapterString) Chapter \(chapterString) of \(lastChapterString)
Page \(pageString) of \(lastPageString) Page \(pageString) of \(lastPageString)
""" """
text += "\nImage size: \(metadata.volumes[progress.v].chapters[progress.c].images[progress.i].size!.width)x\(metadata.volumes[progress.v].chapters[progress.c].images[progress.i].size!.height)" if let size = imageLoader.size[getImagePath(progress: progress).path] {
text += "\nImage size: \(Int(size.width))x\(Int(size.height))"
}
info.text = text info.text = text
} }
func getArchiveURL(_ volumeProgress: Int) -> URL { func getImagePath(progress: ProgressIndices) -> URL {
return currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[volumeProgress].archive)
}
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! let modernPath = currentPath!.appendingPathComponent("images").appendingPathComponent(metadata.volumes[v].chapters[c].images[i].filename)
// print("trying to get path \(modernPath)")
// if fileManager.fileExists(atPath: modernPath.path) { // if fileManager.fileExists(atPath: modernPath.path) {
return modernPath return modernPath
// } // }
@@ -1298,27 +1287,25 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return metadata return metadata
} }
func setImages() { func setImages(path _: URL) {
let scaling: UIView.ContentMode = mode == .scroll ? .scaleAspectFill : .scaleAspectFit let scaling: UIView.ContentMode!
if mode == .scroll {
scaling = UIView.ContentMode.scaleAspectFill
} else { scaling = UIView.ContentMode.scaleAspectFit }
imageLoader.loadImage( imageLoader.loadImage(
archiveURL: getArchiveURL(progress.v), at:
filename: getImagePath(progress), getImagePath(progress: progress),
scaling: scaling, scaling: scaling,
screenSize: UIScreen.main.bounds.size screenSize: UIScreen.main.bounds.size
) { [weak self] image in ) { [weak self] image in
self?.imageView.image = image self?.imageView.image = image
self?.updateInfo()
} }
updateInfo()
var newProgress = progress var newProgress = progress
for _ in 0 ..< preloadCount { for _ in 0 ..< preloadCount {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
imageLoader.preloadImage( imageLoader.preloadImage(at: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size)
archiveURL: getArchiveURL(newProgress.v),
filename: getImagePath(newProgress),
scaling: scaling,
screenSize: UIScreen.main.bounds.size,
)
} }
} }
@@ -1337,14 +1324,14 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
super.viewWillTransition(to: size, with: coordinator) super.viewWillTransition(to: size, with: coordinator)
if mode != .scroll { if mode != .scroll {
imageLoader.loadImage( imageLoader.loadImage(
archiveURL: getArchiveURL(progress.v), at: getImagePath(progress: progress),
filename: getImagePath(progress),
scaling: .scaleAspectFit, scaling: .scaleAspectFit,
screenSize: size screenSize: size
) { image in ) { image in
self.imageView.image = image self.imageView.image = image
} }
} else { } else {
scrollingCollectionView.reloadData()
changedOrientation = true changedOrientation = true
} }
} }
@@ -1388,10 +1375,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
{ {
if collectionView == comicCollectionView { if collectionView == comicCollectionView {
return comics.count return comics.count
} else if collectionView == scrollingCollectionView { } else {
if metadata == nil {
return 0
}
var sum = 0 var sum = 0
for volume in metadata.volumes { for volume in metadata.volumes {
for chapter in volume.chapters { for chapter in volume.chapters {
@@ -1400,7 +1384,6 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
} }
return sum return sum
} }
return 0
} }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
@@ -1421,7 +1404,6 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
withReuseIdentifier: "ScrollingImageCell", for: indexPath withReuseIdentifier: "ScrollingImageCell", for: indexPath
) )
as! ScrollingImageCell as! ScrollingImageCell
if mode != .scroll { return cell }
if metadata == nil { if metadata == nil {
print("metadata is nil, should probably not be the case") print("metadata is nil, should probably not be the case")
return cell return cell
@@ -1432,11 +1414,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
} }
} }
imageLoader.loadImage( imageLoader.loadImage(at: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size) { image in
archiveURL: getArchiveURL(progress.v),
filename: getImagePath(newProgress),
scaling: scaling, screenSize: UIScreen.main.bounds.size
) { image in
cell.imageView.image = image cell.imageView.image = image
} }
return cell return cell
@@ -1550,169 +1528,97 @@ enum Rotation {
class ImageLoader { class ImageLoader {
private let cache = NSCache<NSString, UIImage>() private let cache = NSCache<NSString, UIImage>()
private let portraitFitCache = NSCache<NSString, UIImage>() private let horCache = NSCache<NSString, UIImage>()
private let landscapeFitCache = NSCache<NSString, UIImage>() private var loadingTasks: [String: [(UIImage?) -> Void]] = [:]
private let portraitFillCache = NSCache<NSString, UIImage>() var size: [String: CGSize] = [:]
private let landscapeFillCache = NSCache<NSString, UIImage>()
private var loadingTasks: [String: (UIImage?) -> Void] = [:]
private var activeTokens: [String: CancellationToken] = [:]
private let archives = NSCache<NSString, Archive>()
init() { init() {
// 128 MiB // 128 MiB
cache.totalCostLimit = 128 * 1024 * 1024 cache.totalCostLimit = 128 * 1024 * 1024
portraitFitCache.totalCostLimit = 128 * 1024 * 1024
landscapeFitCache.totalCostLimit = 128 * 1024 * 1024
portraitFillCache.totalCostLimit = 128 * 1024 * 1024
landscapeFillCache.totalCostLimit = 128 * 1024 * 1024
// The reason archives are cached and have a strict limit of four, is because Archive supposedly opens a posix file pointer and keeps it until the object is destroyed.
archives.countLimit = 4
} }
func preloadImage(archiveURL: URL, filename: String, scaling: UIView.ContentMode, screenSize: CGSize) { func preloadImage(at path: URL, scaling: UIView.ContentMode, screenSize: CGSize) {
loadImage(archiveURL: archiveURL, filename: filename, scaling: scaling, screenSize: screenSize, completion: nil) loadImage(at: path, scaling: scaling, screenSize: screenSize, completion: nil)
} }
func loadImage( func loadImage(
archiveURL: URL, filename: String, at path: URL, scaling: UIView.ContentMode,
scaling: UIView.ContentMode,
screenSize: CGSize, screenSize: CGSize,
completion: ((UIImage?) -> Void)? completion: ((UIImage?) -> Void)?
) { ) {
let rotation: Rotation = screenSize.width > screenSize.height ? .horizontal : .vertical let rotation: Rotation!
let key = filename if screenSize.width > screenSize.height {
rotation = Rotation.horizontal
} else { rotation = Rotation.vertical }
if scaling == .scaleAspectFit { if rotation == .vertical {
if rotation == .vertical, let cached = portraitFitCache.object(forKey: key as NSString) { if let cached = cache.object(forKey: path.path as NSString) {
completion?(cached); return completion?(cached)
return
} }
if rotation == .horizontal, let cached = landscapeFitCache.object(forKey: key as NSString) { } else {
completion?(cached); return if let cached = horCache.object(forKey: path.path as NSString) {
completion?(cached)
return
} }
} else if scaling == .scaleAspectFill {
if rotation == .vertical, let cached = portraitFillCache.object(forKey: key as NSString) {
completion?(cached); return
}
if rotation == .horizontal, let cached = landscapeFillCache.object(forKey: key as NSString) {
completion?(cached); return
}
} else { assertionFailure() }
if let completion = completion {
loadingTasks[key] = completion
} }
if let existing = activeTokens[key], !existing.isCancelled {
if loadingTasks[path.path] != nil {
if let completion = completion {
loadingTasks[path.path]?.append(completion)
}
return return
} }
let token = CancellationToken() loadingTasks[path.path] = completion != nil ? [completion!] : []
activeTokens[key] = token
queue.async { [weak self] in queue.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
guard var image = UIImage(contentsOfFile: path.path) else { return }
self.size[path.path] = image.size
func finish() { // If you turn pages fast, completion will not be nil and as such only the needed scaled image should be prepared
DispatchQueue.main.async { if completion == nil || rotation == .vertical {
guard self.activeTokens[key] === token else { return } let vertical = CGSize(
self.activeTokens.removeValue(forKey: key) width: min(screenSize.width, screenSize.height),
self.loadingTasks.removeValue(forKey: key) height: max(screenSize.height, screenSize.width)
} )
} let vScaledSize = aspectSize(
func setImage(_ image: UIImage) { for: image.size, in: vertical, scaling: scaling
DispatchQueue.main.async { )
if let callback = self.loadingTasks[key] { let vScaleImage = resizeImage(image, to: vScaledSize)
callback(image) guard let cost = imageByteSize(vScaleImage) else { return }
} self.cache.setObject(vScaleImage, forKey: path.path as NSString, cost: cost)
if rotation == .vertical {
image = vScaleImage
} }
} }
if token.isCancelled { finish(); return } if completion == nil || rotation == .horizontal {
let horizontal = CGSize(
func extractData() -> Data { width: max(screenSize.width, screenSize.height),
var extractedData = Data() height: min(screenSize.height, screenSize.width)
if self.archives.object(forKey: archiveURL.lastPathComponent as NSString) == nil { )
let archive = try! Archive(url: archiveURL, accessMode: .read) let hScaledSize = aspectSize(
self.archives.setObject(archive, forKey: archiveURL.lastPathComponent as NSString) for: image.size, in: horizontal, scaling: scaling
)
let hScaleImage = resizeImage(image, to: hScaledSize)
guard let cost = imageByteSize(hScaleImage) else { return }
self.horCache.setObject(hScaleImage, forKey: path.path as NSString, cost: cost)
if rotation == .horizontal {
image = hScaleImage
} }
let archive = self.archives.object(forKey: archiveURL.lastPathComponent as NSString)!
_ = try! archive.extract(archive[filename]!) { data in
extractedData.append(data)
}
return extractedData
} }
if token.isCancelled { finish(); return } DispatchQueue.main.async {
self.loadingTasks[path.path]?.forEach { $0(image) }
guard let image = cache.object(forKey: key as NSString) ?? UIImage(data: extractData()) else { self.loadingTasks.removeValue(forKey: path.path)
finish()
return
} }
self.cache.setObject(image, forKey: filename as NSString, cost: imageByteSize(image)!)
if token.isCancelled { finish(); return }
let scaledImage = scaleAndCacheImage(
image: image,
orientation: rotation,
screenSize: screenSize,
scaling: scaling,
filename: key,
)
if token.isCancelled { finish(); return }
setImage(scaledImage)
let otherOrientation: Rotation = rotation == .vertical ? .horizontal : .vertical
_ = scaleAndCacheImage(
image: image,
orientation: otherOrientation,
screenSize: screenSize,
scaling: scaling,
filename: key,
)
finish()
} }
} }
func scaleAndCacheImage(image: UIImage, orientation: Rotation, screenSize: CGSize, scaling: UIView.ContentMode, filename: String) -> UIImage { func imageIfLoaded(at path: String) -> UIImage? {
let resizeTo: CGSize = if orientation == .vertical { return cache.object(forKey: path as NSString)
CGSize(
width: min(screenSize.width, screenSize.height),
height: max(screenSize.height, screenSize.width)
)
} else {
CGSize(
width: max(screenSize.width, screenSize.height),
height: min(screenSize.height, screenSize.width)
)
}
let scaledSize = aspectSize(for: image.size, in: resizeTo, scaling: scaling)
let scaledImage = resizeImage(image, to: scaledSize)
let cost = imageByteSize(scaledImage)!
switch ((orientation, scaling)) {
case (.vertical, .scaleAspectFit):
self.portraitFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
case (.vertical, .scaleAspectFill):
self.portraitFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
case (.horizontal, .scaleAspectFit):
self.landscapeFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
case (.horizontal, .scaleAspectFill):
self.landscapeFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
default:
assertionFailure()
}
return scaledImage
}
func setActiveWindow(_ paths: [String]) {
let desired = Set(paths)
for (key, token) in activeTokens where !desired.contains(key) {
token.cancel()
loadingTasks[key] = nil
}
} }
} }
@@ -1746,64 +1652,3 @@ func aspectSize(for imageSize: CGSize, in boundingSize: CGSize, scaling: UIView.
height: imageSize.height * scale height: imageSize.height * scale
) )
} }
final class CancellationToken {
private var _isCancelled = false
private let lock = NSLock()
var isCancelled: Bool {
lock.lock(); defer { lock.unlock() }
return _isCancelled
}
func cancel() {
lock.lock(); defer { lock.unlock() }
_isCancelled = true
}
}
struct ImageDimensions {
let width: Int
let height: Int
}
enum ImageSizeResult: Error {
case found(ImageDimensions)
}
enum ImageSizeError: Error {
case couldNotDetermineSize
}
func imageDimensions(of entry: Entry, in archive: Archive) throws -> ImageDimensions {
let imageSource = CGImageSourceCreateIncremental(nil)
var accumulatedData = Data()
do {
// This runs until the closure throws, or the entry is fully consumed
_ = try archive.extract(entry) { chunk in
accumulatedData.append(chunk)
CGImageSourceUpdateData(imageSource, accumulatedData as CFData, false)
if let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
let width = properties[kCGImagePropertyPixelWidth as String] as? Int,
let height = properties[kCGImagePropertyPixelHeight as String] as? Int {
// We have what we need abort the rest of the decompression
throw ImageSizeResult.found(ImageDimensions(width: width, height: height))
}
}
} catch ImageSizeResult.found(let dimensions) {
return dimensions
}
// Fallback: some formats only report size once fully loaded (rare, but be safe)
CGImageSourceUpdateData(imageSource, accumulatedData as CFData, true)
if let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
let width = properties[kCGImagePropertyPixelWidth as String] as? Int,
let height = properties[kCGImagePropertyPixelHeight as String] as? Int {
print("This fell back to loading the complete image for size metadata")
return ImageDimensions(width: width, height: height)
}
throw ImageSizeError.couldNotDetermineSize
}
+3 -4
View File
@@ -31,19 +31,18 @@ table ImageMetadata {
double_page:bool; double_page:bool;
filename:string (required); filename:string (required);
first_page:uint; first_page:uint;
size:Size (required); size:Size;
} }
table ChapterMetadata { table ChapterMetadata {
chapter:MetaValue (required); chapter:MetaValue;
name:string; name:string;
images:[ImageMetadata]; images:[ImageMetadata];
} }
table VolumeMetadata { table VolumeMetadata {
volume:MetaValue (required); volume:MetaValue;
title:string; title:string;
archive:string (required);
chapters:[ChapterMetadata]; chapters:[ChapterMetadata];
} }
+17 -24
View File
@@ -291,21 +291,21 @@ public struct ImageMetadata: FlatBufferTable, FlatbuffersVectorInitializable, Ve
public var filename: String! { let o = _accessor.offset(VT.filename); return _accessor.string(at: o) } public var filename: String! { let o = _accessor.offset(VT.filename); return _accessor.string(at: o) }
public var filenameSegmentArray: [UInt8]! { return _accessor.getVector(at: VT.filename) } public var filenameSegmentArray: [UInt8]! { return _accessor.getVector(at: VT.filename) }
public var firstPage: UInt32 { let o = _accessor.offset(VT.firstPage); return o == 0 ? 0 : _accessor.readBuffer(of: UInt32.self, at: o) } public var firstPage: UInt32 { let o = _accessor.offset(VT.firstPage); return o == 0 ? 0 : _accessor.readBuffer(of: UInt32.self, at: o) }
public var size: Size! { let o = _accessor.offset(VT.size); return _accessor.readBuffer(of: Size.self, at: o) } public var size: Size? { let o = _accessor.offset(VT.size); return o == 0 ? nil : _accessor.readBuffer(of: Size.self, at: o) }
public var mutableSize: Size_Mutable! { let o = _accessor.offset(VT.size); return Size_Mutable(_accessor.bb, o: o + _accessor.position) } public var mutableSize: Size_Mutable? { let o = _accessor.offset(VT.size); return o == 0 ? nil : Size_Mutable(_accessor.bb, o: o + _accessor.position) }
public static func startImageMetadata(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 4) } public static func startImageMetadata(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 4) }
public static func add(doublePage: Bool, _ fbb: inout FlatBufferBuilder) { fbb.add(element: doublePage, def: false, public static func add(doublePage: Bool, _ fbb: inout FlatBufferBuilder) { fbb.add(element: doublePage, def: false,
at: VT.doublePage) } at: VT.doublePage) }
public static func add(filename: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: filename, at: VT.filename) } public static func add(filename: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: filename, at: VT.filename) }
public static func add(firstPage: UInt32, _ fbb: inout FlatBufferBuilder) { fbb.add(element: firstPage, def: 0, at: VT.firstPage) } public static func add(firstPage: UInt32, _ fbb: inout FlatBufferBuilder) { fbb.add(element: firstPage, def: 0, at: VT.firstPage) }
public static func add(size: Size?, _ fbb: inout FlatBufferBuilder) { guard let size = size else { return }; fbb.create(struct: size, position: VT.size) } public static func add(size: Size?, _ fbb: inout FlatBufferBuilder) { guard let size = size else { return }; fbb.create(struct: size, position: VT.size) }
public static func endImageMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); fbb.require(table: end, fields: [6, 10]); return end } public static func endImageMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); fbb.require(table: end, fields: [6]); return end }
public static func createImageMetadata( public static func createImageMetadata(
_ fbb: inout FlatBufferBuilder, _ fbb: inout FlatBufferBuilder,
doublePage: Bool = false, doublePage: Bool = false,
filenameOffset filename: Offset, filenameOffset filename: Offset,
firstPage: UInt32 = 0, firstPage: UInt32 = 0,
size: Size size: Size? = nil
) -> Offset { ) -> Offset {
let __start = ImageMetadata.startImageMetadata(&fbb) let __start = ImageMetadata.startImageMetadata(&fbb)
ImageMetadata.add(doublePage: doublePage, &fbb) ImageMetadata.add(doublePage: doublePage, &fbb)
@@ -320,7 +320,7 @@ public struct ImageMetadata: FlatBufferTable, FlatbuffersVectorInitializable, Ve
try _v.visit(field: VT.doublePage, fieldName: "doublePage", required: false, type: Bool.self) try _v.visit(field: VT.doublePage, fieldName: "doublePage", required: false, type: Bool.self)
try _v.visit(field: VT.filename, fieldName: "filename", required: true, type: ForwardOffset<String>.self) try _v.visit(field: VT.filename, fieldName: "filename", required: true, type: ForwardOffset<String>.self)
try _v.visit(field: VT.firstPage, fieldName: "firstPage", required: false, type: UInt32.self) try _v.visit(field: VT.firstPage, fieldName: "firstPage", required: false, type: UInt32.self)
try _v.visit(field: VT.size, fieldName: "size", required: true, type: Size.self) try _v.visit(field: VT.size, fieldName: "size", required: false, type: Size.self)
_v.finish() _v.finish()
} }
} }
@@ -340,8 +340,8 @@ public struct ChapterMetadata: FlatBufferTable, FlatbuffersVectorInitializable,
static let images: VOffset = 8 static let images: VOffset = 8
} }
public var chapter: MetaValue! { let o = _accessor.offset(VT.chapter); return _accessor.readBuffer(of: MetaValue.self, at: o) } public var chapter: MetaValue? { let o = _accessor.offset(VT.chapter); return o == 0 ? nil : _accessor.readBuffer(of: MetaValue.self, at: o) }
public var mutableChapter: MetaValue_Mutable! { let o = _accessor.offset(VT.chapter); return MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) } public var mutableChapter: MetaValue_Mutable? { let o = _accessor.offset(VT.chapter); return o == 0 ? nil : MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) }
public var name: String? { let o = _accessor.offset(VT.name); return o == 0 ? nil : _accessor.string(at: o) } public var name: String? { let o = _accessor.offset(VT.name); return o == 0 ? nil : _accessor.string(at: o) }
public var nameSegmentArray: [UInt8]? { return _accessor.getVector(at: VT.name) } public var nameSegmentArray: [UInt8]? { return _accessor.getVector(at: VT.name) }
public var images: FlatbufferVector<ImageMetadata> { return _accessor.vector(at: VT.images, byteSize: 4) } public var images: FlatbufferVector<ImageMetadata> { return _accessor.vector(at: VT.images, byteSize: 4) }
@@ -349,10 +349,10 @@ public struct ChapterMetadata: FlatBufferTable, FlatbuffersVectorInitializable,
public static func add(chapter: MetaValue?, _ fbb: inout FlatBufferBuilder) { guard let chapter = chapter else { return }; fbb.create(struct: chapter, position: VT.chapter) } public static func add(chapter: MetaValue?, _ fbb: inout FlatBufferBuilder) { guard let chapter = chapter else { return }; fbb.create(struct: chapter, position: VT.chapter) }
public static func add(name: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: name, at: VT.name) } public static func add(name: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: name, at: VT.name) }
public static func addVectorOf(images: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: images, at: VT.images) } public static func addVectorOf(images: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: images, at: VT.images) }
public static func endChapterMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); fbb.require(table: end, fields: [4]); return end } public static func endChapterMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); return end }
public static func createChapterMetadata( public static func createChapterMetadata(
_ fbb: inout FlatBufferBuilder, _ fbb: inout FlatBufferBuilder,
chapter: MetaValue, chapter: MetaValue? = nil,
nameOffset name: Offset = Offset(), nameOffset name: Offset = Offset(),
imagesVectorOffset images: Offset = Offset() imagesVectorOffset images: Offset = Offset()
) -> Offset { ) -> Offset {
@@ -365,7 +365,7 @@ public struct ChapterMetadata: FlatBufferTable, FlatbuffersVectorInitializable,
public static func verify<T>(_ verifier: inout Verifier, at position: Int, of type: T.Type) throws where T: Verifiable { public static func verify<T>(_ verifier: inout Verifier, at position: Int, of type: T.Type) throws where T: Verifiable {
var _v = try verifier.visitTable(at: position) var _v = try verifier.visitTable(at: position)
try _v.visit(field: VT.chapter, fieldName: "chapter", required: true, type: MetaValue.self) try _v.visit(field: VT.chapter, fieldName: "chapter", required: false, type: MetaValue.self)
try _v.visit(field: VT.name, fieldName: "name", required: false, type: ForwardOffset<String>.self) try _v.visit(field: VT.name, fieldName: "name", required: false, type: ForwardOffset<String>.self)
try _v.visit(field: VT.images, fieldName: "images", required: false, type: ForwardOffset<Vector<ForwardOffset<ImageMetadata>, ImageMetadata>>.self) try _v.visit(field: VT.images, fieldName: "images", required: false, type: ForwardOffset<Vector<ForwardOffset<ImageMetadata>, ImageMetadata>>.self)
_v.finish() _v.finish()
@@ -384,43 +384,36 @@ public struct VolumeMetadata: FlatBufferTable, FlatbuffersVectorInitializable, V
private struct VT { private struct VT {
static let volume: VOffset = 4 static let volume: VOffset = 4
static let title: VOffset = 6 static let title: VOffset = 6
static let archive: VOffset = 8 static let chapters: VOffset = 8
static let chapters: VOffset = 10
} }
public var volume: MetaValue! { let o = _accessor.offset(VT.volume); return _accessor.readBuffer(of: MetaValue.self, at: o) } public var volume: MetaValue? { let o = _accessor.offset(VT.volume); return o == 0 ? nil : _accessor.readBuffer(of: MetaValue.self, at: o) }
public var mutableVolume: MetaValue_Mutable! { let o = _accessor.offset(VT.volume); return MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) } public var mutableVolume: MetaValue_Mutable? { let o = _accessor.offset(VT.volume); return o == 0 ? nil : MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) }
public var title: String? { let o = _accessor.offset(VT.title); return o == 0 ? nil : _accessor.string(at: o) } public var title: String? { let o = _accessor.offset(VT.title); return o == 0 ? nil : _accessor.string(at: o) }
public var titleSegmentArray: [UInt8]? { return _accessor.getVector(at: VT.title) } public var titleSegmentArray: [UInt8]? { return _accessor.getVector(at: VT.title) }
public var archive: String! { let o = _accessor.offset(VT.archive); return _accessor.string(at: o) }
public var archiveSegmentArray: [UInt8]! { return _accessor.getVector(at: VT.archive) }
public var chapters: FlatbufferVector<ChapterMetadata> { return _accessor.vector(at: VT.chapters, byteSize: 4) } public var chapters: FlatbufferVector<ChapterMetadata> { return _accessor.vector(at: VT.chapters, byteSize: 4) }
public static func startVolumeMetadata(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 4) } public static func startVolumeMetadata(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 3) }
public static func add(volume: MetaValue?, _ fbb: inout FlatBufferBuilder) { guard let volume = volume else { return }; fbb.create(struct: volume, position: VT.volume) } public static func add(volume: MetaValue?, _ fbb: inout FlatBufferBuilder) { guard let volume = volume else { return }; fbb.create(struct: volume, position: VT.volume) }
public static func add(title: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: title, at: VT.title) } public static func add(title: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: title, at: VT.title) }
public static func add(archive: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: archive, at: VT.archive) }
public static func addVectorOf(chapters: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: chapters, at: VT.chapters) } public static func addVectorOf(chapters: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: chapters, at: VT.chapters) }
public static func endVolumeMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); fbb.require(table: end, fields: [4, 8]); return end } public static func endVolumeMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); return end }
public static func createVolumeMetadata( public static func createVolumeMetadata(
_ fbb: inout FlatBufferBuilder, _ fbb: inout FlatBufferBuilder,
volume: MetaValue, volume: MetaValue? = nil,
titleOffset title: Offset = Offset(), titleOffset title: Offset = Offset(),
archiveOffset archive: Offset,
chaptersVectorOffset chapters: Offset = Offset() chaptersVectorOffset chapters: Offset = Offset()
) -> Offset { ) -> Offset {
let __start = VolumeMetadata.startVolumeMetadata(&fbb) let __start = VolumeMetadata.startVolumeMetadata(&fbb)
VolumeMetadata.add(volume: volume, &fbb) VolumeMetadata.add(volume: volume, &fbb)
VolumeMetadata.add(title: title, &fbb) VolumeMetadata.add(title: title, &fbb)
VolumeMetadata.add(archive: archive, &fbb)
VolumeMetadata.addVectorOf(chapters: chapters, &fbb) VolumeMetadata.addVectorOf(chapters: chapters, &fbb)
return VolumeMetadata.endVolumeMetadata(&fbb, start: __start) return VolumeMetadata.endVolumeMetadata(&fbb, start: __start)
} }
public static func verify<T>(_ verifier: inout Verifier, at position: Int, of type: T.Type) throws where T: Verifiable { public static func verify<T>(_ verifier: inout Verifier, at position: Int, of type: T.Type) throws where T: Verifiable {
var _v = try verifier.visitTable(at: position) var _v = try verifier.visitTable(at: position)
try _v.visit(field: VT.volume, fieldName: "volume", required: true, type: MetaValue.self) try _v.visit(field: VT.volume, fieldName: "volume", required: false, type: MetaValue.self)
try _v.visit(field: VT.title, fieldName: "title", required: false, type: ForwardOffset<String>.self) try _v.visit(field: VT.title, fieldName: "title", required: false, type: ForwardOffset<String>.self)
try _v.visit(field: VT.archive, fieldName: "archive", required: true, type: ForwardOffset<String>.self)
try _v.visit(field: VT.chapters, fieldName: "chapters", required: false, type: ForwardOffset<Vector<ForwardOffset<ChapterMetadata>, ChapterMetadata>>.self) try _v.visit(field: VT.chapters, fieldName: "chapters", required: false, type: ForwardOffset<Vector<ForwardOffset<ChapterMetadata>, ChapterMetadata>>.self)
_v.finish() _v.finish()
} }
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env sh #!/usr/bin/env sh
set -e set -eu
APP_NAME='ImageViewer' APP_NAME='ImageViewer'
SIM_DEVICE='iPad (A16)' SIM_DEVICE='iPad (A16)'