Compare commits
20 Commits
c1bb06a1b4
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
0f1dc88b87
|
|||
|
9acb6a3e3c
|
|||
|
554a4022c9
|
|||
|
9baba413d3
|
|||
|
67bc375e17
|
|||
|
e178681027
|
|||
|
1717ae13d1
|
|||
|
ec33bfd20d
|
|||
|
f42e44e1af
|
|||
|
4cab14d9ae
|
|||
|
9bdd2f350f
|
|||
|
00b3a28a62
|
|||
|
2f0e7f1821
|
|||
|
ea8d128af5
|
|||
|
0fad959e6c
|
|||
|
400febc7b3
|
|||
|
193bef4def
|
|||
|
26e9cf6268
|
|||
|
be5e513cbc
|
|||
|
dcb26e47b4
|
@@ -16,6 +16,9 @@
|
|||||||
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 */
|
||||||
@@ -36,7 +39,10 @@
|
|||||||
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;
|
||||||
};
|
};
|
||||||
@@ -117,7 +123,8 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 840F625C2E7B65B700C8A64A;
|
mainGroup = 840F625C2E7B65B700C8A64A;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
22EF4C592FFDC39600852A4F /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */,
|
9A77032A300317E4004A0222 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
||||||
|
9A77032D30031A32004A0222 /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 840F62662E7B65B700C8A64A /* Products */;
|
productRefGroup = 840F62662E7B65B700C8A64A /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -352,12 +359,23 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCLocalSwiftPackageReference section */
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
22EF4C592FFDC39600852A4F /* XCLocalSwiftPackageReference "dependencies/flatbuffers" */ = {
|
9A77032D30031A32004A0222 /* 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;
|
||||||
@@ -375,6 +393,19 @@
|
|||||||
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 */;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "1d2ea13999643e07c5cb84a07c2cd10221ddf2714932691ba7147378d56f57ac",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "zipfoundation",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/weichsel/ZIPFoundation.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d",
|
||||||
|
"version" : "0.9.20"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
+338
-183
@@ -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 Foundation
|
|
||||||
import FlatBuffers
|
import FlatBuffers
|
||||||
|
import Foundation
|
||||||
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,15 +192,22 @@ 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(contentsOfFile:
|
cover: UIImage(data: extractedData)!,
|
||||||
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")
|
||||||
@@ -256,10 +263,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]+(?:-[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: #"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: "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
|
||||||
@@ -284,31 +291,48 @@ 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 = testFilename!.lastPathComponent
|
let filename = filenames[0].1
|
||||||
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)) == "" {
|
||||||
@@ -352,17 +376,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let regex = pattern!
|
let regex = pattern!
|
||||||
let titleRegex = titlePattern!
|
let titleRegex = titlePattern!
|
||||||
|
|
||||||
let fileURLs = try fileManager.contentsOfDirectory(
|
for (i, (volumeURL, filename)) in filenames.enumerated() {
|
||||||
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) {
|
||||||
@@ -386,13 +403,21 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
|||||||
let (page, doublePage) = (
|
let (page, doublePage) = (
|
||||||
UInt32(pageParts[0])!, pageParts.count > 1
|
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 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))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -402,7 +427,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
|
||||||
),
|
),
|
||||||
@@ -413,12 +438,14 @@ 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,
|
||||||
chaptersVectorOffset: chaptersOffset
|
archiveOffset: volumeURLOffset,
|
||||||
|
chaptersVectorOffset: chaptersOffset,
|
||||||
)
|
)
|
||||||
|
|
||||||
volumes.append(volumeMetadata)
|
volumes.append(volumeMetadata)
|
||||||
@@ -434,7 +461,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
|
||||||
),
|
),
|
||||||
@@ -455,7 +482,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
|
||||||
),
|
),
|
||||||
@@ -466,11 +493,13 @@ 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
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -481,6 +510,7 @@ 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
|
||||||
@@ -666,67 +696,31 @@ 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()
|
||||||
if mode != .scroll {
|
scrollingCollectionView.isHidden = mode != .scroll
|
||||||
scrollingCollectionView.isHidden = true
|
leftView.isHidden = mode == .scroll
|
||||||
leftView.isHidden = false
|
rightView.isHidden = mode == .scroll
|
||||||
rightView.isHidden = false
|
imageView.isHidden = mode == .scroll
|
||||||
setImages(path: path)
|
if mode == .scroll {
|
||||||
|
scrollingCollectionView.reloadData()
|
||||||
} else {
|
} else {
|
||||||
setupScrolling()
|
setImages()
|
||||||
}
|
}
|
||||||
} 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
|
||||||
@@ -1013,11 +1007,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
|||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
if prev == mode { return }
|
if prev == mode { return }
|
||||||
if mode == .scroll {
|
if mode != .scroll {
|
||||||
setupScrolling()
|
let archiveURL = getArchiveURL(progress.v)
|
||||||
} else {
|
|
||||||
imageLoader.loadImage(
|
imageLoader.loadImage(
|
||||||
at: getImagePath(progress: progress),
|
archiveURL: archiveURL,
|
||||||
|
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
|
||||||
@@ -1029,6 +1023,10 @@ 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()
|
||||||
}
|
}
|
||||||
@@ -1156,16 +1154,28 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
progress = newProgress
|
progress = newProgress
|
||||||
let path = getImagePath(progress: progress)
|
updateInfo()
|
||||||
imageLoader.loadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size) { [weak self] image in
|
let path = getImagePath(progress)
|
||||||
self?.imageView.image = image
|
|
||||||
self?.updateInfo()
|
imageLoader.setActiveWindow([path])
|
||||||
}
|
|
||||||
|
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)
|
||||||
let path = getImagePath(progress: newProgress)
|
imageLoader.preloadImage(
|
||||||
imageLoader.preloadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size)
|
archiveURL: getArchiveURL(newProgress.v),
|
||||||
|
filename: getImagePath(newProgress),
|
||||||
|
scaling: scaling,
|
||||||
|
screenSize: UIScreen.main.bounds.size
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveLocalState()
|
saveLocalState()
|
||||||
@@ -1230,16 +1240,17 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
|||||||
Chapter \(chapterString) of \(lastChapterString)
|
Chapter \(chapterString) of \(lastChapterString)
|
||||||
Page \(pageString) of \(lastPageString)
|
Page \(pageString) of \(lastPageString)
|
||||||
"""
|
"""
|
||||||
if let size = imageLoader.size[getImagePath(progress: progress).path] {
|
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)"
|
||||||
text += "\nImage size: \(Int(size.width))x\(Int(size.height))"
|
|
||||||
}
|
|
||||||
info.text = text
|
info.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImagePath(progress: ProgressIndices) -> URL {
|
func getArchiveURL(_ volumeProgress: Int) -> 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 = currentPath!.appendingPathComponent("images").appendingPathComponent(metadata.volumes[v].chapters[c].images[i].filename)
|
let modernPath = 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
|
||||||
// }
|
// }
|
||||||
@@ -1287,25 +1298,27 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
|||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func setImages(path _: URL) {
|
func setImages() {
|
||||||
let scaling: UIView.ContentMode!
|
let scaling: UIView.ContentMode = mode == .scroll ? .scaleAspectFill : .scaleAspectFit
|
||||||
if mode == .scroll {
|
|
||||||
scaling = UIView.ContentMode.scaleAspectFill
|
|
||||||
} else { scaling = UIView.ContentMode.scaleAspectFit }
|
|
||||||
imageLoader.loadImage(
|
imageLoader.loadImage(
|
||||||
at:
|
archiveURL: getArchiveURL(progress.v),
|
||||||
getImagePath(progress: progress),
|
filename: getImagePath(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(at: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size)
|
imageLoader.preloadImage(
|
||||||
|
archiveURL: getArchiveURL(newProgress.v),
|
||||||
|
filename: getImagePath(newProgress),
|
||||||
|
scaling: scaling,
|
||||||
|
screenSize: UIScreen.main.bounds.size,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1324,14 +1337,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(
|
||||||
at: getImagePath(progress: progress),
|
archiveURL: getArchiveURL(progress.v),
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1375,7 +1388,10 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
|||||||
{
|
{
|
||||||
if collectionView == comicCollectionView {
|
if collectionView == comicCollectionView {
|
||||||
return comics.count
|
return comics.count
|
||||||
} else {
|
} else if collectionView == scrollingCollectionView {
|
||||||
|
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 {
|
||||||
@@ -1384,6 +1400,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
|||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
|
||||||
@@ -1404,6 +1421,7 @@ 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
|
||||||
@@ -1414,7 +1432,11 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
|||||||
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
|
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageLoader.loadImage(at: getImagePath(progress: newProgress), scaling: scaling, screenSize: UIScreen.main.bounds.size) { image in
|
imageLoader.loadImage(
|
||||||
|
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
|
||||||
@@ -1528,97 +1550,169 @@ enum Rotation {
|
|||||||
|
|
||||||
class ImageLoader {
|
class ImageLoader {
|
||||||
private let cache = NSCache<NSString, UIImage>()
|
private let cache = NSCache<NSString, UIImage>()
|
||||||
private let horCache = NSCache<NSString, UIImage>()
|
private let portraitFitCache = NSCache<NSString, UIImage>()
|
||||||
private var loadingTasks: [String: [(UIImage?) -> Void]] = [:]
|
private let landscapeFitCache = NSCache<NSString, UIImage>()
|
||||||
var size: [String: CGSize] = [:]
|
private let portraitFillCache = NSCache<NSString, UIImage>()
|
||||||
|
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(at path: URL, scaling: UIView.ContentMode, screenSize: CGSize) {
|
func preloadImage(archiveURL: URL, filename: String, scaling: UIView.ContentMode, screenSize: CGSize) {
|
||||||
loadImage(at: path, scaling: scaling, screenSize: screenSize, completion: nil)
|
loadImage(archiveURL: archiveURL, filename: filename, scaling: scaling, screenSize: screenSize, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImage(
|
func loadImage(
|
||||||
at path: URL, scaling: UIView.ContentMode,
|
archiveURL: URL, filename: String,
|
||||||
|
scaling: UIView.ContentMode,
|
||||||
screenSize: CGSize,
|
screenSize: CGSize,
|
||||||
completion: ((UIImage?) -> Void)?
|
completion: ((UIImage?) -> Void)?
|
||||||
) {
|
) {
|
||||||
let rotation: Rotation!
|
let rotation: Rotation = screenSize.width > screenSize.height ? .horizontal : .vertical
|
||||||
if screenSize.width > screenSize.height {
|
let key = filename
|
||||||
rotation = Rotation.horizontal
|
|
||||||
} else { rotation = Rotation.vertical }
|
|
||||||
|
|
||||||
if rotation == .vertical {
|
if scaling == .scaleAspectFit {
|
||||||
if let cached = cache.object(forKey: path.path as NSString) {
|
if rotation == .vertical, let cached = portraitFitCache.object(forKey: key as NSString) {
|
||||||
completion?(cached)
|
completion?(cached); return
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
if rotation == .horizontal, let cached = landscapeFitCache.object(forKey: key as NSString) {
|
||||||
if let cached = horCache.object(forKey: path.path as NSString) {
|
completion?(cached); return
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingTasks[path.path] = completion != nil ? [completion!] : []
|
let token = CancellationToken()
|
||||||
|
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
|
|
||||||
|
|
||||||
// If you turn pages fast, completion will not be nil and as such only the needed scaled image should be prepared
|
func finish() {
|
||||||
if completion == nil || rotation == .vertical {
|
DispatchQueue.main.async {
|
||||||
let vertical = CGSize(
|
guard self.activeTokens[key] === token else { return }
|
||||||
width: min(screenSize.width, screenSize.height),
|
self.activeTokens.removeValue(forKey: key)
|
||||||
height: max(screenSize.height, screenSize.width)
|
self.loadingTasks.removeValue(forKey: key)
|
||||||
)
|
}
|
||||||
let vScaledSize = aspectSize(
|
}
|
||||||
for: image.size, in: vertical, scaling: scaling
|
func setImage(_ image: UIImage) {
|
||||||
)
|
DispatchQueue.main.async {
|
||||||
let vScaleImage = resizeImage(image, to: vScaledSize)
|
if let callback = self.loadingTasks[key] {
|
||||||
guard let cost = imageByteSize(vScaleImage) else { return }
|
callback(image)
|
||||||
self.cache.setObject(vScaleImage, forKey: path.path as NSString, cost: cost)
|
}
|
||||||
if rotation == .vertical {
|
|
||||||
image = vScaleImage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if completion == nil || rotation == .horizontal {
|
if token.isCancelled { finish(); return }
|
||||||
let horizontal = CGSize(
|
|
||||||
width: max(screenSize.width, screenSize.height),
|
func extractData() -> Data {
|
||||||
height: min(screenSize.height, screenSize.width)
|
var extractedData = Data()
|
||||||
)
|
if self.archives.object(forKey: archiveURL.lastPathComponent as NSString) == nil {
|
||||||
let hScaledSize = aspectSize(
|
let archive = try! Archive(url: archiveURL, accessMode: .read)
|
||||||
for: image.size, in: horizontal, scaling: scaling
|
self.archives.setObject(archive, forKey: archiveURL.lastPathComponent as NSString)
|
||||||
)
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
if token.isCancelled { finish(); return }
|
||||||
self.loadingTasks[path.path]?.forEach { $0(image) }
|
|
||||||
self.loadingTasks.removeValue(forKey: path.path)
|
guard let image = cache.object(forKey: key as NSString) ?? UIImage(data: extractData()) else {
|
||||||
|
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 imageIfLoaded(at path: String) -> UIImage? {
|
func scaleAndCacheImage(image: UIImage, orientation: Rotation, screenSize: CGSize, scaling: UIView.ContentMode, filename: String) -> UIImage {
|
||||||
return cache.object(forKey: path as NSString)
|
let resizeTo: CGSize = if orientation == .vertical {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,3 +1746,64 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,18 +31,19 @@ table ImageMetadata {
|
|||||||
double_page:bool;
|
double_page:bool;
|
||||||
filename:string (required);
|
filename:string (required);
|
||||||
first_page:uint;
|
first_page:uint;
|
||||||
size:Size;
|
size:Size (required);
|
||||||
}
|
}
|
||||||
|
|
||||||
table ChapterMetadata {
|
table ChapterMetadata {
|
||||||
chapter:MetaValue;
|
chapter:MetaValue (required);
|
||||||
name:string;
|
name:string;
|
||||||
images:[ImageMetadata];
|
images:[ImageMetadata];
|
||||||
}
|
}
|
||||||
|
|
||||||
table VolumeMetadata {
|
table VolumeMetadata {
|
||||||
volume:MetaValue;
|
volume:MetaValue (required);
|
||||||
title:string;
|
title:string;
|
||||||
|
archive:string (required);
|
||||||
chapters:[ChapterMetadata];
|
chapters:[ChapterMetadata];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 o == 0 ? nil : _accessor.readBuffer(of: Size.self, at: o) }
|
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 o == 0 ? nil : Size_Mutable(_accessor.bb, o: o + _accessor.position) }
|
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 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]); 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(
|
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? = nil
|
size: Size
|
||||||
) -> 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: false, type: Size.self)
|
try _v.visit(field: VT.size, fieldName: "size", required: true, 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 o == 0 ? nil : _accessor.readBuffer(of: MetaValue.self, at: o) }
|
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 o == 0 ? nil : MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) }
|
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 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)); 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(
|
public static func createChapterMetadata(
|
||||||
_ fbb: inout FlatBufferBuilder,
|
_ fbb: inout FlatBufferBuilder,
|
||||||
chapter: MetaValue? = nil,
|
chapter: MetaValue,
|
||||||
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: 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<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,36 +384,43 @@ 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 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 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 o == 0 ? nil : MetaValue_Mutable(_accessor.bb, o: o + _accessor.position) }
|
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 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: 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(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)); 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(
|
public static func createVolumeMetadata(
|
||||||
_ fbb: inout FlatBufferBuilder,
|
_ fbb: inout FlatBufferBuilder,
|
||||||
volume: MetaValue? = nil,
|
volume: MetaValue,
|
||||||
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: 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<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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user