20 State
(require denxi/state) | package: denxi |
Denxi’s state consists of the contents of a filesystem directory, and a database in that directory. denxi/state encapsulates state I/O.
Denxi implicitly trusts a state, so directly tampering with state is a bad idea. Only use denxi/state to interact with a state.
20.1 Workspaces
A workspace is a directory on the filesystem that holds a database and all installed software. A workspace’s path must match the workspace-directory/c contract. A target workspace is the directory referenced by the value of (DENXI_WORKSPACE). Target workspaces are affected by all filesystem writes in a Denxi process.
value
=
(and/c complete-path? (or/c directory-exists? (and/c (not/c file-exists?) (not/c directory-exists?) (not/c link-exists?))))
That is, a complete path that either refers to an existing directory, or a location on the filesystem where nothing exists.
setting
The directory in which Denxi reads and writes files. If the directory does not exist, then it will be created when Denxi writes a file.
Defaults to (build-path (find-system-path 'home-dir) ".denxi").
procedure
(build-workspace-path path-element) → complete-path?
path-element : (and/c path-string? (not/c complete-path?))
procedure
(call-with-ephemeral-workspace proc) → any
proc : (-> path? any)
procedure
(path-in-workspace? path) → boolean?
path : path-string?
procedure
(make-addressable-file transfer-name in est-size #:on-status on-status #:max-size max-size #:buffer-size buffer-size #:timeout-ms timeout-ms [ #:cache-key cache-key]) → path-record? transfer-name : non-empty-string? in : input-port? est-size : (or/c +inf.0 exact-positive-integer?) on-status : (-> $message? any) max-size : (or/c +inf.0 exact-positive-integer?) buffer-size : exact-positive-integer? timeout-ms : (>=/c 0) cache-key : (or/c bytes? #f) = #f
Effect: Atomically create OR reuse a file in the workspace.
If cache-key is not #f, then make-addressable-file first attempts to find an existing path-record using cache-key. If one is found, make-addressable-file will return that record.
In the event of a cache miss, a file is created under the context of several safety limits. An output port to-file is then given bytes under the context of
(transfer in to-file #:on-status on-status #:transfer-name transfer-name #:max-size max-size #:buffer-size buffer-size #:timeout-ms timeout-ms #:est-size est-size)
In the event of any error, the created file is deleted if it exists, and the database will not be affected. If the file is created successfully, then the database will gain a path-record. If cache-key is not #f, the database will also gain a path-key-record.
procedure
(make-addressable-directory directory) → path-record?
directory : directory-exists?
Effect: Atomically move directory to a path named after the Base32 encoding of a digest. The digest is created using the directory’s own name and contents, discovered recursively. The database will gain a record of the new directory path, which will be returned.
Warning: Any digests that are already computed for a path are preferred over creating new digests from contents on disk. In the event a file is directly modified in the workspace, a digest for a directory containing that file may be incorrect.
procedure
(make-addressable-link target-path-record link-path) → path-record? target-path-record : path-record? link-path : path-string?
Effect: Creates the directory path before the file name in link-path (if necessary), then creates a link at link-path. The net operation is performed non-atomically.
The target path must come from a valid path-record because the links may only point to files and directories created by Denxi.
20.2 Databases
Each workspace may contain a database as a SQLite file called db. The database tracks the other contents of a workspace, and holds the state of any caches.
20.2.1 Record Types
This section covers the Racket structures representing parsed records in a relation. To inspect the underlying database schema, run sqlite3 ./path/to/db in your shell, then .schema in SQLite’s REPL.
struct
(struct record (id) #:extra-constructor-name make-record #:transparent) id : (or/c exact-positive-integer? #f sql-null?)
struct
(struct provider-record record (name))
name : non-empty-string?
struct
(struct package-record record (name provider-id))
name : non-empty-string? provider-id : exact-positive-integer?
struct
(struct edition-record record (name package-id))
name : non-empty-string? package-id : exact-positive-integer?
struct
(struct revision-record record (number edition-id))
number : revision-number? edition-id : exact-positive-integer?
number is a revision number, not a record-id. edition-id is a record-id for an edition-record.
struct
(struct revision-name-record record (name revision-id))
name : non-empty-string? revision-id : exact-positive-integer?
name need not be unique in the relation.
revision-id is unique, and represents the record-id of a revision-record, not a revision number.
struct
(struct path-record record (path digest target-id) #:extra-constructor-name make-path-record) path : (and/c path-string? (not/c complete-path?)) digest : bytes? target-id : exact-positive-integer?
(build-workspace-path path) exists.
The file, directory, or link referenced by path was created by a Denxi process.
The digest was computed in terms of the contents of the corresponding file or directory.
If path refers to a link, then target-id matches the path-record for the target of the link.
A path-record has many path-key-records.
struct
(struct path-key-record record (key path-id))
key : bytes? path-id : exact-positive-integer?
key must be unique.
path-id is a record-id of a path-record.
struct
(struct output-record record (revision-id path-id name))
revision-id : exact-positive-integer? path-id : exact-positive-integer? name : non-empty-string?
path-id refers to the path-record for the package output. It must be unique.
name refers to the name of the output. It need not be unique.
20.2.2 High-Level Queries
procedure
A record-id for a provider name
A provider name
A record-id for a package name
A package name
A record-id for an edition name
An edition name
A record-id for a revision number
A revision number
A record-id for a package output
A package output name
A record-id for a path
A path
The values are always correct together, but do not guarentee the existence of a path on disk. Ignoring the record-ids, each set of values represents a path to a named package output for a specific revision, edition, package, and provider.
procedure
(declare-link link-path target-record) → path-record?
link-path : path-string? target-record : path-record?
After using declare-link, the path in target-record becomes eligible for garbage collection when (not (link-exists? link-path)).
procedure
(declare-output provider-name package-name edition-name revision-number revision-names output-name output-path-record) → output-record? provider-name : non-empty-string? package-name : non-empty-string? edition-name : non-empty-string? revision-number : revision-number? revision-names : (listof non-empty-string?) output-name : string? output-path-record : path-record?
procedure
(find-path-record hint) → (or/c path-record? #f)
hint : any/c
The database query used depends on the type of hint.
exact-positive-integer?: Use hint as an expected primary key value.
path-string?: Use hint as an expected value for a path field in the database.
bytes?: Use hint as an expected value for a digest field in the database.
Anything else: Ignore hint and return #f.
procedure
(call-with-reused-output query output-name continue) → any query : package-query-variant? output-name : string? continue : (-> (or/c #f exn? output-record?) any)
If variant is #f, then the named output is not installed.
If variant is an output-record?, then the named output is installed with that information.
If variant is an exn?, then an error was encountered in preparing or executing a SQL query.
procedure
(in-denxi-objects query) →
(sequence/c path-string? exact-positive-integer? revision-number? exact-positive-integer? path-string?) query : package-query-variant?
An output name
A record-id for a revision number
A record-id for a path
A relative path (w.r.t. the workspace containing the database) to a directory containing package output
procedure
(in-denxi-outputs query output-name)
→ (sequence/c output-record?) query : package-query-variant? output-name : string?
procedure
(start-transaction!) →
(-> void?) (-> void?)
Effect: start-transaction! instructs SQLite to start a transaction against the database. If a transaction is already underway, start-transaction! will handle the corresponding error from SQLite by using the existing transaction.
The first returned procedure ends the transaction, atomically committing any changes to the database.
The second returned procedure rolls back the transaction, atomically undoing any changes to the database.
Failure to call either procedure will leave the transaction open. For safety, use the returned procedures such that the program cannot help but invoke one.
procedure
(build-object-path path-el ...) → complete-path?
path-el : (and/c path-string? (not/c complete-path?))
procedure
(build-addressable-path digest) → complete-path?
digest : bytes?
procedure
The first is a workspace-relative path to a symbolic link created by Denxi.
The second is a workspace-relative path to the file referenced by the link.
20.3 Garbage Collection
struct
(struct $finished-collecting-garbage $message (bytes-recovered) #:prefab) bytes-recovered : exact-nonnegative-integer?
procedure
Returns the estimated number of bytes recovered from disk. Empty directories and links are assumed to have no size.
20.4 Content Addressing
Denxi addresses arbitrary data using digests. One may override how data appears when computing new addresses.
procedure
(make-content-address path) → bytes?
path : (or/c directory-exists? file-exists? link-exists?)
The digest always uses (get-default-chf), but the bytes used to produce the digest depend on current-content-scanner.
value
: (parameter/c (-> path-string? input-port?))
The procedure must accept a path-string? and return an input port. The input port yields bytes that act as representitive content for whatever exists at that location.
Defaults to scan-all-filesystem-content.
procedure
(scan-all-filesystem-content path) → input-port?
path : path-string?
Warning: Large directories and files may take a long time to process. Only use when change detection is more important than performance.
Note: When this definition refers to a “name”, it means the bytes in (string->bytes/utf-8 (~a (file-name-from-path path))).
If (link-exists? path), then return a port that yields bytes from the link’s name. The link is not followed.
If (file-exists? path), then return a port that yields bytes from the file’s name, permissions, and contents.
If (directory-exists? path), then return a port that yields bytes from the directory’s name, permissions, and contents. Contents are processed recursively without tail-call optimization.
Otherwise, raise ($no-content-to-address path).
struct
(struct $no-content-to-address $message (path))
path : path-string?