diff --git a/.gitignore b/.gitignore index e62264a..f1bf5b9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Package.resolved .swiftpm/ Public/images/ +Public/thumbnails/ Public/classifier.version Public/classifier.mlmodel Public/caps.json diff --git a/Sources/App/CapServer.swift b/Sources/App/CapServer.swift index eb66514..dd23006 100644 --- a/Sources/App/CapServer.swift +++ b/Sources/App/CapServer.swift @@ -8,6 +8,11 @@ final class CapServer: ServerOwner { private let imageFolder: URL + private let thumbnailFolder: URL + + /// The file where the cap count is stored for the grid webpage + private let gridCountFile: URL + /// The file where the database of caps is stored private let dbFile: URL @@ -73,6 +78,8 @@ final class CapServer: ServerOwner { init(in folder: URL, writers: [String]) { self.imageFolder = folder.appendingPathComponent("images") + self.thumbnailFolder = folder.appendingPathComponent("thumbnails") + self.gridCountFile = folder.appendingPathComponent("count.js") self.dbFile = folder.appendingPathComponent("caps.json") self.htmlFile = folder.appendingPathComponent("count.html") self.classifierVersionFile = folder.appendingPathComponent("classifier.version") @@ -217,6 +224,10 @@ final class CapServer: ServerOwner { func folder(of cap: Int) -> URL { imageFolder.appendingPathComponent(String(format: "%04d", cap)) } + + func thumbnail(of cap: Int) -> URL { + thumbnailFolder.appendingPathComponent(String(format: "%04d.jpg", cap)) + } func file(of cap: Int, version: Int) -> URL { folder(of: cap).appendingPathComponent(String(format: "%04d-%02d.jpg", cap, version)) @@ -384,6 +395,7 @@ final class CapServer: ServerOwner { cap.classifierVersion = nextClassifierVersion caps[cap.id] = cap saveCapCountHTML() + updateGridCapCount() log("Added cap \(cap.id) '\(cap.name)'") } @@ -426,6 +438,29 @@ final class CapServer: ServerOwner { classifierVersion = version log("Updated classifier to version \(version)") } + + func getListOfMissingThumbnails() -> [Int] { + caps.keys.filter { !fm.fileExists(atPath: thumbnail(of: $0).path) } + } + + func saveThumbnail(_ data: Data, for cap: Int) { + let url = thumbnail(of: cap) + do { + try data.write(to: url) + } catch { + log("Failed to save thumbnail \(cap): \(error)") + } + } + + private func updateGridCapCount() { + do { + try "const numberOfCaps = \(capCount);" + .data(using: .utf8)! + .write(to: gridCountFile) + } catch { + log("Failed to save grid cap count: \(error)") + } + } // MARK: ServerOwner diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index d1b6950..207f9c3 100755 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -29,11 +29,8 @@ func routes(_ app: Application) { // Add or change a cap app.postCatching("cap") { request in try authorize(request) - guard let buffer = request.body.data else { - log("Invalid body data") - throw CapError.invalidBody - } - let cap = try decoder.decode(Cap.self, from: buffer) + let data = try request.getBodyData() + let cap = try decoder.decode(Cap.self, from: data) try server.addOrUpdate(cap) } @@ -44,11 +41,7 @@ func routes(_ app: Application) { log("Invalid parameter for cap") throw Abort(.badRequest) } - guard let buffer = request.body.data else { - log("Missing body data: \(request.body.description)") - throw CapError.invalidBody - } - let data = Data(buffer: buffer) + let data = try request.getBodyData() try server.save(image: data, for: cap) } @@ -62,11 +55,7 @@ func routes(_ app: Application) { guard version > server.classifierVersion else { throw Abort(.alreadyReported) // 208 } - guard let buffer = request.body.data else { - log("Missing body data: \(request.body.description)") - throw CapError.invalidBody - } - let data = Data(buffer: buffer) + let data = try request.getBodyData() try server.save(classifier: data, version: version) return .ok } @@ -83,16 +72,44 @@ func routes(_ app: Application) { } try authorize(request) - guard let buffer = request.body.data else { - log("Missing body data: \(request.body.description)") - throw CapError.invalidBody - } - let data = Data(buffer: buffer) - guard let content = String(data: data, encoding: .utf8) else { - log("Invalid string body: \(request.body.description)") - throw CapError.invalidBody - } - server.updateTrainedClasses(content: content) + let body = try request.getStringBody() + + server.updateTrainedClasses(content: body) server.removeAllEntriesInImageChangeList(before: date) } + + // Get the list of missing thumbnails + app.get("thumbnails", "missing") { request in + let missingThumbnails = server.getListOfMissingThumbnails() + return missingThumbnails.map(String.init).joined(separator: ",") + } + + app.postCatching("thumbnails", ":cap") { request in + guard let cap = request.parameters.get("cap", as: Int.self) else { + log("Invalid cap parameter for thumbnail upload") + throw Abort(.badRequest) + } + let data = try request.getBodyData() + server.saveThumbnail(data, for: cap) + } +} + +private extension Request { + + func getBodyData() throws -> Data { + guard let buffer = body.data else { + log("Missing body data") + throw CapError.invalidBody + } + return Data(buffer: buffer) + } + + func getStringBody() throws -> String { + let data = try getBodyData() + guard let content = String(data: data, encoding: .utf8) else { + log("Invalid string body") + throw CapError.invalidBody + } + return content + } }