Guard reconcile reentrancy and deleted-routine duplication; cache exercise info parsing
This commit is contained in:
@@ -215,12 +215,28 @@ extension ExerciseInfo {
|
||||
/// Finds and parses the bundled `<Exercise Name>.info.md` exported next to the
|
||||
/// motion rigs by `render.py --export`.
|
||||
enum ExerciseInfoLibrary {
|
||||
/// Every bundled `info.md`, parsed once and keyed by exercise name. `info(for:)` is
|
||||
/// fanned out per exercise per keystroke by `ExerciseCatalog.sections(matching:)`, so
|
||||
/// re-doing the `Bundle.main` lookup, file read, and markdown parse on every call was
|
||||
/// wasteful; ~60-odd files parsed once is trivial. Enumerates the bundle directly
|
||||
/// (mirroring `ExerciseMotionLibrary.exerciseNames`'s compound-suffix filter) rather
|
||||
/// than keying off that list, so info lookup doesn't depend on a motion rig existing.
|
||||
private static let cache: [String: ExerciseInfo] = {
|
||||
let urls = Bundle.main.urls(forResourcesWithExtension: "md", subdirectory: nil) ?? []
|
||||
var result: [String: ExerciseInfo] = [:]
|
||||
for url in urls {
|
||||
let filename = url.lastPathComponent
|
||||
guard filename.hasSuffix(".info.md"),
|
||||
let markdown = try? String(contentsOf: url, encoding: .utf8)
|
||||
else { continue }
|
||||
let exerciseName = String(filename.dropLast(".info.md".count))
|
||||
result[exerciseName] = ExerciseInfo.parse(markdown: markdown)
|
||||
}
|
||||
return result
|
||||
}()
|
||||
|
||||
/// The parsed info for `exerciseName`, or `nil` when none is bundled.
|
||||
static func info(for exerciseName: String) -> ExerciseInfo? {
|
||||
guard
|
||||
let url = Bundle.main.url(forResource: exerciseName, withExtension: "info.md"),
|
||||
let markdown = try? String(contentsOf: url, encoding: .utf8)
|
||||
else { return nil }
|
||||
return ExerciseInfo.parse(markdown: markdown)
|
||||
cache[exerciseName]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ final class SyncEngine {
|
||||
/// when the engine isn't connected.
|
||||
@discardableResult
|
||||
func duplicate(routine: Routine) async -> String? {
|
||||
guard store != nil else { return nil }
|
||||
guard store != nil, routine.isLive else { return nil }
|
||||
|
||||
var doc = RoutineDocument(from: routine)
|
||||
doc.id = ULID.make()
|
||||
@@ -767,6 +767,10 @@ final class SyncEngine {
|
||||
/// changes accumulated while the app was closed are picked up.
|
||||
private func reconcile() async {
|
||||
guard let store, let tombstones else { return }
|
||||
// Drop a reconcile that overlaps a running one — redundant, since the
|
||||
// running pass covers the same tree and the metadata observer picks up
|
||||
// anything newer.
|
||||
guard !isSyncing else { return }
|
||||
isSyncing = true
|
||||
defer { isSyncing = false }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user