Skip to content

Media and Google Drive

Where a film’s bytes live and how they are read; the local store and Google Drive as a sharing target.

A film’s pictures, clips and sounds live flat in its project’s media/ folder. A session copy keeps new bytes in memory over the bundled source. An older manifest’s data field does not choose another store. filmopen_media is pure Dart and owns local storage, playback values and shared input descriptions. Importing and thumbnails belong to filmopen_insertion (§6.13).

The store interface is MediaStore: put (always create, so an existing take is never replaced — §18.3), isAvailable, open, exists, length, read, list and delete. LocalMediaStore.of(project) wraps the project’s source. Store paths are file names: reads and writes prefix media/, and list returns only files directly in that folder with the prefix removed. Names with separators cannot escape that flat area. open answers PlayableFile for disk and PlayableBytes for memory.

The application supplies a MediaStore directly. mediaStoreProvider creates LocalMediaStore.of(project) for the open project and answers null when no project is open. Forms, insertion and the plug-in host read this same store. The store’s availability reflects whether its source can write; no manifest field selects a second source.

A reference is resolved twice over, so a take absent from a source listing can still be opened: a take stem is looked for among the takes the loader discovered, and then, failing that, among the import batch records — which are in data/, so they answer offline and for any store, and a take stem carries the batch’s id and author (§5.4) so the record is asked for by name. This fallback also supports a session copy without a listing.

One class for a piece of media is ProjectMedia: the reference as the file wrote it (MediaRef: a take stem, a path or a URL, §6.3), its MediaKind, where its bytes are, whether the project holds its thumbnail (FilmProject.thumbnailPaths, collected by the loader), the name a person would recognise — the original_name the batch record kept, else the basename — its record, recorded, beside batch, the record it is an item of, and open(store). The entry is the first road to the record (§9.0, §12.2): ProjectMedia.of(project, reference, batch:, item:) — ProjectMedia.ofEntry for an entry — takes the item the entry’s batch and item name, where that item’s output is this reference; else the item of that batch, then of the take’s own batch (its name carries the id and the author, §5.4), whose output it is, which is how a take found by its name alone — the takes tab’s — is recorded; an entry that names no batch and no take has no record. An item whose output is another file is never taken for this one, and a malformed output suppresses rather than borrows a thumbnail. liveMediaOf(project, json) lists what a document keeps live, then a preview no entry names, and Entity.media(project) is it for a file. A file-name reference is located through project.source.locate(mediaFilePath(reference)); a reference containing a folder path has no matching local file. A reference with no thumbnail is not an error: the tile draws the kind’s stock picture (§6.3).

Input values remain here: MediaInput, FileInput, BytesInput and UrlInput are the unchanged file, byte and URL descriptions consumed by upload doors and insertion callers. Their types carry no dependency on the importer.

Store failures cross the package boundary through StoreProblem.storeCode (store_problem.dart), a stable string code with no server text. DriveException implements it as drive.<code>; insertion preserves a safe code without importing filmopen_drive. Localized sentences remain above these pure-Dart contracts.

Media on demand (§6.14). MediaFetch is the seam every reader of a project media file’s bytes passes first: MediaFetch.current starts as NoMediaFetch, where nothing is remote, and the application installs filmopen_state’s implementation over the sync. LocalMediaStore.open and read call MediaFetch.ensureIn(store, name, reason:) before they look for the file, passing the store’s own source, so the decision is that film’s (§6.14: here → at once; available → downloaded; else refused notAvailable), so the viewer, a render’s frozen first frame, a plug-in’s granted read and the replay’s hash download a file still in the cloud and then read it; exists and length never download, so a collision probe stays a probe. The plug-in host’s size gate on a granted read calls ensureIn before length, keeping its byte bound. A failed download is a MediaFetchProblem whose storeCode is fetch.<code>, and the host refuses with that code rather than mediaMissing. The store logs media.store.fetchFirst {file, reason: storeOpen | storeRead | pluginRead | viewer, outcome: start | ok | failed, code or type and error, ms} through MediaFetch.log, its start before the wait, for a file on the target only; a file read as it is logs nothing, since every picture and tile opens one, and a failure is logged whatever the file. Copy to a folder logs where it is going first (project.copy.start {film, parent, folder}), then reads the source, not the store: it downloads each of the film’s available media first (project.copy.ensure {film, files}, each file’s media.ensure.*, project.copy.ensured {film, outcome, code, ms}; the folder made logs project.copy.folder {folder, existed}; a copy that then fails logs project.copy.failed {film, parent, folder, stage: copy, type, ms}) and refuses the copy with the first failure’s code; project.copiedToFolder carries the destination as its path. Nothing is evicted.

