dart_smb2 0.0.6 copy "dart_smb2: ^0.0.6" to clipboard
dart_smb2: ^0.0.6 copied to clipboard

Flutter SMB2/3 client built on top of libsmb2. Targets macOS, Windows, Linux, iOS and Android.

dart_smb2 #

SMB2/3 client for Dart & Flutter.

logodart_smb2 is an SMB2/3 client for Dart powered by libsmb2. It provides synchronous FFI bindings, a worker-isolate pool with auto-reconnect, scope-based file helpers and an optional caching layer.


Installation #

Add dart_smb2 to your pubspec.yaml:

dependencies:
  dart_smb2: ^0.0.6

Platform Requirements #

  • Android: SDK 24 (Android 7.0) or above.
  • iOS: 12.0 or above.
  • macOS: 10.14 or above (Apple Silicon).
  • Windows: x86_64.
  • Linux: x86_64.

Platforms #

Platform Architecture Device Emulator libsmb2 version
Android arm64-v8a, x86_64 v6.1.0
iOS arm64, x86_64 v6.1.0
macOS arm64 v6.1.0
Windows x86_64 v6.1.0
Linux x86_64 v6.1.0

Reference #


Visuals #

The following images demonstrate the example app included in the example/ directory. This application serves as a reference client for testing the various features and capabilities of dart_smb2.

Servers
Manage saved connections
Browse
Tree explorer
Read
Reading performance test
Write
Writing performance test

Features #

  • SMB2/3 Protocol — supports SMB 2.02, 2.10, 3.0, 3.02, 3.1.1.
  • 📂 Directory Listing — returns name, type, size, and timestamps per entry with no additional per-entry round-trips.
  • 📄 Partial File Reads — read specific byte ranges with pread — ideal for reading partial content without downloading the full file.
  • 📦 Full File Reads — download entire files into memory.
  • 🔁 Streaming — chunked reads and writes via sync Iterable or async Stream, with progress + cancel callbacks and a single persistent handle.
  • ⬇️ Download to File — writes an SMB file to a local File with progress and cancel support.
  • 🧹 Scoped File AccesswithFile(path, body) opens a read handle, runs your callback with a read-on-demand Smb2File, and guarantees cleanup on any exit path (including exceptions and cancellation).
  • 🔓 File Handles — open once, read or write many times to minimize round-trips. A Dart Finalizer closes leaked handles as a safety net.
  • ✏️ File Writing — write entire files or partial byte ranges with automatic chunking.
  • 🔍 Exists Check — check if a file or directory exists without reading it.
  • 🗂️ File & Directory Management — create directories, delete files/directories, rename/move, and truncate.
  • 📊 File Stat — get size, type, and timestamps via SMB2 compound request (single round-trip).
  • 💽 Filesystem Info — query total/free disk space via statvfs.
  • 🔗 Symlink Resolution — read symbolic link targets with readlink.
  • 🖲️ Connection Health — keepalive echo ping to detect disconnections early.
  • 🔄 Flush & Truncatefsync to persist writes and ftruncate on open handles.
  • 🌐 Share Enumeration — list all shares on a server without an active connection.
  • 🧵 Isolate-Safe — sync Smb2Client designed to run inside Dart isolates; never blocks the UI thread when used via Smb2Pool.
  • 🏊 Worker Pool — multiple isolate workers with automatic reconnect on connection errors.
  • 💾 Caching Layer — optional TTL-based cache for stat and listDirectory calls, with automatic invalidation on write operations.

Quick Start #

import 'dart:io';
import 'dart:typed_data';
import 'package:dart_smb2/dart_smb2.dart';

