Caps-Server/Sources/App/CapServer.swift
2021-11-08 21:58:55 +01:00

148 lines
4.6 KiB
Swift

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)")
}
}