ReadKeep/docs/Offline-Konzept.md

12 KiB
Raw Permalink Blame History

Offline-Konzept für Readeck iOS App

Übersicht

Dieses Dokument beschreibt ein mehrstufiges Konzept zur Offline-Funktionalität der Readeck iOS App. Das Konzept ist modular aufgebaut und ermöglicht eine schrittweise Implementierung von einer einfachen Caching-Strategie für ungelesene Artikel bis hin zu einer vollständig offline-fähigen App.


Stufe 1: Smart Cache für Unread Items (Basis-Offline)

Beschreibung

Die App lädt automatisch eine konfigurierbare Anzahl ungelesener Artikel herunter und hält diese offline verfügbar. Dies ist die Grundlage für eine bessere Offline-Erfahrung ohne großen Implementierungsaufwand.

Features

  • Automatisches Caching: Beim App-Start und bei Pull-to-Refresh werden die neuesten ungelesenen Artikel im Hintergrund heruntergeladen
  • Konfigurierbare Anzahl: User kann in den Einstellungen festlegen, wie viele Artikel gecacht werden sollen (z.B. 10, 25, 50, 100)
  • Nur Artikel-Content: Es wird nur der HTML-Content des Artikels (/api/bookmarks/{id}/article) gecached
  • Automatische Verwaltung: Ältere gecachte Artikel werden automatisch entfernt, wenn neue hinzukommen (FIFO-Prinzip)
  • Offline-Indikator: In der Bookmark-Liste wird angezeigt, welche Artikel offline verfügbar sind

Technische Umsetzung

// Neue Settings
struct OfflineSettings {
    var enabled: Bool = true
    var maxUnreadArticles: Int = 25 // 10, 25, 50, 100
    var onlyWiFi: Bool = true
}

// Neue Repository-Methode
protocol PBookmarksRepository {
    func cacheBookmarkArticle(id: String, html: String) async throws
    func getCachedArticle(id: String) -> String?
    func hasCachedArticle(id: String) -> Bool
}

Datenspeicherung

  • CoreData für Artikel-HTML und Metadaten (Titel, URL, Datum, Reihenfolge)
  • FileManager optional für große HTML-Dateien (falls CoreData zu groß wird)

User Experience

  • Bookmark-Liste zeigt Download-Icon für offline verfügbare Artikel
  • Beim Öffnen eines gecachten Artikels: Sofortiges Laden ohne Netzwerk-Anfrage
  • In Settings: "Offline-Modus" Sektion mit Slider für Anzahl der Artikel
  • Cache-Größe wird angezeigt (z.B. "23 Artikel, 12.5 MB")

Stufe 2: Offline-First mit Sync (Erweitert)

Beschreibung

Die App funktioniert vollständig offline. Alle Aktionen werden lokal gespeichert und bei Netzwerkverbindung mit dem Server synchronisiert.

Features

  • Vollständige Offline-Funktionalität: Alle Lese-Operationen funktionieren offline
  • Lokale Schreib-Operationen:
    • Bookmarks erstellen, bearbeiten, löschen
    • Labels hinzufügen/entfernen
    • Artikel archivieren/favorisieren
    • Lesefortschritt speichern
    • Annotationen/Highlights erstellen
  • Intelligente Synchronisierung:
    • Automatische Sync bei Netzwerkverbindung
    • Konfliktauflösung (Server gewinnt vs. Client gewinnt)
    • Retry-Mechanismus bei fehlgeschlagenen Syncs
  • Sync-Status: User sieht jederzeit, ob und was synchronisiert wird
  • Offline-Indikator: Klarer Status in der UI (Online/Offline/Syncing)

Technische Umsetzung

Sync-Manager

class OfflineSyncManager {
    // Bereits vorhanden für Bookmark-Erstellung
    // Erweitern für alle Operations

    enum SyncOperation {
        case createBookmark(CreateBookmarkRequest)
        case updateBookmark(id: String, BookmarkUpdateRequest)
        case deleteBookmark(id: String)
        case addLabels(bookmarkId: String, labels: [String])
        case removeLabels(bookmarkId: String, labels: [String])
        case updateReadProgress(bookmarkId: String, progress: Int)
        case createAnnotation(AnnotationRequest)
        case deleteAnnotation(bookmarkId: String, annotationId: String)
    }

    func queueOperation(_ operation: SyncOperation) async
    func syncAllPendingOperations() async throws
    func getPendingOperationsCount() -> Int
}

Lokale Datenbank-Struktur

// CoreData Entities
entity OfflineBookmark {
    id: String
    title: String
    url: String
    content: String? // HTML
    metadata: Data // JSON mit allen Bookmark-Daten
    labels: [String]
    isArchived: Bool
    isMarked: Bool
    readProgress: Int
    annotations: [OfflineAnnotation]
    lastModified: Date
    syncStatus: String // "synced", "pending", "conflict"
}