void main() async {
  // Connect the worker pool (auto-reconnect on connection errors)
  final pool = await Smb2Pool.connect(
    host: '192.168.1.100',
    share: 'Files',
    user: 'user',
    password: 'pass',
  );

  // List a directory
  final entries = await pool.listDirectory('Documents/Projects');
  for (final entry in entries) {
    print('${entry.name} — ${entry.size} bytes');
  }

  // Read the first 256 KB of a file
  final header = await pool.readFileRange(
    'Documents/report.pdf',
    length: 256 * 1024,
  );

  // Download a file to disk with progress + cancel
  await pool.downloadToFile(
    'Music/song.flac',
    File('/tmp/song.flac'),
    onProgress: (received, total) =>
        print('${(received / total * 100).toStringAsFixed(1)}%'),
  );

  // Read on-demand with a scoped handle (auto-closed)
  final tags = await pool.withFile('Music/song.flac', (file) async {
    final header = await file.read(length: 64 * 1024);
    return parseTags(header); // your code
  });

  // Write a file
  await pool.writeFile(
    'Documents/notes.txt',
    Uint8List.fromList('Hello SMB!'.codeUnits),
  );

  // Create a directory
  await pool.mkdir('Documents/NewFolder');

  // Get file info
  final info = await pool.stat('Documents/report.pdf');
  print('Size: ${info.size}, Modified: ${info.modified}');

  await pool.disconnect();
}

Guide #

1. Connection & Lifecycle #

dart_smb2 offers three layers of abstraction. Pick the highest one that fits your use case — Smb2Pool is the recommended default.

Layer Class Best for
Sync FFI Smb2Client Scripts, background isolates, maximum control
Worker Pool Smb2Pool Default. Async, multi-worker, auto-reconnect, scope-based file helpers
Cached Pool CachedSmb2Pool Repeated stat/listDirectory calls with TTL

1.1 Sync Client

The core layer. All operations are blocking — run it in an isolate to avoid blocking the UI thread. The bundled native library loads automatically on every supported platform — no path configuration required.

final client = Smb2Client.open();

client.connect(
  host: '192.168.1.100',
  share: 'Files',
  user: 'user',
  password: 'pass',
  domain: 'WORKGROUP',      // optional, defaults to empty
  timeoutSeconds: 30,       // optional, defaults to 30
);

// ... use the client ...

client.disconnect();

1.2 Worker Pool

Spawns N isolate workers connected to the same share. Operations are dispatched round-robin. If a worker loses connection, it automatically reconnects and retries the operation.

For single-connection use cases (the old Smb2Isolate), set workers: 1 — you get the same single-connection semantics plus automatic reconnect.

final pool = await Smb2Pool.connect(
  host: '192.168.1.100',
  share: 'Files',
  user: 'user',
  password: 'pass',
  workers: 4,              // default: 4
  timeoutSeconds: 30,
);

print('Active workers: ${pool.workerCount}');

final entries = await pool.listDirectory('');
await pool.disconnect();

1.3 Cached Pool

Wraps an Smb2Pool with an in-memory TTL cache for stat and listDirectory. All other operations pass through directly.

final pool = await Smb2Pool.connect(
  host: '192.168.1.100',
  share: 'Files',
  user: 'user',
  password: 'pass',
);

final cached = CachedSmb2Pool(pool, ttl: Duration(seconds: 30));

// First call hits the network
final entries = await cached.listDirectory('Documents/Projects');

// Second call within 30s returns from cache
final same = await cached.listDirectory('Documents/Projects');

// Manually invalidate
cached.invalidate('Documents/Projects');

// Clear everything
cached.clearCache();

await cached.disconnect();

1.4 Disconnecting

Always disconnect when done. This releases the native SMB2 context and kills any spawned isolates.

client.disconnect();          // Smb2Client (sync)
await pool.disconnect();      // Smb2Pool
await cached.disconnect();    // CachedSmb2Pool

1.5 Security Options

All connect methods accept optional security parameters:

final pool = await Smb2Pool.connect(
  host: '192.168.1.100',
  share: 'Files',
  user: 'user',
  password: 'pass',
  seal: true,                    // encrypt all traffic (SMB 3.0+)
  signing: true,                 // require message signing
  version: Smb2Version.any3,     // only accept SMB 3.x
);
Parameter Default Description
seal false Enable SMB3 encryption. All traffic is encrypted on the wire. Requires SMB 3.0 or later — the connection fails if the server only supports SMB 2.x.
signing false Require message signing. Prevents tampering. Works with all SMB versions. The client will always sign if the server requires it, regardless of this setting.
version Smb2Version.any Protocol version to negotiate. Default lets the server pick the highest mutually supported version. Use any3 to enforce SMB 3.x (required for encryption).

