dart_smb2 0.0.6
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.

dart_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
- Features
- Quick Start
- Guide
- Types Reference
- Testing
- Permissions
- Project Background
- Funding
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
Iterableor asyncStream, with progress + cancel callbacks and a single persistent handle. - ⬇️ Download to File — writes an SMB file to a local
Filewith progress and cancel support. - 🧹 Scoped File Access —
withFile(path, body)opens a read handle, runs your callback with a read-on-demandSmb2File, 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
Finalizercloses 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
echoping to detect disconnections early. - 🔄 Flush & Truncate —
fsyncto persist writes andftruncateon open handles. - 🌐 Share Enumeration — list all shares on a server without an active connection.
- 🧵 Isolate-Safe — sync
Smb2Clientdesigned to run inside Dart isolates; never blocks the UI thread when used viaSmb2Pool. - 🏊 Worker Pool — multiple isolate workers with automatic reconnect on connection errors.
- 💾 Caching Layer — optional TTL-based cache for
statandlistDirectorycalls, 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: trueis 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).
4.5 Read Symlink
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.partand 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, ordownloadToFilewhenever 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
Smb2PoolHandleis garbage-collected withoutcloseHandlebeing called, acloseHandlecommand is sent to the worker from the finalizer as a best-effort cleanup. Do not rely on this — always close explicitly (or usewithFile) 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:
Smb2Poolhandles reconnection automatically. You only need manual retry logic withSmb2Client.
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