entity PendingSyncOperation {
    id: UUID
    type: String // operation type
    payload: Data // JSON der Operation
    createdAt: Date
    retryCount: Int
    lastError: String?
}

Sync-Strategien

Option A: Last-Write-Wins (Einfach)

  • Server-Version überschreibt bei Konflikt immer lokale Version
  • Einfach zu implementieren
  • Potentieller Datenverlust bei Offline-Änderungen

Option B: Timestamp-basiert (Empfohlen)

  • Neueste Änderung (basierend auf Timestamp) gewinnt
  • Server sendet updated Timestamp mit jeder Response
  • Client vergleicht mit lokalem Timestamp

Option C: Operational Transformation (Komplex)

  • Granulare Merge-Strategien für verschiedene Felder
  • Beispiel: Lokale Labels + Server Labels = Union
  • Aufwändig, aber maximale Datenerhaltung

User Experience

  • Offline-Banner: "Du bist offline. Änderungen werden synchronisiert, sobald du online bist."
  • Sync-Status Indicator:
    • Grün: Alles synchronisiert
    • Gelb: Synchronisierung läuft
    • Rot: Sync-Fehler
    • Grau: Offline
  • Pending Changes Badge: Zeigt Anzahl nicht synchronisierter Änderungen
  • Manual Sync Button: Manuelles Anstoßen der Synchronisierung
  • Sync-Konflikt-Dialog: Bei Konflikten User entscheiden lassen (Lokal behalten / Server übernehmen / Beide behalten)

Stufe 3: Vollständig Offline-Fähig (Maximum)

Beschreibung

Die App kann komplett ohne Server-Verbindung genutzt werden, inklusive lokaler Volltext-Suche und erweiterter Offline-Features.

Zusätzliche Features

  • Lokale Volltext-Suche:
    • SQLite FTS5 (Full-Text Search) für schnelle Suche
    • Suche in Titeln, URLs, Content, Labels
    • Highlighting von Suchbegriffen
  • Intelligente Offline-Strategie:
    • Predictive Caching basierend auf Leseverhalten
    • Automatisches Herunterladen von "Ähnlichen Artikeln"
    • Background Refresh für häufig gelesene Labels/Tags
  • Erweiterte Export-Funktionen:
    • Kompletten Offline-Cache als ZIP exportieren
    • Import von Offline-Daten auf anderem Gerät
    • Backup & Restore
  • Reader Mode Optimierungen:
    • Lokale Schriftarten für Offline-Nutzung
    • CSS/JS lokal gespeichert
    • Keine externen Dependencies
  • Offline-Statistiken:
    • Lesezeit offline vs. online
    • Meistgelesene offline Artikel
    • Speicherplatz-Statistiken

Erweiterte Technische Umsetzung

FTS5 für Suche

// SQLite Schema
CREATE VIRTUAL TABLE bookmarks_fts USING fts5(
    title,
    url,
    content,
    labels,
    content='offline_bookmarks',
    content_rowid='id'
);

// Suche
func searchOfflineBookmarks(query: String) -> [Bookmark] {
    let sql = """
        SELECT * FROM offline_bookmarks
        WHERE id IN (
            SELECT rowid FROM bookmarks_fts
            WHERE bookmarks_fts MATCH ?
        )
        ORDER BY rank
    """
    // Execute and return results
}

Predictive Caching

class PredictiveCacheManager {
    // Analysiere Leseverhalten
    func analyzeReadingPatterns() -> ReadingProfile

    // Lade ähnliche Artikel basierend auf:
    // - Gleiche Labels/Tags
    // - Gleiche Autoren
    // - Gleiche Domains
    func prefetchRelatedArticles(for bookmark: Bookmark) async

    // Machine Learning (optional)
    // CoreML Model für Content-Empfehlungen
    func trainRecommendationModel()
}

Background Sync

// BackgroundTasks Framework
class BackgroundSyncScheduler {
    func scheduleBackgroundSync() {
        let request = BGAppRefreshTaskRequest(identifier: "de.readeck.sync")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 min

        try? BGTaskScheduler.shared.submit(request)
    }

    func handleBackgroundSync(task: BGTask) async {
        // Sync neue Artikel
        // Update gecachte Artikel
        // Cleanup alte Artikel
    }
}

Datenspeicherung

  • SQLite + FTS5: Für Volltextsuche
  • CoreData: Für strukturierte Daten und Relationships
  • FileManager: Für HTML-Content, Bilder, Assets
  • NSUbiquitousKeyValueStore: Optional für iCloud-Sync der Einstellungen

