From 610700840a1a2f4766fdaab057fa6b543637a9c4 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Sun, 12 Jul 2026 21:56:51 +0200 Subject: [PATCH] feat: load images from cbz files instead --- ImageViewer/ViewController.swift | 224 +++++++++++++++++++-------- ImageViewer/metadata.fbs | 7 +- ImageViewer/metadata_generated.swift | 41 +++-- 3 files changed, 189 insertions(+), 83 deletions(-) diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index 41f5322..a944137 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -3,9 +3,10 @@ // TODO: Convert between state for normal and scrolling page turn // TODO: Support reading with scrolling and landscape mode -import Foundation import FlatBuffers +import Foundation import UIKit +import ZIPFoundation 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. @@ -182,7 +183,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { for dir in directories { if !fileManager.fileExists(atPath: dir.appendingPathComponent(metadataFilename).path) { - getMetadataFromFileName(path: dir) + getMetadataFromFilename(path: dir) } currentPath = dir @@ -192,15 +193,20 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { metadata = try! getCheckedRoot(byteBuffer: &byteBuffer) metadataList[dir] = metadata loadLocalState() - + let volCovP = ProgressIndices(v: progress.v, c: 0, i: 0) + let volumeDir = currentPath.appendingPathComponent("volumes") + let archive = try! Archive(url: volumeDir.appendingPathComponent(metadata.volumes[progress.v].archive), accessMode: .read) + let targetEntry = archive[metadata.volumes[volCovP.v].chapters[volCovP.c].images[volCovP.i].filename]! + var extractedData = Data() + _ = try archive.extract(targetEntry) { data in + extractedData.append(data) + } comics.append( Comic( - cover: UIImage(contentsOfFile: - getImagePath(progress: ProgressIndices(v: progress.v, c: 0, i: 0)).path - )!, + cover: UIImage(data: extractedData)!, metadata: metadata, path: dir - )) + )) } } catch { print("Failed to read directories") @@ -256,7 +262,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { return CGSize(width: width, height: height) } - func getMetadataFromFileName(path: URL) { + func getMetadataFromFilename(path: URL) { // Beautiful, is it not? let patterns: [(NSRegularExpression, NSRegularExpression, Int, Int, Int)] = [ (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), @@ -287,28 +293,45 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { 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" } + + 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 + } + + print("example filename in archive: \(filenames[0].1), \(filenames[1].1)") 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 { - let filename = testFilename!.lastPathComponent + let filename = filenames[0].1 let range = NSRange(filename.startIndex..., in: filename) if let match = currentTitlePattern.firstMatch(in: filename, range: range) { if (filename as NSString).substring(with: match.range(at: 1)) == "" { @@ -356,13 +379,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { let regex = pattern! let titleRegex = titlePattern! - let fileURLs = try fileManager.contentsOfDirectory( - at: dir, - includingPropertiesForKeys: [.isRegularFileKey], - options: [.skipsHiddenFiles] - ) - let filenames = fileURLs.map { $0.lastPathComponent }.sorted() - for (i, filename) in filenames.enumerated() { + for (i, (volumeURL, filename)) in filenames.enumerated() { let range = NSRange(filename.startIndex..., in: filename) if i == 0 { if let match = titleRegex.firstMatch(in: filename, range: range) { @@ -387,7 +404,15 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { UInt32(pageParts[0])!, pageParts.count > 1 ) - let size = imageSize(at: dir.appendingPathComponent(filename))! + let archive = try Archive(url: volumeDir.appendingPathComponent(volumeURL), accessMode: .read) + 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 imageMetadata = ImageMetadata.createImageMetadata(&builder, doublePage: doublePage, @@ -413,12 +438,14 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { currentChapters.append(chapterMetadata) let volumeTitle = builder.create(string: "") + let volumeURLOffset = builder.create(string: volumeURL) let chaptersOffset = builder.createVector(ofOffsets: currentChapters) let volumeMetadata = VolumeMetadata.createVolumeMetadata( &builder, volume: MetaValue(main: currentVolume, bonus: 0), titleOffset: volumeTitle, - chaptersVectorOffset: chaptersOffset + archiveOffset: volumeURLOffset, + chaptersVectorOffset: chaptersOffset, ) volumes.append(volumeMetadata) @@ -466,11 +493,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { currentChapters.append(chapterMetadata) let volumeTitle = builder.create(string: "") + let volumeURLOffset = builder.create(string: volumeURL) let chaptersOffset = builder.createVector(ofOffsets: currentChapters) let volumeMetadata = VolumeMetadata.createVolumeMetadata( &builder, volume: MetaValue(main: volume, bonus: 0), titleOffset: volumeTitle, + archiveOffset: volumeURLOffset, chaptersVectorOffset: chaptersOffset ) @@ -662,6 +691,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { var offset = scrollingCollectionView.contentOffset offset.y *= (screenSize.width / screenSize.height) scrollingCollectionView.setContentOffset(offset, animated: false) + // FIXME: This is a caching issue, needs a different cache for scrolling since it Fill instead of Fit. scrollingCollectionView.reloadData() } } @@ -1017,8 +1047,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if mode == .scroll { setupScrolling() } else { + let archiveURL = currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[progress.v].archive) imageLoader.loadImage( - at: getImagePath(progress: progress), + archiveURL: archiveURL, + filename: getImagePath(progress: progress), scaling: .scaleAspectFit, screenSize: UIScreen.main.bounds.size ) { [weak self] image in @@ -1158,22 +1190,21 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } progress = newProgress updateInfo() - var preloadPaths: [URL] = [] let path = getImagePath(progress: progress) - for _ in 0 ..< preloadCount { - newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) - preloadPaths.append(getImagePath(progress: newProgress)) - } - imageLoader.setActiveWindow([path]) - imageLoader.loadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size) { [weak self] image in + let archiveURL = currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[progress.v].archive) + imageLoader.loadImage(archiveURL: archiveURL, filename: getImagePath(progress: progress), scaling: scaling, screenSize: UIScreen.main.bounds.size) { [weak self] image in self?.imageView.image = image } - for path in preloadPaths { - imageLoader.preloadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size) + for _ in 0 ..< preloadCount { + newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) + let archive = metadata.volumes[newProgress.v].archive! + let filename = getImagePath(progress: newProgress) + let archiveURL = currentPath.appendingPathComponent("volumes").appendingPathComponent(archive) + imageLoader.preloadImage(archiveURL: archiveURL, filename: filename, scaling: scaling, screenSize: UIScreen.main.bounds.size) } saveLocalState() @@ -1242,10 +1273,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { info.text = text } - func getImagePath(progress: ProgressIndices) -> URL { + func getImagePath(progress: ProgressIndices) -> String { let (v, c, i) = (progress.v, progress.c, progress.i) - let modernPath = currentPath!.appendingPathComponent("images").appendingPathComponent(metadata.volumes[v].chapters[c].images[i].filename) - // print("trying to get path \(modernPath)") + let modernPath = metadata.volumes[v].chapters[c].images[i].filename! // if fileManager.fileExists(atPath: modernPath.path) { return modernPath // } @@ -1299,8 +1329,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { scaling = UIView.ContentMode.scaleAspectFill } else { scaling = UIView.ContentMode.scaleAspectFit } imageLoader.loadImage( - at: - getImagePath(progress: progress), + archiveURL: currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[progress.v].archive), + filename: getImagePath(progress: progress), scaling: scaling, screenSize: UIScreen.main.bounds.size ) { [weak self] image in @@ -1311,7 +1341,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { var newProgress = progress for _ in 0 ..< preloadCount { newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) - imageLoader.preloadImage(at: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size) + imageLoader.preloadImage(archiveURL: currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[progress.v].archive), filename: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size) } } @@ -1330,7 +1360,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { super.viewWillTransition(to: size, with: coordinator) if mode != .scroll { imageLoader.loadImage( - at: getImagePath(progress: progress), + archiveURL: currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[progress.v].archive), + filename: getImagePath(progress: progress), scaling: .scaleAspectFit, screenSize: size ) { image in @@ -1424,7 +1455,10 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) } } - imageLoader.loadImage(at: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size) { image in + imageLoader.loadImage( + archiveURL: currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[progress.v].archive), + filename: getImagePath(progress: newProgress), + scaling: scaling, screenSize: UIScreen.main.bounds.size) { image in cell.imageView.image = image } return cell @@ -1541,23 +1575,29 @@ class ImageLoader { private let horCache = NSCache() private var loadingTasks: [String: (UIImage?) -> Void] = [:] private var activeTokens: [String: CancellationToken] = [:] + private var archives = NSCache() init() { // 128 MiB cache.totalCostLimit = 128 * 1024 * 1024 + horCache.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(at path: URL, scaling: UIView.ContentMode, screenSize: CGSize) { - loadImage(at: path, scaling: scaling, screenSize: screenSize, completion: nil) + func preloadImage(archiveURL: URL, filename: String, scaling: UIView.ContentMode, screenSize: CGSize) { + loadImage(archiveURL: archiveURL, filename: filename, scaling: scaling, screenSize: screenSize, completion: nil) } + // FIXME: So scrolling is not really handled well in terms of caching. This is because it should have a separate cache since it uses a different scaling (Fill instead of Fit). func loadImage( - at path: URL, scaling: UIView.ContentMode, + archiveURL: URL, filename: String, + scaling: UIView.ContentMode, screenSize: CGSize, completion: ((UIImage?) -> Void)? ) { let rotation: Rotation = screenSize.width > screenSize.height ? .horizontal : .vertical - let key = path.path + let key = filename if rotation == .vertical, let cached = cache.object(forKey: key as NSString) { completion?(cached); return @@ -1597,7 +1637,19 @@ class ImageLoader { if token.isCancelled { finish(nil); return } - guard var image = UIImage(contentsOfFile: key) else { + var extractedData = Data() + if self.archives.object(forKey: archiveURL.lastPathComponent as NSString) == nil { + let archive = try! Archive(url: archiveURL, accessMode: .read) + self.archives.setObject(archive, forKey: archiveURL.lastPathComponent as NSString) + } + let archive = self.archives.object(forKey: archiveURL.lastPathComponent as NSString)! + _ = try! archive.extract(archive[filename]!) { data in + extractedData.append(data) + } + + if token.isCancelled { finish(nil); return } + + guard var image = UIImage(data: extractedData) else { finish(nil) return } @@ -1613,7 +1665,7 @@ class ImageLoader { let vScaledSize = aspectSize(for: image.size, in: vertical, scaling: scaling) let vScaleImage = resizeImage(image, to: vScaledSize) if let cost = imageByteSize(vScaleImage) { - self.cache.setObject(vScaleImage, forKey: path.path as NSString, cost: cost) + self.cache.setObject(vScaleImage, forKey: filename as NSString, cost: cost) } if rotation == .vertical { image = vScaleImage } } @@ -1628,7 +1680,7 @@ class ImageLoader { let hScaledSize = aspectSize(for: image.size, in: horizontal, scaling: scaling) let hScaleImage = resizeImage(image, to: hScaledSize) if let cost = imageByteSize(hScaleImage) { - self.horCache.setObject(hScaleImage, forKey: path.path as NSString, cost: cost) + self.horCache.setObject(hScaleImage, forKey: filename as NSString, cost: cost) } if rotation == .horizontal { image = hScaleImage } } @@ -1637,8 +1689,8 @@ class ImageLoader { } } - func setActiveWindow(_ paths: [URL]) { - let desired = Set(paths.map { $0.path }) + func setActiveWindow(_ paths: [String]) { + let desired = Set(paths) for (key, token) in activeTokens where !desired.contains(key) { token.cancel() loadingTasks[key] = nil @@ -1695,3 +1747,49 @@ final class CancellationToken { _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 +} diff --git a/ImageViewer/metadata.fbs b/ImageViewer/metadata.fbs index 099d372..a15e661 100644 --- a/ImageViewer/metadata.fbs +++ b/ImageViewer/metadata.fbs @@ -31,18 +31,19 @@ table ImageMetadata { double_page:bool; filename:string (required); first_page:uint; - size:Size; + size:Size (required); } table ChapterMetadata { - chapter:MetaValue; + chapter:MetaValue (required); name:string; images:[ImageMetadata]; } table VolumeMetadata { - volume:MetaValue; + volume:MetaValue (required); title:string; + archive:string (required); chapters:[ChapterMetadata]; } diff --git a/ImageViewer/metadata_generated.swift b/ImageViewer/metadata_generated.swift index 8964f6a..619e942 100644 --- a/ImageViewer/metadata_generated.swift +++ b/ImageViewer/metadata_generated.swift @@ -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 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 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 o == 0 ? nil : Size_Mutable(_accessor.bb, o: o + _accessor.position) } + public var size: Size! { let o = _accessor.offset(VT.size); return _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 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, at: VT.doublePage) } 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(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]); 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, 10]); return end } public static func createImageMetadata( _ fbb: inout FlatBufferBuilder, doublePage: Bool = false, filenameOffset filename: Offset, firstPage: UInt32 = 0, - size: Size? = nil + size: Size ) -> Offset { let __start = ImageMetadata.startImageMetadata(&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.filename, fieldName: "filename", required: true, type: ForwardOffset.self) try _v.visit(field: VT.firstPage, fieldName: "firstPage", required: false, type: UInt32.self) - try _v.visit(field: VT.size, fieldName: "size", required: false, type: Size.self) + try _v.visit(field: VT.size, fieldName: "size", required: true, type: Size.self) _v.finish() } } @@ -340,8 +340,8 @@ public struct ChapterMetadata: FlatBufferTable, FlatbuffersVectorInitializable, static let images: VOffset = 8 } - 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 o == 0 ? nil : MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) } + public var chapter: MetaValue! { let o = _accessor.offset(VT.chapter); return _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 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 images: FlatbufferVector { 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(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 endChapterMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); return end } + 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 createChapterMetadata( _ fbb: inout FlatBufferBuilder, - chapter: MetaValue? = nil, + chapter: MetaValue, nameOffset name: Offset = Offset(), imagesVectorOffset images: Offset = Offset() ) -> Offset { @@ -365,7 +365,7 @@ public struct ChapterMetadata: FlatBufferTable, FlatbuffersVectorInitializable, public static func verify(_ verifier: inout Verifier, at position: Int, of type: T.Type) throws where T: Verifiable { var _v = try verifier.visitTable(at: position) - try _v.visit(field: VT.chapter, fieldName: "chapter", required: false, type: MetaValue.self) + try _v.visit(field: VT.chapter, fieldName: "chapter", required: true, type: MetaValue.self) try _v.visit(field: VT.name, fieldName: "name", required: false, type: ForwardOffset.self) try _v.visit(field: VT.images, fieldName: "images", required: false, type: ForwardOffset, ImageMetadata>>.self) _v.finish() @@ -384,36 +384,43 @@ public struct VolumeMetadata: FlatBufferTable, FlatbuffersVectorInitializable, V private struct VT { static let volume: VOffset = 4 static let title: VOffset = 6 - static let chapters: VOffset = 8 + static let archive: VOffset = 8 + static let chapters: VOffset = 10 } - 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 o == 0 ? nil : MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) } + public var volume: MetaValue! { let o = _accessor.offset(VT.volume); return _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 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 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 { return _accessor.vector(at: VT.chapters, byteSize: 4) } - public static func startVolumeMetadata(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 3) } + public static func startVolumeMetadata(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 4) } 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(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 endVolumeMetadata(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); return end } + 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 createVolumeMetadata( _ fbb: inout FlatBufferBuilder, - volume: MetaValue? = nil, + volume: MetaValue, titleOffset title: Offset = Offset(), + archiveOffset archive: Offset, chaptersVectorOffset chapters: Offset = Offset() ) -> Offset { let __start = VolumeMetadata.startVolumeMetadata(&fbb) VolumeMetadata.add(volume: volume, &fbb) VolumeMetadata.add(title: title, &fbb) + VolumeMetadata.add(archive: archive, &fbb) VolumeMetadata.addVectorOf(chapters: chapters, &fbb) return VolumeMetadata.endVolumeMetadata(&fbb, start: __start) } public static func verify(_ verifier: inout Verifier, at position: Int, of type: T.Type) throws where T: Verifiable { var _v = try verifier.visitTable(at: position) - try _v.visit(field: VT.volume, fieldName: "volume", required: false, type: MetaValue.self) + try _v.visit(field: VT.volume, fieldName: "volume", required: true, type: MetaValue.self) try _v.visit(field: VT.title, fieldName: "title", required: false, type: ForwardOffset.self) + try _v.visit(field: VT.archive, fieldName: "archive", required: true, type: ForwardOffset.self) try _v.visit(field: VT.chapters, fieldName: "chapters", required: false, type: ForwardOffset, ChapterMetadata>>.self) _v.finish() }