Files
logos-docs/docs/storage/storage-tutorial.md
2026-05-28 14:31:32 -04:00

7.4 KiB

title, doc_type, product, topics, steps_layout, authors, owner, doc_version, slug
title doc_type product topics steps_layout authors owner doc_version slug
Build a CLI app with Logos Storage procedure storage
sectioned logos 1 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. 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, 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:

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:

    git clone https://github.com/logos-storage/logos-storage-app-skeleton.git
    cd logos-storage-app-skeleton
    
  2. Build with Nix:

    nix build
    

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

    nix build --extra-experimental-features 'nix-command flakes'
    
  3. 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 for all available options.

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.

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:

    //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";
    });
    
  2. Call uploadUrl() with the local file path:

    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:

    LogosResult result = m_logos->storage_module.uploadInit(filename);
    QString sessionId = result.getValue<QString>();
    
  2. Upload chunks:

    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;
        }
    }
    
  3. Finalize:

    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:

    //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";
    });
    
  2. Call downloadToUrl() with the CID and the local destination path:

    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():

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 CLI. See the logoscore README for headless usage and how to wire the storage module plugin into it.