using afConcurrent
using afIoc
const abstract class Datastore {
private static const Log log := Utils.getLog(Datastore#)
private const SynchronizedState conState
const Type entityType
private const Str stateFileName
@Inject private const ActorPools actorPools
new make(Type entityType, |This| in) {
in(this)
this.entityType = entityType
this.stateFileName = "${entityType.name}State.fog"
this.conState = SynchronizedState(actorPools["afQuickFlux.system"]) |->Obj| { loadFromFile(DatastoreState#) }
}
virtual Entity save(Entity entity) {
ent := entity
getState |state| {
state.entities[ent.id] = ent
saveToFile(state)
return null
}
return entity
}
virtual Entity delete(Entity entity) {
withState |state| {
state.entities.remove(entity.id)
saveToFile(state)
}
return entity
}
virtual Entity[] findAll() {
getState {
// injecting deps into entities takes *forever*! So don't do it!
it.entities.vals.toImmutable
}
}
virtual Entity? findById(Int entityId, Bool checked := true) {
getState {
it.entities.get(entityId)
} ?: (checked ? throw Err("Entity not found for ID $entityId") : null)
}
protected Int getNextId() {
getState |state| {
nextId := state.nextId
state.nextId = state.nextId + 1
return nextId
}
}
internal Void withState(|DatastoreState| state) {
conState.withState(state)
}
internal Obj? getState(|DatastoreState -> Obj?| state) {
conState.getState(state)
}
private static File? toFile(Type prefsType, Str name := "${prefsType.name}.fog") {
pathUri := `etc/${QuickFluxModule.appPod.name}/${name}`
envFile := Env.cur.findFile(pathUri, false) ?: Env.cur.workDir + pathUri
file := envFile.normalize // normalize gives the full absolute path
return file
}
private Obj loadFromFile(Type stateType) {
file := toFile(stateType, stateFileName)
state := readFromFile(file)
if (state == null)
state = stateType.make([entityType])
return state
}
private Void saveToFile(Obj prefs) {
file := toFile(prefs.typeof, stateFileName)
file.writeObj(prefs, ["indent":2, "skipDefaults":true])
}
private Obj? readFromFile(File? file) {
Obj? value := null
try {
if (file != null && file.exists) {
log.info("Loading state: $file")
value = file.in.readObj(["makeArgs":[entityType]])
}
} catch (Err e) {
log.err("Cannot load state: $file", e)
}
return value
}
}
@Serializable
internal class DatastoreState {
Int nextId := 1
Int:Obj entities := [:]
new make(Type entityType := Obj#, |This|? in := null) {
in?.call(this)
entities = Utils.makeMap(Int#, entityType).addAll(entities)
}
}