Skip to content

ccs_files_user_client changelog

Versions follow semver. For the package itself see ccs_files_user_client.

Default endpoint. Source-compatible with 1.2.0.

  • FilesClient(apiUrl: ...) is optional: omitted, the production File Service, FilesEndpoints.production (https://files.centralcodestation.com/api/v1). FilesEndpoints.staging and FilesEndpoints.local (http://localhost:50600/api/v1) are the other environments, the same origins as FILES_ORIGINS of the Node SDK @central-code-station/ccs-files-client. FilesClient.fromTokenRoute is unchanged.
  • FilesEnvironment (production, staging, local): FilesEnvironment.parse(String.fromEnvironment('CCS_ENVIRONMENT')) reads a build-time --dart-define=CCS_ENVIRONMENT=... (empty is production; another value throws FilesConfigurationException), .apiUrl its API base. The example app reads CCS_ENVIRONMENT (FILES_API_URL still wins).

Application-managed folders, as the File Service now serves them. Source-compatible with 1.1.0.

  • ManagedBy (clientId, name, managedFolderId, managedAt, toJson) and, on FileRecord and FolderRecord (every FilesItem), managed (false when absent) and managedBy (null when not managed): a folder a tenant’s application manages and everything below it are read-only for people. FilesItem.isReadOnly is true when managed; decide the editing controls from it, not from myActions.
  • folders.releaseManagement(folderId, reason:) (POST P/folders/:id/release-management) answers the FolderRecord: the tenant owner’s emergency release of a mark, audited with the reason. Never retried.
  • folder_managed (403) documented on FilesForbiddenException, with the new isFolderManaged and managedFolderId (the folder that carries the mark, from body['managed_folder_id']); tenant_owner_required (403) answers releaseManagement for anyone but the tenant owner.

Legal hold, and search documented as the File Service now answers it. Source-compatible with 1.0.0.

  • FileHold (reason, placedBy, placedAt, until, inForce([now]), toJson) and FileRecord.hold (null when the file is not held).
  • files.hold.place(fileId, reason:, until:) (PUT P/files/:id/hold) and files.hold.release(fileId, reason:) (DELETE P/files/:id/hold, the reason only when given), each answering the FileRecord; holds.list(placedBy:, after:, limit:) (GET P/holds), a Page<FileRecord> newest hold first. They need the files.manage permission; both writes are retried like other idempotent writes.
  • file_on_hold (409, a purge or a version delete of a held file) documented on FilesConflictException, with the new FilesConflictException.hold reading the hold from the error body.
  • Search wording: SearchField.name matches file names that start with the query, case-insensitively (a prefix, not a substring); SearchField.keywords matches whole words (name, title, description, tags, category, department, custom values); with the default fields a query the words find nothing for falls back to the name prefix. SearchResult.total is exact up to 10,000 and null above.

First release: the Dart/Flutter user client of the CCS File Service, the counterpart of @central-code-station/ccs-files-user-client. Generic: it knows nothing about any record of the app.

  • FilesClient for one (tenant, application) context, from a token route (FilesClient.fromTokenRoute) or a token callback: folders (root, create, get, children, path, update, move, copy, delete, restore, usage), files (get, byExternalRef, update, move, copy, delete, restore, downloadUrl, versions, renditions, myActions), uploads (the protocol step by step), permissions (get, replace, grant, revoke, roles), shares (create, list, mine, revoke), trash.list, settings.public, preview, urls, invalidate, search (with SearchField.content), tags, capabilities, signOut, close.
  • A 401 asks the token provider for a fresh token and replays once; reads and idempotent writes are retried twice after network errors and 5xx; creating writes carry an Idempotency-Key kept across retries; If-Match on updates. Preview and inline URLs are cached until a minute before they expire; urls asks only for what is not cached.
  • TokenProvider(tokenUrl, headers): caches until a minute before expiry, one in-flight request, clear(), configuration from the route’s optional api_url, tenant_id and application_id.
  • UploadController: start, pause, resume, cancel, progress (a Stream<UploadProgress>), checkpoints, done, toJson and UploadController.fromJson (resume after a restart, from the first part the store does not have); sources UploadSource.file, .path, .stream (with a known length), .bytes, or your own. Single PUT with exactly the ticket’s headers; multipart with 4 parts in flight, 5 retries per part with full-jitter backoff, part URLs renewed through the API on a 403 or when older than 50 minutes; a lost completion answer is recovered from the ticket status; cancel aborts the ticket.
  • UploadController.fromTicket(ticket, source): the engine on a ticket of the app’s backend, with no Files token; done gives the parts and their ETags (completionBody()), optional refreshPartUrls.
  • FilesUploadQueue: bounded concurrency, persisted through FilesUploadStore (JsonFileUploadStore, MemoryUploadStore), restore() at startup, interrupted uploads resumed when the app returns to the foreground, optional pauseInBackground.
  • Typed FileRecord, FolderRecord, UploadTicket, UploadRequest, Preview, SearchQuery, SearchResult, AccessEntry, ShareLink, PublicSettings, Capabilities; the error classes of the Node SDK as Dart exceptions (FilesApiException and its subclasses by status, FilesUploadException, FilesUploadSourceMismatchException, FilesContextException, …). The SDK never logs.
  • Uploads run in the foreground isolate; background transfers (iOS background URLSession) are out of scope.
  • Tests: unit tests against a fake service and store (including a 1 GiB upload resumed after a simulated kill) and an integration test against the service started locally. Not yet run on a device.