Whether a render is outdated is one rule over timestamps, read and never stored. MediaStatus.of(project, document, entry, {group, block}) answers current, outdated or unknown for a placed entry, its record found as ProjectMedia.ofEntry finds it. The render’s time is its record’s request.asked_at; the render is outdated when the updated of anything it depended on is later, instants compared to the second as the stale pick compares them, a time without a zone being absent — and by the stage, when the entry’s stage is below its holder’s render_stage (renderStageOf, prototype where absent): a holder moved up outdates the renders below it and none at its stage or above, one moved down outdates nothing, and choosing a stage moves no updated (§6.1a). What it depended on depends on where it is placed:

  • an entity’s entry: the shown document’s updated (a draft over its file, as the caller holds it) and that of each version stem in request.sources, read as the exact file named — a stem the project no longer holds compares to nothing;
  • a block’s entry covering blocks (spans.blocks, or no spans its group allows, which is the owning block alone): the updated of each covered block in the shown document and the stems of request.sources, never the document’s own updated; and outdated too where the ids from the first covered block to the last are no longer exactly the coverage (coverageIntact), so that a block inserted, removed or moved inside it reads outdated with no copy of the blocks kept;
  • a block’s entry playing until an end (spans.until, music and sound effects): the stems of request.sources alone, so that rewriting the dialogue under it outdates nothing.

Unknown is an entry no record names, or a render record without request or a zoned asked_at; the entry of any record that is not a render — an import, a plug-in’s or a manual one — is always current. Since updated moves only when content changes (saveEntity, §6.1a), inserting a take, keeping or rejecting it, or writing a note outdates nothing; a fork keeps its source’s updated and so its renders stay current until it is edited; a revert moves updated again and stays outdated; a style edit outdates the renders whose request named it.

The descriptor is MediaProvenance.of, with the same arguments and optionally the entry’s ProjectMedia the caller has already read (the media place passes its own, so a tile resolves its media once per build): plain data, no sentence — the status, when the render was asked, each MediaChange that outdates it (the document, a block by its 1-based position among the document’s blocks, the coverage, or a source by its stem, each with its time; the coverage with none; the stage with the render’s and the holder’s stages), when the entry was kept, and its facts as MediaFacts — length, width and height as the entry says them beside its record’s item, the record’s drawn, and whether the two disagree. filmopen_media_ui’s provenanceLines(l10n, provenance) says it as the tooltip’s lines: Rendered on …, then for each change The document changed on …, Block n changed on … or <stem> changed on …, or for the coverage A block was added, removed or moved among the blocks it covers, or for the stage Rendered at a lower stage than the object’s — logged where the stage moves, not where it is said: StageControl.choose logs media.stage.outdated {stem, group, reference, record?, item?, stage, holderStage} once for each render the move puts below the holder’s stage, the tooltip logging nothing since a tile asks on every build — then Kept on …, and each fact the file and its record disagree on (The file says 9000 ms, its record 5000 ms). Every time is said in the person’s own clock through the language’s date format; the caller joins the lines — a line break in a tooltip, a comma in a semantics label.

6.10 Google Drive as a sync target — filmopen_drive

Section titled “6.10 Google Drive as a sync target — filmopen_drive”

The package is Google’s sign-in, the Drive HTTP client and DriveSyncTarget, the sync target (§6.14) that keeps a film’s manifest, data/ and media/ in step with the film’s folder in the person’s My Drive. Drive is a place a film is shared, never a media root: project media comes from the local media/ folder, and a manifest’s old locator is ignored. The package has no media store, source UI, folder-id preferences or cache. The application reaches it through filmopen_sharing alone (§9.4), which owns the account model, the choice of target and the Sharing card, and makes the one DriveAuth and the targets; the root package has no filmopen_drive dependency or import. driveAccountProvider constructs DriveAuth lazily, and the card draws the saved sign-in hint without reading credentials: constructing the account or the sync service performs no sign-in and no network request.

Configuration and scope. DriveClientConfig reads desktop and Android OAuth client values from build-time constants supplied by the external keys folder (§3). No client secret is committed. A client missing for the selected platform is unconfigured. The consent flow asks for drive.file alone and requests offline access with access_type=offline and prompt=select_account consent — Google’s account chooser first, even where the browser holds one Google session, as a phone’s usually does, then the consent that brings a refresh token — with a PKCE S256 challenge and a state value checked on return (drive_consent.dart).

