Skip to content

ccs_files_user_client (Flutter)

ccs_files_user_client is the File Service package for a Flutter app’s signed-in user, the Dart counterpart of ccs-files-user-client with the same routes, snake_case wire fields and errors. It runs the upload protocol (a ticket, one PUT or parallel parts with progress, retries, pause, resume after the app was killed, and cancel) and wraps folders, files, previews and URLs, search, tags, access entries, share links, the trash, capabilities and the public settings. It knows nothing about your posts or chats. This page covers installation and the behaviour you rely on; the release history is in the changelog. For a walk-through, start with the Flutter quickstart.

The package is private and not on pub.dev. Add it from the ccs-file-server repository at a release tag, with path: pointing at the package inside it:

dependencies:
ccs_files_user_client:
git:
url: git@github.com:central-code-station/ccs-file-server.git
ref: ccs-files-flutter-v1.3.0 # a release tag, never a moving branch
path: libs/flutter/ccs_files_user_client

Your machine and your CI need read access to the repository. The package depends on flutter and http only and never logs.

Platform Supported
Android, iOS, macOS, Windows, Linux Yes: files are read through dart:io
Flutter web No: use the web client there

Releases are tagged ccs-files-flutter-v<version>; the package keeps its own semver in its pubspec.yaml and changelog.

FileRecord and FolderRecord gain managed, managedBy (ManagedBy) and isReadOnly for application-managed folders, folders.releaseManagement(folderId, reason:) is the tenant owner’s emergency release, and FilesForbiddenException.isFolderManaged and .managedFolderId read a 403 folder_managed. The changelog lists every change.

FileRecord.hold is typed (FileHold), files.hold.place and release and holds.list manage legal holds for a user with files.manage, a 409 file_on_hold carries the hold in FilesConflictException.hold, and SearchField.name is documented as the prefix match it is (Search). The changelog lists every change.

Flow The app needs Use
Your backend creates the ticket No File Service token. Your API creates the ticket with the Node client, the app uploads it, your API completes it UploadController.fromTicket(ticket, source) and result.completionBody()
The app uploads as the user A user token from your backend’s token route TokenProvider, FilesClient.fromTokenRoute, files.upload(source, request) or FilesUploadQueue

This package has no generated reference on this site. The table lists its public API; every member has a doc comment in the package’s source.

Member What it does
TokenProvider(tokenUrl:, headers:): getAccessToken, getConfig, clear Your token route, cached until a minute before the token expires, one request at a time
FilesClient(apiUrl:, tenantId:, applicationId:, getAccessToken:, ...), FilesClient.fromTokenRoute(tokens) The client of one (tenant, application) context; fromTokenRoute reads api_url, tenant_id and application_id from the route; apiUrl is optional (default: production, FilesEndpoints.production)
FilesEnvironment (production, staging, local): parse(String.fromEnvironment('CCS_ENVIRONMENT')), apiUrl; FilesEndpoints The File Service’s API base per environment, picked at build time with --dart-define=CCS_ENVIRONMENT=local
upload(source, request), uploadVersion(fileId, source, ...), resumeUpload(json, source), activeUploads UploadControllers for new files and new versions
preview(fileId, version:) The preview, cached
urls(fileIds, rendition:, disposition:), invalidate([fileId]) Up to 200 URLs in one request, one entry per id in order, cached per item
search(SearchQuery), tags(prefix:, limit:) Search (SearchField.name matches names that start with the query, ignoring case; SearchField.keywords and SearchField.content match whole words; hits carry highlights), tags
capabilities(), settings.public() What the user may do; the application’s upload rules (allows(mimeType) checks a picked file)
folders: root, create, get, children, path, update, move, copy, delete, restore, usage, releaseManagement(folderId, reason:) Folders; releaseManagement is the tenant owner’s release of an application-managed folder
files: get, byExternalRef, update, move, copy, delete, restore, downloadUrl, versions, renditions, myActions Files
files.hold: place(fileId, reason:, until:), release(fileId, reason:); holds.list(placedBy:, after:, limit:) The file’s legal hold and the application’s held files, for a user with files.manage; each answers the FileRecord (the list a Page<FileRecord>)
uploads: create, createVersion, status, parts, complete, abort The protocol step by step
permissions: get, replace, grant, revoke, roles; shares: create, list, mine, revoke; trash.list Sharing and the trash
signOut(), close() Cancels the uploads and clears the caches and the token provider; releases the HTTP clients
UploadController: start, pause, resume, cancel, progress, currentProgress, checkpoints, done, toJson, UploadController.fromJson, UploadController.fromTicket One upload
UploadResult: uploadId, fileId, parts, file, completionBody() What start() and done complete with
UploadSource.file, .path, .stream(stream, length:), .bytes Where the bytes come from
FilesUploadQueue(client:, store:, concurrency:, pauseInBackground:): restore, add, pause, resume, cancel, remove, pauseAll, resumeAll, clearFinished, changes, flush, dispose Uploads with bounded concurrency, persisted, resumed at start and in the foreground
FilesUploadStore: JsonFileUploadStore(file), MemoryUploadStore() Where the queue persists its snapshots

