前回に続き、pysmbを使ってファイルの送信をしてみたいと思います。
インデックス
pysmbの使い方(接続・切断)
pysmbの使い方(ファイル受信)
pysmbの使い方(ファイル送信)
pysmbの使い方(ロギング)
pysmbの使い方(匿名接続)
実行環境
クライアント
- Windows 10
- Python 3.6.3
- pysmb 1.1.22
サーバー
- Windows 7
- User : IEUser
- Pass : Passw0rd!
- HostName : IEWIN7
- Domain : WORKGROUP
- IPAddress : 172.28.0.198
ファイル送信
ファイル送信は以下のように行います。(インスタンスの作成や接続・切断処理は割愛)
今回はクライアントのカレントフォルダにあるsource.txt
をサーバーの共有フォルダShare
へ送信します。
with open('source.txt', 'rb') as file:
conn.storeFile('Share', 'dst.txt', file)
source.txt
が送信するクライアント側のファイル、dst.txt
はサーバー側に作成されるファイルです。
SMBConnection#storeFile
メソッドを使用します。定義は以下の通りです。
def storeFile(self, service_name, path, file_obj, timeout = 30):
- service_name
サーバー側の共有フォルダ名 - path
送信先ファイルのパス - file_obj
書き込むファイルのファイルオブジェクトを渡してあげる - timeout
タイムアウト値です。デフォルト30秒
ここまでretrieveFile()
とほとんど同じです。source.txt
とdst.txt
の位置が逆なくらいですね。
ちなみにサーバー側に同名のファイルが存在する場合、アラート無しで上書きなので注意しましょう。
フォルダがない場合
送信先としてフォルダを含めるパスを指定する場合、そのフォルダが存在していなければなりません。
with open('source.txt', 'rb') as file:
conn.storeFile('Share', 'temp/dst.txt', file)
上記の場合、temp
フォルダが無ければsmb_structs.OperationFailure
になります。
フォルダを作る
フォルダが無ければ作ればいいじゃない!SMBConnection#createDirectory
で作ります。
定義はこうで
def createDirectory(service_name, path, timeout=30)
こう使います
createDirectory('Share', 'temp')
一点注意点として、作成するフォルダは階層を持たない単一のフォルダである必要があります。なので親フォルダごと作成する(`temp/temp'のような)ことはできません。
ですが親フォルダがあるかどうかを1階層ずつチェックしていくのは面倒なので、以下のような関数を定義してフォルダを一気に作ります。(これも若干冗長な気がしますが…)
def exists(service_name, path):
parent = Path(path).parent.as_posix().replace('.', '/')
if parent == '/' or exists(service_name, parent):
return bool([f for f in conn.listPath(service_name, parent) if f.filename == Path(path).name])
def makedirs(service_name, path):
parent = Path(path).parent.as_posix().replace('.', '/')
if not parent == '/' and not exists(service_name, parent):
makedirs(service_name, parent)
if not exists(service_name, path):
conn.createDirectory(service_name, path)
makedirs('Share', 'aaa/bbb/ccc')
makedirs()
では引数として受け取ったpath
の親フォルダの存在をチェックして、「親フォルダが無ければ作成」を繰り返していきます。
parent
を取得する際.replace('.', '/')
としていますが、これはmakedirs()
の引数path
の書き方として、'aaa/bbb/ccc'
と'/aaa/bbb/ccc'
の両方を許容する為です。
フォルダの削除
SMBConnection#deleteDirectory
を使用します。
これも定義はこうで
def deleteDirectory(service_name, path, timeout=30)
こう使います
deleteDirectory('Share', 'temp')
フォルダ削除の注意点としては、削除するフォルダが空フォルダでなければなりません。ファイルやフォルダ(空フォルダであっても)を持つ場合はsmb_structs.OperationFailure
を投げます。
フォルダに中身があっても強制的に削除するには以下のようにします。
def exists(service_name, path):
parent = Path(path).parent.as_posix().replace('.', '/')
if parent == '/' or exists(service_name, parent):
return bool([f for f in conn.listPath(service_name, parent) if f.filename == Path(path).name])
def removedirs(service_name, path):
if exists(service_name, path):
entries = conn.listPath(service_name, path)
for item in (e for e in entries if not e.filename in ['.', '..']):
if item.isDirectory:
removedirs(service_name, f'{path}/{item.filename}')
else:
conn.deleteFiles(service_name, f'{path}/{item.filename}')
conn.deleteDirectory(service_name, path)
removedirs('Share', 'aaa/bbb/ccc')
# -> 'ccc'フォルダ配下は全て削除
ファイル削除
SMBConnection#deleteFiles
を使用します。
これも定義はこうで
def deleteFiles(service_name, path_file_pattern, timeout=30)
こう使います
deleteFiles('Share', 'temp/target.txt')
このメソッドも注意点があります。
ドキュメントでは以下のようにワイルドカードで複数ファイルを一括削除できると書いてありますが、実際やってみると例外が飛んでできません。
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
ただGitHubにはちゃんとテストが書いてあり、通っているはずなので何故例外が発生するのかわかりません…
単一ファイルをフルパス指定で削除する分には問題なく動きます。
読まなくてもいい話
storeFileFromOffsetのtruncate
SMBConnection#storeFileFromOffset
にはtruncate
という引数があり、デフォルトがFalseです。
これはどのような上書きをするかの挙動を指示するもので、以下のような動きをします。
truncate | 送信元のファイル内容 | 送信先のファイル内容 | 上書き結果 |
---|---|---|---|
True | あああ | い | あああ |
True | あ | いいい | あ |
False | あああ | い | あああ |
False | あ | いいい | あいい |
Trueの場合は普通のファイル上書きをするので分かりやすいですが、Falseの場合はファイルの元の内容を残したままバイト単位で上書きします。
SMBConnection#storeFile
も内部的にSMBConnection#storeFileFromOffset
を呼び出していますが、truncate
はTrueで呼んでいるので普通のファイル上書きになっています。
ちなみにsource.txt
、dst.txt
ともにUTF-8で書かれている場合、以下のようなこともできます。
with open('source.txt', 'rb') as file:
conn.storeFileFromOffset('Share', 'dst.txt', file, truncate=False, offset=3)
truncate | 送信元のファイル内容 | 送信先のファイル内容 | 上書き結果 |
---|---|---|---|
False | あ | いいい | いあい |
まぁ…使わないね