sia_storage 0.2.0 copy "sia_storage: ^0.2.0" to clipboard
sia_storage: ^0.2.0 copied to clipboard

Dart bindings for the Sia Storage SDK. Upload, download, and pin objects on the Sia network from Flutter and pure Dart applications.

example/sia_storage_example.dart

/// End-to-end example: authorize an app, upload a stream of random bytes,
/// pin the object, then download and verify it — reporting throughput.
///
/// Run from the package root:
///
/// ```sh
/// dart run example/sia_storage_example.dart --size 125829120
/// ```
library;

import 'dart:io';
import 'dart:math';
import 'dart:typed_data';

import 'package:sia_storage/sia_storage.dart';

final appMeta = AppMetadata(
  id: _hexToBytes(
    '5c0b1af28e6ac76395b2087ea987297b9c496f90d2ab3e3d3d07980ae4c43633',
  ),
  name: 'My Example App',
  description: 'My Example App Description',
  serviceUrl: 'https://myexampleapp.com',
);

Future<void> main(List<String> args) async {
  // Size of the data to upload and download in bytes (default: 120 MiB).
  final size = _parseSize(args);

  // Authorize the app to access the user's storage.
  final builder = await Sia.builder(
    indexerUrl: 'https://sia.storage',
    appMeta: appMeta,
  );

  await builder.requestConnection();
  stdout.writeln(
    'Visit the following URL to authorize the application: '
    '${builder.responseUrl()}',
  );

  await builder.waitForApproval();
  stdout.writeln('Connection approved!');

  stdout.write('Enter recovery phrase: ');
  final phrase = stdin.readLineSync()?.trim();
  if (phrase == null || phrase.isEmpty) {
    stderr.writeln('no recovery phrase provided');
    exit(1);
  }

  final sdk = await builder.register(mnemonic: phrase);
  stdout.writeln('App registered successfully!');

  final seed = Random().nextInt(0x100000000);

  // Upload the data to the network.
  stdout.writeln('Uploading random data...');
  final uploadTimer = Stopwatch()..start();
  final upload = sdk.upload(
    object: PinnedObject(),
    source: _randomData(seed, size),
  );
  final obj = await upload.result;
  uploadTimer.stop();

  // Pin the object to ensure it remains available on the network.
  await sdk.pinObject(object: obj);
  stdout.writeln('Object pinned successfully!');

  // Download the object back from the network, verifying it matches the
  // bytes we uploaded as it streams in.
  stdout.writeln('Downloading object...');
  final verifier = _SeededBytes(seed);
  final downloadTimer = Stopwatch()..start();
  var verified = 0;
  Duration? ttfb;
  var gapMax = Duration.zero;
  var lastChunk = Duration.zero;
  await for (final chunk in sdk.download(object: obj).data) {
    final elapsed = downloadTimer.elapsed;
    ttfb ??= elapsed;
    final gap = elapsed - lastChunk;
    if (gap > gapMax) gapMax = gap;
    lastChunk = elapsed;

    final expected = Uint8List(chunk.length);
    verifier.fill(expected);
    for (var i = 0; i < chunk.length; i++) {
      if (chunk[i] != expected[i]) {
        throw StateError('data mismatch at byte ${verified + i}');
      }
    }
    verified += chunk.length;
  }
  downloadTimer.stop();
  if (verified != size) {
    throw StateError('expected $size bytes, got $verified');
  }

  final uploadDuration = uploadTimer.elapsed;
  final downloadDuration = downloadTimer.elapsed;
  final objSize = obj.size().toInt();
  final encodedSize = obj.encodedSize().toInt();

  stdout.writeln(
    'Object uploaded ID: ${obj.id()}'
    '\tSize: ${_formatBytes(objSize)}'
    '\tEncoded: ${_formatBytes(encodedSize)}'
    '\tElapsed: $uploadDuration'
    '\tThroughput: ${_formatBitrate(objSize, uploadDuration)}'
    '\tEncoded Throughput: ${_formatBitrate(encodedSize, uploadDuration)}',
  );
  stdout.writeln(
    'Object downloaded ID: ${obj.id()}'
    '\tSize: ${_formatBytes(objSize)}'
    '\tEncoded: ${_formatBytes(encodedSize)}'
    '\tElapsed: $downloadDuration'
    '\tTTFB: ${ttfb ?? Duration.zero}'
    '\tThroughput: ${_formatBitrate(objSize, downloadDuration)}'
    '\tMax Write Latency: $gapMax',
  );

  // Tear down the native runtime so the VM can exit on its own: this closes
  // the frb channel ports that otherwise keep the event loop alive.
  Sia.dispose();
}