Available versions:

Smb2Version Protocol Encryption support
any Best available (default) Depends on negotiated version
any2 Any SMB 2.x No
any3 Any SMB 3.x Yes
v202 SMB 2.0.2 No
v210 SMB 2.1 No
v300 SMB 3.0 Yes
v302 SMB 3.0.2 Yes
v311 SMB 3.1.1 (most secure) Yes

Note: When seal: true is set, the connection will fail if the server does not support SMB 3.0+. This is by design — silent fallback to unencrypted would be a security violation.


2. Path Format #

Paths are relative to the share root. Use an empty string '' for the root directory — not /.

pool.listDirectory('');                          // share root
pool.listDirectory('Documents');                 // subfolder
pool.listDirectory('Documents/Projects');        // nested subfolder
pool.stat('Documents/report.pdf');               // file in subfolder
pool.writeFile('Documents/notes.txt', data);     // write to subfolder
pool.mkdir('Documents/NewFolder');               // create in subfolder
pool.rename('Documents/a.txt', 'Archive/a.txt'); // move between folders

3. Directory Listing #

Returns all entries in a directory with full metadata (name, type, size, timestamps) — no additional per-entry round-trips. The . and .. entries are automatically filtered out.

final entries = await pool.listDirectory('Documents/Projects');

for (final entry in entries) {
  final icon = entry.isDirectory ? '📁' : '📄';
  print('$icon ${entry.name}  ${entry.size} bytes  ${entry.stat.modified}');
}

Each entry is an Smb2DirEntry:

Property Type Description
name String Entry name (not full path)
stat Smb2Stat Full metadata (type, size, modified, created)
isDirectory bool Shorthand for stat.type == directory
isFile bool Shorthand for stat.type == file
size int Shorthand for stat.size

4. File Metadata #

4.1 Stat

Returns file metadata via an SMB2 compound request (Create + QueryInfo + Close in a single round-trip).

final info = await pool.stat('Documents/report.pdf');

print('Type: ${info.type}');          // Smb2FileType.file
print('Size: ${info.size}');          // bytes
print('Modified: ${info.modified}');  // DateTime
print('Created: ${info.created}');    // DateTime
print('Is file: ${info.isFile}');     // bool
print('Is dir: ${info.isDirectory}'); // bool

4.2 File Size

Shortcut to get just the file size.

final size = await pool.fileSize('Documents/report.pdf');
print('$size bytes');

4.3 Exists

Check whether a file or directory exists without reading it. Returns false for non-existent paths; throws on connection or permission errors.

// Sync
if (client.exists('Documents/report.pdf')) {
  print('File exists');
}

// Async
if (await pool.exists('Documents/report.pdf')) {
  print('File exists');
}

4.4 Filesystem Info (statvfs)

Query total and free disk space on the share.

// Sync
final vfs = client.statvfs('');
print('Total: ${vfs.totalSize} bytes');
print('Free: ${vfs.freeSize} bytes');
print('Available: ${vfs.availableSize} bytes');

// Async
final vfs = await pool.statvfs('');

Returns an Smb2StatVfs with convenience getters totalSize, freeSize, and availableSize (in bytes).

Read the target path of a symbolic link.

// Sync
final target = client.readlink('Documents/shortcut');

// Async
final target = await pool.readlink('Documents/shortcut');

Throws Smb2Exception if the path is not a symlink.

4.6 Connection Health (echo)

Send a keepalive ping to the server. Returns normally if the connection is alive; throws on failure.

// Sync
client.echo();

// Async
await pool.echo();

Useful for detecting disconnections early during idle periods.


5. Reading Files #

5.1 Read Entire File

Loads the entire file into memory.

final bytes = await pool.readFile('Documents/report.pdf');

5.2 Partial Read (Byte Range)

Reads N bytes starting at an offset. Ideal for reading partial content without downloading the entire file.

// Read first 256 KB of a file
final header = await pool.readFileRange(
  'Documents/report.pdf',
  offset: 0,           // default: 0
  length: 256 * 1024,
);

