LoginSignup
17
11

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

サーバーの共有フォルダ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のリストです…使い道がわからない。

参考

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

17
11
0

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
17
11