From 73d7309b7616b44a2f1ff0556f7663d081926f7e Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Fri, 17 Jul 2026 20:44:34 +0200 Subject: [PATCH] linting --- .editorconfig | 4 + .gitignore | 1 - .swiftlint.yml | 19 +++ ImageViewer/ViewController.swift | 249 ++++++++++++++++++------------- 4 files changed, 165 insertions(+), 108 deletions(-) create mode 100644 .editorconfig create mode 100644 .swiftlint.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..79294b2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.swift] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index 9d60aee..ebcfbb3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /.nvim .DS_Store buildServer.json -.swiftlint.yml /ImageViewer.xcodeproj/xcuserdata /ImageViewer.xcodeproj/project.xcworkspace/xcuserdata /ImageViewer/flatc diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..a4e2ac1 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,19 @@ +function_body_length: 1000 +type_body_length: + warning: 10000 +trailing_comma: + mandatory_comma: true +line_length: 120 +opening_brace: + ignore_multiline_statement_conditions: true +identifier_name: + min_length: 1 + allowed_symbols: ["_"] +file_length: + error: 10000 + warning: 3000 +cyclomatic_complexity: + warning: 50 + error: 100 +disabled_rules: + - todo diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index 6367bc5..52f19d5 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -8,7 +8,8 @@ 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. +/// With a whopping 2 cores, and 2 threads because no hyperthreading, +/// it makes sense to only have a queue as an extra thread to do work with to allow the main thread for ui. let queue = DispatchQueue(label: "queue", qos: .utility) enum PageTurn { @@ -41,7 +42,7 @@ struct Comic { } struct GlobalState: Codable { - var comicName: String? = nil + var comicName: String? } struct LocalState: Codable { @@ -170,7 +171,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } func loadComics() { - let alert = UIAlertController(title: "Failed to load comics.", message: "This is an alert.", preferredStyle: .alert) + let alert = UIAlertController( + title: "Failed to load comics.", + message: "This is an alert.", + preferredStyle: .alert + ) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) do { var directories: [URL] = [] @@ -202,16 +207,18 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { currentPath = dir - guard let data = try? Data(contentsOf: dir.appendingPathComponent(metadataFilename)) + let metadataPath = dir.appendingPathComponent(metadataFilename) + guard let data = try? Data(contentsOf: metadataPath) else { - alert.message = String(format: "Failed to read contents of metadata: \(dir.appendingPathComponent(metadataFilename))") + alert.message = String(format: "Failed to read contents of metadata: \(metadataPath))") pendingAlert = alert return } var byteBuffer = ByteBuffer(data: data) metadata = try? getCheckedRoot(byteBuffer: &byteBuffer) if metadata == nil { - alert.message = String(format: "Failed to read contents of metadata flatbuffer file: \(dir.appendingPathComponent(metadataFilename))") + alert.message = + String(format: "Failed to read contents of metadata flatbuffer file: \(metadataPath))") pendingAlert = alert return } @@ -307,21 +314,60 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { return CGSize(width: width, height: height) } + struct Pattern { + var pattern: NSRegularExpression + var title: NSRegularExpression + var volumePosition: Int + var chapterPosition: Int + var pagePosition: Int + } + func getMetadataFromFilename(path: URL) { + let alert = UIAlertController( + title: "Failed to get metadata from filename.", + message: "This is an alert.", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + // 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]+(?:-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), - ] - var pattern: NSRegularExpression! = nil - var titlePattern: NSRegularExpression! = nil - - var pageRegexPosition: Int! = nil - var chapterRegexPosition: Int! = nil - var volumeRegexPosition: Int! = nil - - var currentVolume: UInt32! = nil - var currentChapter: (UInt32, UInt32?)! + 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 @@ -339,8 +385,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { var titleValue = "" - let alert = UIAlertController(title: "Failed to get metadata from filename.", message: "This is an alert.", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + var currentVolume: UInt32! + var currentChapter: (UInt32, UInt32?)! let volumeDir = path.appendingPathComponent("volumes") @@ -370,18 +416,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { for volumeURL in volumeURLs! { do { let volumeArchive = try Archive(url: volumeURL, accessMode: .read) - var volumeArchiveFiles: [String] = [] - for entry in volumeArchive { - // guard let url = URL(string: entry.path) else { - // alert.message = String(format: "failed to create URL from string: \(entry.path)") - // pendingAlert = alert - // return - // } - volumeArchiveFiles.append(entry.path) - } - for filename in volumeArchiveFiles.sorted() { - filenames.append((volumeURL.lastPathComponent, filename)) - } + volumeArchive.map { $0.path }.sorted().forEach { filenames.append((volumeURL.lastPathComponent, $0)) } } catch { alert.message = String(format: "failed to read archive: \(volumeURL)") pendingAlert = alert @@ -394,77 +429,61 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { return } + var matchingPattern: Pattern? do { - for (currentPattern, currentTitlePattern, currentPageRegexPosition, currentChapterRegexPosition, currentVolumeRegexPosition) in patterns { - guard let currentPattern = currentPattern else { - alert.message = String(format: "failed to get currentPattern") - pendingAlert = alert - continue - } - guard let currentTitlePattern = currentTitlePattern else { - alert.message = String(format: "failed to get currentTitlePattern") - pendingAlert = alert - continue - } + for testPattern in patterns { let filename = filenames[0].1 let range = NSRange(filename.startIndex..., in: filename) - if let match = currentTitlePattern.firstMatch(in: filename, range: range) { + 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 = currentPattern.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: currentPageRegexPosition)) == "" { + 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 = currentPattern.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: currentChapterRegexPosition)) == "" { + 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 = currentPattern.firstMatch(in: filename, range: range) { - if (filename as NSString).substring(with: match.range(at: currentVolumeRegexPosition)) == "" { + if let match = testPattern.pattern.firstMatch(in: filename, range: range) { + if (filename as NSString).substring(with: match.range(at: testPattern.volumePosition)) == "" { continue } } else { continue } - pattern = currentPattern - titlePattern = currentTitlePattern - - pageRegexPosition = currentPageRegexPosition - chapterRegexPosition = currentChapterRegexPosition - volumeRegexPosition = currentVolumeRegexPosition + matchingPattern = testPattern break } - if pattern == nil || titlePattern == nil || pageRegexPosition == nil || chapterRegexPosition == nil || volumeRegexPosition == nil { - print("Could not find pattern, returning") + guard let pattern = matchingPattern else { + alert.message = String(format: "Could not find matching pattern, likely missing regex.") + pendingAlert = alert return } - let regex = pattern! - let titleRegex = titlePattern! - 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) { + 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 = regex.firstMatch(in: filename, range: range) { - let chapterStr = (filename as NSString).substring(with: match.range(at: chapterRegexPosition)) + if let match = pattern.pattern.firstMatch(in: filename, range: range) { + 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)") @@ -480,13 +499,16 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { nChapter0, chapterParts.count > 1 ? UInt32(chapterParts[1]) : nil ) - guard let volume = UInt32((filename as NSString).substring(with: match.range(at: volumeRegexPosition))) else { + guard let volume = + UInt32((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: pageRegexPosition)) + 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)") @@ -798,7 +820,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { super.viewDidLayoutSubviews() guard view.bounds.size != lastLayoutSize else { return } lastLayoutSize = UIScreen.main.bounds.size - if scrollingCollectionView.isHidden == true || mode != .scroll + if scrollingCollectionView.isHidden == true + || mode != .scroll || scrollingCollectionView.contentSize == .zero { if metadata == nil { return } @@ -1172,7 +1195,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { leftView.isHidden = mode == .scroll rightView.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. + // This does not optmize for the case where you switch between scrolling and non-scrolling in the same comic. + // However this is badly supported because of the lack of state conversion. if mode == .scroll { scrollingCollectionView.reloadData() } @@ -1352,7 +1376,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { let chapterValue = metadata.volumes[progress.v].chapters[progress.c].chapter! let chapterString = metaValueToString(m: chapterValue) - let lastChapterValue = metadata.volumes[metadata.volumes.count - 1].chapters[metadata.volumes[metadata.volumes.count - 1].chapters.count - 1].chapter! + let lastChapterValue = metadata + .volumes[metadata.volumes.count - 1] + .chapters[metadata.volumes[metadata.volumes.count - 1] + .chapters.count - 1].chapter! let lastChapterString = metaValueToString(m: lastChapterValue) let volumeValue = metadata.volumes[progress.v].volume! @@ -1389,12 +1416,15 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { Chapter \(chapterString) of \(lastChapterString) Page \(pageString) of \(lastPageString) """ - text += "\nImage size: \(metadata.volumes[progress.v].chapters[progress.c].images[progress.i].size!.width)x\(metadata.volumes[progress.v].chapters[progress.c].images[progress.i].size!.height)" + let imageSize = metadata.volumes[progress.v].chapters[progress.c].images[progress.i].size! + text += "\nImage size: \(imageSize.width)x\(imageSize.height)" info.text = text } func getArchiveURL(_ volumeProgress: Int) -> URL { - return currentPath.appendingPathComponent("volumes").appendingPathComponent(metadata.volumes[volumeProgress].archive) + return currentPath + .appendingPathComponent("volumes") + .appendingPathComponent(metadata.volumes[volumeProgress].archive) } func getImagePath(_ progress: ProgressIndices) -> String { @@ -1440,9 +1470,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } func getMetadata(path: URL) -> Metadata? { - let data = try! Data(contentsOf: path.appendingPathComponent(metadataFilename)) + guard let data = try? Data(contentsOf: path.appendingPathComponent(metadataFilename)) else { return nil } var byteBuffer = ByteBuffer(data: data) - let metadata: Metadata = try! getCheckedRoot(byteBuffer: &byteBuffer) + guard let metadata: Metadata = try? getCheckedRoot(byteBuffer: &byteBuffer) else { return nil } return metadata } @@ -1471,10 +1501,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } func getPathFromComicName(name: String) -> URL? { - for comic in comics { - if comic.metadata.title == name { - return comic.path - } + for comic in comics where comic.metadata.title == name { + return comic.path } return nil } @@ -1512,11 +1540,7 @@ func getDocumentsURL() -> URL? { } extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { - func collectionView( - _ collectionView: UICollectionView, numberOfItemsInSection _: Int - ) - -> Int - { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection _: Int) -> Int { if collectionView == comicCollectionView { return comics.count } else if collectionView == scrollingCollectionView { @@ -1534,24 +1558,30 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl return 0 } - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) - -> UICollectionViewCell - { + func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { if collectionView == comicCollectionView { - let cell = + guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "ComicImageCell", for: indexPath - ) - as! ComicImageCell + ) as? ComicImageCell + else { + return ComicImageCell() + } cell.imageView.image = comics[indexPath.item].cover return cell } else if collectionView == scrollingCollectionView { let scaling = UIView.ContentMode.scaleAspectFill - let cell = + guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "ScrollingImageCell", for: indexPath ) - as! ScrollingImageCell + as? ScrollingImageCell + else { + return ScrollingImageCell() + } if mode != .scroll { return cell } if metadata == nil { print("metadata is nil, should probably not be the case") @@ -1576,11 +1606,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl } // Xcode profiling sucks: - return - collectionView.dequeueReusableCell( - withReuseIdentifier: "ScrollingImageCell", for: indexPath - ) - as! ScrollingImageCell + return ComicImageCell() } func collectionView( @@ -1605,7 +1631,9 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl let imageMetadata = metadata.volumes[newProgress.v].chapters[newProgress.c].images[newProgress.i] return CGSize( width: readerView.bounds.width, - height: CGFloat(imageMetadata.size!.height) * readerView.bounds.width / CGFloat(imageMetadata.size!.width) + height: CGFloat(imageMetadata.size!.height) + * readerView.bounds.width + / CGFloat(imageMetadata.size!.width) ) } else { assertionFailure() @@ -1696,7 +1724,8 @@ class ImageLoader { 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. + // 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 } @@ -1759,14 +1788,16 @@ class ImageLoader { if token.isCancelled { finish(); return } - func extractData() -> Data { + func extractData() -> Data? { 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) + do { + let archive = try Archive(url: archiveURL, accessMode: .read) + self.archives.setObject(archive, forKey: archiveURL.lastPathComponent as NSString) + } catch { return nil } } let archive = self.archives.object(forKey: archiveURL.lastPathComponent as NSString)! - _ = try! archive.extract(archive[filename]!) { data in + _ = try? archive.extract(archive[filename]!) { data in extractedData.append(data) } return extractedData @@ -1774,7 +1805,7 @@ class ImageLoader { if token.isCancelled { finish(); return } - guard let image = cache.object(forKey: key as NSString) ?? UIImage(data: extractData()) else { + guard let image = cache.object(forKey: key as NSString) ?? UIImage(data: extractData()!) else { finish() return } @@ -1808,7 +1839,13 @@ class ImageLoader { } } - func scaleAndCacheImage(image: UIImage, orientation: Rotation, screenSize: CGSize, scaling: UIView.ContentMode, filename: String) -> UIImage { + func scaleAndCacheImage( + image: UIImage, + orientation: Rotation, + screenSize: CGSize, + scaling: UIView.ContentMode, + filename: String + ) -> UIImage { let resizeTo = if orientation == .vertical { CGSize( width: min(screenSize.width, screenSize.height), @@ -1860,9 +1897,7 @@ func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage { return scaledImage! } -func aspectSize(for imageSize: CGSize, in boundingSize: CGSize, scaling: UIView.ContentMode) - -> CGSize -{ +func aspectSize(for imageSize: CGSize, in boundingSize: CGSize, scaling: UIView.ContentMode) -> CGSize { let widthRatio = boundingSize.width / imageSize.width let heightRatio = boundingSize.height / imageSize.height var scale = CGFloat()