/// Nextcloud share permission bitmask helpers. These mirror the constants the /// OCS `files_sharing` API expects in the `permissions` field. Kept as pure /// functions (no Flutter/IO) so they are unit-testable. library; /// Individual permission bits (Nextcloud `OCS\Constants`). const int kPermissionRead = 1; const int kPermissionUpdate = 2; const int kPermissionCreate = 4; const int kPermissionDelete = 8; const int kPermissionShare = 16; /// User-facing presets that map onto a bitmask. enum SharePreset { /// Recipient can only view/download. readOnly, /// Recipient can view, edit, add and remove (full editing). edit, /// Upload-only "file request" — recipient can add files to a folder but not /// see existing contents. Only meaningful for folders. fileDrop, } extension SharePresetLabel on SharePreset { String get label { switch (this) { case SharePreset.readOnly: return 'Nur Lesen'; case SharePreset.edit: return 'Bearbeiten'; case SharePreset.fileDrop: return 'Datei-Anfrage'; } } } /// Returns true if [mask] contains the given [flag]. bool hasPermission(int mask, int flag) => mask & flag == flag; /// Builds the permission bitmask for a [preset]. /// /// [isFolder] matters for the `edit` preset: a file can only carry /// read+update, while a folder additionally supports create+delete. Nextcloud /// rejects create/delete on a file ("Failed to update share"), so they must be /// omitted there. When [allowReshare] is true the reshare bit is added to the /// editing presets — mirroring how the Nextcloud clients respect the /// `resharing` capability. int permissionsFor( SharePreset preset, { bool allowReshare = false, bool isFolder = false, }) { switch (preset) { case SharePreset.readOnly: return kPermissionRead; case SharePreset.edit: var base = kPermissionRead | kPermissionUpdate; if (isFolder) base |= kPermissionCreate | kPermissionDelete; return allowReshare ? base | kPermissionShare : base; case SharePreset.fileDrop: return kPermissionCreate; } } /// Classifies an arbitrary permission bitmask into the closest preset, or null /// if it doesn't match any (e.g. a custom combination). The reshare bit is /// ignored for matching so an "edit" share stays "edit" regardless of reshare. SharePreset? presetFromBitmask(int mask) { final normalized = mask & ~kPermissionShare; if (normalized == kPermissionCreate) return SharePreset.fileDrop; if (normalized == kPermissionRead) return SharePreset.readOnly; // Any read share that also carries a write bit (update/create/delete) is // surfaced as "edit". const writeBits = kPermissionUpdate | kPermissionCreate | kPermissionDelete; if (hasPermission(normalized, kPermissionRead) && normalized & writeBits != 0) { return SharePreset.edit; } return null; }