Skip to content

Version 4.1.0

Latest

Choose a tag to compare

@RuiNelson RuiNelson released this 23 Sep 02:53

What's New

  • Recoverable notification overflow - When a server can no longer list individual changes, typically because too many happened at once, a directory watcher now ends with the identifiable SMB.SMBStatus.notifyEnumDir instead of an unknown status. Re-read the directory and start a new watcher:
do {
    for try await changes in watcher {
        handle(changes)
    }
}
catch SMB.Error.ntStatus(.notifyEnumDir, _, _, _) {
    let entries = try await connection.listDirectory(at: "Inbox")
    rescan(entries)
}
  • Non-atomic uploads replace the destination - uploadFile(atomic: false) now defaults options to [.create, .truncate], so the remote file ends up identical to the local one. Resumed uploads (from: .offset(byte:)) never truncate, even if .truncate is passed.

Bug Fixes

  • Fixed errors being reported with the NT status and message of an earlier, unrelated failure, for example a lock conflict after a failed open surfacing as STATUS_OBJECT_NAME_NOT_FOUND.
  • Fixed directory watchers failing with STATUS_IO_TIMEOUT after staying idle longer than the connection's command timeout.
  • Fixed a potential use of freed memory when cancelling a directory watcher while its notify request was being sent or its reply received.
  • Fixed downloadFile leaving a local temporary file behind when an atomic resume failed before transferring, for example when the local file was shorter than the resume offset.
  • Fixed uploadFile leaving a remote temporary file behind when an atomic resumed upload failed or was cancelled while seeding it.
  • Fixed copyFile silently creating an empty file when the source was a directory; it now throws remotePathIsNotAFile.
  • Fixed copyFile leaving a partial destination when the copy failed, which made retries fail because the destination existed.
  • Resolved a crash in makeLink when the link destination was too long for a reparse point; it now throws ENAMETOOLONG.
  • Resolved a crash in FilesystemStat.freeBytes and availableBytes when server-reported values overflowed; they now saturate at UInt64.max.
  • Fixed File.write re-copying the remaining data on every write when given a transfer chunk size larger than the server's maximum; chunks are now clamped as documented.

Migration Guide

Non-atomic uploads without explicit options now create the destination if needed and truncate existing content. If you relied on writing into an existing file without truncating it, pass options: [] explicitly.

Before:

// Failed if "log.bin" did not exist; left stale bytes if it was longer than the local file.
try await connection.uploadFile(local: localURL, remote: "log.bin", atomic: false) { _, _, _, _ in true }

After:

// Creates or replaces "log.bin" so it matches the local file.
try await connection.uploadFile(local: localURL, remote: "log.bin", atomic: false) { _, _, _, _ in true }

// Previous behavior: write into the existing file without creating or truncating it.
try await connection.uploadFile(local: localURL, remote: "log.bin", options: [], atomic: false) { _, _, _, _ in true }