diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index fa5b79e..cb0951d 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -269,11 +269,14 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { queue.async { [self] in for dir in directories { + var foundFile = false if !fileManager.fileExists(atPath: dir.appendingPathComponent(metadataFilename).path) { getMetadataFromFilename(path: dir) if pendingAlert != nil { return } + } else { + foundFile = true } currentPath = dir @@ -288,7 +291,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { return } var byteBuffer = ByteBuffer(data: data) - guard let metadata: Metadata = try? getCheckedRoot(byteBuffer: &byteBuffer) else { + guard var metadata: Metadata = try? getCheckedRoot(byteBuffer: &byteBuffer) else { DispatchQueue.main.async { alert.message = String(format: "Failed to read contents of metadata flatbuffer file: \(metadataPath))") @@ -296,6 +299,35 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { pendingAlert = alert return } + + if foundFile { + print("found file") + guard let archivePaths = + getArchivePaths(volumeDir: dir.appendingPathComponent("volumes"), alert: alert) + else { + return + } + let archives = archivePaths.map { $0.lastPathComponent } + var missing = false + for path in metadata.volumes { + if let nPath = path.archive { + if !archives.contains(nPath) { + missing = true + } + } + } + if missing { + getMetadataFromFilename(path: dir) + } else if archives.count != metadata.volumes.count { + guard let nM = addNewVolumesToMetadata( + pM: metadata, volumeURLs: archivePaths, volumeDir: dir.appendingPathComponent("volumes") + ) else { + return + } + metadata = nM + } + } + metadataList[dir] = metadata loadLocalState() let volCovP = ProgressIndices(v: state[currentPath.lastPathComponent]!.progress.v, c: 0, i: 0) @@ -418,9 +450,306 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { return CGSize(width: width, height: height) } + func getArchivePaths(volumeDir: URL, alert: UIAlertController) -> [URL]? { + var isDir: ObjCBool = false + if !fileManager.fileExists(atPath: volumeDir.path, isDirectory: &isDir) || !isDir.boolValue { + alert.message = "Subdirectory volumes not found: \"\(volumeDir)\", returning" + pendingAlert = alert + return nil + } + let volumeURLs = try? fileManager.contentsOfDirectory( + at: volumeDir, + includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles] + ).filter { $0.pathExtension == "cbz" } + .sorted { $0.lastPathComponent < $1.lastPathComponent } + + guard let result = volumeURLs else { + alert.message = String(format: "failed to read dir: \(volumeDir)") + pendingAlert = alert + return nil + } + + if result.isEmpty { + alert.message = String(format: "found no volumes in volume dir: \(volumeDir)") + pendingAlert = alert + return nil + } + + return result + } + + func getMatchingPattern(filename: String, alert: UIAlertController) -> Pattern? { + // Beautiful, is it not? + guard let patterns: [Pattern] = try? [ + Pattern( + pattern: NSRegularExpression( + pattern: #"^(.*) - c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-p?[0-9]+)?)"# + ), + titlePosition: 1, + volumePosition: 3, + chapterPosition: 2, + pagePosition: 4 + ), + Pattern( + pattern: NSRegularExpression( + pattern: "^(.*) - v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)" + ), + titlePosition: 1, + volumePosition: 2, + chapterPosition: 4, + pagePosition: 3 + ), + ] else { + alert.message = String(format: "Regular expression is invalid") + pendingAlert = alert + return nil + } + + var matchingPattern: Pattern? + for testPattern in patterns { + let range = NSRange(filename.startIndex..., in: filename) + if let match = testPattern.pattern.firstMatch(in: filename, range: range) { + if (filename as NSString).substring(with: match.range(at: testPattern.titlePosition)) == "" { + continue + } + } else { + continue + } + if let match = testPattern.pattern.firstMatch(in: filename, range: range) { + if (filename as NSString).substring(with: match.range(at: testPattern.pagePosition)) == "" { + continue + } + } else { + continue + } + if let match = testPattern.pattern.firstMatch(in: filename, range: range) { + if (filename as NSString).substring(with: match.range(at: testPattern.chapterPosition)) == "" { + continue + } + } else { + continue + } + if let match = testPattern.pattern.firstMatch(in: filename, range: range) { + if (filename as NSString).substring(with: match.range(at: testPattern.volumePosition)) == "" { + continue + } + } else { + continue + } + matchingPattern = testPattern + break + } + + guard let pattern = matchingPattern else { + alert.message = String(format: "Could not find matching pattern, likely missing regex.") + pendingAlert = alert + return nil + } + return pattern + } + + func getVolumeMetadata( + builder: inout FlatBufferBuilder, pattern: Pattern, + filenames: [(String, String)], volumeDir: URL, alert: UIAlertController + ) -> ([(Offset, MetaValue)], UInt32)? { + var imageCount: UInt32 = 0 + var volumes: [(Offset, MetaValue)] = [] + var currentChapters: [Offset] = [] + var currentImages: [Offset] = [] + var previousVolumeURL = "" + + var currentVolume: UInt32! + var currentChapter: (UInt32, UInt32?)! + + for (i, (volumeURL, filename)) in filenames.enumerated() { + let range = NSRange(filename.startIndex..., in: filename) + if let match = pattern.pattern.firstMatch(in: filename, range: range) { + imageCount += 1 + let chapterStr = (filename as NSString).substring(with: match.range(at: pattern.chapterPosition)) + let chapterParts = chapterStr.split(separator: "x", maxSplits: 1) + if chapterParts.count < 1 { + alert.message = String(format: "Did not find chapter number in filename: \(filename)") + pendingAlert = alert + return nil + } + guard let nChapter0 = UInt32(chapterParts[0]) else { + alert.message = String(format: "Failed to convert chapter number to an int: \(filename)") + pendingAlert = alert + return nil + } + let chapter: (UInt32, UInt32?) = ( + nChapter0, chapterParts.count > 1 ? UInt32(chapterParts[1]) : nil + ) + + guard let volume = + UInt32((filename as NSString) + .substring(with: match.range(at: pattern.volumePosition))) + else { + alert.message = String(format: "failed to get match for volume in filename: \(filename)") + pendingAlert = alert + return nil + } + + let pageStr = (filename as NSString).substring(with: match.range(at: pattern.pagePosition)) + let pageParts = pageStr.split(separator: "-", maxSplits: 1) + guard let nPage = UInt32(pageParts[0]) else { + alert.message = String(format: "failed to read archive: \(filename)") + pendingAlert = alert + return nil + } + let (page, doublePage) = (nPage, pageParts.count > 1) + + let archive: Archive! + do { + let a = try Archive(url: volumeDir.appendingPathComponent(volumeURL), accessMode: .read) + archive = a + } catch { + alert.message = String(format: "failed to read archive: \(volumeURL)") + pendingAlert = alert + return nil + } + guard let entry = archive[filename] else { + alert.message = String(format: "failed to archive entry: \(filename)") + pendingAlert = alert + return nil + } + var size = ImageDimensions(width: 0, height: 0) + do { + size = try imageDimensions(of: entry, in: archive) + } catch { + alert.message = String(format: "Failed to get image dimensions of filename: \(filename)") + pendingAlert = alert + return nil + } + let m_filename = builder.create(string: filename) + let imageMetadata = ImageMetadata.createImageMetadata( + &builder, + doublePage: doublePage, + filenameOffset: m_filename, + firstPage: page, + size: Size(width: UInt32(size.width), height: UInt32(size.height)) + ) + + if currentVolume != nil { + if volume != currentVolume { + assert(volume > currentVolume) + + let chapterName = builder.create(string: "") + let imagesOffset = builder.createVector(ofOffsets: currentImages) + let chapterMetadata = ChapterMetadata.createChapterMetadata( + &builder, + chapter: MetaValue( + main: currentChapter.0, bonus: currentChapter.1 ?? 0 + ), + nameOffset: chapterName, + imagesVectorOffset: imagesOffset + ) + + currentChapters.append(chapterMetadata) + + let volumeTitle = builder.create(string: "") + let volumeURLOffset = builder.create(string: previousVolumeURL) + let chaptersOffset = builder.createVector(ofOffsets: currentChapters) + let volumeMetadata = VolumeMetadata.createVolumeMetadata( + &builder, + volume: MetaValue(main: currentVolume, bonus: 0), + titleOffset: volumeTitle, + archiveOffset: volumeURLOffset, + chaptersVectorOffset: chaptersOffset + ) + + volumes.append((volumeMetadata, MetaValue(main: currentVolume, bonus: 0))) + + currentImages = [imageMetadata] + currentChapters = [] + } else if chapter != currentChapter { + if chapter.0 == currentChapter.0 { + if chapter.1 == nil { + alert.message = String(format: "chapter.1 is nil") + pendingAlert = alert + return nil + } + if chapter.1! != currentChapter.1 ?? 1 { + alert.message = String(format: "chapter != currentChapter") + pendingAlert = alert + return nil + } + assert(chapter.1! == currentChapter.1 ?? 1) + } else { + if chapter.0 != currentChapter.0 + 1 { + alert.message = String(format: "chapter.0 != currentChapter.0 + 1") + pendingAlert = alert + return nil + } + assert(chapter.0 == currentChapter.0 + 1) + } + + let chapterName = builder.create(string: "") + let imagesOffset = builder.createVector(ofOffsets: currentImages) + let chapterMetadata = ChapterMetadata.createChapterMetadata( + &builder, + chapter: MetaValue( + main: currentChapter.0, bonus: currentChapter.1 ?? 0 + ), + nameOffset: chapterName, + imagesVectorOffset: imagesOffset + ) + + currentChapters.append(chapterMetadata) + + currentImages = [imageMetadata] + + } else { + currentImages.append(imageMetadata) + } + } else { + currentImages.append(imageMetadata) + } + if i == filenames.count - 1 { + let chapterName = builder.create(string: "") + let imagesOffset = builder.createVector(ofOffsets: currentImages) + let chapterMetadata = ChapterMetadata.createChapterMetadata( + &builder, + chapter: MetaValue( + main: chapter.0, bonus: chapter.1 ?? 0 + ), + nameOffset: chapterName, + imagesVectorOffset: imagesOffset + ) + + 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 + ) + + volumes.append((volumeMetadata, MetaValue(main: volume, bonus: 0))) + + currentImages = [] + currentChapters = [] + } + currentVolume = volume + currentChapter = chapter + previousVolumeURL = volumeURL + } else { + print("no regex match on \(filename), returning") + return nil + } + } + return (volumes, imageCount) + } + struct Pattern { var pattern: NSRegularExpression - var title: NSRegularExpression + var titlePosition: Int var volumePosition: Int var chapterPosition: Int var pagePosition: Int @@ -434,90 +763,15 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { ) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - // Beautiful, is it not? - var nPatterns: [Pattern]? - do { - let fPatterns: [Pattern] = try [ - Pattern( - pattern: NSRegularExpression( - pattern: #"c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-p?[0-9]+)?)"# - ), - title: NSRegularExpression( - pattern: #"^(.*) - c([0-9]+(?:x[0-9]+)?) \(v([0-9]+)\) - p([0-9]+(?:-p?[0-9]+)?)"# - ), - volumePosition: 2, - chapterPosition: 1, - pagePosition: 3 - ), - Pattern( - pattern: NSRegularExpression( - pattern: "v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)" - ), - title: NSRegularExpression( - pattern: "^(.*) - v([0-9]+) p([0-9]+(?:-[0-9]+)?) - c([0-9]+(?:x[0-9]+)?)" - ), - volumePosition: 1, - chapterPosition: 3, - pagePosition: 2 - ), - ] - nPatterns = fPatterns - } catch { - alert.message = String(format: "Regular expression is invalid") - pendingAlert = alert - return - } - guard let patterns = nPatterns else { - alert.message = String(format: "Regular expression is invalid") - pendingAlert = alert - return - } - - if fileManager.fileExists( - atPath: path.appendingPathComponent(metadataFilename).absoluteString - ) { - print("skipped metadata file creation") - return - } - var builder = FlatBufferBuilder(initialSize: 1024) - var volumes: [Offset] = [] - var currentChapters: [Offset] = [] - var currentImages: [Offset] = [] - var previousVolumeURL = "" - - var titleValue = "" - - var currentVolume: UInt32! - var currentChapter: (UInt32, UInt32?)! - 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 == nil { - alert.message = String(format: "failed to read dir: \(volumeDir)") - pendingAlert = alert - return - } - - if volumeURLs!.isEmpty { - print("found no volumes in volume dir: \(volumeDir)") + guard let volumeURLs = getArchivePaths(volumeDir: volumeDir, alert: alert) else { return } var filenames: [(String, String)] = [] - for volumeURL in volumeURLs! { + for volumeURL in volumeURLs { do { let volumeArchive = try Archive(url: volumeURL, accessMode: .read) volumeArchive.map { $0.path }.sorted().forEach { filenames.append((volumeURL.lastPathComponent, $0)) } @@ -533,237 +787,23 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { return } - var matchingPattern: Pattern? - var imageCount: UInt32 = 0 + guard let pattern = getMatchingPattern(filename: filenames[0].1, alert: alert) else { + return + } - do { - for testPattern in patterns { - let filename = filenames[0].1 - let range = NSRange(filename.startIndex..., in: filename) - if let match = testPattern.title.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: 1)) == "" { - continue - } - } else { - continue - } - if let match = testPattern.pattern.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: testPattern.pagePosition)) == "" { - continue - } - } else { - continue - } - if let match = testPattern.pattern.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: testPattern.chapterPosition)) == "" { - continue - } - } else { - continue - } - if let match = testPattern.pattern.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: testPattern.volumePosition)) == "" { - continue - } - } else { - continue - } - matchingPattern = testPattern - break - } + let titleValue: String! + let range = NSRange(filenames[0].1.startIndex..., in: filenames[0].1) + if let match = pattern.pattern.firstMatch(in: filenames[0].1, range: range) { + titleValue = (filenames[0].1 as NSString).substring(with: match.range(at: pattern.titlePosition)) + } else { + print("Did not find match for title, regex did not handle this case.") + return + } - guard let pattern = matchingPattern else { - alert.message = String(format: "Could not find matching pattern, likely missing regex.") - pendingAlert = alert - return - } - - for (i, (volumeURL, filename)) in filenames.enumerated() { - let range = NSRange(filename.startIndex..., in: filename) - if i == 0 { - if let match = pattern.title.firstMatch(in: filename, range: range) { - titleValue = (filename as NSString).substring(with: match.range(at: 1)) - } else { - print("Did not find match for title, regex did not handle this case.") - return - } - } - if let match = pattern.pattern.firstMatch(in: filename, range: range) { - imageCount += 1 - let chapterStr = (filename as NSString).substring(with: match.range(at: pattern.chapterPosition)) - let chapterParts = chapterStr.split(separator: "x", maxSplits: 1) - if chapterParts.count < 1 { - alert.message = String(format: "Did not find chapter number in filename: \(filename)") - pendingAlert = alert - return - } - guard let nChapter0 = UInt32(chapterParts[0]) else { - alert.message = String(format: "Failed to convert chapter number to an int: \(filename)") - pendingAlert = alert - return - } - let chapter: (UInt32, UInt32?) = ( - nChapter0, chapterParts.count > 1 ? UInt32(chapterParts[1]) : nil - ) - - guard let volume = - UInt32((filename as NSString) - .substring(with: match.range(at: pattern.volumePosition))) - else { - alert.message = String(format: "failed to get match for volume in filename: \(filename)") - pendingAlert = alert - return - } - - let pageStr = (filename as NSString).substring(with: match.range(at: pattern.pagePosition)) - let pageParts = pageStr.split(separator: "-", maxSplits: 1) - guard let nPage = UInt32(pageParts[0]) else { - alert.message = String(format: "failed to read archive: \(filename)") - pendingAlert = alert - return - } - let (page, doublePage) = ( - nPage, pageParts.count > 1 - ) - - let archive = try Archive(url: volumeDir.appendingPathComponent(volumeURL), accessMode: .read) - guard let entry = archive[filename] else { - alert.message = String(format: "failed to read archive: \(filename)") - pendingAlert = alert - return - } - var size = ImageDimensions(width: 0, height: 0) - do { - size = try imageDimensions(of: entry, in: archive) - } catch { - alert.message = String(format: "Failed to get image dimensions of filename: \(filename)") - pendingAlert = alert - return - } - let m_filename = builder.create(string: filename) - let imageMetadata = ImageMetadata.createImageMetadata( - &builder, - doublePage: doublePage, - filenameOffset: m_filename, - firstPage: page, - size: Size(width: UInt32(size.width), height: UInt32(size.height)) - ) - - if currentVolume != nil { - if volume != currentVolume { - assert(volume > currentVolume) - - let chapterName = builder.create(string: "") - let imagesOffset = builder.createVector(ofOffsets: currentImages) - let chapterMetadata = ChapterMetadata.createChapterMetadata( - &builder, - chapter: MetaValue( - main: currentChapter.0, bonus: currentChapter.1 ?? 0 - ), - nameOffset: chapterName, - imagesVectorOffset: imagesOffset - ) - - currentChapters.append(chapterMetadata) - - let volumeTitle = builder.create(string: "") - let volumeURLOffset = builder.create(string: previousVolumeURL) - let chaptersOffset = builder.createVector(ofOffsets: currentChapters) - let volumeMetadata = VolumeMetadata.createVolumeMetadata( - &builder, - volume: MetaValue(main: currentVolume, bonus: 0), - titleOffset: volumeTitle, - archiveOffset: volumeURLOffset, - chaptersVectorOffset: chaptersOffset - ) - - volumes.append(volumeMetadata) - - currentImages = [imageMetadata] - currentChapters = [] - } else if chapter != currentChapter { - if chapter.0 == currentChapter.0 { - if chapter.1 == nil { - alert.message = String(format: "chapter.1 is nil") - pendingAlert = alert - return - } - if chapter.1! != currentChapter.1 ?? 1 { - alert.message = String(format: "chapter != currentChapter") - pendingAlert = alert - return - } - assert(chapter.1! == currentChapter.1 ?? 1) - } else { - if chapter.0 != currentChapter.0 + 1 { - alert.message = String(format: "chapter.0 != currentChapter.0 + 1") - pendingAlert = alert - return - } - assert(chapter.0 == currentChapter.0 + 1) - } - - let chapterName = builder.create(string: "") - let imagesOffset = builder.createVector(ofOffsets: currentImages) - let chapterMetadata = ChapterMetadata.createChapterMetadata( - &builder, - chapter: MetaValue( - main: currentChapter.0, bonus: currentChapter.1 ?? 0 - ), - nameOffset: chapterName, - imagesVectorOffset: imagesOffset - ) - - currentChapters.append(chapterMetadata) - - currentImages = [imageMetadata] - - } else { - currentImages.append(imageMetadata) - } - } else { - currentImages.append(imageMetadata) - } - if i == filenames.count - 1 { - let chapterName = builder.create(string: "") - let imagesOffset = builder.createVector(ofOffsets: currentImages) - let chapterMetadata = ChapterMetadata.createChapterMetadata( - &builder, - chapter: MetaValue( - main: chapter.0, bonus: chapter.1 ?? 0 - ), - nameOffset: chapterName, - imagesVectorOffset: imagesOffset - ) - - 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 - ) - - volumes.append(volumeMetadata) - - currentImages = [] - currentChapters = [] - } - currentVolume = volume - currentChapter = chapter - previousVolumeURL = volumeURL - } else { - print("no regex match on \(filename), returning") - return - } - } - } catch { - print("failed reading image file names") + guard let (volumes, imageCount) = getVolumeMetadata( + builder: &builder, pattern: pattern, filenames: filenames, volumeDir: volumeDir, alert: alert + ) else { + return } if volumes.count == 0 { @@ -776,7 +816,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { let countryOfOrigin = builder.create(string: nil) let description = builder.create(string: nil) let tags = builder.createVector(ofOffsets: []) - let volumeOffsets = builder.createVector(ofOffsets: volumes) + let volumeOffsets = builder.createVector(ofOffsets: volumes.map { $0.0 }) let metadata = Metadata.createMetadata( &builder, titleOffset: title, @@ -2632,6 +2672,135 @@ extension ViewController { } comics[indexPath.item].metadata = newMetadata } + + private func addNewVolumesToMetadata(pM: Metadata, volumeURLs: [URL], volumeDir: URL) -> Metadata? { + let alert = UIAlertController( + title: "Failed to rebuild metadata", + message: "This is an alert.", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + + var volumes: [(Offset, MetaValue)] = [] + var currentChapters: [Offset] = [] + var currentImages: [Offset] = [] + var builder = FlatBufferBuilder(initialSize: 1024) + for volume in pM.volumes { + for chapter in volume.chapters { + for image in chapter.images { + let m_filename = builder.create(string: image.filename) + let imageMetadata = ImageMetadata.createImageMetadata( + &builder, + doublePage: image.doublePage, + filenameOffset: m_filename, + firstPage: image.firstPage, + size: image.size + ) + currentImages.append(imageMetadata) + } + let chapterName = builder.create(string: chapter.name) + let imagesOffset = builder.createVector(ofOffsets: currentImages) + let chapterMetadata = ChapterMetadata.createChapterMetadata( + &builder, + chapter: chapter.chapter, + nameOffset: chapterName, + imagesVectorOffset: imagesOffset + ) + currentChapters.append(chapterMetadata) + currentImages = [] + } + let volumeTitle = builder.create(string: volume.title) + let volumeURLOffset = builder.create(string: volume.archive) + let chaptersOffset = builder.createVector(ofOffsets: currentChapters) + let volumeMetadata = VolumeMetadata.createVolumeMetadata( + &builder, + volume: volume.volume, + titleOffset: volumeTitle, + archiveOffset: volumeURLOffset, + chaptersVectorOffset: chaptersOffset + ) + + volumes.append((volumeMetadata, volume.volume)) + currentChapters = [] + } + + var filenames: [(String, String)] = [] + for volumeURL in volumeURLs { + do { + let volumeArchive = try Archive(url: volumeURL, accessMode: .read) + volumeArchive.map { $0.path }.sorted().forEach { filenames.append((volumeURL.lastPathComponent, $0)) } + } catch { + alert.message = String(format: "failed to read archive: \(volumeURL)") + pendingAlert = alert + continue + } + } + guard let pattern = getMatchingPattern(filename: filenames[0].1, alert: alert) else { + return nil + } + guard let (newVolumeOffsets, newImageCount) = getVolumeMetadata( + builder: &builder, pattern: pattern, + filenames: filenames, volumeDir: volumeDir, alert: alert + ) else { + return nil + } + volumes += newVolumeOffsets + + let title = builder.create(string: pM.title) + let originalLanguage = builder.create(string: pM.originalLanguage) + let countryOfOrigin = builder.create(string: pM.countryOfOrigin) + let description = builder.create(string: pM.description) + let tags = builder.createVector(ofOffsets: []) + let volumeOffsets = builder.createVector(ofOffsets: volumes.sorted { + ($0.1.main < $1.1.main) || ($0.1.main == $1.1.main && $0.1.bonus < $1.1.bonus) + }.map { $0.0 }) + + let metadata = Metadata.createMetadata( + &builder, + titleOffset: title, + format: pM.format, + originalLanguageOffset: originalLanguage, + countryOfOriginOffset: countryOfOrigin, + publicationDemographic: pM.publicationDemographic, + status: pM.status, + contentRating: pM.contentRating, + source: pM.source, + startReleaseDate: pM.startReleaseDate, + endReleaseDate: pM.endReleaseDate, + tagsVectorOffset: tags, + descriptionOffset: description, + anilistId: pM.anilistId, + volumesVectorOffset: volumeOffsets, + imageCount: pM.imageCount + newImageCount + ) + + builder.finish(offset: metadata) + do { + try builder.data.write(to: currentPath.appendingPathComponent(metadataFilename)) + } catch { + alert.message = String(format: "Failed to write metadata file at: \(metadataFilename)") + DispatchQueue.main.async { + self.present(alert, animated: false) + } + } + let metadataPath = currentPath.appendingPathComponent(metadataFilename) + guard let data = try? Data(contentsOf: metadataPath) else { + alert.message = String(format: "Failed to read metadata file at: \(metadataPath)") + DispatchQueue.main.async { + self.present(alert, animated: false) + } + return nil + } + var byteBuffer = ByteBuffer(data: data) + guard let newMetadata: Metadata = try? getCheckedRoot(byteBuffer: &byteBuffer) else { + alert.message = String(format: "Failed to load flatbuffer file at: \(metadataPath)") + DispatchQueue.main.async { + self.present(alert, animated: false) + } + return nil + } + return newMetadata + } } extension ViewController: UIPopoverPresentationControllerDelegate {