/// A deterministic stream of random bytes from a seed, yielded in chunks.
Stream<List<int>> _randomData(
  int seed,
  int size, {
  int chunk = 64 * 1024,
}) async* {
  final bytes = _SeededBytes(seed);
  var remaining = size;
  while (remaining > 0) {
    final buf = Uint8List(min(chunk, remaining));
    bytes.fill(buf);
    yield buf;
    remaining -= buf.length;
  }
}

/// Produces a deterministic byte sequence from a seed. Bytes are drawn from a
/// rolling 32-bit word, so the sequence is independent of how it's chunked —
/// the uploader and the download verifier stay in lockstep regardless of
/// where chunk boundaries fall.
class _SeededBytes {
  final Random _rng;
  int _word = 0;
  int _wordBytesLeft = 0;

  _SeededBytes(int seed) : _rng = Random(seed);

  int _nextByte() {
    if (_wordBytesLeft == 0) {
      _word = _rng.nextInt(0x100000000);
      _wordBytesLeft = 4;
    }
    final b = _word & 0xff;
    _word >>= 8;
    _wordBytesLeft--;
    return b;
  }

  void fill(Uint8List out) {
    for (var i = 0; i < out.length; i++) {
      out[i] = _nextByte();
    }
  }
}

int _parseSize(List<String> args) {
  const defaultSize = 120 * 1024 * 1024;
  for (var i = 0; i < args.length; i++) {
    final arg = args[i];
    if (arg == '-s' || arg == '--size') {
      if (i + 1 >= args.length) {
        stderr.writeln('missing value for $arg');
        exit(2);
      }
      return int.parse(args[i + 1]);
    }
    if (arg.startsWith('--size=')) {
      return int.parse(arg.substring('--size='.length));
    }
  }
  return defaultSize;
}

Uint8List _hexToBytes(String hex) {
  final out = Uint8List(hex.length ~/ 2);
  for (var i = 0; i < out.length; i++) {
    out[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
  }
  return out;
}

String _formatBytes(int bytes) {
  const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
  var value = bytes.toDouble();
  for (final unit in units) {
    if (value < 1024 || unit == units.last) {
      return '${value.toStringAsFixed(2)} $unit';
    }
    value /= 1024;
  }
  throw StateError('unreachable');
}

String _formatBitrate(int bytes, Duration duration) {
  const units = ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];
  var value = (bytes * 8) / (duration.inMicroseconds / 1e6);
  for (final unit in units) {
    if (value < 1000 || unit == units.last) {
      return '${value.toStringAsFixed(2)} $unit';
    }
    value /= 1000;
  }
  throw StateError('unreachable');
}
1
likes
160
points
94
downloads

Documentation

API reference

Publisher

verified publishersia.storage

Weekly Downloads

Dart bindings for the Sia Storage SDK. Upload, download, and pin objects on the Sia network from Flutter and pure Dart applications.

Repository (GitHub)
View/report issues

Topics

#sia #storage #decentralized #rust #ffi

License

MIT (license)

Dependencies

flutter_rust_bridge, hooks, native_toolchain_rust

More

Packages that depend on sia_storage