File Manager
OcaltQL gives full programmatic control over your namespace's filesystem. Every path used with these verbs must start with /root, /mounted, or /external when a server is bound — see Namespace Storage for how those are structured.
Reading and writing
FILE WRITE "Hello World" TO "/root/hello.txt"
(* FILE WRITE "/root/hello.txt" CONTENT "Hello World". Also works *)
AFTER FILE READ "/root/hello.txt" SET ?content
AFTER EMIT ?content
Binary is bytes, not base64
A binary file read out of the namespace is a bytes value - the actual bytes, the way open(f, "rb").read() gives you bytes in Python or file_get_contents does in PHP. It is not base64, and it is not text that happens to look like data.
Bytes go where bytes belong with no ceremony: written back out with FILE WRITE, sent to the response with EMIT, measured with LENGTH, indexed a byte at a time. Base64 is a transport - what you convert to when a JSON field or a URL cannot carry binary - and it appears only because you asked for it.
FILE READ "/root/report.pdf" SET ?bytes
AFTER LENGTH ?bytes SET ?n
AFTER HEADER "Content-Type" AS "application/pdf"
AFTER EMIT ?bytes
(* The response IS the PDF *)
Writing binary - the BASE64 modifier
BASE64 tells FILE WRITE that the content it has been handed is already base64, and that the decoded bytes are what should land on disk. Without it, base64 is written as the literal text it is. This is how a PNG, a PDF, or any other binary arrives in the namespace as a real file, and the encoded form can come from anywhere - a captured screenshot's image_b64, a fetched response, or an ENCODE result.
The modifier goes at the end and works with both spellings: after CONTENT, or after the TO form - FILE WRITE ?png TO "/root/pixel.png" BASE64.
STRING "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC" SET ?png
AFTER FILE WRITE "/root/pixel.png" CONTENT ?png BASE64
(* AFTER FILE WRITE ?png TO "/root/pixel.png" BASE64. Also works *)
AFTER FILE STAT "/root/pixel.png" SET ?s
AFTER EMIT "wrote " & ?s("size") & " bytes"
(* Emits: wrote 69 bytes — a real 1x1 PNG on disk.
Without BASE64 the same call writes 92 bytes: the base64 text itself *)
Invalid base64 content. and nothing is written - test !THIS('state') or read !ERROR('message') to catch it. Line breaks inside the base64, and a leading data:image/png;base64, prefix, both count as invalid - strip them before writing.FILE READ returns a string for a text file and a bytes value for a binary one - a PNG read back is the PNG, not null. A bytes value can be written straight out again with FILE WRITE "path" CONTENT ?b (no modifier: it is already binary), measured with LENGTH, indexed a byte at a time with ?b(0), or turned back into text form with ENCODE ?b AS BASE64. The BASE64 modifier is still what you want when the script is holding base64 text - from a screenshot, a fetched response, or an ENCODE result.Checking and inspecting
FILE EXISTS "/root/hello.txt" SET ?bool
AFTER EMIT ?bool
AFTER FILE STAT "/root/hello.txt" SET ?info
AFTER EMIT ?info
FILE STAT returns an object with exists, is_dir, is_file, size, and modified.
Listing a folder
FILE LIST "/root" SET ?items
AFTER EMIT ?items
FILE LIST returns an array of objects, each with name, is_dir, size, and modified.
Folders
FILE MKDIR "/root/photos"
Moving and copying
FILE MOVE "/root/old.txt" TO "/root/new.txt"
AFTER FILE COPY "/mounted/source.txt" TO "/root/copy.txt"
AFTER FILE COPY "/root/photos" TO "/mounted/photos-backup"
FILE MOVE and FILE COPY both take a file or a folder. A folder is copied with everything inside it, at every depth, and the copy is named by the destination path. A link inside the folder is copied as a link, pointing where the original pointed; it is never followed. A folder cannot be copied into itself or into one of its own subfolders, and FILE COPY refuses that with A folder cannot be copied into itself.
Deleting
FILE DELETE "/root/hello.txt"
Storage usage
STORAGE USAGE SET ?info
AFTER EMIT ?info
Returns an object with a root entry and a mounted entry, measured in bytes. Each carries used, what the location holds now, and limit, its size on your plan. mounted also carries enabled, which is false on Free, where there is no mounted storage and its limit is 0.
STORAGE USAGE SET ?info
AFTER CALCULATE ?info("root")("limit") - ?info("root")("used") SET ?free
AFTER EMIT ?free & " bytes free in /root"
Selections
A selection groups multiple paths together so a batch operation can run across all of them at once.
FILE SELECT "/root/file1" AND "/root/folder1" AND "/root/file2" SET ?selarray
FILE SELECT ALL "/root/folder" SET ?selarray
Acting on a selection
FILE SELECT "/root/a.txt" AND "/root/b.txt" SET ?selarray
AFTER FILE SELECTION ?selarray COPY TO "/root/backup"
AFTER FILE SELECTION ?selarray DELETE
FILE SELECTION ... COPY TO and ... MOVE TO place every selected item into the destination folder, keeping its original name. A selected folder is copied or moved with everything inside it. FILE SELECTION ... DELETE removes every item in the selection.
Compressing and extracting
FILE SELECT "/root/a.txt" AND "/root/b.txt" SET ?selarray
AFTER FILE COMPRESS ZIP ?selarray TO "/root/archive.zip"
AFTER FILE COMPRESS TAR ?selarray TO "/root/archive.tar"
Each selected item is stored in the archive under its own name. A selected file /root/reports/q3.pdf becomes q3.pdf, and a selected folder /root/photos becomes photos/ with its contents beneath it, whichever location it came from, exactly as any archive tool would store them. Extracting the archive anywhere gives back what was selected, with nothing of the namespace layout around it. Links are stored as links. The archive is written fresh: an archive already at the destination is replaced, not added to, and an archive may not be written over one of the files it contains.
Unpacking an archive
FILE EXTRACT is the other half. INTO names the folder the contents should land in, and it is created if it is not there. The format is read from the archive itself, so a .tgz that someone named .zip still opens.
FILE EXTRACT "/root/archive.zip" INTO "/root/unpacked" SET ?folder
AFTER EMIT ?folder
It returns the folder it wrote, so the next verb can walk straight into it.
FILE EXTRACT "/root/build.tar.gz" INTO "/mounted/builds/latest" SET ?folder
AFTER FILE LIST ?folder SET ?items
AFTER FOREACH ?items SET ?item
OPEN
EMIT ?item("name") & " - " & ?item("size") & " bytes"
CLOSE
Zip, tar and tar.gz are all read. Nothing has to say which - the archive is inspected, not its name.
FILE SELECT "/root/notes.txt" AND "/root/data.csv" SET ?picked
AFTER FILE COMPRESS ZIP ?picked TO "/root/backup.zip"
AFTER FILE EXTRACT "/root/backup.zip" INTO "/root/restored" SET ?where
AFTER EMIT "restored into " & ?where
(* /root/restored now holds notes.txt and data.csv *)
Public sharing
A file can be made publicly accessible at a generated URL under ocalt.com/shared/, without requiring the visitor to have an Ocalt account.
FILE SHARE "/root/file.mp3" SET ?newpublicurl
AFTER EMIT ?newpublicurl
/root/.ql, the platform's working area, cannot be shared; FILE SHARE refuses them. Copy such a file into your own folders and share the copy.FILE SHARE LIST SET ?sharedarray
AFTER FILE UNSHARE "/root/file.mp3"
AFTER FILE UNSHARE ALL
QR Code
The QR verb generates a QR code image from a publicly accessible URL. It only accepts shared file URLs (FILE SHARE) or paths inside /root/sites/ - anything else returns an error directing the user to share the file first or move it to a public folder.
QR returns the namespace path of a PNG image inside /root/.ql. The image is named for the URL it encodes, so asking for the QR code of the same URL again rewrites the same file rather than adding another. The image itself is not public. To show it on a page, read it and embed it as a data URL, as in the last example below, or copy it into your own folders and share the copy.
FILE SHARE "/root/report.pdf" SET ?url
AFTER QR ?url SET ?qrpath
AFTER EMIT ?qrpath
QR "/root/sites/mysite/index.html" SET ?qrpath
AFTER EMIT ?qrpath
(* The QR encodes https://mysite.ocalt.site/index.html *)
QR "https://google.com" SET ?qr
OR CATCH ERROR SET ?e
AFTER EMIT ?e("message")
(* "URL not available publicly. Please share the file first
or place it in a public folder." *)
FILE SHARE "/root/report.pdf" SET ?url
AFTER QR ?url SET ?qrpath
AFTER FILE READ ?qrpath SET ?png
AFTER ENCODE ?png AS BASE64 SET ?b64
AFTER EMIT "<img alt='QR code' src='data:image/png;base64," & ?b64 & "'>"