import Routing import Vapor import SQLite // MARK: Paths private let baseFolder = URL(fileURLWithPath: "/caps") private let logFile = baseFolder.appendingPathComponent("logs/server.log").path private let publicFolder = baseFolder.appendingPathComponent("Public") private let imageFolder = publicFolder.appendingPathComponent("images") private let dbFile = publicFolder.appendingPathComponent("db.sqlite3").path private let tempImageFile = publicFolder.appendingPathComponent("temp.jpg") // MARK: Variables private let fm = FileManager.default private var db: Connection! // MARK: SQLite private let id = Expression("id") private let name = Expression("name") private let table = Table("caps") func loadDatabase() throws { db = try Connection(dbFile) } // MARK: Helper private func select(_ cap: Int) -> Table { table.filter(id == cap) } private func folder(of cap: Int) -> URL { imageFolder.appendingPathComponent(String(format: "%04d", cap)) } private func file(of cap: Int, version: Int) -> URL { folder(of: cap).appendingPathComponent(String(format: "%04d-%02d.jpg", cap, version)) } private func countImages(in folder: URL) throws -> Int { try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil).filter({ $0.pathExtension == "jpg" }).count } private func count(of cap: Int) throws -> Int { let f = folder(of: cap) guard fm.fileExists(atPath: f.path) else { throw CapError.unknownId } return try countImages(in: f) } // MARK: Routes /// Register your application's routes here. /// /// [Learn More →](https://docs.vapor.codes/3.0/getting-started/structure/#routesswift) public func routes(_ router: Router) throws { try Log.set(logFile: logFile) // Get the name of a cap router.getCatching("name", Int.parameter) { request -> Data in let cap = try request.parameters.next(Int.self) guard let row = try db.pluck(table.select(name).filter(id == cap)) else { throw CapError.unknownId } return row[name].data(using: .utf8)! } // Set the name of a cap router.postCatching("name", Int.parameter) { request in let cap = try request.parameters.next(Int.self) guard let data = request.http.body.data, let string = String(data: data, encoding: .utf8) else { throw CapError.invalidBody } guard let _ = try db.pluck(select(cap)) else { try db.run(select(cap).insert(id <- cap, name <- string)) return } try db.run(select(cap).update(name <- string)) } // Upload an image router.postCatching("images", Int.parameter) { request -> Data in let cap = try request.parameters.next(Int.self) guard let data = request.http.body.data else { throw CapError.invalidBody } let c = try count(of: cap) let f = file(of: cap, version: c) guard !fm.fileExists(atPath: f.path) else { throw CapError.dataInconsistency } try data.write(to: f) return "\(c)".data(using: .utf8)! } // Get count of a cap router.getCatching("count", Int.parameter) { request -> Data in let cap = try request.parameters.next(Int.self) let c = try count(of: cap) return "\(c)".data(using: .utf8)! } // Get the count of all caps router.getCatching("count", "all") { request -> Data in let counts: [(id: Int, count: Int)] = try fm.contentsOfDirectory(at: imageFolder, includingPropertiesForKeys: nil).compactMap { folder in guard let id = Int(folder.lastPathComponent) else { return nil } guard let count = try? countImages(in: folder) else { return nil } return (id, count) } return counts.map { "\($0.id)#\($0.count)"}.joined(separator: ";").data(using: .utf8)! } // Set a different version as the main image router.getCatching("switch", Int.parameter, Int.parameter) { request in let cap = try request.parameters.next(Int.self) let version = try request.parameters.next(Int.self) guard version > 0 else { return } let file1 = file(of: cap, version: 0) guard fm.fileExists(atPath: file1.path) else { throw CapError.invalidFile } let file2 = file(of: cap, version: version) guard fm.fileExists(atPath: file2.path) else { throw CapError.invalidFile } try fm.moveItem(at: file1, to: tempImageFile) try fm.moveItem(at: file2, to: file1) try fm.moveItem(at: tempImageFile, to: file2) } }