5.3 Scoped File Access (withFile)

Opens a file for reading, runs your callback with a scoped [Smb2File], and guarantees the handle is closed on any exit path — including exceptions, early returns, and cancellation.

This is the recommended way to read a file when you need more than a single one-shot read (e.g. parsing metadata that may need ranged fallback reads).

final tags = await pool.withFile('Music/song.flac', (file) async {
  print('File is ${file.size} bytes');

  // Initial header read — typically enough for most tag formats.
  final header = await file.read(length: 64 * 1024);

  // Some tag formats need bytes beyond the header (e.g. Vorbis comments
  // stored at arbitrary offsets). Expose `file.read` as a fallback reader
  // and only fetch more bytes when the parser actually needs them.
  return parseVorbisComments(
    header,
    fileSize: file.size,
    fallbackRead: (offset, length) => file.read(offset: offset, length: length),
  );
});

If you already know the file size from a prior stat or directory listing, pass it via knownSize to skip the fstat round-trip:

final stat = await pool.stat('Music/song.flac');
await pool.withFile(
  'Music/song.flac',
  (file) async { /* … */ },
  knownSize: stat.size,
);

5.4 Streaming (Chunked)

Reads a file in chunks via a single persistent handle (one Create + N Read + one Close on the wire), with optional progress and cancellation callbacks.

Sync (Smb2Client):

for (final chunk in client.readFileChunked('data/large_file.bin', chunkSize: 1024 * 1024)) {
  sink.add(chunk);
}

Async (Smb2Pool / CachedSmb2Pool):

bool canceled = false;

await for (final chunk in pool.streamFile(
  'data/large_file.bin',
  chunkSize: 1024 * 1024,
  onProgress: (received, total) {
    print('${(received / total * 100).toStringAsFixed(1)}% '
          '($received / $total bytes)');
  },
  isCanceled: () => canceled,
)) {
  sink.add(chunk);
}
Parameter Default Description
chunkSize 1 MiB Dart-side buffer per iteration. Network-level chunking is automatic (libsmb2 splits into server-negotiated MaxReadSize packets, typically 1 MiB).
onProgress Called after each chunk with (received, total) byte counts.
isCanceled Polled after each chunk. Returning true aborts the stream with Smb2Exception.

The handle is closed automatically when the stream completes, errors, or the listener cancels its subscription.

5.5 Download to File

One-call convenience that streams an SMB file to a local File via [streamFile]. Equivalent to streaming and piping to File.openWrite(), with onProgress and isCanceled wired through.

import 'dart:io';

await pool.downloadToFile(
  'Music/song.flac',
  File('/tmp/song.flac'),
  onProgress: (received, total) {
    print('${(received / total * 100).toStringAsFixed(1)}%');
  },
  isCanceled: () => userHitCancelButton,
);

Atomicity: if the download is canceled or errors, the destination file is left as-is (truncated or partially written). For safe replacement of an existing file, write to dest.part and rename on success.

5.6 Low-Level File Handles

When you need finer control than withFile provides — e.g. a handle that outlives a single scope, or reusing a handle across multiple independent reads — the raw open/close primitives are still available.

Prefer withFile, streamFile, or downloadToFile whenever you can. They are safer (guaranteed cleanup) and more efficient (persistent handle). Reach for the raw API only when those don't fit.

Sync (Smb2Client):

final handle = client.openFileHandle('data/file.bin');

final start = client.readHandle(handle, offset: 0, length: 4096);
final mid   = client.readHandle(handle, offset: 100000, length: 4096);

client.closeHandle(handle);

With size (Smb2Client):

final (handle, size) = client.openFileWithSize('data/file.bin');
print('File is $size bytes');

// ... read from handle ...

client.closeHandle(handle);

Pool (auto-reconnect on failure):

final (handle, size) = await pool.openFileWithSize('data/file.bin');

try {
  final data = await pool.readFromHandle(handle, offset: 0, length: size);
  // ...
} finally {
  await pool.closeHandle(handle);
}

If the connection drops during a read, the pool automatically reconnects the worker and reopens the file handle before retrying.

