Complete Code Examples
JVM: File Backup Script
A complete backup script that connects to a NAS share, creates a daily backup directory, uploads all files from a local folder, lists the results, and cleans up backups older than 7 days.
import com.ctreesoft.smb.SmbClient
import com.ctreesoft.smb.SmbConfig
import com.ctreesoft.smb.TransferChunkSize
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.io.File
fun main() = runBlocking {
val config = SmbConfig(
transferChunkSize = TransferChunkSize.ServerMax
)
val client = SmbClient(config)
val share = client.connectShare(
host = "nas.local",
shareName = "backups",
username = "backupuser",
password = "s3cureP@ss",
domain = "WORKGROUP"
)
try {
// Create today's backup directory
val today = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)
val backupDir = "daily/$today"
share.createDirectory(backupDir)
// Upload all files from the local source directory
val sourceDir = File("/opt/appdata")
val localFiles = sourceDir.listFiles() ?: emptyArray()
for (file in localFiles) {
if (file.isFile) {
val remotePath = "$backupDir/${file.name}"
share.writeFile(remotePath) { sink ->
sink.write(file.readBytes())
}
println("Uploaded: ${file.name}")
}
}
// List everything in today's backup to confirm
val uploaded = share.listDirectory(backupDir).toList()
println("Backup complete: ${uploaded.size} files in $backupDir")
// Clean up backups older than 7 days
val cutoff = LocalDate.now().minusDays(7)
val allBackups = share.listDirectory("daily").toList()
for (entry in allBackups) {
val dirDate = runCatching {
LocalDate.parse(entry.name, DateTimeFormatter.ISO_LOCAL_DATE)
}.getOrNull()
if (dirDate != null && dirDate.isBefore(cutoff)) {
share.deleteDirectory("daily/${entry.name}", recursive = true)
println("Deleted old backup: ${entry.name}")
}
}
} finally {
share.close()
client.close()
}
} Android: File Browser ViewModel
A ViewModel for an Android file browser that connects to an SMB share, navigates directories, and downloads files. Uses StateFlow for reactive UI updates and handles cleanup properly.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.ctreesoft.smb.SmbClient
import com.ctreesoft.smb.SmbShare
import com.ctreesoft.smb.SmbDirectoryEntry
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import java.io.File
data class BrowserState(
val connected: Boolean = false,
val currentPath: String = "",
val entries: List<SmbDirectoryEntry> = emptyList(),
val loading: Boolean = false,
val error: String? = null
)
class SmbBrowserViewModel : ViewModel() {
private val _state = MutableStateFlow(BrowserState())
val state: StateFlow<BrowserState> = _state
private var client: SmbClient? = null
private var share: SmbShare? = null
fun connect(host: String, shareName: String, user: String, pass: String) {
viewModelScope.launch {
_state.value = _state.value.copy(loading = true, error = null)
try {
val smbClient = SmbClient()
val smbShare = smbClient.connectShare(
host = host,
shareName = shareName,
username = user,
password = pass
)
client = smbClient
share = smbShare
_state.value = _state.value.copy(
connected = true,
loading = false
)
browse("")
} catch (e: Exception) {
_state.value = _state.value.copy(
loading = false,
error = "Connection failed: ${e.message}"
)
}
}
}
fun browse(path: String) {
val smbShare = share ?: return
viewModelScope.launch {
_state.value = _state.value.copy(loading = true, error = null)
try {
val entries = smbShare.listDirectory(path).toList()
_state.value = _state.value.copy(
currentPath = path,
entries = entries,
loading = false
)
} catch (e: Exception) {
_state.value = _state.value.copy(
loading = false,
error = "Browse failed: ${e.message}"
)
}
}
}
fun downloadFile(remotePath: String, localDir: File) {
val smbShare = share ?: return
viewModelScope.launch {
_state.value = _state.value.copy(loading = true, error = null)
try {
val fileName = remotePath.substringAfterLast("/")
val localFile = File(localDir, fileName)
smbShare.readFile(remotePath) { source ->
localFile.writeBytes(source.readByteArray())
}
_state.value = _state.value.copy(loading = false)
} catch (e: Exception) {
_state.value = _state.value.copy(
loading = false,
error = "Download failed: ${e.message}"
)
}
}
}
override fun onCleared() {
super.onCleared()
share?.close()
client?.close()
}
} BlackBerry Dynamics Integration
BlackBerry Dynamics (formerly Good Dynamics) requires all network traffic to go through its secure container. Use the SmbSocketFactory parameter to provide GDSocket instances so smb-kotlin routes traffic through the BlackBerry infrastructure.
import com.ctreesoft.smb.SmbClient
import com.ctreesoft.smb.SmbConfig
import com.ctreesoft.smb.SmbSocketFactory
import com.good.gd.net.GDSocket
suspend fun connectViaBBD(
host: String,
shareName: String,
username: String,
password: String
): SmbShare {
val config = SmbConfig(
socketFactory = SmbSocketFactory { GDSocket() }
)
val client = SmbClient(config)
return client.connectShare(
host = host,
shareName = shareName,
username = username,
password = password
)
} The SmbSocketFactory callback is invoked each time smb-kotlin needs a new TCP connection. By returning a GDSocket, all SMB traffic is automatically routed through the BlackBerry Dynamics secure channel.
Server-Side: Processing Uploaded Files
A server-side function that scans an inbox folder on a file share for new CSV files, reads their content, and moves them to a processed folder after handling.
import com.ctreesoft.smb.SmbClient
import com.ctreesoft.smb.SmbShare
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.toList
suspend fun processNewUploads(share: SmbShare) {
// Find all CSV files in the inbox
val csvFiles = share.listDirectory("inbox")
.filter { it.name.endsWith(".csv", ignoreCase = true) }
.toList()
println("Found ${csvFiles.size} CSV files to process")
for (entry in csvFiles) {
val remotePath = "inbox/${entry.name}"
// Read the file content
val content = StringBuilder()
share.readFile(remotePath) { source ->
content.append(source.readUtf8())
}
// Parse and process the CSV data
val lines = content.lines()
val header = lines.firstOrNull() ?: continue
val dataRows = lines.drop(1).filter { it.isNotBlank() }
println("Processing ${entry.name}: $header")
println(" ${dataRows.size} data rows")
// Move to processed folder after handling
val destPath = "processed/${entry.name}"
share.rename(remotePath, destPath)
println(" Moved to $destPath")
}
} Streaming Large File Upload
Upload a large file to an SMB share without loading the entire file into memory. smb-kotlin uses Okio for I/O, so you can stream data directly from a local file source to the remote sink.
import com.ctreesoft.smb.SmbClient
import com.ctreesoft.smb.SmbConfig
import com.ctreesoft.smb.TransferChunkSize
import okio.buffer
import okio.source
import java.io.File
suspend fun uploadLargeFile(
share: SmbShare,
localFile: File,
remotePath: String
) {
share.writeFile(remotePath) { sink ->
localFile.source().buffer().use { fileSource ->
val buffer = ByteArray(8 * 1024 * 1024) // 8 MB chunks
while (true) {
val bytesRead = fileSource.read(buffer)
if (bytesRead == -1) break
sink.write(buffer, 0, bytesRead)
}
}
}
}
// Usage
suspend fun main() {
val config = SmbConfig(
transferChunkSize = TransferChunkSize.ServerMax
)
val client = SmbClient(config)
val share = client.connectShare(
host = "fileserver.local",
shareName = "uploads",
username = "svcaccount",
password = "p@ssword"
)
try {
val bigFile = File("/data/database-export.sql.gz")
uploadLargeFile(share, bigFile, "imports/${bigFile.name}")
println("Upload complete: ${bigFile.length() / 1_048_576} MB transferred")
} finally {
share.close()
client.close()
}
}