Signing in. DriveAuth accepts the token store, configuration, HTTP client, sign-in and refresh functions, and an optional redirect stream. The desktop flow opens the person’s browser and receives the answer through a loopback listener on 127.0.0.1; Android uses its own client, with no secret, and a redirect scheme derived from the client id, supplied through DriveRedirects: driveAccountProvider gives its link door, IncomingLinks, to the DriveAuth it makes, read when the redirect stream is asked for. The account is separate from the filmopen.ai account; Settings → General → Sharing owns its sign-in and sign-out, and Activity asks the same sign-in before it sends a job’s signIn action (§9.7). Every platform’s code exchange and refresh are explicit HTTP requests in drive_tokens.dart, omitting client_secret where the client has none. Every call to Google is bounded: a refresh and both code exchanges time out after 30 seconds with unavailable, and close their temporary clients in finally; the consent’s wait for the person — the loopback’s answer on a desktop, the door on a phone — gives up after five minutes. A sign-in can be abandoned: it takes an abandon future, which the redirect wait races; abandoned, the wait gives its listener back, and nothing that comes back afterwards is stored. The web uses a stub that refuses sign-in. A token request logs drive.token.request {op: exchange | refresh, platform, uri, form, secret} just before the secret, the code, the verifier or the refresh token joins the form — form the ordinary fields, client_id, grant_type and, for the exchange, redirect_uri — then drive.token.done {op, refreshToken, expiresIn, scope, tokenType, ms}, drive.token.refused {op, status, reason: status | notJson | noAccessToken, body?, bytes, ms} — Google’s body for a refusal, never a 200’s, which holds the tokens — or drive.token.failed {op, type, error, ms}, each with the sync operation’s op_id where it runs inside one. The consent page’s opening logs drive.consent.open {flow: loopback | link, clientId, redirect, scope}, without the address, which carries the challenge and the state; a consent that fails otherwise than with a Drive code logs drive.consent.failed {flow, stage?, type, error, ms}, and one refused drive.consent.refused {flow, reason: browserNotOpened | noCode | stateMismatch | listenerClosed, error?, description?, ms} (Google’s error and error_description, never the code).

Credentials. SecureDriveTokenStore keeps access and refresh tokens under filmopen.drive.tokens in the operating system’s credential store, never in a project file, preferences or the log. Refresh preserves the existing refresh token when Google omits a new one. DriveAuth.client answers an authenticated client of its own, which the caller closes: it carries the token in its Authorization header, and only to a googleapis.com host, and it is made after a refresh of a token that has run out, or of any token when asked with refresh: true — what a caller does once Drive has refused the token it holds. DriveAuth.signOut clears tokens without deleting Drive files. Its log entries report sign-in or sign-out without credentials; a store that fails logs drive.tokens.failed {op: read | write | clear, type, error}, and stored tokens that do not parse {op: parse, offset}, since the text is the tokens and its length is not logged either — both with the sync operation’s op_id where they run inside one.

Authentication ordering. Sign-out and a newer sign-in invalidate older consent or refresh answers. Credential operations are serialized locally without holding storage while browser consent or HTTP waits, so a write and a sign-out’s clear land in the order they were asked, and an answer for an intent no longer current is not written; a sign-in overtaken while its write runs answers refused, the later sign-in or sign-out standing after it. A cancellation arriving while the device write already runs keeps what was written. Each is an operation of the log, with its start, its end and its time, and each line carries the sync operation’s op_id where it runs inside one — a client made for a target’s operation reads as that operation’s. A sign-in logs drive.signIn.start {intent}, then drive.signedIn {intent, abandoned, ms} (abandoned true where a cancel came while the write ran), drive.signIn.refused {reason: unconfigured | abandoned | superseded, intent, ms}, or drive.signIn.failed {intent, abandoned, stage: consent | store, code?, type, error, ms} — the consent flow that failed, whose own lines say where, or a credential store that would not take the tokens, which is this machine’s failure and never said as a cancel or a newer sign-in. A sign-out logs drive.signOut.start {intent}, then drive.signedOut {intent, ms} or drive.signOut.failed {intent, stage: store, code?, type, error, ms}. A client logs drive.client.start {intent, refresh}, the decision to ask Google drive.client.refresh {intent, reason: forced | stale}, then drive.client.done {intent, refreshed, ms}, drive.client.refused {reason: unconfigured | signedOut | superseded | noRefreshToken, intent, ms} (unconfigured before the start line, nothing having been read) or drive.client.failed {intent, stage: refresh | store, code?, type, error, ms}, the refresh’s request having logged its own lines. Construction reads no credentials.

