Help us shape the future of Go! Take the 2026 Go Developer Survey

cliclient

package
v0.56.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package cliclient implements the HTTP client behind `filex client` — the subcommand family that talks to a REMOTE filex server over its public REST API. It deliberately reuses only the wire contracts (no server-side imports) so the same binary can act as a pure API consumer from any machine.

upload.go — recursive (tree) upload on top of the single-file Upload core in api.go. Only existing server verbs are used: `newfolder` to materialize directories (empty ones included) and the streaming multipart `upload` for files. Symlinks are never followed — they are reported and skipped, so a cyclic link can't wedge the walk. Named pipes, sockets and devices are reported and skipped too: opening a pipe nobody writes to never returns (issue #38).

Index

Constants

View Source
const (
	TagPersonal = "personal"
	TagTeam     = "team"
)

Tag kinds.

View Source
const (
	TreeDir     = "dir"     // remote directory ensured (created or existing)
	TreeFile    = "file"    // file uploaded
	TreeSymlink = "symlink" // local symlink skipped
	TreeSpecial = "special" // local named pipe, socket or device skipped
	TreeErr     = "error"   // one item failed (walk continues)
)

Tree event kinds delivered to a TreeProgress callback.

View Source
const (
	VaultCodeLocked     = "VAULT_LOCKED"
	VaultCodeLockLost   = "VAULT_LOCK_LOST"
	VaultCodeNotAVault  = "NOT_A_VAULT"
	VaultCodePackExists = "VAULT_PACK_EXISTS"
	VaultCodeGeneration = "VAULT_GENERATION"
	VaultCodeBadObject  = "VAULT_BAD_OBJECT"
	VaultCodeKeep       = "VAULT_KEEP"
	VaultCodeVaultPath  = "VAULT_PATH"
	VaultClientCLI      = "cli"
	VaultClientMount    = "mount"
)

The error codes of the vault API this client acts on.

View Source
const DefaultOpPollInterval = 500 * time.Millisecond

DefaultOpPollInterval is how often WaitOp asks about a running op.

View Source
const DefaultStagedThreshold = 8 << 20

DefaultStagedThreshold is the size at which the CLI switches from the one-shot multipart POST to the resumable staged protocol. It matches the server's default chunk size, so "large" means the same thing on both ends.

View Source
const VaultLockHeader = "X-Filex-Vault-Lock"

VaultLockHeader carries the write lock's token on every vault write.

Variables

View Source
var ErrActionNeedsInput = errors.New("this action asks for its input in a form")

ErrActionNeedsInput is an action that opens a form in the explorer and was run with no parameters: the server answered the form instead of queueing.

View Source
var ErrArchivePassword = errors.New("the archive is encrypted")

ErrArchivePassword is an encrypted archive extracted with no password or a wrong one (401 PASSWORD_REQUIRED / BAD_PASSWORD). Nothing was extracted.

View Source
var ErrChangesUnsupported = errors.New("the server cannot report changes")

ErrChangesUnsupported means the server has no `action=changes` (anything before it answers 501): the caller has to walk the tree to find out.

View Source
var ErrNoRange = errors.New("cliclient: the server ignored the requested range")

ErrNoRange means the server answered a ranged request with the whole object.

⚠ It is a real error, not a fallback: a caller that asked for bytes [1000,2000) and silently received [0,N) would write the WRONG BYTES into a file at the offset it asked about. Wrong data is worse than a refusal.

View Source
var ErrObjectTooLarge = errors.New("the file is larger than it may be")

ErrObjectTooLarge: a whole read stopped at its limit.

View Source
var ErrPreconditionFailed = errors.New("the file changed on the server since it was listed")

ErrPreconditionFailed is a conditional upload the server refused because the target is no longer what the caller last saw (HTTP 412, code PRECONDITION_FAILED). Nothing was written.

View Source
var ErrTargetExists = errors.New("the target already exists")

ErrTargetExists is a copy or move to a full path whose name is already taken. Nothing was sent: the CLI never replaces, and the server would put the item beside it under another name rather than at the path that was typed.

Functions

func ConversionWrite added in v0.50.0

func ConversionWrite(ctx context.Context) context.Context

ConversionWrite marks every upload made under the returned context as an end-to-end CONVERSION write (`e2e_convert=1`): ciphertext replacing the plaintext it was made from, inside a folder whose key file says a conversion is under way. The server keeps no version of what such a write replaces - and only for a write it has checked is one; anything else is an ordinary overwrite. The flag rides where the server reads it: the multipart form, and the staged commit.

func DefaultConfigPath

func DefaultConfigPath() (string, error)

DefaultConfigPath returns the CLI config location: $FILEX_CLI_CONFIG when set (tests / unusual homes), otherwise ~/.filex/cli.yaml.

func DefaultResumeDir

func DefaultResumeDir() (string, error)

DefaultResumeDir is where upload bookmarks live: $FILEX_UPLOAD_STATE when set (tests, unusual homes), otherwise ~/.filex/uploads.

func IsTOTPRequired added in v0.50.0

func IsTOTPRequired(err error) bool

IsTOTPRequired reports whether err is the server asking for (or refusing) the second factor: an answer whose body carries totp_required.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err is an APIError with status 401 — the CLI uses it to append the "run `filex client login`" hint.

func IsVaultCode added in v0.54.0

func IsVaultCode(err error, code string) bool

IsVaultCode reports whether err is a vault API answer with that code.

func SaveFileConfig

func SaveFileConfig(path string, cfg FileConfig) error

SaveFileConfig writes the CLI config with owner-only permissions. The explicit Chmod after the write covers a pre-existing file whose looser mode WriteFile would otherwise keep.

Types

type APIError

type APIError struct {
	Status int
	// Code is the refusal's code (`error` in the server's envelope,
	// internal/apierr), for a program to branch on; "" when it sent none.
	Code string
	// Message is what to print: the server's sentence in the reader's
	// language (`message`), else its `error`, else a clip of the body.
	Message string
	Body    []byte
}

APIError is a non-2xx response mapped to an error. Body keeps the raw payload so callers can inspect extra fields (e.g. totp_required).

func (*APIError) Error

func (e *APIError) Error() string

Error renders "HTTP <code>: <server message>".

type AppAction added in v0.50.0

