Python
Python3
pysmb

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

More than 1 year has passed since last update.

前回に続き、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



サーバーの共有フォルダSharesource.txtを配置している。


ファイル受信

ファイル受信は以下のように行います。(インスタンスの作成や接続・切断処理は割愛)

with open('dst.txt', 'wb') as file:

conn.retrieveFile('Share', 'source.txt', file)

source.txtが受信するサーバー側のファイル、dst.txtはクライアント側に作成されるファイルです。

SMBConnection#retrieveFileメソッドを使用します。定義は以下の通り。

def retrieveFile(self, service_name, path, file_obj, timeout = 30):


  • service_name

    サーバー側の共有フォルダ名

  • path

    目的のファイルのパス

  • file_obj

    書き込むファイルのファイルオブジェクトを渡してあげる

  • timeout

    タイムアウト値です。デフォルト30秒


フォルダがある場合

受信先がフォルダ配下の場合、当然フォルダは存在していなければなりません。

with open('tmp/dst.txt', 'wb') as file:

conn.retrieveFile('Share', 'source.txt', file)

この場合でtmpフォルダがない場合、FileNotFoundErrorになります。

受信元がフォルダ配下にある場合、下記のように指定すれば良いです。

with open('dst.txt', 'wb') as file:

conn.retrieveFile('Share', 'tmp/source.txt', file)

ちなみに受信元のファイルが存在しない場合はsmb_structs.OperationFailureになります。


戻り値

必要になることはまず無いと思いますが…retrieveFile()の戻り値としてファイル属性(int)受信バイト数(int)のタプルを受け取ることができます。

ファイル属性とはsmb_constants.pyに定義されているSMB_FILE_ATTRIBUTE_xxxのビット論理和です。


中身しかいらない場合

ファイル自身を受信する必要は無くファイルの中身だけ読めればいい場合、io.BytesIO()を使うことで内容だけ受信できます。

import io

with io.BytesIO() as file:
conn.retrieveFile('Share', 'source.txt', file)
file.seek(0)
print(file.read().decode())

decode()のエンコーディングはデフォルトでutf-8なので、例えばshift-jisのファイルであれば以下のように指定することで読み込めます。

with io.BytesIO() as file:

conn.retrieveFile('Share', 'source.txt', file)
file.seek(0)
print(file.read().decode('shift-jis'))


ファイル一覧の取得

サーバーの共有フォルダにどのようなファイルがあるか知りたい場合、SMBConnection#listPathメソッドを利用してファイルのリストを取得することができます。

files = conn.listPath('Share', '/')

上記の例では共有フォルダ直下のファイル(フォルダ)のみが取得できます。共有フォルダ内にあるフォルダ配下のリストを取得したい場合は、pathに目的のフォルダパスを設定します。

# /tmp フォルダのリストを取得

files = conn.listPath('Share', '/tmp')

# /tmp/hoge フォルダのリストを取得
files = conn.listPath('Share', '/tmp/hoge')

この時pathのルート('/')はあっても無くてもいいです。

共有フォルダ直下のリストを取得する場合は'/'でも''でもいいし、tmpフォルダのリストを取得する場合は'/tmp'でも'tmp'でもいいです。

少し詳しく見てみます、SMBConnection#listPathの定義は以下の形です。

def listPath(self, service_name, path,

search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE,
pattern = '*', timeout = 30):


  • service_name

    サーバー側の共有フォルダ名

  • path

    ファイルリストを取得するフォルダパス


  • search

    検索対象の絞り込みをSMB_FILE_ATTRIBUTE_xxxのビット論理和で指定する。

    ただしSMB1でのみ有効、SMB2を使用する場合この値は使われない。

  • pattern

    検索結果のフィルタ、例えばテキストのみ抽出する場合は*.txtのように指定する。

  • timeout

    タイムアウト値です。デフォルト30秒


SharedFile

SMBConnection#listPathの結果はSharedFileインスタンスの配列で返って来ます。

その為純粋なファイル名のみが必要な場合は一工夫必要です。

files = [f.filename for f in conn.listPath('Share', '/')]

ここで返ってくるSharedFileクラスは共有フォルダから取得できるファイル/フォルダの情報を持ちます。具体的には以下のようなプロパティです。


  • filename

    ファイル名(もしくはフォルダ名)

  • short_name

    8.3形式の短いファイル名

  • file_attributes

    SMB_EXT_FILE_ATTRの論理和、詳しくは[MS-CIFS]: 2.2.1.2.3を参照

  • alloc_size

    ファイルを格納する為割り当てられたサイズ、いわゆるディスク上のサイズ(Byte)

  • file_size
    ファイルのサイズ(Byte)

  • create_time

    1970-01-01 00:00:00からの経過秒数でファイル作成日時を表す。

  • last_access_time

    1970-01-01 00:00:00からの経過秒数で最終ファイルアクセス日時を表す。

  • last_write_time

    1970-01-01 00:00:00からの経過秒数で最終ファイル更新日時を表す。

  • last_attr_change_time

    1970-01-01 00:00:00からの経過秒数で最終ファイル属性変更日時を表す。

  • isDirectory

    フォルダかどうか

  • isReadOnly

    読み取り専用かどうか

あくまで情報のみを持つクラスでこのインスタンスから直接受信処理などを行うことはできません。filenameを用いて別途retrieveFile()で受信しなければならないです。


共有フォルダのファイルを全部列挙

listPath()である程度深さのある共有フォルダからリストを取得したい場合、フォルダを指定しなければならないので若干取り回しが悪い(と思います)。

なので以下のように共有フォルダ以下(もしくは指定のフォルダ以下)のファイルを全て列挙するようにして使うと扱いやすいです。ただファイルがべらぼうに多いと非常に時間が掛かるので見に行くフォルダがどの程度の規模かは知っておく必要があります。

def list_all(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:
yield from list_all(service_name, path=f'{path}/{item.filename}')
else:
yield f'{path}/{item.filename}'

print([f for f in list_all('Share')])


共有フォルダ一覧の取得

SMBConnection#listSharesメソッドではサーバーの共有フォルダをリストで取得することができます。

SharedDeviceのリストを戻り値として受け取ることができます。フォルダ名一覧を取得する場合は以下のように使用します。

print([dev.name for dev in conn.listShares()])

SMBConnection#listSharesの引数はtimeoutのみです。デフォルト30秒でございます。


SharedDevice

SharedDeviceSharedFileのように、共有フォルダの情報を持ちます。以下のプロパティを持ちます。


  • name

    共有フォルダ名です。

  • comments

    共有フォルダのコメントです。

  • isSpecial
    IPC$ADMIN$C$D$など予約された共有名の場合Trueを返します。

  • isTemporary
    データが永続化されない一時的な共有の場合Trueを返します。

  • type
    共有フォルダのタイプです。詳しくは[MS-SRVS]: 2.2.2.4を参照


読まなくてもいい話

読まなくてもいい話というか、どう使えばいいかわからないメソッド達


retrieveFileFromOffset

retrieveFile()に対して、バイト単位でファイルの一部を受信できるretrieveFileFromOffset()というメソッドもあります…が、敢えてこのメソッドを使うシーンはほとんど無いと思います。

ただretrieveFile()では最初から最後までという条件(offset=0, max_length=-1)で、内部的にretrieveFileFromOffset()を利用してファイル受信しているようです。


listSnapshots

Windows7以降(厳密にはVista Business以降?)利用可能なフォルダのスナップショット機能が有効な場合、その一覧を取得することができます。

が、戻り値はdatetimeのリストです…使い道がわからない。


参考

再帰とジェネレータの組み合わせ