The client. DriveClient provides childrenOf, childNamed and fileById (each answering a file’s size and MD5), createFolder, upload (one multipart request), the three calls of a resumable session — startResumableUpload, which answers the session’s upload id, read from the Location Drive answers, never the address; resumableCommitted, which asks the session with Content-Range: bytes */<length> how many bytes it holds (the end of a 308’s Range plus one, none without one, all of them once the file is made); and sendChunk, one part with its Content-Range, answering nothing while Drive wants more and the file once the last part is in — then replaceContent, a PATCH of a file’s bytes that keeps its id, and download, over an injected HTTP client. A session’s address is made again from its upload id whenever it is needed, never kept. Request bodies are bytes and can be replayed. Every call and its answer is bounded at 30 seconds — a metadata call, the one-request upload and replaceContent, and a session’s startResumableUpload and resumableCommitted — but a session’s part, at two minutes, and a download’s answer, bounded between its chunks; a call that runs out of time is unavailable. A request answered 429 or 503 is sent once more, two seconds later, then reported as busy if either status persists. A 401 is refused, a 404 or a 410 notFound, a 409 alreadyThere, and any other status the client does not name unavailable. A 403 is read by the reason code of Google’s answer (error.errors[].reason), never its message, and is not sent again: a Drive with no room left — its storage used up, or a folder at Drive’s limit of files — is full; a rate limit, busy; anything else, a permission Drive will not grant among it or an answer that is not Google’s JSON, refused. Its body is read within the request’s bound, so a 403 whose body never ends is unavailable. Failures are DriveError codes, never Google’s message text; DriveException implements StoreProblem as drive.<code>.

Ranged downloads. download(id, from: 0) returns DriveDownload, with its actual starting byte and a bounded byte stream. A positive start sends Range: bytes=<from>- on GET files/<id>?alt=media. A 206 must carry a valid Content-Range starting at the requested offset; malformed or mismatched metadata is unavailable, with the response body subscribed and cancelled even when no bytes are accepted. A 200 means Drive returned the whole file and the actual start is zero. A 416 is unavailable, and a negative requested start is refused before any request. The existing timeout bounds each gap between chunks, and stream errors carry only DriveException codes.

The target. DriveSyncTarget(auth:, folderId:, clientFor:, resumableFrom: 5 MiB, chunkSize: 8 MiB), kind drive, is the film’s folder: its identity is the folder’s id, the name the sync’s journal keeps the target by (§6.14), and it holds the manifest at its top and a data and a media folder under it. Their ids come from one lookup per film folder and DriveAuth, not per target: the application makes a target at every capture (§6.14), so the lookup outlives the target, and it has one DriveAuth for its run, which a sign-out does not end. The account signed in next may not see the ids — after a sign-out, or through a consent whose account chooser landed on another account — so DriveAccount.signOut and DriveAccount.signIn forget them (DriveSyncTarget.forgetLookups) and each film’s next operation looks its folders up afresh, an operation under way keeping the lookup it started with. Each logs driveAccount.signIn or driveAccount.signOut .start {hint, abandonable?}, a .step as it forgets the lookups (lookupsForgotten) and as it sets the hint ({step: hint, from, to, bound}, handed to the settings where the account is bound), then .done {hint, ms}, or .refused {code, hint, ms} for a DriveException and .failed {type, error, hint, ms} at warning for anything else, what it caught thrown on unchanged. The lookup is made when the first operation needs it: the film’s folder asked for by its id, once per lookup, since a listing inside a folder that is gone answers nothing rather than a refusal; then data and media found by name, or made; a data or media name that Drive holds as a file is folderGone for good. Every operation that starts meanwhile, whatever target it runs on, waits for that lookup, so two folders of one name are never made; the ids are kept, never asked again, until a request finds a folder gone, and a lookup that fails is forgotten. inventory is the manifest by name and the listings of the two folders, read to their last page, every file with its size and MD5; it leaves out a name the layout would refuse (a stray file a person put in the folder), an entry without a size (a folder, or a file Drive keeps in a format of its own) and the second of two files of one name, rather than failing. find is one file by name in its path’s folder; a path that is no place of the layout is invalidPath before any request. A document is sent whole, as one request: replaced in place where its name is there, so that its id — which a folder shared later names — stays what it was, and uploaded where it is not. A media file is never replaced: its name is looked up first and a hit is nameTaken with nothing sent, since Drive would take a second file of one name. Under resumableFrom it goes as one request; from it, as a resumable session: startResumableUpload, the checkpoint {upload, length} reported at once — the upload id in hexadecimal, as every journal on disk holds it; a checkpoint of another shape — no hexadecimal id or no non-negative length — is unsafeCheckpoint, its keys as the cause — then parts of chunkSize, a multiple of 256 KiB as Drive requires, in order, since one session takes its ranges in order — the next part read while one is sent, and the last read to the source’s end before it is sent — with the progress after each. committed asks the session a checkpoint names how much it holds, 0 where Drive has let it go. A resumed send asks the same and takes the session up from its count; a session Drive let go of is opened again from byte 0, its new checkpoint reported, where the send starts there. A session gone under a send that resumes past byte 0, or alive at another count than the send’s, is sessionMismatch, tried again, the sync asking committed anew. Arguments that break the transfer’s rules — a length or a start out of range, a document or a media file under resumableFrom resumed past byte 0, a resume without a checkpoint, a checkpoint of another length, a document sent without replacing, or a replacement asked for media — are invalidTransfer, and a stream that holds more or fewer bytes than it said is lengthMismatch, both before anything is published. The bytes are read before anything is published, the last part to the stream’s end, so an error after the last byte never becomes a send. The receipt is the file Drive answered: its size and its MD5. One client per operation, asked of DriveAuth as the operation starts and closed after its last request or the receiving stream it owns. A 401 is replayed through the client: its answer closes the client, a refresh is forced, and that same request, its bytes kept, is sent once more with the new client, while the requests before it stay as they went; a second 401, or a refresh Google turns down, asks for a sign-in.

