518 lines
20 KiB
Dart
518 lines
20 KiB
Dart
// Raw FFI bindings for the C ABI in native/src/ffi.rs: one lookup per entry
|
|
// point, no logic. Handles are opaque pointers at this layer; the typed
|
|
// methods accept plain addresses (ints) so they cross isolates freely.
|
|
// Every string this ABI returns is freed with smolFreeString — Dart's GC
|
|
// runs no Rust destructor.
|
|
|
|
import "dart:convert";
|
|
import "dart:ffi";
|
|
import "dart:typed_data";
|
|
|
|
import "package:ffi/ffi.dart";
|
|
|
|
import "package:smol_mail/native/library.dart";
|
|
|
|
// Stable error codes, mirrored from native/src/ffi.rs. The space is split
|
|
// like the wire's: 0-10 are protocol status codes, verbatim, and local
|
|
// failures live from 64 up, so a recovery path can be picked by range —
|
|
// RATE_LIMITED (8) retries on the live session it arrived on, while
|
|
// HANDSHAKE_REFUSED (71) means there is no session to retry on.
|
|
const smolOk = 0;
|
|
const smolMalformed = 1;
|
|
const smolBadVersion = 2;
|
|
const smolUnknownUser = 3;
|
|
const smolAuthRequired = 4;
|
|
const smolAuthFailed = 5;
|
|
const smolQuotaExceeded = 6;
|
|
const smolTooLarge = 7;
|
|
const smolRateLimited = 8;
|
|
const smolNotPermitted = 9;
|
|
const smolInternalError = 10;
|
|
/// Unassigned status byte; the raw byte is in the payload's "status".
|
|
const smolUnknownStatus = 11;
|
|
|
|
const smolNotPinned = 64;
|
|
const smolPinMismatch = 65;
|
|
const smolKeyChanged = 66;
|
|
const smolNotRegistered = 67;
|
|
const smolChainLimit = 68;
|
|
const smolSchemaVersion = 69;
|
|
const smolUnreachable = 70;
|
|
const smolHandshakeRefused = 71;
|
|
const smolStorage = 72;
|
|
const smolNoise = 73;
|
|
const smolIo = 74;
|
|
const smolOther = 75;
|
|
const smolPanic = 76;
|
|
|
|
/// What a failed native call reports: the decision code plus the structured
|
|
/// payload the ABI carries (host, known, offered — keys as base32).
|
|
class NativeSmolException implements Exception {
|
|
final int code;
|
|
final String message;
|
|
final Map<String, dynamic> details;
|
|
|
|
const NativeSmolException(this.code, this.message, this.details);
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|
|
|
|
/// The raw ABI. Opened per isolate — handles are plain addresses, so the
|
|
/// same store can be called from any isolate, which is what the async
|
|
/// wrapper relies on.
|
|
class SmolFfi {
|
|
SmolFfi._(this._lib);
|
|
|
|
final DynamicLibrary _lib;
|
|
|
|
static SmolFfi? _cached;
|
|
|
|
factory SmolFfi.open() => _cached ??= SmolFfi._(openSmolLibrary());
|
|
|
|
Pointer<Void> _h(int address) => Pointer<Void>.fromAddress(address);
|
|
|
|
late final Pointer<Utf8> Function() _lastError = _lib
|
|
.lookupFunction<Pointer<Utf8> Function(), Pointer<Utf8> Function()>(
|
|
"smol_last_error");
|
|
late final void Function(Pointer<Utf8>) _freeString = _lib.lookupFunction<
|
|
Void Function(Pointer<Utf8>),
|
|
void Function(Pointer<Utf8>)>("smol_free_string");
|
|
|
|
late final Pointer<Void> Function(Pointer<Utf8>) _storeOpen = _lib
|
|
.lookupFunction<Pointer<Void> Function(Pointer<Utf8>),
|
|
Pointer<Void> Function(Pointer<Utf8>)>("smol_store_open");
|
|
late final Pointer<Void> Function() _storeMemory = _lib.lookupFunction<
|
|
Pointer<Void> Function(),
|
|
Pointer<Void> Function()>("smol_store_memory");
|
|
late final void Function(Pointer<Void>) _storeFree = _lib.lookupFunction<
|
|
Void Function(Pointer<Void>),
|
|
void Function(Pointer<Void>)>("smol_store_free");
|
|
late final Pointer<Void> Function(Pointer<Uint8>, int) _accountNew = _lib
|
|
.lookupFunction<
|
|
Pointer<Void> Function(Pointer<Uint8>, Uint32),
|
|
Pointer<Void> Function(
|
|
Pointer<Uint8>, int)>("smol_account_new");
|
|
late final void Function(Pointer<Void>) _accountFree = _lib.lookupFunction<
|
|
Void Function(Pointer<Void>),
|
|
void Function(Pointer<Void>)>("smol_account_free");
|
|
late final Pointer<Utf8> Function(Pointer<Void>) _accountPk = _lib
|
|
.lookupFunction<Pointer<Utf8> Function(Pointer<Void>),
|
|
Pointer<Utf8> Function(Pointer<Void>)>("smol_account_pk");
|
|
late final Pointer<Void> Function() _flagNew = _lib.lookupFunction<
|
|
Pointer<Void> Function(),
|
|
Pointer<Void> Function()>("smol_flag_new");
|
|
late final void Function(Pointer<Void>, int) _flagSet = _lib.lookupFunction<
|
|
Void Function(Pointer<Void>, Int32),
|
|
void Function(Pointer<Void>, int)>("smol_flag_set");
|
|
late final void Function(Pointer<Void>) _flagFree = _lib.lookupFunction<
|
|
Void Function(Pointer<Void>),
|
|
void Function(Pointer<Void>)>("smol_flag_free");
|
|
|
|
late final int Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>)
|
|
_pinServer = _lib.lookupFunction<
|
|
Int32 Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>),
|
|
int Function(
|
|
Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>)>("smol_pin_server");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>)
|
|
_serverPin = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>)>(
|
|
"smol_server_pin");
|
|
late final Pointer<Utf8> Function(Pointer<Void>) _storeAccount = _lib
|
|
.lookupFunction<Pointer<Utf8> Function(Pointer<Void>),
|
|
Pointer<Utf8> Function(Pointer<Void>)>("smol_store_account");
|
|
late final int Function(Pointer<Void>) _rotations = _lib.lookupFunction<
|
|
Int32 Function(Pointer<Void>),
|
|
int Function(Pointer<Void>)>("smol_rotations");
|
|
late final int Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, Pointer<Utf8>, int) _register = _lib.lookupFunction<
|
|
Int32 Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
|
|
Pointer<Utf8>, Uint64),
|
|
int Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int)
|
|
>("smol_register");
|
|
late final int Function(Pointer<Void>, Pointer<Uint8>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int) _restore = _lib.lookupFunction<
|
|
Int32 Function(Pointer<Void>, Pointer<Uint8>, Pointer<Utf8>,
|
|
Pointer<Utf8>, Uint64),
|
|
int Function(Pointer<Void>, Pointer<Uint8>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int)>("smol_restore");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>,
|
|
Pointer<Utf8>, int) _rotate = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Void>, Pointer<Utf8>, Uint64),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
int)>("smol_rotate");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, int, int,
|
|
Pointer<Utf8>, int, Pointer<Void>) _fetch = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, Int32, Int32,
|
|
Pointer<Utf8>, Uint64, Pointer<Void>),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, int, int,
|
|
Pointer<Utf8>, int, Pointer<Void>)>("smol_fetch");
|
|
late final Pointer<Utf8> Function(
|
|
Pointer<Void>,
|
|
Pointer<Void>,
|
|
Pointer<Utf8>,
|
|
Pointer<Utf8>,
|
|
Pointer<Utf8>,
|
|
Pointer<Utf8>,
|
|
int,
|
|
int,
|
|
Pointer<Utf8>,
|
|
int) _send = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>,
|
|
Pointer<Void>,
|
|
Pointer<Utf8>,
|
|
Pointer<Utf8>,
|
|
Pointer<Utf8>,
|
|
Pointer<Utf8>,
|
|
Int32,
|
|
Int32,
|
|
Pointer<Utf8>,
|
|
Uint64),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int,
|
|
Pointer<Utf8>, int)>("smol_send");
|
|
late final int Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int) _delete = _lib.lookupFunction<
|
|
Int32 Function(
|
|
Pointer<Void>, Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, Uint64),
|
|
int Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int)>("smol_delete");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>) _mail = _lib
|
|
.lookupFunction<Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>),
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Utf8>)>("smol_mail");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>,
|
|
Pointer<Utf8>) _describe = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>),
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Void>, Pointer<Utf8>)>("smol_describe");
|
|
late final Pointer<Utf8> Function(Pointer<Void>) _contacts = _lib
|
|
.lookupFunction<Pointer<Utf8> Function(Pointer<Void>),
|
|
Pointer<Utf8> Function(Pointer<Void>)>("smol_contacts");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>) _contact =
|
|
_lib.lookupFunction<
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>)>(
|
|
"smol_contact");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>,
|
|
Pointer<Utf8>, Pointer<Utf8>, int) _accept = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, Uint64),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int)>("smol_accept");
|
|
late final int Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int) _block = _lib.lookupFunction<
|
|
Int32 Function(
|
|
Pointer<Void>, Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, Uint64),
|
|
int Function(Pointer<Void>, Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int)>("smol_block");
|
|
late final Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, int) _resolve = _lib
|
|
.lookupFunction<
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, Uint64),
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Utf8>,
|
|
Pointer<Utf8>, int)>("smol_resolve");
|
|
late final int Function(Pointer<Void>, Pointer<Utf8>) _accountSet = _lib
|
|
.lookupFunction<Int32 Function(Pointer<Void>, Pointer<Utf8>),
|
|
int Function(Pointer<Void>, Pointer<Utf8>)>("smol_account_set");
|
|
late final int Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, int)
|
|
_contactSave = _lib.lookupFunction<
|
|
Int32 Function(
|
|
Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>, Int32),
|
|
int Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
|
|
int)>("smol_contact_save");
|
|
late final int Function(Pointer<Void>, Pointer<Utf8>) _unpinServer = _lib
|
|
.lookupFunction<Int32 Function(Pointer<Void>, Pointer<Utf8>),
|
|
int Function(Pointer<Void>, Pointer<Utf8>)>("smol_unpin_server");
|
|
late final Pointer<Utf8> Function(Pointer<Void>) _pins = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(Pointer<Void>),
|
|
Pointer<Utf8> Function(Pointer<Void>)>("smol_pins");
|
|
late final int Function(Pointer<Void>) _syncOk = _lib.lookupFunction<
|
|
Int32 Function(Pointer<Void>),
|
|
int Function(Pointer<Void>)>("smol_sync_ok");
|
|
late final int Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>)
|
|
_deleteLocal = _lib.lookupFunction<
|
|
Int32 Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>),
|
|
int Function(
|
|
Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>)>("smol_delete_local");
|
|
late final Pointer<Utf8> Function(Pointer<Utf8>) _parseAddress = _lib
|
|
.lookupFunction<Pointer<Utf8> Function(Pointer<Utf8>),
|
|
Pointer<Utf8> Function(Pointer<Utf8>)>("smol_parse_address");
|
|
late final int Function(Pointer<Void>, Pointer<Utf8>) _importContact = _lib
|
|
.lookupFunction<Int32 Function(Pointer<Void>, Pointer<Utf8>),
|
|
int Function(Pointer<Void>, Pointer<Utf8>)>("smol_import_contact");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Uint8>)
|
|
_exportBackup = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(Pointer<Void>, Pointer<Uint8>),
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Uint8>)>("smol_export_backup");
|
|
late final Pointer<Utf8> Function(Pointer<Void>, Pointer<Uint8>,
|
|
Pointer<Utf8>) _importBackup = _lib.lookupFunction<
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Uint8>, Pointer<Utf8>),
|
|
Pointer<Utf8> Function(
|
|
Pointer<Void>, Pointer<Uint8>, Pointer<Utf8>)>(
|
|
"smol_import_backup");
|
|
|
|
// --- helpers over the raw surface ------------------------------------------
|
|
|
|
/// Throws when a status call returned non-zero, decoding the error slot.
|
|
/// The slot is thread-local: this only works because it runs inside the
|
|
/// same Isolate.run closure that made the failing call — after the
|
|
/// closure returns, the slot on that thread is gone.
|
|
Never _fail() {
|
|
final slot = _lastError();
|
|
if (slot == nullptr) {
|
|
throw const NativeSmolException(smolOther, "the native call failed", {});
|
|
}
|
|
final text = take(slot);
|
|
final Map<String, dynamic> details;
|
|
try {
|
|
details = jsonDecode(text) as Map<String, dynamic>;
|
|
} on FormatException {
|
|
throw NativeSmolException(smolOther, text, {});
|
|
}
|
|
throw NativeSmolException(
|
|
details["code"] as int? ?? smolOther,
|
|
details["message"] as String? ?? "the native call failed",
|
|
details);
|
|
}
|
|
|
|
/// Reads one returned buffer into a String and frees the buffer — the
|
|
/// ownership rule every caller owes a returned pointer.
|
|
String take(Pointer<Utf8> ptr) {
|
|
if (ptr == nullptr) _fail();
|
|
final text = ptr.toDartString();
|
|
_freeString(ptr);
|
|
return text;
|
|
}
|
|
|
|
Pointer<Utf8> _text(String text) => text.toNativeUtf8();
|
|
|
|
Pointer<Uint8> _bytes(Uint8List bytes) {
|
|
final buf = malloc<Uint8>(32);
|
|
buf.asTypedList(32).setAll(0, bytes);
|
|
return buf;
|
|
}
|
|
|
|
// --- typed surface: handles cross as ints ----------------------------------
|
|
|
|
int openStore(String path) {
|
|
final ptr = _storeOpen(_text(path));
|
|
if (ptr == nullptr) _fail();
|
|
return ptr.address;
|
|
}
|
|
|
|
int openMemoryStore() {
|
|
final ptr = _storeMemory();
|
|
if (ptr == nullptr) _fail();
|
|
return ptr.address;
|
|
}
|
|
|
|
void closeStore(int store) => _storeFree(_h(store));
|
|
|
|
int newAccount(Uint8List master, int index) {
|
|
final buf = _bytes(master);
|
|
final ptr = _accountNew(buf, index);
|
|
malloc.free(buf);
|
|
if (ptr == nullptr) _fail();
|
|
return ptr.address;
|
|
}
|
|
|
|
void freeAccount(int account) => _accountFree(_h(account));
|
|
|
|
String accountPk(int account) => take(_accountPk(_h(account)));
|
|
|
|
int newFlag() => _flagNew().address;
|
|
|
|
void setFlag(int flag, bool value) => _flagSet(_h(flag), value ? 1 : 0);
|
|
|
|
void freeFlag(int flag) => _flagFree(_h(flag));
|
|
|
|
void pinServer(int store, String host, String keyB32) {
|
|
if (_pinServer(_h(store), _text(host), _text(keyB32)) != smolOk) _fail();
|
|
}
|
|
|
|
void unpinServer(int store, String host) {
|
|
if (_unpinServer(_h(store), _text(host)) != smolOk) _fail();
|
|
}
|
|
|
|
/// Every pin: [{host, key}] with keys as base32.
|
|
List<dynamic> pins(int store) =>
|
|
jsonDecode(take(_pins(_h(store)))) as List<dynamic>;
|
|
|
|
/// Whether the local accept-token set may replace the server's (sec 4).
|
|
bool syncOk(int store) => _syncOk(_h(store)) == 1;
|
|
|
|
void saveContact(int store, String address, String keyB32,
|
|
{required bool verified}) =>
|
|
_contactSave(
|
|
_h(store), _text(address), _text(keyB32), verified ? 1 : 0);
|
|
|
|
/// Null when nothing is pinned.
|
|
String? serverPin(int store, String host) {
|
|
final text = take(_serverPin(_h(store), _text(host)));
|
|
return text.isEmpty ? null : text;
|
|
}
|
|
|
|
/// Null when not registered.
|
|
String? accountAddress(int store) {
|
|
final text = take(_storeAccount(_h(store)));
|
|
return text.isEmpty ? null : text;
|
|
}
|
|
|
|
int rotations(int store) => _rotations(_h(store));
|
|
|
|
/// Binds the account locally, as register and restore do at their end.
|
|
void setAccount(int store, String address) {
|
|
if (_accountSet(_h(store), _text(address)) != smolOk) _fail();
|
|
}
|
|
|
|
void register(int store, int account, String address,
|
|
{String? invite, String? dial, int timeout = 30}) {
|
|
if (_register(
|
|
_h(store),
|
|
_h(account),
|
|
_text(address),
|
|
invite == null ? nullptr : _text(invite),
|
|
dial == null ? nullptr : _text(dial),
|
|
timeout) !=
|
|
smolOk) {
|
|
_fail();
|
|
}
|
|
}
|
|
|
|
void restore(int store, Uint8List master, String address,
|
|
{String? dial, int timeout = 30}) {
|
|
final buf = _bytes(master);
|
|
final code = _restore(_h(store), buf, _text(address),
|
|
dial == null ? nullptr : _text(dial), timeout);
|
|
malloc.free(buf);
|
|
if (code != smolOk) _fail();
|
|
}
|
|
|
|
Map<String, dynamic> rotate(int store, int account,
|
|
{String? dial, int timeout = 30}) =>
|
|
jsonDecode(take(_rotate(_h(store), _h(account),
|
|
dial == null ? nullptr : _text(dial), timeout)))
|
|
as Map<String, dynamic>;
|
|
|
|
Map<String, dynamic> fetch(int store, int account,
|
|
{bool keep = false,
|
|
bool reset = false,
|
|
String? dial,
|
|
int timeout = 30,
|
|
int? cancel}) {
|
|
final text = take(_fetch(_h(store), _h(account), keep ? 1 : 0, reset ? 1 : 0,
|
|
dial == null ? nullptr : _text(dial), timeout,
|
|
cancel == null ? nullptr : _h(cancel)));
|
|
return jsonDecode(text) as Map<String, dynamic>;
|
|
}
|
|
|
|
Map<String, dynamic> send(int store, int account, String to, String body,
|
|
{String? subject,
|
|
String? replyTo,
|
|
bool anonymous = false,
|
|
bool noPad = false,
|
|
String? dial,
|
|
int timeout = 30}) {
|
|
final text = take(_send(
|
|
_h(store),
|
|
_h(account),
|
|
_text(to),
|
|
subject == null ? nullptr : _text(subject),
|
|
_text(body),
|
|
replyTo == null ? nullptr : _text(replyTo),
|
|
anonymous ? 1 : 0,
|
|
noPad ? 1 : 0,
|
|
dial == null ? nullptr : _text(dial),
|
|
timeout));
|
|
return jsonDecode(text) as Map<String, dynamic>;
|
|
}
|
|
|
|
/// Ids are 64 hex characters everywhere — the same shape `mail` and
|
|
/// `describe` return, so no caller hex-decodes solely to delete.
|
|
void delete(int store, int account, List<String> ids,
|
|
{String? dial, int timeout = 30}) {
|
|
if (_delete(_h(store), _h(account), _text(jsonEncode(ids)),
|
|
dial == null ? nullptr : _text(dial), timeout) !=
|
|
smolOk) {
|
|
_fail();
|
|
}
|
|
}
|
|
|
|
List<dynamic> mail(int store, String folder) =>
|
|
jsonDecode(take(_mail(_h(store), _text(folder)))) as List<dynamic>;
|
|
|
|
Map<String, dynamic> describe(int store, int account, String idHex) =>
|
|
jsonDecode(take(_describe(_h(store), _h(account), _text(idHex))))
|
|
as Map<String, dynamic>;
|
|
|
|
List<dynamic> contacts(int store) =>
|
|
jsonDecode(take(_contacts(_h(store)))) as List<dynamic>;
|
|
|
|
Map<String, dynamic> contact(int store, String address) =>
|
|
jsonDecode(take(_contact(_h(store), _text(address))))
|
|
as Map<String, dynamic>;
|
|
|
|
int accept(int store, int account, String address,
|
|
{String? dial, int timeout = 30}) =>
|
|
(jsonDecode(take(_accept(_h(store), _h(account), _text(address),
|
|
dial == null ? nullptr : _text(dial), timeout)))
|
|
as Map<String, dynamic>)["held"] as int;
|
|
|
|
void block(int store, int account, String address,
|
|
{String? dial, int timeout = 30}) {
|
|
if (_block(_h(store), _h(account), _text(address),
|
|
dial == null ? nullptr : _text(dial), timeout) !=
|
|
smolOk) {
|
|
_fail();
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> resolve(int store, String address,
|
|
{String? dial, int timeout = 30}) {
|
|
final text = take(_resolve(_h(store), _text(address),
|
|
dial == null ? nullptr : _text(dial), timeout));
|
|
return jsonDecode(text) as Map<String, dynamic>;
|
|
}
|
|
|
|
/// The reader's delete: removes locally and marks seen, so a kept
|
|
/// server copy is not re-stored by the next fetch.
|
|
void deleteLocal(int store, String folder, List<String> ids) {
|
|
if (_deleteLocal(_h(store), _text(folder), _text(jsonEncode(ids))) !=
|
|
smolOk) {
|
|
_fail();
|
|
}
|
|
}
|
|
|
|
/// One address for the UI: {user, host, port, short, scheme}, or a
|
|
/// NativeSmolException when it does not parse.
|
|
Map<String, dynamic> parseAddress(String text) {
|
|
return jsonDecode(take(_parseAddress(_text(text))))
|
|
as Map<String, dynamic>;
|
|
}
|
|
|
|
void importContact(int store, String uri) {
|
|
if (_importContact(_h(store), _text(uri)) != smolOk) _fail();
|
|
}
|
|
|
|
String exportBackup(int store, Uint8List master) {
|
|
final buf = _bytes(master);
|
|
final text = take(_exportBackup(_h(store), buf));
|
|
malloc.free(buf);
|
|
return text;
|
|
}
|
|
|
|
String importBackup(int store, Uint8List master, String text) {
|
|
final buf = _bytes(master);
|
|
final summary = take(_importBackup(_h(store), buf, _text(text)));
|
|
malloc.free(buf);
|
|
return summary;
|
|
}
|
|
}
|