Anrede/Suffix/PLZ etc. hatten max-width-Container, das InputText darin
behielt aber die Default-Breite und ueberlief. Globale CSS-Regel sorgt
nun dafuer, dass jedes Input/Select seinen Field-Container ausfuellt.
field-row wrappt auf schmalen Dialogen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- caldav.py sendet SSE-Notifications bei Event-PUT/DELETE und Kalender-Loeschung,
damit das Web-UI auch auf Aenderungen aus DAVx5 sofort reagiert.
- FullCalendar navLinks: Klick auf Tagesnummer im Monatsraster wechselt in
die Tagesansicht.
- Neue Listen-Ansicht mit Volltext-Suche, Datumsbereich, Kalender-Filter,
Sortierung nach Datum/Titel und Loeschen-Button pro Zeile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Master-Event eines Serientermins liegt oft vor dem sichtbaren Bereich -
das FullCalendar-RRULE-Plugin braucht ihn trotzdem zur Expansion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die CardDAV-Route /<username>/<ab_part>/ fing REPORT auf Kalender-URLs
(z.B. /dav/Adam/cal-1/) mit 404 ab, weil 'cal-1' nicht mit 'ab-' startet.
DAVx5 bekam bei der calendar-query einen 404 und markierte den EVENTS-
Sync als Hard Error. Fix analog zu PROPFIND/OPTIONS: wenn ab_part nicht
ab-* ist, an den CalDAV-REPORT-Handler delegieren.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 registriert Dienste basierend auf dem DAV-Response-Header. Ohne
'addressbook' im Header wurde CardDAV bei der Auto-Discovery ignoriert,
obwohl addressbook-home-set korrekt gemeldet wurde. Das erklaert warum
nur der caldav-Service fuer Adam angelegt wurde.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die zuletzt eingefuehrten Sub-Container (calendars/, addressbooks/) bei
PROPFIND Depth 1 auf /dav/<user>/ wurden von DAVx5 als leere Kalender
gezaehlt (DEFAULT_TASK_CALENDAR_NAME-Phantom-Eintraege). Da die CardDAV-
Route jetzt korrekt an den Home-Set-Handler delegiert, reicht es wenn der
Principal nur sich selbst liefert - Clients folgen den Home-Sets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die CardDAV-Route /<username>/<ab_part>/ ist in Flask spezifischer als
die generische /<path:subpath> des CalDAV-Handlers und hat daher auch
/dav/<user>/calendars/ abgefangen - mit 404, weil 'calendars' nicht mit
'ab-' anfaengt. Ergebnis: DAVx5 bekam auf das Home-Set eine 404 und
zeigte keine Eintraege mehr an.
Fix: wenn ab_part nicht mit 'ab-' anfaengt, an den CalDAV-PROPFIND/OPTIONS
delegieren statt 404 zurueckzugeben.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checkbox pro Eintrag, "Alle auswaehlen" oben und roter Loesch-Button mit
Anzahl. Sicherheitsabfrage vor dem Loeschen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 brauchte Kind-Container unter /dav/<user>/ - sonst blieben die
Listen nach Aktualisieren leer. Die Home-Sets bleiben getrennt
(calendar-home-set vs addressbook-home-set), aber der Principal zeigt
beide Sub-Container jetzt explizit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kalender und Adressbuecher teilten sich den gleichen Home-Set
(/dav/<user>/). DAVx5 hat bei Depth-1-PROPFIND beide Collection-
Typen angezeigt und mangels bekanntem Resourcetype als
"DEFAULT_TASK_CALENDAR_NAME"-Kacheln gelistet.
Loesung:
* calendar-home-set zeigt auf /dav/<user>/calendars/
* addressbook-home-set zeigt auf /dav/<user>/addressbooks/
* Beide Pfade sind eigene Container-Collections - PROPFIND Depth 1
liefert nur den jeweils passenden Typ
* /dav/<user>/ selbst gibt bei Depth 1 keine Kinder mehr zurueck,
Clients folgen den Home-Sets
* Die konkreten URLs cal-<id> / ab-<id> liegen weiterhin unter
/dav/<user>/ (keine Breaking Change fuer existierende Clients;
nur die Discovery-URL aendert sich)
Frontend:
CalendarView + ContactsView zeigen als Auto-Discovery-URL nur
noch den Hostname - PROPFIND auf / funktioniert ja jetzt. Die
Direkt-URL bleibt vollstaendig mit /dav/<user>/cal-<id> bzw.
ab-<id> fuer Clients die das brauchen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Komplette Kontakte-Ueberarbeitung analog zum Kalender-Ausbau.
Backend-Model:
* AddressBook: color (pro Buch), ausserdem Per-User-Color via
AddressBookShare.color wie bei CalendarShare.
* Contact: volle Outlook-artige Struktur - prefix/first/middle/
last/suffix, display_name, nickname, organization, department,
job_title, birthday, anniversary, notes, photo sowie JSON-
Spalten fuer mehrfach vorhandene Felder (emails, phones,
addresses mit allen Adressteilen, websites, impp, categories).
Backend-API:
* REST CRUD uebernimmt die neuen Felder und generiert vCard 3.0
als Source of Truth fuer CardDAV. Voller vCard-Parser +
-Builder mit Escape/Unescape, TYPE-Parametern, Line-Folding.
* Neuer Endpoint PUT /addressbooks/<id>/my-color - persoenliche
Farbe pro Buch ohne den Besitzer zu beeinflussen.
* SSE-Events vom Typ 'addressbook' an Besitzer + alle Share-
Empfaenger bei jeder Aenderung.
CardDAV-Server (backend/app/dav/carddav.py):
* Volle Discovery via principal - addressbook-home-set wird
neben calendar-home-set annonciert.
* PROPFIND/REPORT/GET/PUT/DELETE/MKCOL fuer
/dav/<user>/ab-<id>/ und /<...>/{uid}.vcf
* addressbook-query + addressbook-multiget REPORTs
* ETag-basierte Konfliktpruefung via If-Match/If-None-Match
Frontend (ContactsView.vue):
* Komplett neuer Editor mit vier Tabs: Allgemein (Name, Org),
Kommunikation (Emails/Phones/Websites/IMPP dynamisch),
Adressen (mehrere mit allen Teilen), Details (Geburtstag,
Jahrestag, Kategorien, Notizen).
* Avatar mit Fotoauswahl oder Initialen-Farbkreis.
* Kalender-Sharing-Flow 1:1 uebernommen: Autocomplete fuer
Benutzersuche, Share-Liste mit Stift zum Bearbeiten, Muelleimer
zum Entfernen, Per-User-Farbe, CardDAV-URL-Info-Block pro
Adressbuch, Live-Refresh via SSE.
* Suche durchsucht Displayname, E-Mail und Firma.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher wurde immer die komplette calendar-data mitgeschickt, auch
wenn der Client nur getetag wollte. DAVx5 macht einen zweistufigen
Sync: erst calendar-query nach ETags, dann multiget fuer die
neuen/geaenderten Events. Server-seitig zu viel zu liefern bricht
diesen Ablauf - Client denkt er hat alles und ueberspringt die
zweite Stufe, aber die Events landen nicht in der Android-Kalender-
DB.
Jetzt: calendar-query schaut nach ob <c:calendar-data/> in den
angefragten Props steht und liefert entsprechend.
calendar-multiget liefert weiterhin immer die vollen Daten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 sendet bei calendar-query oft nur <time-range start=.../>
ohne end. Mein Code hat dann blind CalendarEvent.dtstart < None
gefiltert, was SQLAlchemy mit TypeError abbrechen liess - Ergebnis
HTTP 500, Sync scheitert komplett.
Zwei Korrekturen:
* end-Filter wird nur gesetzt wenn end wirklich vorhanden ist
* time-range-Parser strippt tzinfo, damit Vergleiche mit den
tz-naiven DB-Spalten keine Exception werfen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* GET-Route akzeptiert jetzt auch HEAD - manche Clients pruefen
Existenz einer Ressource via HEAD bevor sie GET senden.
* Neue PROPPATCH-Route auf der Kalender-Collection: erkennt
calendar-color + displayname und persistiert beides. Andere
Properties werden als "angewendet" bestaetigt, damit DAVx5
und Apple Calendar nicht enttaeuscht sind.
Damit sollten die 500-Fehler beim Sync verschwinden. Falls nicht,
bitte Server- oder DAVx5-Log posten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher war der href in der Response immer /dav/, auch wenn DAVx5
einen PROPFIND auf / oder /.well-known/caldav gemacht hat. Das
kann Clients verwirren - die erwarten, dass der Response-Pfad zum
angefragten Pfad passt. current-user-principal zeigt weiterhin
korrekt auf /dav/Adam/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 macht beim Account-Setup zuerst PROPFIND auf / bevor es
/.well-known/caldav probiert. Der Server antwortete mit 405
Method Not Allowed (weil / nur fuer SPA-GET registriert war),
woraufhin DAVx5 den gesamten Server als "kein DAV" verwirft.
Jetzt: PROPFIND und OPTIONS auf / werden an die DAV-Handler
delegiert (gleiches Verhalten wie auf /dav/). GET/HEAD auf /
laeuft unveraendert zur SPA.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flask hat trotz expliziter OPTIONS-Route Auto-OPTIONS generiert,
wodurch der DAV-Header fehlte. DAVx5 sieht so keinen Calendar-
Access und lehnt den Server ab.
Konsolidiert zu einem Handler mit method-basiertem Dispatch und
provide_automatic_options=False, damit Flask nicht dazwischenfunkt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aenderungen fuer besseren DAVx5-Support:
* /.well-known/caldav reagiert jetzt direkt auf PROPFIND/OPTIONS
ohne Redirect-Zickerei. GET/HEAD redirecten weiterhin auf /dav/
als visuelle Fallback.
* strict_slashes app-weit aus: /dav und /dav/ sind gleichwertig,
ebenso die Unterpfade. DAVx5 nutzt beides gemischt.
* Jede DAV-Response traegt jetzt den DAV-Header (1, 2, 3,
calendar-access), nicht nur OPTIONS.
* Kalender-Response enthaelt jetzt supported-report-set mit
calendar-query + calendar-multiget (DAVx5 prueft das).
* current-user-privilege-set wird mit konkreten Privilegien gefuellt
(read, write, write-properties, write-content, bind, unbind)
statt leer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Property-Elemente wurden unter einem Container mit demselben Tag
erzeugt, z.B.:
<current-user-principal>
<current-user-principal> <!-- falsch, doppelt -->
<href>/dav/adam/</href>
</current-user-principal>
</current-user-principal>
Clients wie DAVx5 und Thunderbird erkennen dadurch den Principal
nicht und melden "Kein CalDAV-Dienst gefunden". XML-Generierung
umgebaut - Response-Helfer bekommen jetzt eine populate_prop-
Callback, die die tatsaechlichen Property-Children direkt ins
<prop>-Element setzt.
Zusaetzlich:
* /.well-known/caldav und /carddav akzeptieren jetzt auch PROPFIND,
OPTIONS, HEAD (einige Clients halten die Methode beim ersten
Aufruf bei).
* Kalender-Response enthaelt current-user-privilege-set (leer, als
Signal dass der Client nicht ACL-abhaengig pruefen muss).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CalendarShare bekommt color-Spalte. Im Kalender-Menue kann jeder
Benutzer eine eigene Anzeigefarbe fuer einen mit ihm geteilten
Kalender setzen, ohne dass sich dadurch die Farbe beim
Eigentuemer oder anderen Share-Empfaengern aendert.
* Owner: Farbe aendert den Kalender direkt (wie bisher).
* Share-Empfaenger: Farbe landet in CalendarShare.color und wird
nur fuer ihn ausgeliefert (list_calendars injiziert sie in
'color', Owner-Farbe bleibt in 'owner_color' als Referenz).
Neuer Endpoint: PUT /calendars/<id>/my-color.
UI-Hinweis: "Nur fuer deine Ansicht - <Owner> behaelt seine Farbe".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
Neuer Event-Typ 'calendar' im Broadcaster. Wird bei Event-CRUD,
Serien-Ausnahmen, Freigaben hinzufuegen/entfernen und beim
Loeschen ganzer Kalender emittiert. Empfaenger: Eigentuemer +
alle User mit CalendarShare auf dem jeweiligen Kalender.
Frontend:
CalendarView oeffnet beim Mount eine EventSource zu
/api/sync/events und reloaded Kalenderliste + Events bei jedem
'calendar'-Event (300ms debounced). Damit sehen beteiligte
Nutzer Aenderungen in praktisch Echtzeit - kein F5 mehr noetig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Im Drei-Punkte-Menue jedes Kalenders wird jetzt ein Info-Block mit
den CalDAV-URLs angezeigt:
* Auto-Discovery URL fuer Thunderbird / DAVx5 / Apple Calendar
* Direkt-URL fuer diesen speziellen Kalender (z.B. Outlook
CalDAV-Synchronizer)
* Kurz-Hinweis welcher Client welche URL nimmt
Jede URL hat ein Kopier-Icon. Ergaenzt den bestehenden iCal-Link
um die bidirektionale Sync-Moeglichkeit ueber CalDAV.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analog zu den Datei-Freigaben: Stift neben der Muelltonne in der
Share-Liste macht die Zeile zur Inline-Edit-Zeile mit Permission-
Dropdown + Check/X. Speichern nutzt denselben POST /share-
Endpoint, der auch das initiale Teilen erledigt - er erkennt den
existierenden User und aktualisiert nur die Berechtigung.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User.calendars hat bereits backref='owner', mein zusaetzlich
hinzugefuegtes Calendar.owner kollidierte damit und SQLAlchemy
weigerte sich, die Mappers zu initialisieren ("Error creating
backref 'owner'..."). Damit waren alle Auth-Endpoints tot.
Jetzt nur noch Kommentar, die backref uebernimmt die Aufgabe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sharing-Fix:
Calendar-Model hatte keine owner-Relation zu User - list_calendars
stuerzte beim Listen geteilter Kalender ab (c.owner.username ->
AttributeError). Jetzt mit explizitem foreign_keys Relationship.
Benutzer-Autocomplete:
"Kalender teilen" nutzt jetzt /users/search wie bei Dateien.
Tippt man 2+ Zeichen, erscheint ein Dropdown mit passenden
Benutzernamen. Klick uebernimmt den Namen.
Bestehende Freigaben werden im Menue angezeigt mit Muelleimer
zum Entfernen.
Privat-Flag fuer Termine:
CalendarEvent bekommt is_private-Spalte. Checkbox im Termin-
Dialog "🔒 Privat (Teilnehmer sehen nur den Zeitblock)".
Redaction greift an drei Stellen:
* GET /events: Nicht-Owner sehen summary="Privat", description
und location = null. Zeitfenster bleibt voll sichtbar.
* iCal-Export (/ical/<token>): Privat-Events werden mit
CLASS:PRIVATE ausgegeben und SUMMARY/DESCRIPTION/LOCATION
werden gestrippt.
* CalDAV: aktuell werden eh nur eigene Kalender exportiert,
also keine Redaction noetig. Kommt bei Share-Support rein.
Der Eigentuemer sieht natuerlich in seiner eigenen Ansicht alle
Details seines privaten Termins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vollstaendige CalDAV-Implementierung unter /dav/ - Thunderbird,
DAVx5, Apple Calendar und Outlook (CalDAV-Synchronizer) koennen
sich einfach ueber HTTP-Basic-Auth mit ihrem Mini-Cloud-Account
anmelden und ihre Kalender synchronisieren.
Unterstuetzte Methoden:
* OPTIONS - DAV-Capabilities
* PROPFIND - Discovery, Principal, Calendar-Home, Kalender,
Termin-Listings (Depth 0/1 beachtet)
* REPORT - calendar-query + calendar-multiget mit
optionalem Zeitraumfilter (<time-range>)
* GET - einzelner Termin als VCALENDAR
* PUT - Termin erstellen/aktualisieren (mit ETag-Check
via If-Match + If-None-Match)
* DELETE - Termin oder ganzer Kalender
* MKCALENDAR - neuen Kalender vom Client aus anlegen
iCal-Parser verarbeitet SUMMARY, DESCRIPTION, LOCATION, DTSTART,
DTEND, RRULE, EXDATE - inklusive Line-Folding (RFC 5545).
Ganztages-Termine (VALUE=DATE) werden korrekt erkannt.
ETags basieren auf updated_at-Zeitstempel und werden pro
PUT-Response zurueckgegeben, damit Clients Konflikte erkennen.
nginx.example.conf: /dav/ mit proxy_request_buffering off fuer
groessere PUTs und Weiterleitung der .well-known-URLs.
README: eigener "CalDAV-Zugriff"-Block mit Tabelle pro Client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
eventDisplay: 'block' zwingt FullCalendar dazu, auch zeitlich
terminierte Termine in der Monatsansicht als farbige Balken
anzuzeigen statt als Punkt mit Uhrzeit-Label. Damit sieht ein
per "Neuer Termin"-Button angelegter Termin genauso aus wie einer,
der per Klick auf den Tag erstellt wurde.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Klick auf einen wiederkehrenden Termin oeffnet zuerst einen Dialog:
"Nur diesen Termin" oder "Ganze Serie".
* Serie: bearbeitet den Master wie bisher
* Nur dieser: fuegt EXDATE fuer das geklickte Datum zum Master
hinzu und legt einen eigenstaendigen Ersatz-Termin mit den
bearbeiteten Daten an
Backend:
* CalendarEvent.exdates speichert Ausnahmedaten kommasepariert
* POST /events/<id>/exception fuegt EXDATE hinzu, erstellt
optional das Replacement-Event mit frischer UID
* _build_vevent schreibt jetzt EXDATE-Zeilen in die ical_data,
sodass CalDAV-Clients die Ausnahmen auch sehen werden
Frontend:
* FullCalendar rrule-Plugin bekommt die exdate-Liste und blendet
die uebersprungenen Tage aus
* Drag & Drop verschiebt weiterhin die ganze Serie (Shortcut -
fuer Einzelverschiebung Termin anklicken und bearbeiten)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 📅-Icon bei ganztaegigen Terminen
* 🔁-Icon bei wiederkehrenden Terminen
* Anzeige "09:00-10:30" statt nur "09:00" in Woche/Tag-Ansicht
* Mouseover-Tooltip mit allen Termin-Infos inklusive Ort und
Beschreibung
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Neben der Muelltonne jetzt ein Stift-Icon im Share-Dialog:
Klick macht die Zeile zur Inline-Edit-Zeile mit Permission-
Dropdown + Weiterteilen-Checkbox + Speichern/Abbrechen-Buttons.
Speichern ruft POST /permissions mit user_id auf - Backend
erkennt die bestehende Freigabe und aktualisiert sie, statt
loeschen + neu anlegen zu muessen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
set_permission und remove_permission feuern jetzt ein SSE-Event vom
Typ 'permission' an Target-User + Owner + weitere Share-Empfaenger.
Damit aktualisieren sich die Dateilisten aller Beteiligten in
Echtzeit - auch beim Betroffenen, der gerade seinen Zugriff
verliert.
Frontend:
FilesView wrapped loadFiles in safeLoadCurrentFolder(). Bei
403/404 erscheint ein Toast "Dieser Ordner wurde geloescht oder
die Freigabe wurde entfernt" und nach 600ms wird zurueck zum
Root navigiert. Greift beim Direktaufruf, beim Ordnerwechsel und
bei durch SSE ausgeloesten Auto-Reloads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Neues Berechtigungs-Modell fuer Benutzerfreigaben:
* FilePermission bekommt zwei neue Spalten:
- can_reshare (bool): darf dieser Nutzer die Freigabe weiterverteilen?
- granted_by (user_id): wer hat diese Freigabe erstellt?
* set_permission / create_share_link erlauben jetzt auch Nicht-Owner,
sofern sie can_reshare haben. Dabei gilt:
- Lesend + reshare -> kann nur lesend weiterteilen
- Schreibend + reshare -> kann lesend ODER schreibend weiterteilen
- Admin kann nur der Eigentuemer vergeben
- Jeder Re-Sharer kann wiederum can_reshare weitergeben
* remove_permission: Owner kann alle Freigaben entfernen; Re-Sharer
nur die von ihnen selbst erstellten.
* get_permissions: Owner sieht alle; Re-Sharer nur selbst-erstellte.
* list_files liefert my_permission + my_can_reshare pro Eintrag -
Frontend kann Rename/Delete/Share-Buttons gezielt ein- und
ausblenden statt blind alle anzuzeigen.
Frontend:
* Rename/Delete-Buttons nur fuer Write-Zugriff
* Share-Button nur fuer Owner oder Re-Sharer
* "darf weiterteilen" Checkbox neben Permission-Dropdown im Dialog
* Dropdown-Optionen nach eigenem Level gefiltert (Re-Sharer sieht
keine hoeheren Stufen als seine eigene)
* Hinweis-Text "Du hast X - du kannst maximal X weiterteilen"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Neuer Abschnitt "Was das Lock wirklich kann (und was nicht)" mit
Tabelle + Beispielszenario Adam/Anna. Zeigt Laien, dass das Lock
Web-GUI, Client und Upload schuetzt, aber nicht den Windows-
Explorer - und dass die Konflikt-Kopie das Sicherheitsnetz ist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die lokale Dateiliste im Client zeigt jetzt pro Datei ein 🔒-Badge
mit Nutzername wenn ausgecheckt (wie Server-Ansicht + Web-GUI).
browse_sync_folder zieht den Server-Tree bei jedem Aufruf und
korreliert via Journal-Lookup (oder .cloud-Metadaten) die lokale
Datei mit dem File-Lock-Status.
Rechtsklick-Menue reagiert jetzt auf den Lock-Status:
- Frei -> "Auschecken (sperren)"
- Eigener/fremder -> "Entsperren (einchecken)"
Neuer Tauri-Command lock_file_cmd fuer reines Sperren ohne Oeffnen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Beim Verbindungsaufbau (open-Event) wird jetzt ein initialer Reload
ausgeloest, damit eventuelle Changes in der Zeit zwischen letzter
Anzeige und SSE-Verbindung nicht verloren gehen. Gilt fuer eigene
und freigegebene Ordner gleichermassen (selbe FilesView-Komponente).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die Server-Dateiliste im Client wartete bisher auf einen abgeschlossenen
Sync-Durchlauf, bevor Lock-Aenderungen anderer Nutzer sichtbar wurden.
Ausloeser von Events ohne Datei-Download (reine Lock/Unlock-Events)
landeten teils gar nicht in der UI.
Frontend hoert jetzt direkt auf das sse-event vom Backend und ruft
loadFileTree + loadLocalFiles auf - damit Lock-Icons im Server-Tree
in Echtzeit erscheinen/verschwinden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher hat der Client nur beim ersten Oeffnen (.cloud-Platzhalter ->
Download) gesperrt. Nach dem Einchecken und erneutem Doppelklick
blieb die Datei ungesperrt, weil der Open-Pfad fehlte.
Neuer Tauri-Command open_offline_file loest die Server-Datei-ID
ueber das Sync-Journal auf, sperrt auf dem Server und oeffnet
lokal mit der Standard-App. Im lokalen Dateibrowser:
- Doppelklick auf eine bereits offline vorhandene Datei checkt sie
nun aus und oeffnet sie (vorher: keine Reaktion)
- Rechtsklick-Menue hat "Oeffnen (auschecken)" fuer Offline-Dateien
Das Lock triggert wie gehabt notify_file_change -> SSE -> Web-UI
aktualisiert den Lock-Status sofort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
/files/<id>/save (Text/HTML/Spreadsheet) und der OnlyOffice-
Callback aktualisierten Inhalt + Checksum, riefen aber
notify_file_change nicht auf. Der Client bekam dadurch keinen
SSE-Trigger und merkte die neue Server-Version erst beim naechsten
30s-Fallback-Sync - wenn ueberhaupt.
Jetzt: beide Endpoints emittieren 'updated' an Owner + Share-
Empfaenger, Desktop- und Web-Clients reagieren sofort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mit 2 Gunicorn-Workern laeuft der In-Memory-Broadcaster in zwei
voneinander getrennten Prozessen. Landet ein Lock-Request auf
Worker A und die SSE-Verbindung des Empfaengers auf Worker B, kommt
das Event nie beim Client an - genau deshalb klappte der Live-
Refresh bei freigegebenen Ordnern nicht zuverlaessig.
Jetzt: 1 Worker mit 32 Threads. Threads teilen Memory, der
Broadcaster ist fuer alle Verbindungen derselbe. Fuer mehr Durchsatz
waere Redis Pub/Sub noetig - hier reicht aber Single-Process-Modus.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mit 4 synchronen Workern hielt jede SSE-Verbindung dauerhaft einen
ganzen Worker besetzt. 4 offene Browser-Tabs -> alle anderen
Requests blockiert -> "Dateien laden dauert ewig".
Loesung: gthread worker-class mit 2 Workern x 16 Threads = 32
gleichzeitige Slots. Lang laufende SSE-Streams belegen nur je
einen Thread, regulaere Requests laufen unbeeintraechtigt.
nginx.example.conf: separater Location-Block fuer /api/sync/events
mit proxy_buffering off und 24h Read-Timeout, damit die Events
sofort durchkommen und die Verbindung nicht abbricht.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drei Probleme in einem:
1. create_folder/get_sync_tree parsten die Response auch bei HTTP-
Fehlern als JSON. Bei 401/409/etc. kam "error decoding response
body" statt der eigentlichen Fehlermeldung. Status wird jetzt
zuerst geprueft, Body-Text wird bei Fehlern zurueckgegeben.
2. Ohne Journal-Eintrag und unterschiedlichen Hashes wurde vorher
eine Konflikt-Kopie erstellt. Fuer Server-Edits aus dem Web-UI
(wo der Client die Datei gar nie mit Journal erfasst hatte) war
das falsch. Nextcloud-Ansatz: beim Erstkontakt Server
autoritativ - Download statt Konflikt-Kopie.
3. run_sync_now uebernimmt neu konfigurierte sync_paths aus dem
State, damit manuelle Syncs auch nach add_sync_path greifen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FilesView abonniert beim Mount die SSE-Events des Backends. Lock/
Unlock, Create, Update oder Delete durch andere Clients loest einen
debounced Reload der aktuellen Ordner-Ansicht aus. EventSource
reconnected automatisch; wird beim Unmount sauber geschlossen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
list_files filterte Kinder-Dateien nach owner_id=current_user, wodurch
in einem freigegebenen Ordner (der einem anderen User gehoert) keine
Dateien angezeigt wurden. Jetzt wird beim Betreten eines Ordners die
Zugriffsberechtigung geprueft; bei eigenem Ordner wie gehabt, bei
freigegebenem Ordner werden alle Kinder-Dateien gelistet.
_check_file_access laeuft jetzt auch den Ordner-Baum hoch, damit
eine Permission auf einem Vorfahren-Ordner automatisch Zugriff auf
alle Nachkommen gewaehrt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
apiClient hat baseURL '/api' - die URL darf nicht nochmal mit /api
anfangen, sonst wird daraus /api/api/... und der Request geht ins
Leere.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary-Drop-Order: MutexGuard hielt Referenz auf State-Binding,
das am Block-Ende schon fallen gelassen wurde. Zwischenvariable
erzwingt Drop der MutexGuard vor dem Binding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Download-Endpoint unterstuetzt jetzt ?inline=1, wodurch
Content-Disposition auf inline statt attachment gesetzt wird.
PDF- und Bild-Preview nutzen diesen Parameter, damit der
Browser das PDF im Preview-Iframe rendert statt einen Download
auszuloesen. Normale Download-Buttons bleiben unveraendert.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Desktop-Client komplett ueberarbeitet nach Nextcloud-Vorbild:
- Persistentes SQLite-Journal (journal.rs) speichert letzten bekannten
Stand pro Datei - ueberlebt Client-Neustarts (Hauptbug behoben).
- Engine.rs neu: 3-Wege-Vergleich Local <-> Journal <-> Server mit
sauberer Konflikt-Kopie (inkl. Username + Zeitstempel).
- Loesch-Propagation: Lokal geloeschte Dateien landen im Server-
Papierkorb des Owners (auch bei Freigaben). Auf dem Server
geloeschte Dateien werden lokal entfernt.
- Lock-Flow repariert: frischer Token bei jedem Call, Fehler-Feedback.
Echtzeit-Sync:
- Backend: SSE-Endpoint /api/sync/events mit In-Memory-Broadcaster.
Events bei Create/Update/Delete/Lock/Unlock, Zustellung an Owner
plus alle User mit Share-Permission.
- Client: persistente SSE-Verbindung mit Auto-Reconnect. Events
triggern sofortigen Sync (<100ms). 30s-Polling bleibt als
Fallback fuer Netzwerk-Aussetzer.
Weitere Fixes:
- /api/sync/tree filtert is_trashed=False (Papierkorb wird nicht
mehr an Clients gesynct).
- Web-GUI: Lock/Unlock-Buttons pro Datei, Admin darf fremde Locks
zwangsweise loesen. Rename/Delete disabled bei fremdem Lock.
- Lock-Check im Backend bei PUT/DELETE (423 Locked Response).
- Background-Sync nur noch einmal pro Prozess gestartet, liest
sync_paths pro Iteration neu - add/remove wirkt sofort, kein
Client-Neustart mehr noetig.
- Watcher werden pro Sync-Pfad individuell verwaltet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>