Safety net: if an Smb2PoolHandle is garbage-collected without closeHandle being called, a closeHandle command is sent to the worker from the finalizer as a best-effort cleanup. Do not rely on this — always close explicitly (or use withFile) for deterministic, prompt cleanup.


6. Writing Files #

6.1 Write Entire File

Creates or overwrites a file with the given data. The file is truncated before writing.

Sync (Smb2Client):

client.writeFile('Documents/notes.txt', Uint8List.fromList('Hello!'.codeUnits));

Async (Smb2Pool / CachedSmb2Pool):

await pool.writeFile('Documents/notes.txt', Uint8List.fromList('Hello!'.codeUnits));

6.2 Partial Write (Byte Range)

Writes data at a specific offset without truncating the file. Creates the file if it doesn't exist.

Sync (Smb2Client):

// Overwrite bytes 100–199 of an existing file
client.writeFileRange('data/file.bin', myBytes, offset: 100);

Async (Smb2Pool / CachedSmb2Pool):

await pool.writeFileRange('data/file.bin', myBytes, offset: 100);

6.3 Streaming Write (Chunked)

Writes data from chunks without loading the entire file into RAM. Uses a sync Iterable on Smb2Client and an async Stream on Smb2Pool and CachedSmb2Pool.

Sync (Smb2Client):

client.writeFileChunked('data/large_file.bin', generateChunks());

Async (Smb2Pool / CachedSmb2Pool):

final fileStream = File('local_file.bin').openRead().cast<Uint8List>();
await pool.streamWrite('data/large_file.bin', fileStream);

6.4 File Handles (Write)

Open a file once for writing and write to it multiple times without reopening. This minimizes round-trips for repeated writes on the same file.

Sync (Smb2Client):

final handle = client.openFileHandleWrite('data/output.bin');

client.writeHandle(handle, chunk1, offset: 0);
client.writeHandle(handle, chunk2, offset: chunk1.length);

client.closeHandle(handle);

Pool (auto-reconnect on failure):

final handle = await pool.openFileWrite('data/output.bin');

await pool.writeToHandle(handle, chunk1, offset: 0);
await pool.writeToHandle(handle, chunk2, offset: chunk1.length);

await pool.closeHandle(handle);

Note: Write handles use the same closeHandle() method as read handles.

6.5 Flush (fsync)

Flush all buffered writes on an open file handle to the server, ensuring data is persisted.

Sync (Smb2Client):

final handle = client.openFileHandleWrite('data/important.bin');
client.writeHandle(handle, data);
client.fsync(handle);  // ensure data is on disk
client.closeHandle(handle);

Pool:

final handle = await pool.openFileWrite('data/important.bin');
await pool.writeToHandle(handle, data);
await pool.fsyncHandle(handle);
await pool.closeHandle(handle);

6.6 Truncate Handle (ftruncate)

Truncate an open file handle to a specific size. More efficient than path-based truncate() when the file is already open.

Sync (Smb2Client):

final handle = client.openFileHandleWrite('data/file.bin');
client.ftruncate(handle, 1024);
client.closeHandle(handle);

Pool:

final handle = await pool.openFileWrite('data/file.bin');
try {
  await pool.ftruncateHandle(handle, 1024);
} finally {
  await pool.closeHandle(handle);
}

7. File & Directory Management #

7.1 Create Directory

// Sync
client.mkdir('Documents/NewFolder');

// Async
await pool.mkdir('Documents/NewFolder');

7.2 Delete File

// Sync
client.deleteFile('Documents/old_report.pdf');

// Async
await pool.deleteFile('Documents/old_report.pdf');

7.3 Delete Directory

Removes an empty directory. Throws Smb2Exception if the directory is not empty.

// Sync
client.rmdir('Documents/EmptyFolder');

// Async
await pool.rmdir('Documents/EmptyFolder');

7.4 Rename / Move

Renames or moves a file or directory within the same share.

// Sync
client.rename('Documents/old_name.txt', 'Documents/new_name.txt');

// Async — also works for moving between directories
await pool.rename('Documents/report.pdf', 'Archive/report.pdf');

7.5 Truncate

