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
|
/// Finds and parses the bundled `<Exercise Name>.info.md` exported next to the
|
||||||
/// motion rigs by `render.py --export`.
|
/// motion rigs by `render.py --export`.
|
||||||
enum ExerciseInfoLibrary {
|
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.
|
/// The parsed info for `exerciseName`, or `nil` when none is bundled.
|
||||||
static func info(for exerciseName: String) -> ExerciseInfo? {
|
static func info(for exerciseName: String) -> ExerciseInfo? {
|
||||||
guard
|
cache[exerciseName]
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ final class SyncEngine {
|
|||||||
/// when the engine isn't connected.
|
/// when the engine isn't connected.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func duplicate(routine: Routine) async -> String? {
|
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)
|
var doc = RoutineDocument(from: routine)
|
||||||
doc.id = ULID.make()
|
doc.id = ULID.make()
|
||||||
@@ -767,6 +767,10 @@ final class SyncEngine {
|
|||||||
/// changes accumulated while the app was closed are picked up.
|
/// changes accumulated while the app was closed are picked up.
|
||||||
private func reconcile() async {
|
private func reconcile() async {
|
||||||
guard let store, let tombstones else { return }
|
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
|
isSyncing = true
|
||||||
defer { isSyncing = false }
|
defer { isSyncing = false }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user