Receiving from the target. fetch(path, from:) applies the same layout validation and cached film-folder lookup, then asks for the named file in its parent. An absent name, a folder or a file without a size is retryable unavailable; an offset below zero or beyond the whole file is invalidTransfer. It downloads the file by its in-memory Drive id and returns SyncBytes with the actual response offset, the metadata’s whole length and MD5, and the stream. A full response to a range request starts at zero so the sync can discard its old prefix. A fetch after inventory reuses the folder lookup. The existing refreshing client replays a download’s 401 once, preserving its Range header.

A receiving operation transfers client ownership to its returned stream. It holds a real upstream subscription paused until a consumer listens, then forwards pause and resume, closes the client and cancels upstream on completion, error or cancellation. If nothing listens within the client’s existing timeout, it closes and cancels immediately and queues unavailable for any later listener; it never waits for an unlistened controller’s close future. This is an abandonment deadline, with no target request on its timer. Upstream cancellation is bounded by the same timeout after both clients close; a cleanup failure is contained and cannot replace the consumer’s terminal result or expose transport text as an uncaught error. Stream errors use the same stable sync-code mapping as requests. A lookup or download failure before the stream is returned closes the operation’s client immediately.

Failures become the sync’s codes. refused is refused with the verdict signIn — a 403 permission at once, since a refresh grants no permission; busy and unavailable (a timeout among them) are tried again; a Drive with no room left is driveFull, tried again with the sync’s growing delay until a person is asked, since freeing room is theirs; the film’s folder gone on the lookup is folderGone for good; a request that puts something into a folder — a folder made, a file uploaded, a session opened — that Drive answers “not found” is folderGone tried again, with the lookup forgotten, so the next attempt makes a deleted data or media again or finds the film’s folder gone; any other “not found” — a session Drive let go of between two parts, a document deleted between its lookup and its replacement, a listing inside a folder that is gone — is unavailable, tried again, and a listing answered so forgets the lookup too, so that the next attempt asks for the film’s folder again rather than list a folder gone for the rest of the run; unconfigured, nameTaken (a name Drive already holds, found by the lookup or answered as alreadyThere), invalidPath, invalidTransfer, lengthMismatch and unsafeCheckpoint are permanent. A failure that reaches the target through the sync’s own byte stream is not translated — a film closed, a send stopped — and goes back as it came. No token or session address enters a checkpoint or a code, and no token the log; the target’s own lines are its trail, below. The target never deletes a remote file.

