File System
File system architecture and organization in TMA Cloud.
File system architecture and organization in TMA Cloud.
File Structure
Hierarchical Organization
- Root Level: User's root directory
- Folders: Nested folder structure
- Files: Stored within folders or root
- Parents: An item's parent is always a folder in the same account. An upload, new folder or copy into a file, a folder in the trash or another account's folder is refused with
Target folder not found. See Database Schema for the triggers that enforce this.
File Metadata
- ID: Unique identifier (stable across operations)
- Name: File or folder name
- Type: 'file' or 'folder'
- Size: File size in bytes
- MIME Type: Actual MIME type detected from file content (not from extension)
- Parent ID: Parent folder reference
- Path: The storage key, not a user-visible path
- User ID: The account the item belongs to
- Last Access Time: When the item was last read (
accessed_at), shown in the UI as "Last opened" - Shared At: When the item most recently became shared (
shared_at); null while the item is not shared
File Names
A name is stored exactly as the user's system wrote it. Uploads, new folders and renames all follow one rule, close to Nextcloud's default, which also refuses only separators and control characters:
- Up to 255 characters, and not empty,
.or... - No
/or\, which are path separators, and no control characters (ASCII 0–31 and 127).
Everything else is allowed, including spaces, accents, emoji, the narrow no-break space that newer Windows time formats put before AM and PM, and the characters Windows reserves (: * ? " < > |). Names such as CON or ones ending in a dot are also accepted, since macOS, Linux, and Android allow them.
Where a name has to become a Windows file, it is translated rather than refused:
- Folder ZIP downloads replace
: * ? " < > |with_and drop trailing dots and spaces in each entry, as browsers do for single downloads. - The Cloud Drive shows each reserved character, and a trailing dot or space, as the private-use character
U+F000plus its code, the mapping Cygwin, WSL and Syncthing use. Renames and saves turn it back, so the stored name never changes. WSL and Git Bash show the original characters.
Name Clashes
A new folder or a rename cannot take a name that another file or folder in the same folder already has. Letter case is ignored, as in Explorer and Finder, so Report.pdf and report.PDF clash, and a file and a folder cannot share a name. Items in the trash do not count. The server refuses the request with 409, and the New folder and Rename dialogs say so before sending.
Copies, moves and pastes keep both instead, giving the new item a numbered name such as Photos (1), and an upload asks whether to replace the existing file or keep both; see Upload Files and Manage Folders.
File Operations
Supported Operations
- Upload: Add new files
- Download: Retrieve files
- Create Folder: Create new directories
- Move: Move files/folders
- Copy: Duplicate files/folders
- Rename: Change file/folder names
- Delete: Move to trash
- Star: Mark as favorite
- Share: Create share links
Large File Handling
- Streaming: Files streamed without loading into memory
- Upload: Files encrypted and streamed directly to the bucket
- Download: Files streamed from storage to client; single-file downloads support HTTP
Range(partial content) for seeking - ZIP Archives: Archive entries are read through a database cursor and streamed one at a time, keeping database pages and open storage streams bounded. Trees of up to 20,000 entries are read at the start so the database connection is released right away; larger trees are limited by
DB_HELD_CURSOR_LIMIT. Each entry already carries its wrapped data key, so no per-file key lookup is made. The archive stops when the client disconnects
Performance
- Streaming prevents memory exhaustion for large files (>1GB)
- Rename operations update database metadata without moving stored objects
- Copy operations are queued on the background worker, use server-side object-store copies (24 at once), and read folder trees once before batched inserts
- File lists use stable cursor batches and load continuously as the user scrolls. Only visible grid/list rows plus a small overscan are rendered
- Folder sizes and descendant counts are maintained by database triggers. Size sorting uses those stored totals and the matching page-order index instead of walking a folder tree during a listing. Nothing recomputes them on read; a weekly worker job recomputes them from the rows and reports any folder whose stored totals were wrong. See Background Workers
Path Management
- Renaming or moving an item changes its
parent_idandname, not its storage key, so no stored object is touched - Stored object keys are validated as flat keys without path separators or traversal sequences
Last Access Time
Every file and folder carries an accessed_at timestamp, surfaced in the UI as Last opened in Get Info and as a sort option in the file list. It answers "when was this last read", which modified cannot, since modified only moves when the contents change. The server also keeps who read it, the owner or one of their sub-users, so the dashboard's Recent files can say "Opened 2:18 AM by Sam Rivera". A new upload counts as opened by whoever uploaded it. Each person has their own update window, so a sub-user opening a file the owner just opened still counts.
What Counts as Reading
| Action | Updates |
|---|---|
| Downloading a file | That file |
| Downloading a folder as ZIP | The folder and everything in it |
| Bulk download | Every item in the archive |
| Opening a document in OnlyOffice | That file |
| Reading a file through a share link | That file |
| Opening a folder in the file list | The folder only, not its contents |
| Uploading or replacing a file | That file |
Listing a folder marks the folder and leaves its children alone. This matches how Windows treats directory enumeration, and it keeps the value meaningful: if browsing past a file counted as opening it, every file in a folder you visit would look recently used. Cloud Drive's listings do not count either, since it lists every folder it keeps current on each resync whether or not anyone looks; files opened from it still count, through the download.
Searching, viewing Get Info, renaming, moving, starring and sharing do not count as reads.
Write Behavior
Recording a read on every request would turn each read into a database write. Two rules keep that cost bounded, both taken from how filesystems handle the same problem:
- Update window, one hour by default. Once an item's timestamp is written, further reads of it are ignored until the window passes. NTFS guarantees its last-access time only to within an hour for the same reason; Linux's
relatimeuses a comparable rule. - Buffered writes. Updates accumulate in memory and are written in one batched statement every 10 seconds by default, so no read waits on a write. Linux's
lazytimeworks the same way.
The effect is at most one write per item per window, batched. The number of statements is set by the flush interval rather than by how many users are reading, so it does not grow with traffic. Timestamps may lag by the flush interval plus the window, which is why the value is documented as approximate.
The first user can turn tracking off and change the window and the write interval under Settings → Administration → Recently opened. See Session Timeout and Recently Opened.
On the Cloud Drive
On Windows the drive reports this value as the NTFS LastAccessTime, so Explorer's Date accessed column reflects it. The other three NTFS timestamps come from the modification time. Times Windows writes back locally are never sent to the server, because the server decides what counts as a read. See Desktop App.
Storage
Physical Storage
- S3: Files stored in S3-compatible object storage. Database stores object key.
- The first user connects the bucket in Settings → Storage; file contents cannot be stored or read until it is saved.
- Original filenames preserved in database
File Encryption
- Files encrypted with AES-256-GCM in Google Tink's AES-GCM-HKDF-STREAMING format (the
AES256_GCM_HKDF_1MBscheme) - Each newly encrypted object version uses a random data key (DEK).
FILE_ENCRYPTION_KEYis the key-encryption key (KEK) that wraps the DEK; a server-side logical copy reuses the source ciphertext and wrapped DEK - Each object has a 40-byte header (a random salt and nonce prefix) followed by 1 MB segments, each sealed with its own authentication tag. The per-file encryption key is derived from the DEK with HKDF-SHA256
- Segments make stored objects seekable: a download serves an HTTP
Rangerequest by fetching and decrypting only the overlapping segments, so a large file opens without reading all of it - Automatic decryption on download
- Rotating
FILE_ENCRYPTION_KEYonly rewraps the stored DEKs and never re-encrypts the objects. See Key Rotation
Storage Limits
See Storage Management.
Trash System
Soft Delete
- Files moved to trash (not deleted)
deleted_attimestamp set- Restorable within retention period
- Share links stop serving trashed items until they are restored
Automatic Cleanup
- Items in the trash for more than 15 days are deleted for good by the background worker
- Delete forever and Empty Trash also run on the worker
Search
Trigram Similarity Search
- PostgreSQL pg_trgm extension for fuzzy text matching
- GIN index on file names for fast searches
- Prefix matching for short queries
- Indexed trigram matching for longer queries
- User-scoped searches
%and_in a query match literally, not as wildcards
Related Topics
- Storage Management - Storage limits
- Sharing Model - How files are shared
- User Guide: Upload Files - How to use the file system
Last updated on