Skip to content

Commit

Permalink
To be able to use different sessions for each share.
Browse files Browse the repository at this point in the history
  • Loading branch information
kishikawakatsumi committed Oct 29, 2024
1 parent 8411fee commit a768a00
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 9 deletions.
6 changes: 5 additions & 1 deletion Sources/SMBClient/SMBClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ public class SMBClient {
FileWriter(session: session, path: Pathname.normalize(path))
}

public func availableSpace() async throws -> UInt64 {
public func treeAccessor(share: String) -> TreeAccessor {
session.treeAccessor(share: share)
}

public func availableSpace(share: String? = nil) async throws -> UInt64 {
let response = try await session.queryInfo(path: "", infoType: .fileSystem, fileInfoClass: .fileFsSizeInformation)

let sizeInformation = FileFsSizeInformation(data: response.buffer)
Expand Down
41 changes: 33 additions & 8 deletions Sources/SMBClient/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
public class Session {
private var messageId = SequenceNumber<UInt64>()
private var sessionId: UInt64 = 0
private var treeId: UInt32 = 0
private(set) var treeId: UInt32 = 0

private var signingKey: Data?

Expand All @@ -22,16 +22,39 @@ public class Session {

private let connection: Connection

public init(host: String) {
connection = Connection(host: host)
onDisconnected = { _ in }
public convenience init(host: String) {
self.init(Connection(host: host))
}

public convenience init(host: String, port: Int) {
self.init(Connection(host: host, port: port))
}

public init(host: String, port: Int) {
connection = Connection(host: host, port: port)
private init(_ connection: Connection) {
self.connection = connection
onDisconnected = { _ in }
}

func newSession() -> Session {
let session = Session(connection)

session.messageId = messageId
session.sessionId = sessionId
session.treeId = 0

session.signingKey = signingKey

session.maxTransactSize = maxTransactSize
session.maxReadSize = maxReadSize
session.maxWriteSize = maxWriteSize

return session
}

func treeAccessor(share: String) -> TreeAccessor {
TreeAccessor(session: self, share: share)
}

public func connect() async throws {
try await connection.connect()
}
Expand Down Expand Up @@ -135,16 +158,17 @@ public class Session {
}

public func enumShareAll() async throws -> [Share] {
try await treeConnect(path: "IPC$")
let treeAccessor = treeAccessor(share: "IPC$")

let createResponse = try await create(
let createResponse = try await treeAccessor.session.create(
desiredAccess: [.readData, .writeData, .appendData, .readAttributes],
fileAttributes: [.normal],
shareAccess: [.read, .write],
createDisposition: .open,
createOptions: [.nonDirectoryFile],
name: "srvsvc"
)

try await bind(fileId: createResponse.fileId)
let ioCtlResponse = try await netShareEnum(fileId: createResponse.fileId)

Expand Down Expand Up @@ -182,6 +206,7 @@ public class Session {

treeId = response.header.treeId
connectedTree = path

return response
}

Expand Down
141 changes: 141 additions & 0 deletions Sources/SMBClient/TreeAccessor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import Foundation

public class TreeAccessor {
let session: Session
let share: String

init(session: Session, share: String) {
self.session = session.newSession()
self.share = share
}

deinit {
let this = self
Task {
try await this.session().treeDisconnect()
}
}

public func listDirectory(path: String, pattern: String = "*") async throws -> [File] {
let files = try await session().queryDirectory(path: Pathname.normalize(path), pattern: pattern)
return files.map { File(fileInfo: $0) }
}

public func createDirectory(share: String? = nil, path: String) async throws {
try await session().createDirectory(path: Pathname.normalize(path.precomposedStringWithCanonicalMapping))
}

public func rename(share: String? = nil, from: String, to: String) async throws {
try await move(share: share, from: Pathname.normalize(from), to: Pathname.normalize(to))
}

public func move(share: String? = nil, from: String, to: String) async throws {
try await session().move(from: Pathname.normalize(from), to: Pathname.normalize(to.precomposedStringWithCanonicalMapping))
}

public func deleteDirectory(share: String? = nil, path: String) async throws {
try await session().deleteDirectory(path: Pathname.normalize(path))
}

public func deleteFile(share: String? = nil, path: String) async throws {
try await session().deleteFile(path: Pathname.normalize(path))
}

public func fileStat(share: String? = nil, path: String) async throws -> FileStat {
let response = try await session().fileStat(path: Pathname.normalize(path))
return FileStat(response)
}

public func existFile(share: String? = nil, path: String) async throws -> Bool {
try await session().existFile(path: Pathname.normalize(path))
}

public func existDirectory(share: String? = nil, path: String) async throws -> Bool {
try await session().existDirectory(path: Pathname.normalize(path))
}

public func fileInfo(share: String? = nil, path: String) async throws -> FileAllInformation {
let response = try await session().queryInfo(path: Pathname.normalize(path))
return FileAllInformation(data: response.buffer)
}

public func download(share: String? = nil, path: String) async throws -> Data {
let fileReader = fileReader(path: Pathname.normalize(path))

let data = try await fileReader.download()
try await fileReader.close()

return data
}

public func upload(share: String? = nil, content: Data, path: String) async throws {
try await upload(share: share, content: content, path: Pathname.normalize(path), progressHandler: { _ in })
}

public func upload(share: String? = nil, content: Data, path: String, progressHandler: (_ progress: Double) -> Void) async throws {
let fileWriter = fileWriter(share: share, path: Pathname.normalize(path))

try await fileWriter.upload(data: content, progressHandler: progressHandler)
try await fileWriter.close()
}

public func upload(share: String? = nil, fileHandle: FileHandle, path: String) async throws {
try await upload(share: share, fileHandle: fileHandle, path: path, progressHandler: { _ in })
}

public func upload(share: String? = nil, fileHandle: FileHandle, path: String, progressHandler: (_ progress: Double) -> Void) async throws {
let fileWriter = fileWriter(share: share, path: Pathname.normalize(path))

try await fileWriter.upload(fileHandle: fileHandle, progressHandler: progressHandler)
try await fileWriter.close()
}

public func upload(share: String? = nil, localPath: URL, remotePath path: String) async throws {
try await upload(share: share, localPath: localPath, remotePath: path, progressHandler: { _, _, _ in })
}

public func upload(
share: String? = nil,
localPath: URL,
remotePath path: String,
progressHandler: (_ completedFiles: Int, _ fileBeingTransferred: URL, _ bytesSent: Int64) -> Void
) async throws {
let fileWriter = fileWriter(share: share, path: Pathname.normalize(path))

try await fileWriter.upload(localPath: localPath, progressHandler: progressHandler)
try await fileWriter.close()
}

public func fileReader(share: String? = nil, path: String) -> FileReader {
FileReader(session: session, path: Pathname.normalize(path))
}

public func fileWriter(share: String? = nil, path: String) -> FileWriter {
FileWriter(session: session, path: Pathname.normalize(path))
}

public func availableSpace(share: String? = nil) async throws -> UInt64 {
let response = try await session().queryInfo(path: "", infoType: .fileSystem, fileInfoClass: .fileFsSizeInformation)

let sizeInformation = FileFsSizeInformation(data: response.buffer)
let availableAllocationUnits = sizeInformation.availableAllocationUnits
let sectorsPerAllocationUnit = sizeInformation.sectorsPerAllocationUnit
let bytesPerSector = sizeInformation.bytesPerSector

let bytesPerAllocationUnit = UInt64(sectorsPerAllocationUnit * bytesPerSector)
let availableSpaceBytes = availableAllocationUnits * bytesPerAllocationUnit

return availableSpaceBytes
}

public func keepAlive() async throws -> Echo.Response {
try await session().echo()
}

private func session() async throws -> Session {
if session.treeId == 0 {
try await session.treeConnect(path: share)
}
return session
}
}

0 comments on commit a768a00

Please sign in to comment.