【Python】wxPythonを使って共有フォルダにフォルダを転送してみた

　　
　　
　　
**この記事はPythonど素人が趣味で適当に書いたものです。
そのつもりでお読みください。あと助けて下さい。**

　　
##目的
[前回](https://qiita.com/OKsaiyowa/items/6b2ffe00eff8c799d0f1)の続きです。


①最新のUnityプロジェクトを共有フォルダに置いておきたい。
②でも、毎回共有フォルダ開いて新しいプロジェクトをD＆Dするのちょっとめんどくさい。
③専用Window作ってそのWindow上で完結したらちょっと楽かもね。
④あとプロジェクトのフォルダって基本重いから必要なものだけ移動できて、すぐ完了すると助かるね。

というストーリーです。
③まではなんとかなりましたが、④はまだ解決してません。
長いと30分くらいかかりました(使えねえ...)。

いい方法ないかな～と模索中です。

##最終的なコード

```python

import wx
import os
import platform
from smb.SMBConnection import SMBConnection
import shutil
from distutils.dir_util import copy_tree
import traceback


class Main(wx.Frame):

    def __init__(self, parent, id, title):

        wx.Frame.__init__(self, parent, id, title, size=(500, 300))
        panel = wx.Panel(self, id=wx.ID_ANY)

        # 旧プロジェクトファイル選択ボタン
        wx.StaticText(panel, wx.ID_ANY, label="アップロード先", pos=(10, 10))
        self.old = wx.TextCtrl(panel, wx.ID_ANY, pos=(10, 30), size=(320, 20))
        choose_button_old = wx.Button(panel, label="フォルダの選択", pos=(350, 25))
        choose_button_old.Bind(wx.EVT_BUTTON, self.choose_folder_old)

        # 最新プロジェクトファイル選択ボタン
        wx.StaticText(panel, wx.ID_ANY, label="最新プロジェクト", pos=(10, 60))
        self.latest = wx.TextCtrl(
            panel, wx.ID_ANY, pos=(10, 80), size=(320, 20))
        choose_button_latest = wx.Button(panel, label="フォルダの選択", pos=(350, 75))
        choose_button_latest.Bind(wx.EVT_BUTTON, self.choose_folder_latest)

        # 実行ボタン
        change_floder = wx.Button(panel, label="実行", pos=(350, 175))
        change_floder.Bind(wx.EVT_BUTTON, self.replace_folder)

        # 画面を表示
        self.Show(True)
        # 画面の表示位置を中央に
        self.Centre()

        # 共有フォルダに接続
        self.conn = SMBConnection(
            '',
            '',
            platform.uname().node,
            '',
            domain='WORKGROUP',
            use_ntlm_v2=True)
        self.conn.connect('IPあどれす', 139)

    def choose_folder_old(self, event):
        """ フォルダの選択ボタンを押すと呼ばれるイベント。フォルダ選択ダイアログを開き、choose_textに反映 """

        folder = wx.DirDialog(self, style=wx.DD_CHANGE_DIR,
                              message="保存先フォルダ")

        if folder.ShowModal() == wx.ID_OK:
            folder_path = folder.GetPath()
        folder.Destroy()
        self.old.SetLabel(folder_path)

    def choose_folder_latest(self, event):
        """ フォルダの選択ボタンを押すと呼ばれるイベント。フォルダ選択ダイアログを開き、choose_textに反映 """

        folder = wx.DirDialog(self, style=wx.DD_CHANGE_DIR,
                              message="保存先フォルダ")

        if folder.ShowModal() == wx.ID_OK:
            folder_path = folder.GetPath()
        folder.Destroy()
        self.latest.SetLabel(folder_path)

    def replace_folder(self, event):
        """ 実行ボタンを押すと呼ばれるイベント。旧フォルダを最新フォルダに置換 """

        sub_folder_name = ["\Assets", "\Packages", "\ProjectSettings", "\obj"]
        counter = 0

        path_old_list = []
        path_latest_list = []

        try:
            while counter < 4:
                path_old_list.append(os.path.normpath(
                    self.old.GetLabel() + sub_folder_name[counter]))
                path_latest_list.append(os.path.normpath(
                    self.latest.GetLabel() + sub_folder_name[counter]))
                counter += 1

            counter -= 1
           
            #間違って最新プロジェクトのフォルダを吹き飛ばしたらクビになるのでここで防ぐ
            if "共有フォルダに含まれる文字列" not in path_old_list[0]:
                print("パス逆じゃない？")
                raise Exception

            while counter >= 0:
                if os.path.exists(path_old_list[counter]):
                    shutil.rmtree(path_old_list[counter])
                    copy_tree(path_latest_list[counter],
                              path_old_list[counter])
                else:
                    shutil.copytree(
                        path_latest_list[counter], path_old_list[counter])
                counter -= 1

        # 例外が出たらエラーウィンドウを表示して終了
        except:
            ChildFrame(None, wx.ID_ANY, "Failed")
            traceback.print_exc()

        print("実行完了")


# 例外時に呼ばれるクラス
class ChildFrame(wx.Frame):

    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, "エラー", size=(400, 80))
        panel = wx.Panel(self, id=wx.ID_ANY)
        wx.StaticText(panel, wx.ID_ANY,
                      label="Failed : ターミナルを確認してください", pos=(10, 10))

        # 画面を表示
        self.Show(True)
        # 画面の表示位置を中央に
        self.Centre()


def main():
    app = wx.App(False)
    Main(None, wx.ID_ANY, "Project Updater")
    app.MainLoop()


if __name__ == "__main__":
    main()

```

##SMBConnection

共有フォルダにアクセスするときに便利なモジュールです。
なぜかユーザー名、パス、リモート端末名がなくても入れました。
たぶん、[匿名接続](https://qiita.com/t2kojima/items/c84a803c73daf043f053)ができていたのだと思います。
当たり前ですが、IPアドレスだけはちゃんといれないとダメでした。

**SMBConnection**は実行中プラットフォームの固有情報を必要とするので、
**platform**モジュールを使います。

```python 
import platform
from smb.SMBConnection import SMBConnection

# 共有フォルダに接続
        self.conn = SMBConnection(
            '',
            '',
            platform.uname().node,
            '',
            domain='WORKGROUP',
            use_ntlm_v2=True)
        self.conn.connect('IPあどれす', 139)
```

##shutil
本当は**[フォルダの中にファイルがあるかないか確認して、同じファイルがあればそのまま使う](https://teratail.com/questions/181052)**
みたいなことやりたかったのですが、やり方がいまいちわかりませんでした。

```python
while counter >= 0:
      if os.path.exists(path_old_list[counter]):
         shutil.rmtree(path_old_list[counter])
         copy_tree(path_latest_list[counter],
         path_old_list[counter])
      else:
         shutil.copytree(
         path_latest_list[counter], path_old_list[counter])
         counter -= 1
```

フォルダを移す方法は二通り用意してます。[【ここ】](https://qiita.com/supersaiakujin/items/12451cd2b8315fe7d054)に全てがありました。ありがとうございます。

一つは**copytree**です。
`copytree(A,B)`はA→Bにフォルダをコピーします。
**Bというフォルダを作成してAの中身をまるごとコピー**することが可能です。
　　

もう一つは**copy_tree**です。
`copy_tree(A,B)`はA→Bにフォルダをコピーします。
**既にBというフォルダが存在していた場合でもコピーが可能**です。

ただし、**上書き保存してくれるわけではない**ので、
Aフォルダの中身はAフォルダ内のフォルダおよびファイルとBフォルダ内のフォルダおよびファイルが混在した状態になります。

そのため、**shutil**モジュールの`rmtree`を使って一度共有フォルダ側のフォルダを全部消してます。

理想はこうです。
①A、Bフォルダ共通のファイル、フォルダが存在していればBフォルダに残す
②それ以外のAフォルダ内のファイル、フォルダをBフォルダにコピー

上記が最もスムーズなのですが、ファイルとフォルダが混在しているというのが非常に厄介です。
[filecmp](https://docs.python.org/ja/3/library/filecmp.html)とかでなんとかなるんじゃないかと思ったんですが
うまくいきませんでした。引き続き検証していこうと思います。

##クビになるのを防ぐ

テストしている際に、誤って逆の操作(アップロード先→ローカルの最新フォルダ)を行ってしまい
うっかりローカル環境の最新フォルダを吹き飛ばしてしまいました(もちろんテスト用に用意したフォルダです)。

"ローカルにUnityプロジェクトのフォルダおきっぱなしでバックアップとってませんでした"
なんてことはあり得ないのですが、凡ミスが重なるとクビになりかねないのでなんとかしようと思いました。

```python

#間違って最新プロジェクトのフォルダを吹き飛ばしたらクビになるのでここで防ぐ
if "共有フォルダに含まれる文字列" not in path_old_list[0]:
    print("パス逆じゃない？")
    raise Exception
```
共有フォルダに`固有の文字列`が存在すると思うので、
アップロード先として指定したパスに`固有の文字列`が含まれていないと例外が発生します。


##例外

例外時には適当なWindowを表示してエラーを知らせるようにしてます。

使うのはエンジニアなのでエラーごとに表示メッセージを変えるとかは行わずに
ターミナルで確認できるようにしました。

```python
 # 例外が出たらエラーウィンドウを表示して終了
        except:
            ChildFrame(None, wx.ID_ANY, "Failed")
            traceback.print_exc()
        print("実行完了")
```
**traceback**モジュールの`print_exc()`を利用することで
例外時にエラーの内容をターミナルに表示できます。


```python
# 例外時に呼ばれるクラス
class ChildFrame(wx.Frame):

    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, "エラー", size=(400, 80))
        panel = wx.Panel(self, id=wx.ID_ANY)
        wx.StaticText(panel, wx.ID_ANY,
                      label="Failed : ターミナルを確認してください", pos=(10, 10))

        # 画面を表示
        self.Show(True)
        # 画面の表示位置を中央に
        self.Centre()
```

　　
　　

とても勉強になりました。
もっと使いやすくしたいです。

---
2019/06/13 追記
もっと使いやすくしました　→　[プログレスバー、マルチスレッド編](https://qiita.com/OKsaiyowa/items/121149be9c788265d653)