Skip to content
218 changes: 218 additions & 0 deletions docs/storage/storage-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is called libstorage-tutorial.md which is not correct anymore

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed

title: Build a CLI app with Logos Storage
doc_type: procedure
product: storage
topics: []
steps_layout: sectioned
authors:
owner: logos
doc_version: 1
slug: storage-tutorial
---

# Build a CLI app with Logos Storage

#### Get started building a CLI application that transfers files over the Logos Storage network.

This tutorial walks you through building a simple CLI application that uploads and downloads files over the Logos Storage network using the [Logos Storage Module API v0.3.2](https://logos-co.github.io/logos-storage-module/api_reference.html). It is intended for developers who are setting up a new application using the skeleton project and working through the module lifecycle for the first time.

The tutorial uses the [Logos Storage App Skeleton](https://github.com/logos-storage/logos-storage-app-skeleton), which provides a ready-made entry point at `app_main` that uses the `LogosModules` object to access the API. The skeleton also provides a set of Qt-compatible synchronization utilities.

**Before you start**, make sure you have the following:

- [Nix package manager](https://nixos.org/download/)
- Git

## What to expect

- You can initialize, start, and cleanly shut down the storage module within your application.
- You can upload a file to the network and receive a Content Identifier (CID) that uniquely identifies it.
- You can download a file from the network using a Content Identifier (CID).

## Step 1: Build the skeleton app

Clone the skeleton repository and compile the binary so you have a working entry point before adding any storage logic.

1. Clone the skeleton repository:

```bash
git clone https://github.com/logos-storage/logos-storage-app-skeleton.git
cd logos-storage-app-skeleton
```

1. Build with Nix:

```bash
nix build
```

If you don't have flakes enabled globally, add experimental flags:

```bash
nix build --extra-experimental-features 'nix-command flakes'
```

1. Confirm the compiled binary is available at `./result/bin/storage-app`.

## Step 2: Initialize the module

The skeleton's `app_main` already initializes and starts the module, so you have a working baseline. The snippets in the rest of this tutorial show the calls in isolation; extend `app/main.cpp` to add upload and download logic on top.

Call `init()` with your configuration once at startup, passing a JSON configuration string. See the [API Reference](https://logos-co.github.io/logos-storage-module/api_reference.html) for all available options.

```cpp
const QString jsonConfig = "{"
"\"listen-addrs\": [\"/ip4/0.0.0.0/tcp/8000\"],"
"\"disc-port\": 9000,"
"\"data-dir\": \"./app-data\","
"\"nat\": \"none\""
"}";
bool result = m_logos->storage_module.init(jsonConfig);
```

> [!CAUTION]
> Do not call `init()` more than once per instance unless you call `destroy()` first.

## Step 3: Start the node

Subscribe to the `storageStart` event and call `start()` afterward, to be able to detect failures.

```cpp
m_logos->storage_module.on("storageStart", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString error = data[1].toString();
// Handle error
}
});

bool result = m_logos->storage_module.start();
```

## Step 4: Upload a file

The Storage Module allows for two upload approaches. Choose `uploadUrl` for straightforward cases. Use the streaming API when you need fine-grained control over how data is sent.

### Upload with `uploadUrl`

The simplest way to upload files is to subscribe to the upload events, then call `uploadUrl()` with the path to your file. The network returns a Content Identifier (CID) on success.

1. Subscribe to the `storageUploadDone` and `storageUploadProgress` events:

```cpp
//m_logos is the LogosModules object, used for API calls
m_logos->storage_module.on("storageUploadDone", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString sessionId = data[1].toString();
QString cidOrError = data[2].toString();

if (success) {
qDebug() << "Upload complete. CID:" << cidOrError;
} else {
qDebug() << "Upload failed:" << cidOrError;
}
});

m_logos->storage_module.on("storageUploadProgress", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString sessionId = data[1].toString();
int bytes = data[2].toInt();
qDebug() << "Uploaded" << bytes << "bytes";
});
```

1. Call `uploadUrl()` with the local file path:

```cpp
QUrl fileUrl = QUrl::fromLocalFile("/path/to/myfile");
LogosResult result = m_logos->storage_module.uploadUrl(fileUrl);
```

### Upload with the streaming API

Use the streaming upload API for chunk-level control.

1. Initialize the session:

```cpp
LogosResult result = m_logos->storage_module.uploadInit(filename);
QString sessionId = result.getValue<QString>();
```

1. Upload chunks:

```cpp
QFile file(filepath);
file.open(QIODevice::ReadOnly);
int chunkSize = 1024 * 64;
while (!file.atEnd()) {
QByteArray chunk = file.read(chunkSize);
result = m_logos->storage_module.uploadChunk(sessionId, chunk);
if (!result.success) {
// Handle error
break;
}
}
```

1. Finalize:

```cpp
result = m_logos->storage_module.uploadFinalize(sessionId);
if (result.success) {
QString cid = result.getValue<QString>();
qDebug() << "CID:" << cid;
}
```

## Step 5: Download a file

To download content, you need the CID returned during upload. The storage module discovers the content on the network using the CID.

1. Subscribe to the `storageDownloadDone` and `storageDownloadProgress` events:

```cpp
//m_logos is the LogosModules object, used for API calls
m_logos->storage_module.on("storageDownloadDone", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString message = data[1].toString();
if (success) {
qDebug() << "Download complete";
} else {
qDebug() << "Download failed:" << message;
}
});

m_logos->storage_module.on("storageDownloadProgress", [this](const QVariantList& data) {
bool success = data[0].toBool();
QString sessionId = data[1].toString();
int size = data[2].toInt();
qDebug() << "Downloaded" << size << "bytes";
});
```

1. Call `downloadToUrl()` with the CID and the local destination path:

```cpp
QUrl destination = QUrl::fromLocalFile("/path/to/output");
LogosResult result = m_logos->storage_module.downloadToUrl(cid, destination /*, local = false*/);
```

- Set the `local` (third) parameter of `downloadToUrl` to `true` to retrieve only locally-cached data.
- Leave `local` as `false` (default) to fetch from the network.

## Step 6: Stop and clean up

Always stop the node before destroying resources to avoid leaving sessions open. To do so, call `stop()` and wait for the `storageStop` event, then call `destroy()`:

```cpp
LogosResult result = m_logos->storage_module.stop();
// Wait for storageStop event...
result = m_logos->storage_module.destroy();
```

## Frequently asked questions

### Can I run the storage module without a UI?

Yes. The storage module is a Qt plugin that can be loaded by any Logos Core host, including the headless [`logoscore`](https://github.com/logos-co/logos-logoscore-cli) CLI. See the [`logoscore` README](https://github.com/logos-co/logos-logoscore-cli) for headless usage and how to wire the storage module plugin into it.