Types: FileRecord (with hold: a FileHold with reason, placedBy, placedAt, until and inForce(), or null), FolderRecord (both FilesItem, with managed, managedBy and isReadOnly), ManagedBy (clientId, name, managedFolderId, managedAt), UploadTicket, UploadRequest, UploadedPart, UploadPartUrl, Preview, DownloadUrl, UrlResult, SearchQuery, SearchResult, SearchHit, AccessEntry, AccessEntryInput, ItemPermissions, ShareLink, PublicSettings, Capabilities, Page<T>. Each keeps the decoded wire object as raw for fields the package does not model.

A folder a tenant’s backend marks managed, and everything below it, is read-only for every user, the tenant owner included: uploads, versions, renames, moves, edits, copies into it, trash, restore, purge, access entries and share links answer 403 folder_managed, and FilesForbiddenException.isFolderManaged is true, with the managing folder in .managedFolderId. Viewing, downloads, previews, search, copying out and legal holds work as usual. myActions still lists what the user holds: hide the editing controls when item.isReadOnly is true, and show who manages the item from item.managedBy?.name (or clientId when the name is null).

folders.releaseManagement(folderId, reason:) answers the FolderRecord. Only the tenant owner may call it (capabilities() says so; anyone else gets tenant_owner_required), with a reason of 1 to 500 characters, on the folder that carries the mark (managedBy.managedFolderId; 400 invalid_request otherwise). It is never retried. See Application-managed folders.

Behaviour Rule
Ticket Created with an Idempotency-Key kept in the snapshot, so a retried or resumed creation answers the same ticket. A refusal (quota_exceeded, file_too_large, type_not_allowed) surfaces here, before any byte
Single PUT Exactly the ticket’s headers
Multipart concurrency parts in flight (default 4); the first 100 part URLs come with the ticket, the rest in batches
Retries Each PUT up to 5 retries on network errors and 5xx, backoff 1 to 30 seconds; a 403 on a part URL renews it once
Pause and resume Pause stops the PUTs in flight and keeps the confirmed parts; resume asks the service which parts the store has and sends only the missing ones
Completion Every part with its ETag, in order; a lost answer is recovered from the ticket’s status
Cancel Stops the PUTs and releases the ticket (client uploads); done completes with FilesUploadCancelledException
States idle, creating, uploading, paused, completing, done, failed, cancelled; resume() retries a failed upload
Resume after a restart toJson() holds the ticket, the part URLs, the confirmed parts and what identifies the source (a file’s path, length and modification time). A changed source is refused with FilesUploadSourceMismatchException

Snapshots contain signed URLs: keep them in the app’s private storage.

Dart Node and web When
FilesApiException (status, code, body, retryAfter) FilesApiError Any non-2xx answer
FilesUnauthorizedException FilesUnauthorizedError 401 after one token refresh
FilesForbiddenException, FilesNotFoundException, FilesConflictException the same names with Error 403 (for example folder_managed, with isFolderManaged and managedFolderId), 404, 409 (for example scan_pending, or file_on_hold with the hold in FilesConflictException.hold)
FilesPreconditionFailedException, FilesPayloadTooLargeException, FilesUnprocessableException, FilesRateLimitedException, FilesNotImplementedException the same 412, 413, 422, 429 (never retried), 501
FilesContextException FilesContextError No tenant or application
FilesUploadException (code, partNumber, uploadId, cause) FilesUploadError A PUT failed after its retries, was refused, or the ticket expired; part_url_expired for a ticket upload without refreshPartUrls
FilesUploadSourceMismatchException UploadFileMismatchError upload_file_mismatch on resume
FilesUploadCancelledException UploadCancelledError done after cancel()
FilesNetworkException, FilesProtocolException, FilesTokenException, FilesConfigurationException The network, an unreadable answer, the token route, invalid arguments

All extend FilesException. Reads and idempotent writes are retried twice after a network error or a 5xx; the writes that create (folders, copies, shares, tickets) send an Idempotency-Key that stays the same across their retries. A 401 asks the token provider for a fresh token and replays once.