Use json database
This commit is contained in:
parent
a6b61ef796
commit
3f4202d9ad
30
Sources/App/Cap.swift
Normal file
30
Sources/App/Cap.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Cap: Codable {
|
||||||
|
|
||||||
|
let id: Int
|
||||||
|
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
var count: Int
|
||||||
|
|
||||||
|
var mainImage: Int
|
||||||
|
|
||||||
|
/// The version of the first classifier trained on this cap
|
||||||
|
var classifierVersion: Int
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id = "i"
|
||||||
|
case name = "n"
|
||||||
|
case count = "c"
|
||||||
|
case mainImage = "m"
|
||||||
|
case classifierVersion = "v"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Cap: Comparable {
|
||||||
|
|
||||||
|
static func < (lhs: Cap, rhs: Cap) -> Bool {
|
||||||
|
lhs.id < rhs.id
|
||||||
|
}
|
||||||
|
}
|
@ -7,20 +7,31 @@ final class CapServer {
|
|||||||
|
|
||||||
private let imageFolder: URL
|
private let imageFolder: URL
|
||||||
|
|
||||||
private let nameFile: URL
|
/// The file where the database of caps is stored
|
||||||
|
private let dbFile: URL
|
||||||
private let tempImageFile: URL
|
|
||||||
|
|
||||||
private let fm = FileManager.default
|
private let fm = FileManager.default
|
||||||
|
|
||||||
// MARK: Caps
|
// MARK: Caps
|
||||||
|
|
||||||
private var caps = [String]()
|
private var saveImmediatelly = true
|
||||||
|
|
||||||
|
private var caps = [Int: Cap]() {
|
||||||
|
didSet {
|
||||||
|
guard saveImmediatelly else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try? saveCaps()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextClassifierVersion: Int {
|
||||||
|
caps.values.map { $0.classifierVersion }.max() ?? 1
|
||||||
|
}
|
||||||
|
|
||||||
init(in folder: URL) throws {
|
init(in folder: URL) throws {
|
||||||
self.imageFolder = folder.appendingPathComponent("images")
|
self.imageFolder = folder.appendingPathComponent("images")
|
||||||
self.nameFile = folder.appendingPathComponent("names.txt")
|
self.dbFile = folder.appendingPathComponent("caps.json")
|
||||||
self.tempImageFile = folder.appendingPathComponent("temp.jpg")
|
|
||||||
|
|
||||||
var isDirectory: ObjCBool = false
|
var isDirectory: ObjCBool = false
|
||||||
guard fm.fileExists(atPath: folder.path, isDirectory: &isDirectory),
|
guard fm.fileExists(atPath: folder.path, isDirectory: &isDirectory),
|
||||||
@ -28,18 +39,23 @@ final class CapServer {
|
|||||||
log("Public directory \(folder.path) is not a folder, or doesn't exist")
|
log("Public directory \(folder.path) is not a folder, or doesn't exist")
|
||||||
throw CapError.invalidConfiguration
|
throw CapError.invalidConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try loadCaps()
|
||||||
|
try updateCounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: SQLite
|
private func loadCaps() throws {
|
||||||
|
let data = try Data(contentsOf: dbFile)
|
||||||
func loadCapNames() throws {
|
caps = try JSONDecoder().decode([Cap].self, from: data)
|
||||||
let s = try String(contentsOf: nameFile)
|
.reduce(into: [:]) { $0[$1.id] = $1 }
|
||||||
caps = s
|
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
.components(separatedBy: "\n")
|
|
||||||
log("\(caps.count) caps loaded")
|
log("\(caps.count) caps loaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func saveCaps() throws {
|
||||||
|
let data = try JSONEncoder().encode(caps.values.sorted())
|
||||||
|
try data.write(to: dbFile)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Paths
|
// MARK: Paths
|
||||||
|
|
||||||
func folder(of cap: Int) -> URL {
|
func folder(of cap: Int) -> URL {
|
||||||
@ -52,10 +68,25 @@ final class CapServer {
|
|||||||
|
|
||||||
// MARK: Counts
|
// MARK: Counts
|
||||||
|
|
||||||
func countImages(in folder: URL) throws -> Int {
|
private func updateCounts() throws {
|
||||||
try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil).filter({ $0.pathExtension == "jpg" }).count
|
saveImmediatelly = false
|
||||||
|
caps = try caps.mapValues {
|
||||||
|
var cap = $0
|
||||||
|
cap.count = try count(of: $0.id)
|
||||||
|
return cap
|
||||||
|
}
|
||||||
|
saveImmediatelly = true
|
||||||
|
try? saveCaps()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func countImages(in folder: URL) throws -> Int {
|
||||||
|
try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil)
|
||||||
|
.filter({ $0.pathExtension == "jpg" }).count
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the image count of a cap.
|
||||||
|
*/
|
||||||
func count(of cap: Int) throws -> Int {
|
func count(of cap: Int) throws -> Int {
|
||||||
let f = folder(of: cap)
|
let f = folder(of: cap)
|
||||||
guard fm.fileExists(atPath: f.path) else {
|
guard fm.fileExists(atPath: f.path) else {
|
||||||
@ -66,40 +97,19 @@ final class CapServer {
|
|||||||
|
|
||||||
// MARK: Names
|
// MARK: Names
|
||||||
|
|
||||||
func name(for cap: Int) throws -> Data {
|
func set(name: String, for capId: Int) throws {
|
||||||
let index = cap - 1
|
guard var cap = caps[capId] else {
|
||||||
guard index >= 0, index < caps.count else {
|
let url = server.folder(of: capId)
|
||||||
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) {
|
if !fm.fileExists(atPath: url.path) {
|
||||||
try fm.createDirectory(at: url, withIntermediateDirectories: false)
|
try fm.createDirectory(at: url, withIntermediateDirectories: false)
|
||||||
}
|
}
|
||||||
log("Added cap \(cap)")
|
caps[capId] = Cap(id: capId, name: name, count: 0, mainImage: 0, classifierVersion: nextClassifierVersion)
|
||||||
} else {
|
log("Added cap \(capId)")
|
||||||
caps[index] = name
|
return
|
||||||
log("Set name for cap \(cap)")
|
|
||||||
}
|
}
|
||||||
try caps.joined(separator: "\n").data(using: .utf8)!.write(to: server.nameFile)
|
cap.name = name
|
||||||
}
|
caps[capId] = cap
|
||||||
|
log("Set name for cap \(capId)")
|
||||||
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
|
// MARK: Images
|
||||||
@ -111,37 +121,32 @@ final class CapServer {
|
|||||||
- Parameter data: The image data
|
- Parameter data: The image data
|
||||||
- Parameter cap: The id of the cap.
|
- Parameter cap: The id of the cap.
|
||||||
- Returns: The new image count for the cap
|
- Returns: The new image count for the cap
|
||||||
- Throws: `CapError.dataInconsistency` if an image already exists for the current count.
|
- Throws: `CapError.unknownId`, if the cap doesn't exist. `CapError.dataInconsistency` if an image already exists for the current count.
|
||||||
*/
|
*/
|
||||||
func save(image data: Data, for cap: Int) throws -> Int {
|
func save(image data: Data, for cap: Int) throws -> Int {
|
||||||
let c = try count(of: cap)
|
guard var count = caps[cap]?.count else {
|
||||||
let f = file(of: cap, version: c)
|
throw CapError.unknownId
|
||||||
guard !fm.fileExists(atPath: f.path) else {
|
}
|
||||||
log("Image \(c) for cap \(cap) already exists on disk")
|
var id = 0
|
||||||
throw CapError.dataInconsistency
|
var f = file(of: cap, version: id)
|
||||||
|
while fm.fileExists(atPath: f.path) {
|
||||||
|
id += 1
|
||||||
|
f = file(of: cap, version: id)
|
||||||
}
|
}
|
||||||
try data.write(to: f)
|
try data.write(to: f)
|
||||||
return c
|
count += 1
|
||||||
|
caps[cap]!.count = count
|
||||||
|
log("Added image \(id) for cap \(cap)")
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func switchMainImage(to version: Int, for cap: Int) throws {
|
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)
|
let file2 = file(of: cap, version: version)
|
||||||
guard fm.fileExists(atPath: file2.path) else {
|
guard fm.fileExists(atPath: file2.path) else {
|
||||||
log("No image \(version) for cap \(cap)")
|
log("No image \(version) for cap \(cap)")
|
||||||
throw CapError.invalidFile
|
throw CapError.invalidFile
|
||||||
}
|
}
|
||||||
try fm.moveItem(at: file1, to: tempImageFile)
|
caps[cap]?.mainImage = version
|
||||||
try fm.moveItem(at: file2, to: file1)
|
|
||||||
try fm.moveItem(at: tempImageFile, to: file2)
|
|
||||||
log("Switched cap \(cap) to version \(version)")
|
log("Switched cap \(cap) to version \(version)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ public func configure(_ app: Application) throws {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server = try CapServer(in: URL(fileURLWithPath: publicDirectory))
|
server = try CapServer(in: URL(fileURLWithPath: publicDirectory))
|
||||||
try server.loadCapNames()
|
|
||||||
|
|
||||||
// Register routes to the router
|
// Register routes to the router
|
||||||
try routes(app)
|
try routes(app)
|
||||||
|
@ -5,15 +5,6 @@ import Vapor
|
|||||||
/// [Learn More →](https://docs.vapor.codes/3.0/getting-started/structure/#routesswift)
|
/// [Learn More →](https://docs.vapor.codes/3.0/getting-started/structure/#routesswift)
|
||||||
func routes(_ app: Application) throws {
|
func routes(_ app: Application) throws {
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
return try server.name(for: cap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the name of a cap
|
// Set the name of a cap
|
||||||
app.postCatching("name", ":n") { request in
|
app.postCatching("name", ":n") { request in
|
||||||
guard let cap = request.parameters.get("n", as: Int.self) else {
|
guard let cap = request.parameters.get("n", as: Int.self) else {
|
||||||
@ -42,28 +33,13 @@ func routes(_ app: Application) throws {
|
|||||||
return "\(newCount)".data(using: .utf8)!
|
return "\(newCount)".data(using: .utf8)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get count of a cap
|
|
||||||
app.getCatching("count", ":c") { request -> Data in
|
|
||||||
guard let cap = request.parameters.get("c", as: Int.self) else {
|
|
||||||
log("Invalid parameter for cap")
|
|
||||||
throw Abort(.badRequest)
|
|
||||||
}
|
|
||||||
let c = try server.count(of: cap)
|
|
||||||
return "\(c)".data(using: .utf8)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the count of all caps
|
|
||||||
app.getCatching("counts") { request -> Data in
|
|
||||||
try server.getCountData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a different version as the main image
|
// Set a different version as the main image
|
||||||
app.getCatching("switch", ":n", ":v") { request in
|
app.getCatching("switch", ":n", ":v") { request in
|
||||||
guard let cap = request.parameters.get("n", as: Int.self) else {
|
guard let cap = request.parameters.get("n", as: Int.self), cap >= 0 else {
|
||||||
log("Invalid parameter for cap")
|
log("Invalid parameter for cap")
|
||||||
throw Abort(.badRequest)
|
throw Abort(.badRequest)
|
||||||
}
|
}
|
||||||
guard let version = request.parameters.get("v", as: Int.self) else {
|
guard let version = request.parameters.get("v", as: Int.self), version >= 0 else {
|
||||||
log("Invalid parameter for cap version")
|
log("Invalid parameter for cap version")
|
||||||
throw Abort(.badRequest)
|
throw Abort(.badRequest)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user