type AppAction struct {
	Plugin  string            `json:"plugin"`
	ID      string            `json:"id"`
	Label   map[string]string `json:"label"`
	Applies struct {
		Kind  string   `json:"kind,omitempty"`
		Ext   []string `json:"ext,omitempty"`
		Mime  []string `json:"mime,omitempty"`
		Multi bool     `json:"multi,omitempty"`
	} `json:"applies"`
	// View names the form the action asks its input in, in the explorer.
	View string `json:"view,omitempty"`
}

AppAction is one action an installed app offers the caller (GET /api/files/plugins/actions).

func (AppAction) LabelIn added in v0.50.0

func (a AppAction) LabelIn(lang string) string

LabelIn is the action's label in lang (then English, then any language).

type ArchiveCreateOptions added in v0.50.0

type ArchiveCreateOptions struct {
	Format       string // zip | 7z | tar | tar.gz | tar.bz2 | tar.xz | tar.zst (what the server allows)
	Password     string // zip and 7z only
	EncryptNames bool   // 7z: encrypt the file names too
	Compression  int    // 0-9; 0 = the server's default
}

ArchiveCreateOptions are the choices of POST /api/files/archive/create. Zero values are the server's defaults (the format from the archive's extension, then the administrator's default).

type ChangeStream

type ChangeStream struct {
	Client *Client
	// OnChange receives every change frame. Called from the stream's reader
	// goroutine; it must not block for long.
	OnChange func(TreeChange)
	// OnState receives every state transition, with a human-readable detail.
	OnState func(state StreamState, detail string)
	// OnConnected runs after every successful subscribe — the first one and
	// every reconnect. Changes made while the stream was down were not
	// announced to anybody; the caller answers this by asking what changed.
	OnConnected func()
	// OnUnauthorized is told when the server refuses the client's token
	// (HTTP 401 on the ticket). The stream stops for good: retrying a
	// revoked token only fills the server's log.
	OnUnauthorized func(error)

	// Tunables. Zero values mean the production defaults; tests shrink them.
	PingEvery        time.Duration // keep-alive + dead-connection detection
	AckTimeout       time.Duration // how long to wait for `watching`
	UnsupportedRetry time.Duration // re-probe a server without the stream
	MaxBackoff       time.Duration // ceiling of the reconnect backoff
	// contains filtered or unexported fields
}

ChangeStream follows the server's change stream for a set of watch roots.

func (*ChangeStream) Run

func (s *ChangeStream) Run(ctx context.Context)

Run keeps the stream up until ctx ends: connect, subscribe, read, and on any failure back off and do it again.

func (*ChangeStream) SetRoots

func (s *ChangeStream) SetRoots(roots []string)

SetRoots replaces the folders being watched. Safe to call at any time; a live connection re-subscribes at once.

func (*ChangeStream) State

func (s *ChangeStream) State() StreamState

State reports the current state ("" before the first attempt).

type Client

type Client struct {
	BaseURL string
	Token   string
	HTTP    *http.Client

	// StagedThreshold is the file size at or above which uploads use the
	// resumable staged protocol instead of one multipart POST. 0 means the
	// default (DefaultStagedThreshold); a negative value disables the staged
	// path, which is how a caller pins the old behaviour.
	StagedThreshold int64
	// ChunkSize is the part size asked for at `begin`. 0 lets the server
	// choose — and the server's answer is binding either way.
	ChunkSize int64
	// ResumeDir holds the bookmarks that let an interrupted upload continue
	// across process restarts. Empty disables persistence: uploads still
	// resume within a run, but a restart begins the file again.
	ResumeDir string
	// DownLimit and UpLimit cap download and upload bodies (bytes per
	// second), shared by every transfer of this client. nil = no limit.
	DownLimit *RateLimiter
	UpLimit   *RateLimiter
	// OpPollInterval is how often WaitOp asks about a queued operation. 0
	// means DefaultOpPollInterval.
	OpPollInterval time.Duration
}

Client is a thin REST client for one filex server. Token may be either a session token minted by /api/auth/login or a durable API token — the server accepts both on the Authorization: Bearer header.

func New

func New(conn Conn) *Client

New builds a Client from a resolved Conn. No global timeout is set — uploads/downloads stream arbitrarily large bodies; cancellation is the caller's context (Ctrl-C in the CLI).

func (*Client) AccountLanguage added in v0.54.0

func (c *Client) AccountLanguage(ctx context.Context) (string, error)

AccountLanguage is the interface language the signed-in account chose (GET /api/auth/me → user.locale), "" when it chose none. The sync engine's event stream (`filex sync run --json`) speaks it when nobody named a language, the way the server says a notification in the account's language when the request names none.

func (*Client) AllTags added in v0.50.0

func (c *Client) AllTags(ctx context.Context) ([]TagItem, []byte, error)

AllTags lists every tag the caller can see, both kinds (GET /api/files/manager/tags/all).

func (*Client) AppActions added in v0.50.0

func (c *Client) AppActions(ctx context.Context) ([]AppAction, []byte, error)

AppActions lists the actions the caller may run.

func (*Client) Changes

func (c *Client) Changes(ctx context.Context, remote, since string) (cursor string, changed bool, err error)

Changes asks whether anything under the folder remote changed since the cursor since (empty = never asked; always "changed"), and returns the cursor to send next time. One request, instead of listing the whole tree.

func (*Client) Copy added in v0.50.0

func (c *Client) Copy(ctx context.Context, src, dst string) (*Transfer, error)

Copy implements Unix-cp semantics with Move's target rules (no rename short-cut: a copy into its own folder under another name is a copy). A copy into a folder that already holds the name lands beside it as `name-copy.ext`; a copy to a taken full path is refused (ErrTargetExists). Folders are copied whole.

func (*Client) CreateArchive added in v0.50.0

func (c *Client) CreateArchive(ctx context.Context, dest string, sources []string, opt ArchiveCreateOptions) (*Op, []byte, error)

CreateArchive packs sources (files and folders, `adapter://path`) into a new archive at dest and waits for it. A dest that exists is refused (409 TARGET_EXISTS); nothing is overwritten.

func (*Client) Download

func (c *Client) Download(ctx context.Context, remote string, w io.Writer) (int64, error)

Download streams the remote file into w and returns the byte count.

⚠ Only a body that IS the file is ever copied: a 200, or a 206 answering this request's own `bytes=0-` with the whole object. Servers from v0.20 to v0.42 answered an unranged download of a big file on a slow storage with `202 {"state":"preparing",…}`, and the old check here — anything 2xx is the file — wrote that JSON to disk under the file's name. The sync engine then saw a locally edited file and uploaded the JSON over the real one: 45 files of 70–290 MB on one deployment, unrecoverable from version history.