Drive’s trail. Each call of the target — inventory, find, committed, put, fetch — is one operation, with an id of its own (op_id, a random UUID) on every line, starting with drive.start {op, area, path} once the call is past its synchronous checks; for one file, its request, opening and end lines carry its area and its project-relative path as it is. Every request the client sends inside it logs drive.request {op, stage, method, uri, area, path, bytes, from, body}, before the token is added, body being a JSON control request and never a file’s bytes, — op one of lookup, create, list, find, committed, fetch, put, resumable, chunk, replace; stage send, or again for the one repeat of a busy answer — and drive.answered {op, status, bytes, ms}, and a control answer read whole drive.body {op, status, body} as received, an upload’s receipt among them; a status that becomes a DriveError logs drive.refused {op, status, code, body, ms}, the code and Google’s own words (a body that cannot be read logs drive.unreadable {op, status, type, error, ms}), and a busy answer about to be repeated drive.busy {op, status, body, ms} (a download whose range does not match adds reason: range); a request whose answer could not be had or read whole logs drive.unreachable {op, stage: send | response | stream, type, error, ms}, the transport’s runtime type and text before it becomes unavailable. A put that succeeds logs drive.uploaded {bytes, mbps, ms} over the bytes it sent. The operation ends with drive.done {op, ms} or drive.failed {op, stage, code, type, error, ms}, its stage arguments (a call refused before any request, thrown as before — at once where the call throws at once), read (a put’s first source bytes, the source’s own error passing unchanged) or drive; the probe of a resumable session logs drive.committed {bytes, sessionGone}, and a 401 replay drive.refreshing. A fetch logs drive.fetch.opened {op, ms} when it hands back its stream, and its operation ends with the stream: drive.fetch.ended {outcome: eof | error | cancelled | abandoned, bytes, stage, code, type, error, ms}, with the bytes actually delivered. No line carries a header or a token. The client logs only the requests of the connection its operation owns, the trail being carried in the zone the operation runs in: its other users, and another client a callback of the operation calls, log nothing.

The package’s tests use stand-ins, without a Google account: a MockClient answering by request, to check configuration, consent, token requests and the Android redirect flow, and a Drive held in memory (test/support/drive_stand_in.dart) that keeps every request it received, to check the client’s calls and the target from what was sent. Its alt=media responses cover full and ranged binary bytes, ignored ranges, absent or trashed files and native-file refusal. A separate MockClient.streaming stand-in lets tests feed individual chunks, observe cancellation and count client closure, so timeout and ownership checks do not rely on a buffered response.

FilmOpen Cloud as a sync target — filmopen_cloud

Section titled “FilmOpen Cloud as a sync target — filmopen_cloud”

FilmOpenCloudTarget is a pure-Dart SyncTarget (§6.14), with kind filmopen and identity <username>/<tag>. The username follows ShortName.validateUsername; the tag is one to 32 lowercase letters, digits or dashes, beginning with a letter or digit. A manifest’s sync.target: filmopen selects it through sharingTargetFor, using sync.id and the account’s access-token callback; an invalid identity or a session copy selects no target. Selection logs sharing.target with the kind, the film’s project key, the identity (a folder’s path instead), the outcome and its reason — Cloud selected/badIdentity with an invalid identity’s refusal text, Drive selected (manifest)/noId (manifestWithoutId), a folder selected (machinePath)/noPath (noPathOnThisMachine), a target not chosen at warning —, an operation id and milliseconds — when that decision differs from the last one logged, of any kind, not at every capture, which asks again with nothing changed; a session copy, or a film whose manifest names no target, forgets the last decision, so the next one is logged again. CloudClient takes the caller’s host, asynchronous access-token reader, optional HTTP connection and timeout. Construction reads no credential and sends nothing. Without an injected connection, each operation owns one until completion; an injected connection remains the caller’s.

File requests use HTTPS and the session bearer alone, with encoded path segments under /v1/cloud/{username}/{tag}/files. Inventory and find use GET, the latter with path in its query. The JSON answer supplies files entries with a path, nonnegative integer size and a document’s MD5. Inventory skips and counts string paths outside the root manifest and flat data/ and media/ layout before reading their size or MD5; admitted entries are read strictly. Find accepts an empty list or one matching entry. Whole PUT uses replace=1 for documents and replace=0 for media, with Content-Length and a document’s X-FilmOpen-Md5. Its 201 receipt supplies path, size and MD5 for the engine to verify. A source read’s non-target error is logged as cloud.failed with stage read, code sendFailed and its type, then passes unchanged to the engine, which raises sendFailed needing attention. Media of 32 MiB or more opens a resumable upload with POST /v1/cloud/{username}/{tag}/uploads and {path, length}. The answer supplies an opaque upload id and partBytes in 1..32 MiB. The id travels as one encoded path segment. Each PUT …/uploads/{upload}/parts/{n} carries a whole part and Content-Length, and acknowledges the same part number and a bounded etag; up to four parts are in flight at once, the next read while they are sent, and they are taken in order however they answer. Only after the source reaches EOF does POST …/uploads/{upload}/complete send exactly {parts: [{part, etag}, …]} in order and return the receipt. A stream whose length differs from its promise is permanent lengthMismatch and never completes.

The resumable checkpoint is {upload, length, partBytes, parts: [{part, etag}, …]}, reported immediately after open and each time the contiguous acknowledged prefix grows: a part answered before an earlier one waits for it, and a failed part leaves the prefix before it. The upload id is lowercase hexadecimal of its UTF-8, as every journal on disk holds it. committed returns min(parts.length × partBytes, length) without a request; an empty map or exactly {upload: '', parts: []} returns zero. A checkpoint not of the shape this target writes — a hexadecimal upload id, an integer length, a positive integer part size and parts each with an integer part and a string etag — is permanent unsafeCheckpoint, its refusal naming shape and the keys it held; a parts checkpoint on a whole transfer names whole. A resume must use that offset and the checkpoint’s length, otherwise permanent invalidTransfer; parts checkpoints are refused for whole transfers. A part or completion answered 404 upload-gone, or a completion answered 409 parts-incomplete, reports the reset checkpoint before retryable sessionMismatch or partsIncomplete, respectively, so the next attempt starts at zero. A completion refused for balance keeps the checkpoint; a retry with every part acknowledged sends only completion. A Stop between parts passes the source error unchanged, logs cloud.failed at read with the acknowledged part count, and sends no completion; the engine records cancellation. A Stop after the final bytes have been consumed can complete, with the cancelled job settled by the next inventory. Multipart request logs keep op: put and add request: open | part | complete and n for a part; each part is a call of its own under the upload’s op_id, its cloud.part.start {bytes}, request, answer, refusal and cloud.part.done {bytes, status} lines carrying its own n, stage and milliseconds, a failed upload names the parts it leaves in flight in cloud.abandoned {parts}, and a completed upload logs cloud.uploaded {bytes, parts, mbps}.

Fetch sends Range: bytes=<from>-. A 200 starts at zero and requires Content-Length. A 206 supplies the actual offset and whole length through Content-Range; its start must equal the request or zero. The EOF shape bytes */<total> is read first and accepted only when total equals the requested offset. Zero-length files accept either shape. A malformed range, absent full-response length or malformed lowercase-hex MD5 is retryable malformed, with the check named in the log. An absent response MD5 remains null.

A 401 is refused with a sign-in verdict; the target performs no refresh or replay. Before a request, a missing or empty token logs reason noToken and answers noToken with a sign-in verdict, the code the sync waits on for the account’s sign-in (§6.14); every thrown token-read error, including a timeout, logs cloud.unreachable at stage token with its type and is retryable unavailable — the account’s own lookup, not the network. 402 storage-no-balance is retryable noBalance; 400 md5-mismatch is retryable corrupt; other 400 and 403 answers retain their safe server code with a permanent verdict. 404 no-such-project is permanent noRecord, 404 not-found retryable notFound, 404 upload-gone retryable sessionMismatch, 409 parts-incomplete retryable partsIncomplete, 409 path-busy retryable busy, and 409 name-taken permanent nameTaken. 411, 413 and 415 retain their safe server code with a permanent verdict, including length-required, too-large and json-required. 416 is permanent invalidTransfer. 502 and 503 are retryable unavailable; transport failures and timeouts of the request or its stream are retryable unreachable, the one code that says the network; unexpected answer shapes are retryable malformed. Only the string error matching ^[a-z][a-z0-9-]{0,63}$ is read from a refusal, otherwise unknown; the server’s prose decides nothing and is logged with the answer’s body. The sync engine owns the retry ladder.

