Calendarr direkt vom Server
Bisher las Onyx nur den Schnappschuss aus dem gemeinsamen Ordner. Der ist so aktuell wie der letzte Start der Calendarr-App — wer sie eine Woche nicht öffnet, sieht wochenalte Termine, und Onyx kann nichts dafür und nichts dagegen. Der Live-Weg stand im Plan als „bewusst weggelassen, später ohne Umbau nachrüstbar". Das stimmte: das Quellenprotokoll hat gehalten. Ein einziger Aufruf genügt. /api/caldav/events fasst CalDAV-Konten, lokale Kalender, Geburtstage, iCal-Abos und Google bereits zusammen — nachgebaut wird nichts. Angemeldet wird mit Adresse, Benutzername und Passwort statt mit einem Token, und der Grund steht im Backend: auth.py:17 ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days Ein eingetragenes Token wäre nach einer Woche tot. Onyx holt sich seines deshalb selbst und erneuert es, bevor es abläuft — die Gültigkeit liest es aus dem Token, statt sieben Tage anzunehmen; die stehen heute so im Server und morgen vielleicht nicht mehr. Kommt trotzdem ein 401, wird einmal neu angemeldet und der Abruf wiederholt. Adresse und Benutzername stehen in den Einstellungen, das Passwort im Schlüsselbund — `defaults read` ist keine Stelle, an der ein Passwort mitgelesen werden soll. Geprüft wird beim Verbinden, nicht erst beim nächsten Laden: ein Tippfehler, den man am Folgetag bemerkt, ist ein vermeidbarer. Der Schnappschuss bleibt das Netz darunter. Ohne Zugangsdaten ändert sich nichts, und ohne Netzwerk hält er, was Calendarr zuletzt geschrieben hat. Nachgesehen wird am Server alle fünf Minuten statt alle zwei: ein Dateizugriff kostet nichts, eine Anfrage über das Netz schon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,10 @@ public final class CalendarModel {
|
||||
|
||||
private let appleSource: CalendarSource
|
||||
private let calendarrSource: CalendarSource
|
||||
/// Der Weg über den Server. Wird benutzt, sobald Zugangsdaten hinterlegt
|
||||
/// sind — dann hängt die Aktualität nicht mehr daran, ob jemand die
|
||||
/// Calendarr-App geöffnet hat.
|
||||
private let calendarrLive: CalendarrLiveSource
|
||||
private let defaults: UserDefaults
|
||||
private var calendar: Calendar
|
||||
private var refreshTask: Task<Void, Never>?
|
||||
@@ -61,10 +65,12 @@ public final class CalendarModel {
|
||||
|
||||
public init(appleSource: CalendarSource = EventKitSource(),
|
||||
calendarrSource: CalendarSource = CalendarrSource(),
|
||||
calendarrLive: CalendarrLiveSource = CalendarrLiveSource(),
|
||||
defaults: UserDefaults = .standard,
|
||||
calendar: Calendar = .autoupdatingCurrent) {
|
||||
self.appleSource = appleSource
|
||||
self.calendarrSource = calendarrSource
|
||||
self.calendarrLive = calendarrLive
|
||||
self.defaults = defaults
|
||||
self.calendar = calendar
|
||||
self.kind = defaults.string(forKey: Self.kindKey)
|
||||
@@ -78,11 +84,23 @@ public final class CalendarModel {
|
||||
private var activeSources: [CalendarSource] {
|
||||
switch kind {
|
||||
case .appleCalendar: [appleSource]
|
||||
case .calendarr: [calendarrSource]
|
||||
case .both: [appleSource, calendarrSource]
|
||||
case .calendarr: [calendarrChoice]
|
||||
case .both: [appleSource, calendarrChoice]
|
||||
}
|
||||
}
|
||||
|
||||
/// Server oder Schnappschuss.
|
||||
///
|
||||
/// Sind Zugangsdaten hinterlegt, gilt der Server — er ist die genauere
|
||||
/// Auskunft. Der Schnappschuss bleibt das Netz darunter: er braucht kein
|
||||
/// Netzwerk und hält, was Calendarr zuletzt geschrieben hat.
|
||||
private var calendarrChoice: CalendarSource {
|
||||
calendarrLive.isAuthorized ? calendarrLive : calendarrSource
|
||||
}
|
||||
|
||||
/// Ob gerade direkt vom Server gelesen wird.
|
||||
public var usesCalendarrServer: Bool { calendarrLive.isAuthorized }
|
||||
|
||||
/// Ob gelesen werden darf. Fragt nicht nach — nur zum Anzeigen.
|
||||
///
|
||||
/// **Eine** berechtigte Quelle genügt: bei „beide" darf ein gesperrter
|
||||
@@ -119,9 +137,12 @@ public final class CalendarModel {
|
||||
// bis Calendarr das nächste Mal schreibt. Das Nachsehen kostet das
|
||||
// Einlesen einer Datei von wenigen Kilobyte; das ist billiger als eine
|
||||
// Terminliste, die aus unerfindlichen Gründen von gestern ist.
|
||||
// Am Server seltener als an der Datei: ein Dateizugriff kostet nichts,
|
||||
// eine Anfrage über das Netz schon.
|
||||
calendarrTimer?.invalidate()
|
||||
calendarrTimer = wanted
|
||||
? Timer.scheduledTimer(withTimeInterval: 120, repeats: true) { _ in
|
||||
? Timer.scheduledTimer(withTimeInterval: usesCalendarrServer ? 300 : 120,
|
||||
repeats: true) { _ in
|
||||
MainActor.assumeIsolated { [weak self] in self?.refresh() }
|
||||
}
|
||||
: nil
|
||||
@@ -151,6 +172,15 @@ public final class CalendarModel {
|
||||
|
||||
/// Termine für den sichtbaren Monat plus Puffer nach beiden Seiten, damit
|
||||
/// die Randtage der Nachbarmonate im Raster nicht fälschlich leer wirken.
|
||||
/// Nach dem Verbinden oder Trennen: Quelle und Takt neu bestimmen.
|
||||
public func calendarrAccountChanged() {
|
||||
calendarrObserver = nil
|
||||
calendarrTimer?.invalidate()
|
||||
calendarrTimer = nil
|
||||
observeCalendarrIfNeeded()
|
||||
refresh()
|
||||
}
|
||||
|
||||
public func refresh(around date: Date = Date()) {
|
||||
refreshTask?.cancel()
|
||||
refreshTask = Task { [weak self] in
|
||||
|
||||
Reference in New Issue
Block a user