`bytes=0-` is the request no server version answers with 202 (a Range comes from a client already committed to a body), so the CLI never waits on a server-side copy of a file it reads exactly once anyway. Any other 2xx is still refused, and nothing of it is written.

func (*Client) DownloadSized

func (c *Client) DownloadSized(ctx context.Context, remote string, w io.Writer, want int64) (int64, error)

DownloadSized is Download for a caller that knows how big the file is — the sync engine does, from the listing it planned against. want < 0 means unknown. A body of any other length is a *SizeMismatchError, which on its own would have caught the 202 above: the listing said 151,983,227 bytes and the body was 88.

func (*Client) E2EAllowed added in v0.51.0

func (c *Client) E2EAllowed(ctx context.Context, remote, kind string) (answer, reason string, err error)

E2EAllowed asks the server whether this account may start encrypting at remote, as kind: "folder" (encrypt the folder where it is), "new_folder" (make a new encrypted folder in it) or "file". answer is "allowed", "request" (an administrator's approval is needed first) or "denied"; reason names the layer that said no (tenant_disabled, policy_off, admins_only, permission, approval_required). A server older than the encryption policy has no such question (404) and answers "allowed": every write it takes is asked on its own anyway.

func (*Client) E2ECleanup added in v0.50.0

func (c *Client) E2ECleanup(ctx context.Context, remote string, versions, trash bool) (*E2ECleanupResult, error)

E2ECleanup removes what filex still holds from before a folder was encrypted: thumbnails and extracted search text always, and - when asked, by the folder's owner or an administrator - older versions and trash entries.

func (*Client) E2EEscrowKey added in v0.50.0

func (c *Client) E2EEscrowKey(ctx context.Context) (string, error)

E2EEscrowKey is the installation's escrow PUBLIC key (base64 SPKI), as /api/capabilities publishes it to the browser, or "" when escrow is off. A folder made while it is on gets a slot sealed to it, wherever it is made.

func (*Client) EditTags added in v0.50.0

func (c *Client) EditTags(ctx context.Context, nodeID int64, edit TagEdit) (*FileTags, error)

EditTags applies edit to the file's current tags and saves the result: the read-modify-write the explorer's tag picker does. Names compare without regard to case, as the server's do.

func (*Client) ExtractArchive added in v0.50.0

func (c *Client) ExtractArchive(ctx context.Context, path, dest, password string, members []string) (*Op, []byte, error)

ExtractArchive unpacks the archive at path into dest (a folder on the same storage; "" = the archive's own folder) and waits for it. members, given, extracts only those entries.

func (*Client) GetOp added in v0.50.0

func (c *Client) GetOp(ctx context.Context, id int64) (*Op, []byte, error)

GetOp reads one op (GET /api/files/ops/{id}); the answer is the op itself.

func (*Client) IsDir added in v0.50.0

func (c *Client) IsDir(ctx context.Context, remote string) (bool, error)

IsDir reports whether remote is a folder the caller can list (the storage root always is).

func (*Client) List

func (c *Client) List(ctx context.Context, remote string) (*ListResult, error)

List returns the directory listing at remote (`adapter://rel`). An empty remote asks the server for its default view — useful only for discovering the Storages slice (adapter names).

func (*Client) ListStats

func (c *Client) ListStats(ctx context.Context, remote string) ([]*Stat, error)

ListStats returns one directory's entries as stats.

func (*Client) Login

func (c *Client) Login(ctx context.Context, in LoginRequest) (*LoginResponse, error)

Login exchanges the credentials for a session token. Call on a token-less Client.

On a multi-tenant server a sign-in whose realm names a tenant with an address of its own is not given a session at the platform's address: the answer is a one-use handoff code for the tenant's address ({"handoff": {origin, code}}). Login follows it - it redeems the code there (POST /api/auth/handoff) and returns that session, with URL set to the tenant's address.

A realm nobody has is answered by the server exactly as a wrong password, and Login reports it as the same error.

func (*Client) Mkdir

func (c *Client) Mkdir(ctx context.Context, remote string) ([]byte, error)

Mkdir creates the folder named by the last segment of remote inside its parent (server verb: newfolder).

func (*Client) Move

func (c *Client) Move(ctx context.Context, src, dst string) (*Transfer, error)

Move implements Unix-mv semantics. dst may be an existing folder (the item moves into it, keeping its name - a name already taken there gets a free one beside it, as in the web explorer) or the item's full new path:

  • in the same folder of the same storage, a rename (the server's rename verb: a taken name is refused with 409 NAME_TAKEN, a case-only rename works);
  • anywhere else - another folder, another storage, or both - one queued move that also names the item, so nothing can stop half-way between a move and a rename. A taken target is refused before anything is sent (ErrTargetExists).

A move between two storages is the server's: it streams the bytes across and removes the source once they have arrived.

func (*Client) MyShares added in v0.50.0

func (c *Client) MyShares(ctx context.Context, activeOnly bool, limit, offset int) (*SharePage, error)

MyShares lists the links the caller created. activeOnly leaves out expired and used-up ones; limit 0 is the server's page (50, at most 500).

func (*Client) NodeID added in v0.50.0

func (c *Client) NodeID(ctx context.Context, remote string) (int64, error)

NodeID is the server's catalogue id of the file or folder at remote - what versions and tags are addressed by.

func (*Client) PluginRequests added in v0.49.0

func (c *Client) PluginRequests(ctx context.Context, status string) ([]PluginRequest, []byte, error)

PluginRequests lists the requests in one state: pending (""), approved, rejected, expired, superseded or all.

func (*Client) ReadRange

func (c *Client) ReadRange(ctx context.Context, remote string, off, length int64) ([]byte, error)

ReadRange fetches [off, off+length) of a remote file.

It returns the bytes actually read, which may be fewer at the end of the object. A server that answers 200 instead of 206 gets ErrNoRange rather than having its whole-object body treated as the window that was asked for.

func (*Client) ReadSmall added in v0.50.0

func (c *Client) ReadSmall(ctx context.Context, remote string, max int64) ([]byte, error)

ReadSmall downloads a small file whole: a key file, a name sidecar. More than max bytes is an error, not a truncation.

func (*Client) ReadWhole added in v0.54.0

func (c *Client) ReadWhole(ctx context.Context, remote string, limit int64) ([]byte, error)

ReadWhole downloads a file whole, refusing more than limit bytes (an index file: at most 64 MiB).

func (*Client) Remove

func (c *Client) Remove(ctx context.Context, remote string) ([]byte, error)

Remove sends the item to the server-side trash (server verb: delete — filex soft-deletes into `.filex-trash`, restorable from the panel).

func (*Client) Rename added in v0.50.0

func (c *Client) Rename(ctx context.Context, item RemotePath, newName string) error

Rename gives the item a new name in the same folder.

func (*Client) RequestPlugin added in v0.49.0

func (c *Client) RequestPlugin(ctx context.Context, in PluginRequestInput) (*PluginRequestAnswer, error)

RequestPlugin leaves a plugin install (or upgrade) request.

func (*Client) RestoreTrash added in v0.50.0

func (c *Client) RestoreTrash(ctx context.Context, id int64) ([]byte, error)

RestoreTrash puts one trash entry back where it was deleted from (POST /api/files/manager/restore). A path that is taken again answers 409 EXISTS and nothing moves.

func (*Client) RestoreVersion added in v0.50.0

func (c *Client) RestoreVersion(ctx context.Context, nodeID, versionID int64) ([]byte, error)

RestoreVersion makes version versionID the file's live content (POST /api/files/versions/restore). The server records the content it replaces as a version first, so a restore can itself be undone.

func (*Client) RunAction added in v0.50.0

func (c *Client) RunAction(ctx context.Context, plugin, action string, paths []string, params map[string]any) (*Op, []byte, error)

RunAction runs an app's action on paths (`adapter://path`, one storage) with params and waits for its job (POST /api/files/plugins/actions/{plugin}/{action}/run). The final op carries the files the action wrote (Op.Outputs).

params nil sends none: an action that asks for input in a form then answers ErrActionNeedsInput, and its fields are given as params.

func (*Client) Search

func (c *Client) Search(ctx context.Context, query, scope string, storageID int64, limit int) (*SearchResult, error)

Search queries the server-side index. scope is "name", "content" or "all" ("" = server default, all). storageID 0 searches every storage.

func (*Client) SearchFiltered added in v0.54.0

func (c *Client) SearchFiltered(ctx context.Context, query, scope string, storageID int64, limit int, filters url.Values) (*SearchResult, error)

SearchFiltered is Search with the server's narrowing (type, mime, modified_after, modified_before, min_size, max_size, under, not_under, owner, hidden - docs/SEARCH.md), applied on the server before the limit.

func (*Client) SetTags added in v0.50.0

func (c *Client) SetTags(ctx context.Context, nodeID int64, items []TagItem) (*FileTags, error)

SetTags makes the tags the caller can see on the file exactly items (POST /api/files/manager/tags with `items`). Tags the caller cannot see - another person's personal ones - are never touched.

func (*Client) Share

func (c *Client) Share(ctx context.Context, remote string, pin bool, expiresDays int) (*ShareResult, error)

Share mints a public download link for remote. pin=true asks the server to generate an unlock PIN (returned once, in PIN); expiresDays > 0 sets the expiry that many days from now.

func (*Client) SizeOf

func (c *Client) SizeOf(ctx context.Context, remote string) (int64, error)

SizeOf reads an object's length from a HEAD-shaped request.

It uses a one-byte ranged GET rather than HEAD: the manager endpoint answers HEAD, but a Content-Range header is the only place the TOTAL size appears in a partial response, and this is the same request the read path makes.

func (*Client) StatPath

func (c *Client) StatPath(ctx context.Context, remote string) (*Stat, error)

StatPath returns metadata for one path.

⚠ It answers from the PARENT's listing rather than a per-file endpoint, because the manager API has no stat verb: `action=index` on a file 404s. The listing is what the server already computes, so this costs one request and stays correct when a driver reports a size the database has not caught up with.

func (*Client) TaggedFiles added in v0.50.0

func (c *Client) TaggedFiles(ctx context.Context, tag, kind string, limit int) ([]TaggedFile, []byte, error)

TaggedFiles lists the files carrying tag, newest first (GET /api/files/manager/tagged). kind "" is both kinds.

func (*Client) TagsOf added in v0.50.0

func (c *Client) TagsOf(ctx context.Context, nodeID int64) (*FileTags, error)

TagsOf reads the tags on one file (GET /api/files/manager/tags?node_id=).

func (*Client) Trash added in v0.50.0

func (c *Client) Trash(ctx context.Context, storageID int64, limit, offset int) (*TrashPage, error)

Trash lists what the caller may see in the trash, newest first. storageID 0 is every storage; limit 0 is the server's default page (50, at most 500).

func (*Client) UILocale added in v0.54.0

func (c *Client) UILocale(ctx context.Context, code string) (map[string]string, error)

UILocale is the strings one language pack adds for code (GET /api/public/ui-locales/{code}): the interface's keys and the server's own `server.*` sentences, merged over every running pack. nil, nil when no running pack carries the language (404).

func (*Client) Unshare added in v0.50.0

func (c *Client) Unshare(ctx context.Context, id int64) ([]byte, error)

Unshare revokes the link with id: its URL stops working at once.

func (*Client) Upload

func (c *Client) Upload(ctx context.Context, localPath, remote string) (RemotePath, []byte, error)

Upload streams localPath to the server. remote may be a destination folder (`docs://reports/` — trailing slash or an existing dir) or a full target path (`docs://reports/renamed.pdf`); an existing remote folder wins, otherwise the last segment becomes the uploaded filename. Nothing is ever buffered in memory, and a large file is resumable — see uploadFile.

func (*Client) UploadTo

func (c *Client) UploadTo(ctx context.Context, localPath string, destDir RemotePath, name, expect string) ([]byte, error)

UploadTo sends localPath to destDir/name with no destination probing and an optional overwrite precondition — the call `filex sync` makes.

expect is "" (no precondition), "none" (the target must not exist yet) or "<size>:<last_modified-ms>" (the target must still be exactly what a listing reported). A refusal comes back as ErrPreconditionFailed. An older server ignores the field and writes unconditionally — no worse than before.

⚠ No remoteIsDir probe, unlike Upload: the caller already knows the target is a file path, and the probe is a full listing round-trip in front of every file — on a server behind a CDN proxy (~0.35 s per request, measured) that was a third of the time a one-line edit took to leave the machine.

func (*Client) UploadTree

func (c *Client) UploadTree(ctx context.Context, localDir, remote string, progress TreeProgress) (*TreeReport, error)

UploadTree uploads localDir recursively into remote. Empty local folders are created remotely too. Per-item failures accumulate in the report (a failed mkdir skips its subtree); only pre-flight problems (bad remote path, unreadable root) and context cancellation return a non-nil error. A plain file with -r degrades to a single upload, matching `cp -r` behavior.

func (*Client) VaultBreak added in v0.54.0

func (c *Client) VaultBreak(ctx context.Context, remote string) error

VaultBreak ends someone else's lock (the folder's owner, an administrator).

func (*Client) VaultDelete added in v0.54.0

func (c *Client) VaultDelete(ctx context.Context, remote, token string, packs []string, indexes []uint64) (int, int, error)

VaultDelete deletes packs (hex ids) and index files (generations) for good. At most 1 000 names per call; a missing one counts as deleted.

func (*Client) VaultIdleMinutes added in v0.54.0

func (c *Client) VaultIdleMinutes(ctx context.Context) (int, error)

VaultIdleMinutes is the person's idle time before a write lock ends.

func (*Client) VaultList added in v0.54.0

func (c *Client) VaultList(ctx context.Context, remote, kind, after string, limit int) ([]VaultListItem, string, error)

VaultList is one page of GET /list; kind is "index" or "pack". next is the cursor of the following page, "" at the end.

func (*Client) VaultListAll added in v0.54.0

func (c *Client) VaultListAll(ctx context.Context, remote, kind string) ([]VaultListItem, error)

VaultListAll pages through GET /list.

func (*Client) VaultLock added in v0.54.0

func (c *Client) VaultLock(ctx context.Context, remote, client, label string) (*VaultLock, error)

VaultLock takes the write lock. client is "cli" or "mount"; label is a short free text naming this machine. A lock someone else holds is a *VaultError VAULT_LOCKED with the holder.

func (*Client) VaultPutIndex added in v0.54.0

func (c *Client) VaultPutIndex(ctx context.Context, remote, token string, gen uint64, file []byte) (uint64, error)

VaultPutIndex commits the index file of generation gen (latest + 1, or VAULT_GENERATION with the latest).

func (*Client) VaultPutPack added in v0.54.0

func (c *Client) VaultPutPack(ctx context.Context, remote, token, id string, pack []byte) error

VaultPutPack stores a pack (created, never replaced: VAULT_PACK_EXISTS). id is 32 lower-case hex digits.

func (*Client) VaultRelease added in v0.54.0

func (c *Client) VaultRelease(ctx context.Context, remote, token string) error

VaultRelease gives the lock back (whether or not the token still held it).

func (*Client) VaultRenew added in v0.54.0

func (c *Client) VaultRenew(ctx context.Context, remote, token string, active bool) (*VaultRenewal, error)

VaultRenew is the heartbeat; active says this session is about to write. A lock that ended is a *VaultError VAULT_LOCK_LOST with its reason.

func (*Client) VaultState added in v0.54.0

func (c *Client) VaultState(ctx context.Context, remote string) (*VaultState, error)

VaultState asks for a vault's id, pack size, latest generation and lock.

func (*Client) Versions added in v0.50.0

func (c *Client) Versions(ctx context.Context, nodeID int64) (*VersionList, error)

Versions lists the recorded versions of the file with catalogue id nodeID (NodeID resolves a path).

func (*Client) WaitOp added in v0.50.0

func (c *Client) WaitOp(ctx context.Context, id int64) (*Op, []byte, error)

WaitOp follows an op until it is finished and returns its final row. An op that did not end `ok` is an *OpError (the row is returned as well). A cancelled ctx stops the waiting, not the op: the server goes on with it.

func (*Client) WriteSmall added in v0.50.0

func (c *Client) WriteSmall(ctx context.Context, destDir RemotePath, name string, data []byte, expect string) error

WriteSmall uploads data as destDir/name (a key file, a name sidecar), with the overwrite precondition expect ("" none, "none" must not exist).

type Conn

type Conn struct {
	URL   string
	Token string
	// Realm is the realm saved with URL in the config file ("" when the
	// resolved URL is another server's, or none was saved).
	Realm string
	// SavedURL is set when the config file holds a session that was NOT used
	// because it belongs to another address (this one): the caller says so
	// instead of a bare "no token".
	SavedURL string
}

Conn is the resolved server coordinate a client command runs against.

func Resolve

func Resolve(flagURL, flagToken, cfgPath string, getenv func(string) string) (Conn, error)

Resolve merges connection settings by precedence:

--url/--token flags  >  FILEX_URL/FILEX_TOKEN env  >  config file.

⚠ What the config file holds belongs to the address it was saved with - the session token above all: a --url or FILEX_URL that names another address gets neither the saved token nor the saved realm (sameServer). Sending it there would hand one server's session to another. A token given on purpose (--token, FILEX_TOKEN) goes to whichever address it is given with.

getenv is injected so tests don't have to mutate the process env.

type E2ECleanupResult added in v0.50.0

type E2ECleanupResult struct {
	VersionsDeleted   int `json:"versions_deleted"`
	TrashPurged       int `json:"trash_purged"`
	ThumbnailsDropped int `json:"thumbnails_dropped"`
	IndexCleared      int `json:"index_cleared"`
}

E2ECleanupResult is what POST /api/files/e2e/cleanup removed.

type FileConfig

type FileConfig struct {
	URL   string `yaml:"url"`
	Token string `yaml:"token"`
	// Realm is the tenant realm the session was signed in to, on a
	// multi-tenant server; it belongs to URL and is used again only by a
	// login at that address (Resolve). Omitted when empty, so a file without
	// one reads the same to an older CLI.
	Realm string `yaml:"realm,omitempty"`
}

FileConfig is the on-disk shape of ~/.filex/cli.yaml. It carries a bearer token, so SaveFileConfig always writes it owner-only (0600).

func LoadFileConfig

func LoadFileConfig(path string) (FileConfig, error)

LoadFileConfig reads the CLI config. A missing file is not an error — it just yields the zero config so flags/env can still win.

type FileTags added in v0.50.0

type FileTags struct {
	NodeID      int64     `json:"node_id"`
	Items       []TagItem `json:"items"`
	CanEditTeam bool      `json:"can_edit_team"`
	Raw         []byte    `json:"-"`
}

FileTags is a file's tags as the caller can see them.

type ListEntry

type ListEntry struct {
	// ID is the server's catalogue id (versions and tags are addressed by it);
	// 0 when the folder was listed straight from the storage, before a scan
	// catalogued it.
	ID           int64  `json:"id,omitempty"`
	Path         string `json:"path"`
	Basename     string `json:"basename"`
	Type         string `json:"type"` // "file" | "dir"
	Extension    string `json:"extension"`
	Size         int64  `json:"size"`
	MimeType     string `json:"mime_type"`
	LastModified int64  `json:"last_modified"` // Unix millis; 0 = unknown
	// E2E is true on a folder that is an end-to-end encrypted folder (it
	// holds a key file).
	E2E bool `json:"e2e,omitempty"`
}

ListEntry is one row of a directory listing (server FileNode shape).

type ListResult

type ListResult struct {
	Adapter  string      `json:"adapter"`
	Storages []string    `json:"storages"`
	Dirname  string      `json:"dirname"`
	ReadOnly bool        `json:"read_only"`
	Files    []ListEntry `json:"files"`
	// E2ERoot is the encrypted folder the listed folder is in, or at the
	// root of (adapter://rel); "" when it is in none.
	E2ERoot string `json:"e2e_root,omitempty"`
	Raw     []byte `json:"-"`
}

ListResult is GET /api/files/manager?action=index.

type LoginRequest added in v0.50.0

type LoginRequest struct {
	// Email is what the person signs in with: an e-mail address, or a user
	// name (the server resolves both).
	Email    string
	Password string
	// TOTP is the second-factor code, for accounts with TOTP enabled.
	TOTP string
	// Realm names the tenant on a multi-tenant server (docs/MULTI-TENANCY.md,
	// Realms): "" is the tenant the server's address names, else the
	// platform's own accounts. Sent only when set; a single-tenant server - or
	// an older one - does not read it.
	Realm string
}

LoginRequest is what a password sign-in sends to POST /api/auth/login.

type LoginResponse

type LoginResponse struct {
	Token string `json:"token"`
	// URL is the address the session belongs to: the server signed in to, or -
	// when that server handed the sign-in to the tenant's own address - that
	// address, which later commands should use.
	URL string `json:"-"`
	// HandedOff reports that the server answered with a handoff and the session
	// was opened at URL, the tenant's own address.
	HandedOff bool   `json:"-"`
	Raw       []byte `json:"-"`
}

LoginResponse is the subset of the sign-in answer the CLI needs.

type MyShare added in v0.50.0

type MyShare struct {
	Share struct {
		ID            int64      `json:"id"`
		Token         string     `json:"token"`
		Kind          string     `json:"kind"` // download | drop
		HasPin        bool       `json:"has_pin"`
		ExpiresAt     *time.Time `json:"expires_at,omitempty"`
		MaxDownloads  *int       `json:"max_downloads,omitempty"`
		DownloadCount int        `json:"download_count"`
		CreatedAt     time.Time  `json:"created_at"`
	} `json:"share"`
	NodePath    string `json:"node_path,omitempty"`
	StorageName string `json:"storage_name,omitempty"`
	URL         string `json:"url,omitempty"`
}

MyShare is one link the caller created.

func (MyShare) Location added in v0.50.0

func (s MyShare) Location() string

Location is the shared item's `adapter://path`.

type Op added in v0.50.0

type Op struct {
	ID      int64      `json:"id"`
	Kind    string     `json:"kind"`
	Status  string     `json:"status"` // pending | running | ok | failed | partial | cancelling | cancelled
	Total   int        `json:"total"`
	Done    int        `json:"done"`
	Failed  int        `json:"failed"`
	Error   string     `json:"error,omitempty"`
	Message string     `json:"message,omitempty"`
	Outputs []OpOutput `json:"outputs,omitempty"`
	// ErrorCode / ErrorText are the server's code for a failure and its
	// sentence in the reader's language (internal/apierr): the CLI prints the
	// sentence; Error is the English detail.
	ErrorCode string `json:"error_code,omitempty"`
	ErrorText string `json:"error_text,omitempty"`
}

Op is one job of the operations queue, the fields the CLI reads.

func (*Op) Finished added in v0.50.0

func (o *Op) Finished() bool

Finished reports whether the op has reached a final state.

type OpError added in v0.50.0

type OpError struct {
	Op *Op
}

OpError is an op that ended any other way than `ok`: failed, partly done or cancelled. Op carries the final row.

func (*OpError) Error added in v0.50.0

func (e *OpError) Error() string

type OpOutput added in v0.50.0

type OpOutput struct {
	Path string `json:"path"`
}

OpOutput is a file an app's action wrote (`adapter://path` or a storage-relative path, as the server reports it).

type PluginRequest added in v0.49.0

type PluginRequest struct {
	ID           int64     `json:"id"`
	Kind         string    `json:"kind"`
	Op           string    `json:"op"`
	Name         string    `json:"name"`
	Version      string    `json:"version"`
	FromVersion  string    `json:"from_version"`
	SHA256       string    `json:"sha256"`
	Permissions  []string  `json:"permissions"`
	Requester    string    `json:"requester"`
	TokenLabel   string    `json:"token_label"`
	Reason       string    `json:"reason"`
	Status       string    `json:"status"`
	DecisionNote string    `json:"decision_note"`
	ExpiresAt    time.Time `json:"expires_at"`
	CreatedAt    time.Time `json:"created_at"`
}

PluginRequest is one request as the server answers it.

type PluginRequestAnswer added in v0.49.0

type PluginRequestAnswer struct {
	Request PluginRequest `json:"request"`
	Created bool          `json:"created"`
	Message string        `json:"message"`
	Raw     []byte        `json:"-"`
}

PluginRequestAnswer is POST /api/admin/plugin-requests' answer. Created is false when a request for the same source was already waiting (that one is answered).

type PluginRequestInput added in v0.49.0

type PluginRequestInput struct {
	Kind        string `json:"kind"`
	Op          string `json:"op,omitempty"`
	Name        string `json:"name,omitempty"`
	PluginID    int64  `json:"plugin_id,omitempty"`
	GitHubRepo  string `json:"github_repo,omitempty"`
	Ref         string `json:"ref,omitempty"`
	ManifestURL string `json:"manifest_url,omitempty"`
	URL         string `json:"url,omitempty"`
	SHA256      string `json:"sha256,omitempty"`
	Source      string `json:"source,omitempty"`
	Reason      string `json:"reason"`
}

PluginRequestInput is a request to leave: the plugin's kind (app | storage), the operation (install | upgrade), where it comes from and why.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter caps a direction of traffic at a number of bytes per second.

One limiter is shared by every transfer of that direction, so the four parallel transfers of a sync run TOGETHER stay under the limit — which is the point: a nine-hour first sync saturated a server's ~18 Mbit line and everyone else using that server slowed down, and `--transfers` only changes how many files move at once, not how fast.

It paces the bodies, never the connection: HTTP/2 health pings and window updates share the connection, and a throttled connection would delay them past the client's own dead-connection timeout.

A token bucket holding one second's worth of bytes; a stream that runs ahead goes into debt and sleeps it off, so concurrent streams share the rate fairly. A nil *RateLimiter never waits.

func NewRateLimiter

func NewRateLimiter(bytesPerSecond int64) *RateLimiter

NewRateLimiter returns a limiter for bytesPerSecond, or nil (no limit) for zero or less.

func (*RateLimiter) Reader

func (l *RateLimiter) Reader(ctx context.Context, r io.Reader) io.Reader

Reader paces r through the limiter. A nil limiter returns r itself.

func (*RateLimiter) WaitN

func (l *RateLimiter) WaitN(ctx context.Context, n int) error

WaitN takes n bytes' worth from the bucket, sleeping as long as that puts the bucket in debt. It returns early with the context's error.

type RemotePath

type RemotePath struct {
	Adapter string
	Rel     string
}

RemotePath is a parsed `adapter://relative/path` reference. Adapter is the storage name on the server; Rel is the slash-separated relative path inside it ("" = storage root).

func ParseRemotePath

func ParseRemotePath(raw string) (RemotePath, error)

ParseRemotePath validates and splits an `adapter://rel/path` argument. The adapter prefix is mandatory — a bare relative path is ambiguous when the server hosts several storages. `..` segments are rejected before they ever reach the wire.

func (RemotePath) Base

func (p RemotePath) Base() string

Base returns the last path segment ("" at the storage root).

func (RemotePath) Dir

func (p RemotePath) Dir() RemotePath

Dir returns the parent directory (the storage root stays the root).

func (RemotePath) IsRoot

func (p RemotePath) IsRoot() bool

IsRoot reports whether the path points at the storage root.

func (RemotePath) Join

func (p RemotePath) Join(name string) RemotePath

Join appends one segment to the path.

func (RemotePath) String

func (p RemotePath) String() string

String renders the wire form `adapter://rel` (root: `adapter://`).

type SearchHit

type SearchHit struct {
	ID        int64  `json:"id"`
	StorageID int64  `json:"storage_id"`
	Name      string `json:"name"`
	Path      string `json:"path"`
	Type      string `json:"type"`
	Size      int64  `json:"size"`
	Snippet   string `json:"snippet"`
	Matched   string `json:"matched"` // "name" | "content" | "both"
}

SearchHit is one result row from /api/files/search (node fields plus the v0.2 content-search additions).

type SearchResult

type SearchResult struct {
	Results []SearchHit `json:"results"`
	// Truncated: more matched than came back. Total is how many matched,
	// exact unless Truncated (then a lower bound).
	Truncated bool   `json:"truncated"`
	Total     int    `json:"total"`
	Raw       []byte `json:"-"`
}

SearchResult is the /api/files/search envelope.

type SharePage added in v0.50.0

type SharePage struct {
	Items []MyShare `json:"items"`
	Total int64     `json:"total"`
	Raw   []byte    `json:"-"`
}

SharePage is one page of the caller's links, newest first.

type ShareResult

type ShareResult struct {
	URL       string     `json:"url"`
	Token     string     `json:"token"`
	HasPin    bool       `json:"has_pin"`
	PIN       string     `json:"password_pin"` // only set when the server generated one
	ExpiresAt *time.Time `json:"expires_at"`
	Raw       []byte     `json:"-"`
}

ShareResult is the nested `share` object of POST /api/files/share.

type SizeMismatchError

type SizeMismatchError struct {
	Remote string
	Want   int64 // what the listing said
	Got    int64 // what the server declared or sent
}

SizeMismatchError is a download whose body is not the length the caller's listing reported. When the server DECLARES the other length up front (Content-Length, or the total of a Content-Range) not one byte reaches the writer; a body that only turns out short or long at its end has reached it, and the caller — which writes to a temporary file — throws it away.

func (*SizeMismatchError) Error

func (e *SizeMismatchError) Error() string

type Stat

type Stat struct {
	Name    string
	Size    int64
	IsDir   bool
	ModTime time.Time
	Mime    string
}

Stat describes one remote entry.

type StreamState

type StreamState string

StreamState is what the stream can honestly say about itself.

const (
	// StreamLive: subscribed; changes arrive as they happen.
	StreamLive StreamState = "connected"
	// StreamPolling: the server cannot announce changes (older than the
	// `watch` subscription, or realtime is switched off). Only the interval
	// poll finds them.
	StreamPolling StreamState = "polling"
	// StreamOffline: the server could not be reached; retrying with backoff.
	StreamOffline StreamState = "offline"
)

type TagEdit added in v0.50.0

type TagEdit struct {
	Add    []string
	Remove []string
	// Kind is the kind an added name gets: TagPersonal (default) or TagTeam.
	// A removal with Kind set removes only that kind; "" removes the name in
	// either kind.
	Kind string
}

TagEdit is one change to a file's tags: names to add (with the kind a new one gets) and names to remove.

type TagItem added in v0.50.0

type TagItem struct {
	Name string `json:"name"`
	Kind string `json:"kind"`
}

TagItem is one tag as the caller sees it.

type TaggedFile added in v0.50.0

type TaggedFile struct {
	ID      int64  `json:"id"`
	Storage string `json:"storage"`
	Path    string `json:"path"`
	Name    string `json:"name"`
	Type    string `json:"type"`
	Size    int64  `json:"size"`
}

TaggedFile is one file carrying a tag.

func (TaggedFile) Location added in v0.50.0

func (f TaggedFile) Location() string

Location is the file's `adapter://path`.

type Transfer added in v0.50.0

type Transfer struct {
	// Into is true when the target was a folder the item went into under its
	// own name. A name already taken there makes the server pick a free one
	// beside it (`a-copy.txt`), so To is then where the item WOULD be.
	Into bool
	// Folder is the folder the item went into.
	Folder RemotePath
	// To is the item's path: the full path the target named, or Folder joined
	// with the item's own name.
	To RemotePath
	// Op is the queued operation's final row; nil for a rename in place.
	Op *Op
	// Raw is the server's last answer (the op's final row).
	Raw []byte
}

Transfer is what one copy or move did.

type TrashEntry added in v0.50.0

type TrashEntry struct {
	ID            int64     `json:"id"`
	StorageID     int64     `json:"storage_id"`
	StorageName   string    `json:"storage_name,omitempty"`
	Path          string    `json:"path"`
	Name          string    `json:"name"`
	Size          int64     `json:"size"`
	DeletedAt     time.Time `json:"deleted_at"`
	DeletedByName string    `json:"deleted_by_name,omitempty"`
	DeletedBySelf bool      `json:"deleted_by_self,omitempty"`
	Draft         bool      `json:"draft,omitempty"`
	// E2eRoot is the end-to-end encrypted folder the item was deleted from;
	// its name may then be ciphertext.
	E2eRoot string `json:"e2e_root,omitempty"`
}

TrashEntry is one item in the trash (GET /api/files/manager/trash). ID is the id a restore names.

func (TrashEntry) Location added in v0.50.0

func (e TrashEntry) Location() string

Location is the entry's original place as `adapter://path`.

type TrashPage added in v0.50.0

type TrashPage struct {
	Entries []TrashEntry `json:"entries"`
	Total   int          `json:"total"`
	Limit   int          `json:"limit"`
	Offset  int          `json:"offset"`
	Raw     []byte       `json:"-"`
}

TrashPage is one page of the caller's trash.

type TreeChange

type TreeChange struct {
	Root     string
	Dirs     []string
	Overflow bool
	Action   string
	Name     string
}

TreeChange is one `tree_change` frame: something changed at or below Root. Dirs are relative to Root ("" = Root itself). Overflow means too many folders changed to name them — treat the whole root as changed.

type TreeError

type TreeError struct {
	Local string
	Err   error
}

TreeError is one failed item of a recursive upload.

type TreeEvent

type TreeEvent struct {
	Kind   string     // TreeDir | TreeFile | TreeSymlink | TreeSpecial | TreeErr
	Local  string     // local path of the item
	Remote RemotePath // resolved remote target (zero for symlink/local errors)
	Err    error      // set when Kind == TreeErr
}

TreeEvent is one progress notification from UploadTree.

type TreeProgress

type TreeProgress func(ev TreeEvent)

TreeProgress receives one call per processed item during UploadTree — the CLI streams these as they happen. May be nil.

type TreeReport

type TreeReport struct {
	Files    int         // files uploaded successfully
	Dirs     int         // remote directories ensured (created or pre-existing)
	Symlinks []string    // local symlinks skipped (never followed)
	Special  []string    // local named pipes, sockets and devices skipped (never opened)
	Errors   []TreeError // items that failed (upload, mkdir, walk)
}

TreeReport summarizes a finished UploadTree run. Errors are per-item: the walk keeps going past a failed file, and a failed mkdir skips just that subtree.

type VaultError added in v0.54.0

type VaultError struct {
	Status  int
	Code    string
	Message string
	// Holder, Since and RetryAfter come with VAULT_LOCKED.
	Holder     *VaultHolder
	Since      time.Time
	RetryAfter int
	// Reason comes with VAULT_LOCK_LOST: expired, idle, broken, released, taken.
	Reason string
	// Latest comes with VAULT_GENERATION.
	Latest uint64
}

VaultError is an error answer of the vault API.

func (*VaultError) Error added in v0.54.0

func (e *VaultError) Error() string

type VaultHolder added in v0.54.0

type VaultHolder struct {
	Name   string `json:"name"`
	Client string `json:"client"`
	Label  string `json:"label"`
}

VaultHolder is who holds a vault's write lock.

func (VaultHolder) String added in v0.54.0

func (h VaultHolder) String() string

String is "Ayşe (web, Firefox, ofis)".

type VaultListItem added in v0.54.0

type VaultListItem struct {
	Generation uint64    `json:"generation"`
	ID         string    `json:"id"`
	Size       int64     `json:"size"`
	MTime      VaultTime `json:"mtime"`
}

VaultListItem is one row of GET /list: an index file (Generation) or a pack (ID, 32 lower-case hex digits).

type VaultLock added in v0.54.0

type VaultLock struct {
	Token        string    `json:"token"`
	Generation   uint64    `json:"generation"`
	LeaseSeconds int       `json:"lease_seconds"`
	IdleSeconds  int       `json:"idle_seconds"`
	ExpiresAt    VaultTime `json:"expires_at"`
}

VaultLock is a taken write lock.

type VaultLockInfo added in v0.54.0

type VaultLockInfo struct {
	Holder    VaultHolder `json:"holder"`
	Since     VaultTime   `json:"since"`
	ExpiresAt VaultTime   `json:"expires_at"`
	Mine      bool        `json:"mine"`
}

VaultLockInfo is `state`'s lock.

type VaultRenewal added in v0.54.0

type VaultRenewal struct {
	ExpiresAt VaultTime `json:"expires_at"`
	IdleUntil VaultTime `json:"idle_until"`
}

VaultRenewal is POST /lock/renew.

type VaultState added in v0.54.0

type VaultState struct {
	VaultID    string         `json:"vault_id"`
	PackLog2   int            `json:"pack_log2"`
	Generation uint64         `json:"generation"`
	Lock       *VaultLockInfo `json:"lock"`
}

VaultState is GET /state.

type VaultTime added in v0.54.0

type VaultTime struct{ time.Time }

VaultTime is a time the vault API sends: RFC 3339, or milliseconds since 1970 as a number; null is the zero time.

func (*VaultTime) UnmarshalJSON added in v0.54.0

func (t *VaultTime) UnmarshalJSON(b []byte) error

UnmarshalJSON reads either form.

type Version added in v0.50.0

type Version struct {
	ID        int64     `json:"id"`
	NodeID    int64     `json:"node_id"`
	VersionN  int       `json:"version_n"`
	Size      int64     `json:"size"`
	CreatedAt time.Time `json:"created_at"`
}

Version is one recorded version of a file (GET /api/files/versions).

type VersionList added in v0.50.0

type VersionList struct {
	NodeID   int64     `json:"node_id"`
	Versions []Version `json:"versions"`
	Raw      []byte    `json:"-"`
}

VersionList is a file's version timeline.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL