Use configuration file for paths
This commit is contained in:
parent
77c7397692
commit
0b34187841
3
Resources/paths.conf
Normal file
3
Resources/paths.conf
Normal file
@ -0,0 +1,3 @@
|
||||
/data/logs/capserver/server.log
|
||||
/data/public/capserver
|
||||
|
147
Sources/App/CapServer.swift
Normal file
147
Sources/App/CapServer.swift
Normal file
@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
final class CapServer {
|
||||
|
||||
// MARK: Paths
|
||||
|
||||
private let imageFolder: URL
|
||||
|
||||
private let nameFile: URL
|
||||
|
||||
private let tempImageFile: URL
|
||||
|
||||
private let fm = FileManager.default
|
||||
|
||||
// MARK: Caps
|
||||
|
||||
private var caps = [String]()
|
||||
|
||||
init(in folder: URL) throws {
|
||||
self.imageFolder = folder.appendingPathComponent("images")
|
||||
self.nameFile = folder.appendingPathComponent("names.txt")
|
||||
self.tempImageFile = folder.appendingPathComponent("temp.jpg")
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
guard fm.fileExists(atPath: folder.path, isDirectory: &isDirectory),
|
||||
isDirectory.boolValue else {
|
||||
log("Public directory \(folder.path) is not a folder, or doesn't exist")
|
||||
throw CapError.invalidConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SQLite
|
||||
|
||||
func loadCapNames() throws {
|
||||
let s = try String(contentsOf: nameFile)
|
||||
caps = s
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.components(separatedBy: "\n")
|
||||
log("\(caps.count) caps loaded")
|
||||
}
|
||||
|
||||
// MARK: Paths
|
||||
|
||||
func folder(of cap: Int) -> URL {
|
||||
imageFolder.appendingPathComponent(String(format: "%04d", cap))
|
||||
}
|
||||
|
||||
func file(of cap: Int, version: Int) -> URL {
|
||||
folder(of: cap).appendingPathComponent(String(format: "%04d-%02d.jpg", cap, version))
|
||||
}
|
||||
|
||||
// MARK: Counts
|
||||
|
||||
func countImages(in folder: URL) throws -> Int {
|
||||
try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil).filter({ $0.pathExtension == "jpg" }).count
|
||||
}
|
||||
|
||||
func count(of cap: Int) throws -> Int {
|
||||
let f = folder(of: cap)
|
||||
guard fm.fileExists(atPath: f.path) else {
|
||||
return 0
|
||||
}
|
||||
return try countImages(in: f)
|
||||
}
|
||||
|
||||
// MARK: Names
|
||||
|
||||
func name(for cap: Int) throws -> Data {
|
||||
let index = cap - 1
|
||||
guard index >= 0, index < caps.count else {
|
||||
log("Trying to get name for invalid cap \(cap) (\(caps.count) caps loaded)")
|
||||
throw CapError.unknownId
|
||||
}
|
||||
return caps[index].data(using: .utf8)!
|
||||
}
|
||||
|
||||
func set(name: String, for cap: Int) throws {
|
||||
let index = cap - 1
|
||||
guard index <= caps.count else {
|
||||
log("Trying to set name for cap \(cap), but only \(caps.count) caps exist")
|
||||
throw CapError.unknownId
|
||||
}
|
||||
|
||||
if index == caps.count {
|
||||
caps.append(name)
|
||||
// Create image folder
|
||||
let url = server.folder(of: cap)
|
||||
if !fm.fileExists(atPath: url.path) {
|
||||
try fm.createDirectory(at: url, withIntermediateDirectories: false)
|
||||
}
|
||||
log("Added cap \(cap)")
|
||||
} else {
|
||||
caps[index] = name
|
||||
log("Set name for cap \(cap)")
|
||||
}
|
||||
try caps.joined(separator: "\n").data(using: .utf8)!.write(to: server.nameFile)
|
||||
}
|
||||
|
||||
func getCountData() throws -> Data {
|
||||
#warning("Counts are encoded as UInt8, works only while count < 256")
|
||||
return Data(try (1...caps.count).map({ UInt8(try server.count(of: $0)) }))
|
||||
}
|
||||
|
||||
// MARK: Images
|
||||
|
||||
/**
|
||||
Save a cap image to disk.
|
||||
|
||||
Automatically creates the image name with the current image count.
|
||||
- Parameter data: The image data
|
||||
- Parameter cap: The id of the cap.
|
||||
- Returns: The new image count for the cap
|
||||
- Throws: `CapError.dataInconsistency` if an image already exists for the current count.
|
||||
*/
|
||||
func save(image data: Data, for cap: Int) throws -> Int {
|
||||
let c = try count(of: cap)
|
||||
let f = file(of: cap, version: c)
|
||||
guard !fm.fileExists(atPath: f.path) else {
|
||||
log("Image \(c) for cap \(cap) already exists on disk")
|
||||
throw CapError.dataInconsistency
|
||||
}
|
||||
try data.write(to: f)
|
||||
return c
|
||||
}
|
||||
|
||||
func switchMainImage(to version: Int, for cap: Int) throws {
|
||||
guard version > 0 else {
|
||||
log("Not switching cap \(cap) to image \(version)")
|
||||
return
|
||||
}
|
||||
let file1 = file(of: cap, version: 0)
|
||||
guard fm.fileExists(atPath: file1.path) else {
|
||||
log("No image 0 for cap \(cap)")
|
||||
throw CapError.invalidFile
|
||||
}
|
||||
let file2 = file(of: cap, version: version)
|
||||
guard fm.fileExists(atPath: file2.path) else {
|
||||
log("No image \(version) for cap \(cap)")
|
||||
throw CapError.invalidFile
|
||||
}
|
||||
try fm.moveItem(at: file1, to: tempImageFile)
|
||||
try fm.moveItem(at: file2, to: file1)
|
||||
try fm.moveItem(at: tempImageFile, to: file2)
|
||||
log("Switched cap \(cap) to version \(version)")
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ enum CapError: Error {
|
||||
case invalidBody
|
||||
case dataInconsistency
|
||||
case invalidFile
|
||||
case invalidConfiguration
|
||||
|
||||
var response: HTTPResponseStatus {
|
||||
switch self {
|
||||
@ -25,6 +26,8 @@ enum CapError: Error {
|
||||
case .dataInconsistency: return .conflict
|
||||
/// 412
|
||||
case .invalidFile: return .preconditionFailed
|
||||
/// 500
|
||||
case .invalidConfiguration: return .internalServerError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import Vapor
|
||||
|
||||
|
||||
private(set) var server: CapServer!
|
||||
|
||||
// configures your application
|
||||
public func configure(_ app: Application) throws {
|
||||
// uncomment to serve files from /Public folder
|
||||
@ -8,6 +11,23 @@ public func configure(_ app: Application) throws {
|
||||
app.http.server.configuration.port = 6001
|
||||
app.routes.defaultMaxBodySize = "2mb"
|
||||
|
||||
let configFile = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
||||
.appendingPathComponent("paths.conf")
|
||||
let configData = try String(contentsOf: configFile)
|
||||
.components(separatedBy: "\n")
|
||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
.filter { !$0.isEmpty }
|
||||
guard configData.count == 2 else {
|
||||
throw CapError.invalidConfiguration
|
||||
}
|
||||
let logFile = URL(fileURLWithPath: configData[0])
|
||||
let publicDirectory = URL(fileURLWithPath: configData[1])
|
||||
|
||||
try Log.set(logFile: logFile.path)
|
||||
|
||||
server = try CapServer(in: publicDirectory)
|
||||
try server.loadCapNames()
|
||||
|
||||
// Register routes to the router
|
||||
try routes(app)
|
||||
// Configure the rest of your application here
|
||||
|
@ -1,77 +1,17 @@
|
||||
import Vapor
|
||||
|
||||
// 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 nameFile = publicFolder.appendingPathComponent("names.txt")
|
||||
|
||||
private let tempImageFile = publicFolder.appendingPathComponent("temp.jpg")
|
||||
|
||||
// MARK: Variables
|
||||
|
||||
private let fm = FileManager.default
|
||||
|
||||
private var caps = [String]()
|
||||
|
||||
// MARK: SQLite
|
||||
|
||||
func loadCapNames() throws {
|
||||
let s = try String(contentsOf: nameFile)
|
||||
caps = s.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: "\n")
|
||||
log("\(caps.count) caps loaded")
|
||||
}
|
||||
|
||||
// MARK: Helper
|
||||
|
||||
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 {
|
||||
return 0
|
||||
}
|
||||
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)
|
||||
func routes(_ app: Application) throws {
|
||||
|
||||
try Log.set(logFile: logFile)
|
||||
try loadCapNames()
|
||||
|
||||
// Get the name of a cap
|
||||
app.getCatching("name", ":n") { request -> Data in
|
||||
guard let cap = request.parameters.get("n", as: Int.self) else {
|
||||
log("Invalid body data")
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
let index = cap - 1
|
||||
guard index >= 0, index < caps.count else {
|
||||
log("Trying to get name for invalid cap \(cap) (\(caps.count) caps loaded)")
|
||||
throw CapError.unknownId
|
||||
}
|
||||
return caps[index].data(using: .utf8)!
|
||||
return try server.name(for: cap)
|
||||
}
|
||||
|
||||
// Set the name of a cap
|
||||
@ -80,30 +20,11 @@ func routes(_ app: Application) throws {
|
||||
log("Invalid parameter for cap")
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
let index = cap - 1
|
||||
guard let buffer = request.body.data, let name = String(data: Data(buffer: buffer), encoding: .utf8) else {
|
||||
log("Invalid body data")
|
||||
throw CapError.invalidBody
|
||||
}
|
||||
guard index <= caps.count else {
|
||||
log("Trying to set name for cap \(cap), but only \(caps.count) caps exist")
|
||||
throw CapError.unknownId
|
||||
}
|
||||
|
||||
if index == caps.count {
|
||||
caps.append(name)
|
||||
// Create image folder
|
||||
let url = folder(of: cap)
|
||||
if !fm.fileExists(atPath: url.path) {
|
||||
try fm.createDirectory(at: url, withIntermediateDirectories: false)
|
||||
}
|
||||
log("Added cap \(cap)")
|
||||
} else {
|
||||
caps[index] = name
|
||||
log("Set name for cap \(cap)")
|
||||
}
|
||||
try caps.joined(separator: "\n").data(using: .utf8)!.write(to: nameFile)
|
||||
|
||||
try server.set(name: name, for: cap)
|
||||
}
|
||||
|
||||
// Upload an image
|
||||
@ -117,14 +38,8 @@ func routes(_ app: Application) throws {
|
||||
throw CapError.invalidBody
|
||||
}
|
||||
let data = Data(buffer: buffer)
|
||||
let c = try count(of: cap)
|
||||
let f = file(of: cap, version: c)
|
||||
guard !fm.fileExists(atPath: f.path) else {
|
||||
log("Image \(c) for cap \(cap) already exists on disk")
|
||||
throw CapError.dataInconsistency
|
||||
}
|
||||
try data.write(to: f)
|
||||
return "\(c)".data(using: .utf8)!
|
||||
let newCount = try server.save(image: data, for: cap)
|
||||
return "\(newCount)".data(using: .utf8)!
|
||||
}
|
||||
|
||||
// Get count of a cap
|
||||
@ -133,13 +48,13 @@ func routes(_ app: Application) throws {
|
||||
log("Invalid parameter for cap")
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
let c = try count(of: cap)
|
||||
let c = try server.count(of: cap)
|
||||
return "\(c)".data(using: .utf8)!
|
||||
}
|
||||
|
||||
// Get the count of all caps
|
||||
app.getCatching("counts") { request -> Data in
|
||||
Data(try (1...caps.count).map({ UInt8(try count(of: $0)) }))
|
||||
try server.getCountData()
|
||||
}
|
||||
|
||||
// Set a different version as the main image
|
||||
@ -152,23 +67,6 @@ func routes(_ app: Application) throws {
|
||||
log("Invalid parameter for cap version")
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
guard version > 0 else {
|
||||
log("Not switching cap \(cap) to image \(version)")
|
||||
return
|
||||
}
|
||||
let file1 = file(of: cap, version: 0)
|
||||
guard fm.fileExists(atPath: file1.path) else {
|
||||
log("No image 0 for cap \(cap)")
|
||||
throw CapError.invalidFile
|
||||
}
|
||||
let file2 = file(of: cap, version: version)
|
||||
guard fm.fileExists(atPath: file2.path) else {
|
||||
log("No image \(version) for cap \(cap)")
|
||||
throw CapError.invalidFile
|
||||
}
|
||||
try fm.moveItem(at: file1, to: tempImageFile)
|
||||
try fm.moveItem(at: file2, to: file1)
|
||||
try fm.moveItem(at: tempImageFile, to: file2)
|
||||
log("Switched cap \(cap) to version \(version)")
|
||||
try server.switchMainImage(to: version, for: cap)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user