Each operation logs its start, token and send stages, answer, decisions and terminal success or failure under one op_id. Every line carries elapsed milliseconds; file references use their area and the project-relative path as it is, as the sync trace does. Each request logs cloud.request {method, uri, bytes, from, md5, body} just before its bearer is added, body being a control request’s JSON and never a file’s bytes, and each JSON answer its cloud.body {status, body} as received, an acknowledgement or a refusal’s error and says alike. A server refusal logs cloud.refused with the status, mapped code and validated server code (or unknown), retaining the operation and request context. Local refusals, skipped inventory paths and malformed-answer checks have named reasons; an unsafe checkpoint adds its fixed check word, and cloud.reset names uploadGone or partsIncomplete as its reason. Fetch retains its id through EOF, error, cancellation or bounded abandonment, logging bytes delivered and releasing its response subscription. Token, response and stream waits are bounded by the configured timeout. A failure logs its type, its code and the error’s own words as they are (cloud.unreachable {type, code, error}, cloud.failed {code, type, error}); the bearer token, the one credential, never enters this trail, nor does a header.

Sharing on FilmOpen Cloud from Settings. SharingModel.shareOnFilmOpenCloud shares an existing film as <username>/<tag>, the username the viewer’s: refused where the sync could not start or the film has no manifest, signed out of the account, with a balance known to be zero, with the account’s usernames not readable (accountUnavailable), or with a username that is not one of the account’s (notYourUsername, read from AccountProjects.identities()); then it records the film (§6.6; provider filmopen) — a record not made stops it —, restarts that place’s stopped sends as every explicit Share does, and writes sync = {target: filmopen, id: <username>/<tag>}, whose re-read reconciles the film, forgetting this machine’s sharing folder for it as Drive’s does. It answers a CloudShareOutcome: ready, or the stage it stopped at (identity, record, restart, manifest) with a safe code. Stop sharing removes sync and leaves the record. A film on FilmOpen Cloud signs in on Share to the account, not to Google: when a row asks, the application reads the open film’s target and keeps it for that job; for filmopen it opens the account’s popup with the row’s sentence and waits for the account to be signed in, following whichever account source is installed, at most 30 s; any other target is Google’s sign-in as before; a Cancel ends the wait its job started, though the film changed meanwhile. Its log: sharing.cloud.start at the press, before the gesture’s first check (its ms include the wait in the queue), sharing.cloud.step {stage, outcome, code?, ms} — the restart’s started before its wait for stopped sends, cleared after it —, then sharing.cloud.done {ms} with sharing.started {target: filmopen, ms}, sharing.refused {target: filmopen, stage, reason, cause?, ms} (cause the restart’s timedOut, notDismissable or dismissRefused behind restartRefused) or sharing.cloud.failed {stage, code | type, ms}; share.signIn.start {provider: account | drive}, share.signIn.waiting {provider, joined?}, share.signIn.cancel {provider}, share.signIn.failed {provider, type} and one share.signIn {provider, outcome: signedIn | cancelled | timedOut | notSignedIn | failed, ms} — never an identity.

Opening a project the account records — filmopen_sharing

Section titled “Opening a project the account records — filmopen_sharing”

SharingModel.pullManifest(record, parentPath) is the one door the open-project dialog (§9.2) calls for a FilmOpen Cloud or Drive record on a device that does not have it. It builds the record’s target — FilmOpen Cloud at the record’s <username>/<tag>, asking the state’s AccountAccess seam for the session’s token (the account installs it beside its snapshots; installing connects nothing, and the default answers no token, so the target sends nothing); Drive at the folder whose id driveFolderOf(record) answers, after the sign-in (ensureSignedIn) — findDriveFolder(tag) is a read-only lookup of FilmOpen in My Drive, then the tag in it, answering the folder or nothing and never making one, logged sharing.drive.lookup {tag, stage: client | root | folder, outcome: start | found with the folder's id | absent | refused with Drive's own code | failed with the type and the error, ms}, a refusal said as the lookup’s refusal by that code, never as an unforeseen failure; a caller that has the id passes it (driveFolderId) — fetches filmopen-project.json no further than a manifest can be, verifies it (verifiedManifest, §6.1) and bootstraps the folder (bootstrapProjectFolder); the dialog then opens it, and the ordinary sync brings the documents first and leaves the media available, downloaded on demand. A refusal before the bootstrap leaves no folder. It throws RecordPullFailure(stage: lookup | manifest | bootstrap, code) — unsupportedProvider, driveUnconfigured, signInRefused, driveFolderAbsent (The Drive folder FilmOpen/{tag} was not found.), badIdentity, a target’s code, a manifest’s, notEmpty, badTag, or an unforeseen failure’s type — and logs each stage’s start and end as project.openRecord.step {stage, outcome, code — or type and the error's message as it is where nothing foresaw the failure — ms}. A local folder opened behind the cloud needs nothing of its own: the per-path difference fetches what changed only at the target and never sends the stale copy over it.