User Experience

  • Offline-First Ansatz: App fühlt sich immer schnell an, auch bei schlechter Verbindung
  • Smart Downloads:
    • "20 neue Artikel verfügbar. Jetzt herunterladen?"
    • "Für dich empfohlen: 5 Artikel zum Offline-Lesen"
  • Storage Dashboard:
    • Visualisierung des genutzten Speichers
    • Top 10 größte Artikel
    • "Speicher optimieren" Funktion (Bilder komprimieren, alte Artikel löschen)
  • Offline-Modus Toggle: Bewusster "Nur Offline"-Modus aktivierbar
  • Sync-Schedule: "Täglich um 6:00 Uhr synchronisieren"

Implementierungs-Roadmap

Phase 1: Basis (Stufe 1) - ca. 2-3 Wochen

  • Settings für Offline-Anzahl
  • CoreData Schema für cached Articles
  • Download-Logic in BookmarksRepository
  • UI-Indikatoren für gecachte Artikel
  • Cleanup-Logic (FIFO)

Phase 2: Offline-First mit Sync (Stufe 2) - ca. 4-6 Wochen

  • Erweiterte CoreData Entities
  • OfflineSyncManager erweitern
  • Konfliktauflösung implementieren
  • Sync-Status UI
  • Comprehensive Testing (Edge Cases)

Phase 3: Advanced Features (Stufe 3) - ca. 4-6 Wochen

  • SQLite FTS5 Integration
  • Predictive Caching
  • Background Tasks
  • Export/Import
  • Analytics & Optimizations

Technische Überlegungen

Performance

  • Lazy Loading: Artikel-Content nur laden, wenn benötigt
  • Pagination: Auch offline große Listen paginieren
  • Image Optimization: Bilder komprimieren vor dem Speichern (WebP, HEIC)
  • Incremental Sync: Nur Änderungen synchronisieren, nicht alles neu laden

Speicherplatz

  • Quotas: Maximale Größe für Offline-Cache (z.B. 500MB, 1GB, 2GB)
  • Cleanup-Strategien:
    • Älteste zuerst (FIFO)
    • Größte zuerst
    • Am wenigsten gelesen zuerst
  • Kompression: HTML und JSON komprimieren (gzip)

Sicherheit

  • Verschlüsselung: Offline-Daten mit iOS Data Protection verschlüsseln
  • Sensitive Data: Passwörter niemals lokal speichern (nur Token mit Keychain)
  • Cleanup bei Logout: Alle Offline-Daten löschen

Testing

  • Unit Tests: Für Sync-Logic, Conflict Resolution
  • Integration Tests: Offline→Online Szenarien
  • UI Tests: Offline-Modi, Sync-Status
  • Edge Cases:
    • App-Kill während Sync
    • Netzwerk-Loss während Download
    • Speicher voll
    • Server-Konflikte

Bestehende Features die von Offline profitieren

Bereits implementiert

  • Offline Bookmark Erstellung: Bookmarks werden lokal gespeichert und bei Verbindung synchronisiert (OfflineSyncManager.swift)
  • Server Reachability Check: Check ob Server erreichbar ist vor Sync-Operationen
  • Sync Status UI: isSyncing und syncStatus werden bereits getrackt

Erweiterbar

  • Text-to-Speech: Geht bereits offline, wenn Artikel gecacht ist
  • Annotations/Highlights: Können offline erstellt und später synchronisiert werden
  • Lesefortschritt: Kann lokal getrackt und bei Sync übertragen werden
  • Labels: Offline hinzufügen/entfernen mit Sync
  • Read Progress: Bereits vorhanden im BookmarkDetail Model, kann offline getrackt werden

Metriken für Erfolg

User-Metriken

  • Durchschnittliche Offline-Nutzungszeit
  • % der Nutzer, die Offline-Features aktivieren
  • Anzahl der offline gelesenen Artikel pro User
  • User-Feedback zur Offline-Erfahrung

Technische Metriken

  • Erfolgsrate der Synchronisierungen
  • Durchschnittliche Sync-Dauer
  • Anzahl der Sync-Konflikte
  • Speicherplatz-Nutzung pro User
  • Crash-Rate während Offline-Operationen

Performance-Metriken

  • App-Start-Zeit mit/ohne Offline-Cache
  • Ladezeit für gecachte vs. nicht-gecachte Artikel
  • Netzwerk-Traffic Reduktion durch Caching
  • Battery Impact durch Background-Sync

Nächste Schritte

  1. Entscheidung: Welche Stufe soll zuerst implementiert werden?
  2. Prototyping: Quick PoC für CoreData Schema und Cache-Logic
  3. UI/UX Design: Mockups für Offline-Indikatoren und Settings
  4. Implementation: Schrittweise nach Roadmap
  5. Testing: Ausgiebiges Testen von Edge Cases
  6. Beta: TestFlight mit fokussiertem Offline-Testing
  7. Launch: Schrittweises Rollout mit Feature-Flags

Dokument erstellt: 2025-11-01 Version: 1.0