# Custom I/O for Stream Cache

Stream Cache servers do their I/O over a pair of abstract classes. This allows a user to write their own classes that inherit from these abstract base classes to customize I/O. This can be useful if you want to, for example, do your SC I/O over websockets or another protocol, or lay your files out in a way that would make them normally unreadable by the default SC filesystem methods.

In order to specify an AbstractFilesystem you only need to provide a pointer to it in your `SC::Server::StreamCacheServer::Config`.

When opening SCZ files for viewing, they will need to be extracted. To do this, they are unzipped and their contents written to the workspace, which is specified as part of `SC::Server::StreamCacheServer::Config`.

## The abstract classes

There are two classes that need to be implemented: `AbstractFilesystem` and `AbstractFile`. Both can be found in sc_io.h.

AbstractFilesystem

This provides libsc with the means to navigate the filesystem and query files. The following methods will need to be implemented:

Method | Description
--- | ---
`std::unique_ptr<AbstractFile> NewFile()` | Creates a new object inherited from `AbstractFile`.
`std::unique_ptr<AbstractFile> NewFile(path const& path)` | Creates a new `AbstractFile` object with a path.
`path Cleaned(path const& path) const` | Cleans a path. For example, removes `..` and `.`.
`bool Exists(path const& path) const` | Returns `true` if a file or directory exists at `path`.
`bool IsDirectory(path const& path) const` | Returns `true` if a directory exists at `path`.
`bool IsRegularFile(path const& path) const` | Returns `true` if a regular file (not a directory or symlink) exists at `path`.
`bool IsSymlink(path const& path) const` | Returns `true` if a symlink exists at `path`.
`bool IsEmpty(path const& path) const` | Returns `true` if the file or directory at `path` is empty.
`void GetChildren(path const& path, std::vector<IO::path>& out_children) const` | Fills `out_children` with the children of the directory at `path`.
`uintmax_t RemoveAll(path const& path)` | Removes everything in the directory at path. Returns the number of items removed.
`bool Remove(path const& path)` | Removes the file at `path`. Returns `true` if successful.
`bool Rename(path const& old_path, path const& new_path)` | Renames the file at `old_path` to `new_path`. Returns true if successful.
`bool CreateDir(path const& path)` | Creates a directory at path. Returns true if successful.
`bool CreateDirs(path const& path)` | Creates a directory at path. Creating any other directories needed ahead of it. Returns true if successful.

AbstractFile

This provides `libsc` with the means to interact with files, including reading and writing data. The following methods will need to be implemented:

Method | Description
--- | ---
`State DoAcquire(bool read_only, bool create_if_missing, bool truncate)` | Acquires the resource. At the very least this must update the `_state` property to indicate the mode the file was opened in, or if it has failed to open. Returns the state that was set.
`void DoRelease()` | Releases the resource (closes the file).
`int64_t DoSeek(int64_t offset, Position position)` | Moves the read/write offset to somewhere within the file. Can be offset from the beginning, current position or end depending on the `position` argument. Returns the offset from the beginning of the file.
`bool DoTruncate()` | Truncates data after the current offset. Returns true if successful.
`int64_t DoGetSize() const` | Gets the size of the file.
`size_t DoRead(void* data, size_t n_bytes)` | Reads `n_bytes` from the file starting at the offset into `data`. `data` should already be allocated to hold `n_bytes`. Retuns the number of bytes read.
`size_t DoWrite(void const* data, size_t n_bytes)` | Writes `n_bytes` from `data` into the file starting at the offset. Returns the number of bytes written.
`bool DoFlush()` | Flush the cache, if any. Returns true if successful.

## Node SC server REST I/O

The node server included with the HOOPS Communicator package is capable of doing I/O over REST for use cases where you might want to read SC data directly over the network. To make use of this, you’d write a server that exposes a REST API that the SC server would communicate with to query and retrieve SC data.

### Usage with the Node server

To use REST with the node server included with the package, a few things will need to be set up.

In your config, you’ll need to set the `restFileserver` parameter to the address of your REST file server.

The paths that will be requested from your REST server will be dependent on the `modelDirs` parameter in your config. For example, if your SC server is on Windows, your `modelDirs` parameter is set to `["C:\\models"]` and when you request `myModel`, you can expect to see queries about `C:\\models\\myModel`.

### The API

All endpoints should send JSON responses except `read` which sends an octet-stream.

Endpoint | Parameters | Description | Returns
--- | --- | --- | ---
`/read/:path` | offset, size | Reads `size` number of bytes from the file at `path`, starting at offset | An octet-stream with the requested bytes
`/exists/:path` | None | Checks if the file or directory at `path` exists | { exists: boolean }
`/size/:path` | None | Returns the size of a file | { size: u64 }
`/isDir/:path` | None | Checks if `path` is a directory | { isDir: boolean }
`/isRegularFile/:path` | None | Checks if `path` is a regular file (not a directory or a symlink) | { isRegularFile: boolean }
`/isSymlink/:path` | None | Checks if `path` is a symlink | { isSymlinkFile: boolean }
`/isEmpty/:path` | None | Checks if the file or directory at `path` is empty (no contents or zero bytes) | { isEmpty: boolean }
`/getChildren/:path` | None | Gets the children of the directory at `path` | { children: string[] }