LoginSignup
5

More than 5 years have passed since last update.

pysmbの使い方(ファイル送信)

Last updated at Posted at 2018-04-23

前回に続き、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.txtdst.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を呼び出していますが、truncateTrueで呼んでいるので普通のファイル上書きになっています。

ちなみにsource.txtdst.txtともにUTF-8で書かれている場合、以下のようなこともできます。

with open('source.txt', 'rb') as file:
    conn.storeFileFromOffset('Share', 'dst.txt', file, truncate=False, offset=3)
truncate 送信元のファイル内容 送信先のファイル内容 上書き結果
False いいい いあい

まぁ…使わないね

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5