Truncates a file to a specific size in bytes.

// Sync
client.truncate('Documents/logfile.txt', 0); // empty the file

// Async
await pool.truncate('Documents/logfile.txt', 1024); // keep first 1 KB

8. Share Enumeration #

List all shares available on a server. This does not require an active connection — it connects to IPC$ internally.

Sync:

final shares = client.listShares(
  host: '192.168.1.100',
  user: 'user',
  password: 'pass',
);

for (final share in shares) {
  print('${share.name} — disk: ${share.isDisk}, hidden: ${share.isHidden}');
}

Pool (static method, no active connection needed):

final shares = await Smb2Pool.listSharesOn(
  host: '192.168.1.100',
  user: 'user',
  password: 'pass',
);

CachedSmb2Pool (delegates to the underlying pool, not cached):

final shares = await cached.listShares(
  host: '192.168.1.100',
  user: 'user',
  password: 'pass',
);

Each share is an Smb2ShareInfo with:

Property Type Description
name String Share name
type int Raw share type
isDisk bool True if disk/folder share
isHidden bool True if hidden share

9. Caching #

CachedSmb2Pool wraps an Smb2Pool with a TTL-based in-memory cache. Only stat and listDirectory results are cached — all other operations always hit the network. Write operations automatically invalidate the cache for the affected path and its parent directory.

final cached = CachedSmb2Pool(pool, ttl: Duration(seconds: 30));
Method Cached Auto-invalidates
stat()
listDirectory()
readFile()
readFileRange()
streamFile()
withFile()
downloadToFile()
fileSize()
openFile() / openFileWithSize()
readFromHandle() / closeHandle()
exists() ✅ (via stat)
listShares()
writeFile() ✅ path + parent
writeFileRange() ✅ path + parent
deleteFile() ✅ path + parent
mkdir() ✅ parent
rmdir() ✅ path + parent
rename() ✅ old + new paths
truncate() ✅ path + parent
streamWrite() ✅ path + parent
openFileWrite() / writeToHandle()
echo()
statvfs()
readlink()
fsyncHandle() / ftruncateHandle()

Invalidation:

cached.invalidate('Documents/Projects');  // remove one path from cache
cached.clearCache();                      // clear all cached data

10. Error Handling #

10.1 Smb2Exception

All errors throw Smb2Exception with a message, an optional POSIX errno, and a semantic error type.

try {
  await pool.readFile('nonexistent/path.txt');
} on Smb2Exception catch (e) {
  print(e.message);              // Human-readable error from libsmb2
  print(e.errorCode);            // POSIX errno (nullable)
  print(e.type);                 // Smb2ErrorType
  print(e.isConnectionError);    // true for retriable errors
}

10.2 Error Types

Smb2ErrorType maps native errno values to semantic categories:

Type Meaning errno examples
connection Network disconnected, pipe broken ENETRESET, ECONNRESET, ECONNABORTED, EPIPE, ENOTCONN, ENETDOWN, ENETUNREACH, EHOSTDOWN, EHOSTUNREACH
timeout Operation timed out ETIMEDOUT
auth Authentication failed ECONNREFUSED
fileNotFound File or path not found ENOENT
accessDenied Permission denied EACCES
notADirectory Path is not a directory ENOTDIR
alreadyExists Target already exists EEXIST
diskFull No space left on device ENOSPC
io I/O error EIO
invalidParam Invalid argument EINVAL
unknown Unmapped error code

Use isConnectionError to decide whether to retry:

on Smb2Exception catch (e) {
  if (e.isConnectionError) {
    // Safe to retry — connection or timeout issue
  }
}

Note: Smb2Pool handles reconnection automatically. You only need manual retry logic with Smb2Client.


Types Reference #

Smb2Stat #

Property Type Description
type Smb2FileType file, directory, or link
size int Size in bytes
modified DateTime Last modification time
created DateTime Creation time
isFile bool true if regular file
isDirectory bool true if directory

Smb2DirEntry #

Property Type Description
name String Entry name (not full path)
stat Smb2Stat Full metadata
isDirectory bool Shorthand
isFile bool Shorthand
size int Shorthand for stat.size

Smb2StatVfs #

Property Type Description
blockSize int Fundamental block size
fragmentSize int Fragment size
totalBlocks int Total data blocks
freeBlocks int Free blocks
availableBlocks int Available blocks for non-root
maxNameLength int Max filename length
totalSize int Total size in bytes (computed)
freeSize int Free space in bytes (computed)
availableSize int Available space in bytes (computed)

Smb2ShareInfo #

Property Type Description
name String Share name
type int Raw share type
isDisk bool True if disk/folder share
isHidden bool True if hidden share

Smb2Version #

Value Protocol
any Best available (default)
any2 Any SMB 2.x
any3 Any SMB 3.x
v202 SMB 2.0.2
v210 SMB 2.1
v300 SMB 3.0
v302 SMB 3.0.2
v311 SMB 3.1.1

Smb2FileType #

Value Description
file Regular file
directory Directory
link Symbolic link

Smb2ErrorType #

Value Description
connection Network disconnected
timeout Operation timed out
auth Authentication failed
fileNotFound File or path not found
accessDenied Permission denied
notADirectory Path is not a directory
alreadyExists Target already exists
diskFull No space left
io I/O error
invalidParam Invalid argument
unknown Unmapped error

Testing #

The test suite is split into unit tests (no server required) and integration tests (require a live SMB server).

Unit tests #

dart test test/smb2_error_type_test.dart

Integration tests #

Integration tests are for contributors to this package — a Flutter app consuming dart_smb2 never needs any of this; the native library loads automatically.

Require a running SMB2/3 server and a path to the platform-specific library (since the test process runs outside the Flutter plugin system that normally handles loading). Use flutter test so the bundled binary is resolved under macos/libs/ / linux/libs/ / etc:

Variable Description
SMB2_HOST Server IP or hostname
SMB2_SHARE Share name
SMB2_USER Username (optional)
SMB2_PASS Password (optional)
SMB2_LIB_PATH Absolute path to the bundled libsmb2 binary for your host OS
SMB2_TEST_FILE Path to an existing file on the share (optional — auto-detected if unset)
SMB2_HOST=192.168.1.1 \
SMB2_SHARE=Files \
SMB2_USER=user \
SMB2_PASS=pass \
SMB2_LIB_PATH="$PWD/macos/libs/libsmb2.dylib" \
SMB2_TEST_FILE=Documents/report.pdf \
flutter test test/ -r expanded

smb2_pool_test.dart covers Smb2Pool end-to-end: basic operations, file handles, streaming, round-robin distribution, disconnect behaviour, and performance benchmarks (sequential/parallel throughput, stat latency, handle cycle time).

smb2_client_test.dart covers the sync Smb2Client directly: directory listing, stat, file size, partial reads, and error paths.

smb2_cached_pool_test.dart covers CachedSmb2Pool: cache hits, TTL expiry, and invalidation.

smb2_write_test.dart covers all write operations on both Smb2Client and Smb2Pool: writeFile, writeFileRange, writeFileChunked/streamWrite, write handles, exists, mkdir, rmdir, deleteFile, rename, truncate, and concurrent writes. Requires write access to the share.


Permissions #

Android #

<uses-permission android:name="android.permission.INTERNET" />

macOS #

Add to DebugProfile.entitlements and Release.entitlements:

<key>com.apple.security.network.client</key>
<true/>

Project Background #

The native bindings, FFI wrappers, and isolate logic were implemented through the use of Claude Code and Antigravity, Gemini models were used for the example app UI.


Funding #

If you find this library useful and want to support its development, consider becoming a supporter on Patreon:


Developed by Alessandro Di Ronza

5
likes
160
points
457
downloads

Documentation

API reference

Publisher

verified publisherales-drnz.com

Weekly Downloads

Flutter SMB2/3 client built on top of libsmb2. Targets macOS, Windows, Linux, iOS and Android.

Repository (GitHub)
View/report issues

Topics

#cross-platform #smb2 #smb3 #samba #libsmb2

License

BSD-3-Clause (license)

Dependencies

ffi

More

Packages that depend on dart_smb2

Packages that implement dart_smb2