Open36

pythonでなんかGUI作ってみる

mbsmbs

簡単なファイル監視常駐ツールが欲しくなった

tauriとかelectoron...うーん、あ!そうだpythonでよくね?の流れ

mbsmbs

pythonでGUIアプリ作る
・tkinter/PySimpleGUI:GUIフレームワーク
・pystray:タスクトレイ常駐
・pyinstaller:ビルド(マルチOS対応)
・plyer:通知とかOS機能のアクセス
・watchdoc:フォルダ監視
※ただしウイルス判定されやすい

mbsmbs

python 対話モードの抜け方は ctrl+ZからのEnter

$ python --version
Python 3.11.1
$ pip --version
pip 22.3.1 from (python 3.11)
mbsmbs

npm に該当するものはデフォルトでは無いらしく、今回はpoetry というものを入れる

https://qiita.com/ksato9700/items/b893cf1db83605898d8a

$ curl -sSL https://install.python-poetry.org | python -

in your PATH で環境変数に入れろって言われる
結局手動かーーーい

$ poetry --version
Poetry (version 1.6.0)
mbsmbs

常駐するやつ作ってみる

https://qiita.com/bassan/items/3025eeb6fd2afa03081b

$ poetry new <project-name>

もしかするとハイフンよりアンダーバーが良い?

なんかこんな感じのプロジェクトが立ち上がる

いいね

パッケージの追加は add
これをすると pyproject.toml に追記される

$ poetry add pystray schedule

実行する

$ poetry run python py_dl_eye/__init__.py

ほーん?
めちゃ簡単やな

mbsmbs
py_dl_eye/dir_watch_handler.py
from watchdog.events import PatternMatchingEventHandler

class DirWatchHandler(PatternMatchingEventHandler):
    def __init__(self, patterns):
        super(DirWatchHandler, self).__init__(patterns=patterns)

    # def _run_command(self):
    #     # subprocess.call([self.command, ])
    #     print(self)

    def on_moved(self, event):
        # self._run_command()
        print(event)

    def on_created(self, event):
        # self._run_command()
        print(event)

    def on_deleted(self, event):
        # self._run_command()
        print(event)

    def on_modified(self, event):
        # self._run_command()
        print(event)
py_dl_eye/__init__.py
from watchdog.observers import Observer
from dir_watch_handler import DirWatchHandler

...

    def runProgram(self):
        ....

        self.status = True

        # ディレクトリ監視
        path = "C:\\xxxxxxxx\\test"
        extension = ""
        event_handler = DirWatchHandler(["*"+extension])
        observer = Observer()
        observer.schedule(event_handler, path, recursive=True)
        observer.start()
...


mbsmbs
from watchdog.events import PatternMatchingEventHandler
from plyer import notification

class DirWatchHandler(PatternMatchingEventHandler):
    def __init__(self, patterns):
        super(DirWatchHandler, self).__init__(patterns=patterns)

    # def _run_command(self):
    #     # subprocess.call([self.command, ])
    #     print(self)

    def on_moved(self, event):
        # self._run_command()
        # print(event)
        notification.notify(
            title='on_moved',
            message=str(event),
            app_name='アプリ名だよ',
            app_icon='icon.ico'
        )

    def on_created(self, event):
        # self._run_command()
        # print(event)
        notification.notify(
            title='on_created',
            message=str(event),
            app_name='アプリ名だよ',
            app_icon='icon.ico'
        )

    def on_deleted(self, event):
        # self._run_command()
        # print(event)
        notification.notify(
            title='on_deleted',
            message=str(event),
            app_name='アプリ名だよ',
            app_icon='icon.ico'
        )

    def on_modified(self, event):
        # self._run_command()
        # print(event)
        notification.notify(
            title='on_modified',
            message=str(event),
            app_name='アプリ名だよ',
            app_icon='icon.ico'
        )

できてる!!!!簡単すぎる

mbsmbs

あとなんだろ、ファイルのコピー機能かな?

mbsmbs

コピーで追加したとき:
・created
・modified

新規に作成したとき:
・created
名前をつけたあとに
・moved
・modified

リネームしたとき:
・moved

中身を書き換えたとき:
・modified

削除したとき:
・deleted

移動したとき:
・deleted


なるほど、modifiedは中身に関係があるときに発火
created deleted に関しては、このフォルダに増えたか減ったで見ている
同じファイルで名前が変わったときだけmovedが発火する

mac だと コピーや新規のときに modified が発火しないみたい
https://qiita.com/to_obara/items/3d174b13f227cd898bae

mbsmbs

今回はダウンロードフォルダの監視 and 置き換えなので

  • created だけを監視すれば良いはず
  • ★ 監視するのは、再帰処理した全ファイル(拡張によってはフォルダを掘ることもあるので)
  • ★ byte 0 のものは対象外としたい(一時ファイルの拡張子を変えるとうまく行かないので)
  • ★ まずはwindowsのみを想定する
  • 末尾に .crdownload が付くものは、(1) (2) が付いているかを監視
    • windows以外の場合どうなるかを確認する
    • もしついている場合、重複している通知を発火する
  • 上記以外のファイルに関して監視対象とする
    • もしイベントが走ったら通知?(うるさそうなので検討)
    • 別の場所にファイルを移動する
      • このとき、フォルダ階層は維持する
    • 元の場所にbyte 0のダミーファイルを作成する

これだけ

mbsmbs

名前をつけて保存のとき、一旦.tmpが発火するみたい

<FileCreatedEvent: event_type=created, src_path='D:\Downloads\86b58529-4915-4ddd-aab6-dee5c627d4d7.tmp', is_directory=False>
<FileCreatedEvent: event_type=created, src_path='D:\Downloads\Screenshot from 2022-06-05 19-04-23.png', is_directory=False>

mbsmbs

pythonのクラスメソッドは第一引数にselfを入れないといけない
罠すぎる。。。

mbsmbs

True / False も気持ち悪いな...

mbsmbs

        notification.notify(
            title='ファイルの保存',
            message=str(event),
            app_name='アプリ名だよ',
            app_icon='icon.ico'
        )
mbsmbs

拡張でディレクトリ保存したときのイベント

<DirModifiedEvent: event_type=modified, src_path='D:\\Downloads\\2d', is_directory=True>
<FileMovedEvent: src_path='D:\\Downloads\\2d\\hinatazaka46_art202306 (1).jpg.crdownload', dest_path='D:\\Downloads\\2d\\hinatazaka46_art202306 (1).jpg', is_directory=False>
<DirModifiedEvent: event_type=modified, src_path='D:\\Downloads\\2d', is_directory=True>
<FileModifiedEvent: event_type=modified, src_path='D:\\Downloads\\2d\\hinatazaka46_art202306 (1).jpg', is_directory=False>
<FileModifiedEvent: event_type=modified, src_path='D:\\Downloads\\2d\\hinatazaka46_art202306 (1).jpg', is_directory=False>

名前をつけて保存したとき

<FileCreatedEvent: event_type=created, src_path='D:\\Downloads\\thumb.avif', is_directory=False>
<FileDeletedEvent: event_type=deleted, src_path='D:\\Downloads\\thumb.avif', is_directory=False>
<FileMovedEvent: src_path='D:\\Downloads\\thumb.avif.crdownload', dest_path='D:\\Downloads\\thumb.avif', is_directory=False>
<FileModifiedEvent: event_type=modified, src_path='D:\\Downloads\\thumb.avif', is_directory=False>
mbsmbs
poetry run python py_dl_eye/__init__.py
<FileMovedEvent: src_path='D:\\Downloads\\387bb838-7c16-4f25-a6e2-46834bae151d.tmp', dest_path='D:\\Downloads\\a2NXcYnb83mXMtq8yIBVgDKCzq-n5C_rFsUEUhd6nMo.webp.crdownload', is_directory=False>
src_ext: .tmp
[skip] Not target.
<FileMovedEvent: src_path='D:\\Downloads\\a2NXcYnb83mXMtq8yIBVgDKCzq-n5C_rFsUEUhd6nMo.webp.crdownload', dest_path='D:\\Downloads\\a2NXcYnb83mXMtq8yIBVgDKCzq-n5C_rFsUEUhd6nMo.webp', is_directory=False>
src_ext: .crdownload
move_path: D:\Move\a2NXcYnb83mXMtq8yIBVgDKCzq-n5C_rFsUEUhd6nMo.webp
move_parent_dir: D:\Move
copied: D:\Move\a2NXcYnb83mXMtq8yIBVgDKCzq-n5C_rFsUEUhd6nMo.webp
cleared: D:\Downloads\a2NXcYnb83mXMtq8yIBVgDKCzq-n5C_rFsUEUhd6nMo.webp

<FileMovedEvent: src_path='D:\\Downloads\\2d\\71o86vaForL._AC_UL320_.jpg.crdownload', dest_path='D:\\Downloads\\2d\\71o86vaForL._AC_UL320_.jpg', is_directory=False>
src_ext: .crdownload
move_path: D:\Move\71o86vaForL._AC_UL320_.jpg
move_parent_dir: D:\Move
copied: D:\Move\71o86vaForL._AC_UL320_.jpg
cleared: D:\Downloads\2d\71o86vaForL._AC_UL320_.jpg
mbsmbs

何がしたかったかというと、ダウンロードフォルダ、自動移動するけど、(1) とかは維持したい仕組み
つまりdownloadフォルダは履歴リストになる感じ

mbsmbs

ディレクトリが維持できてないな...

mbsmbs
$ poetry add pyinstaller
Using version ^5.13.1 for pyinstaller

Updating dependencies
Resolving dependencies... (0.0s)

The current project's Python requirement (>=3.11,<4.0) is not compatible with some of the required packages Python requirement:
  - pyinstaller requires Python <3.13,>=3.7, so it will not be satisfied for Python >=3.13,<4.0

Because no versions of pyinstaller match >5.13.1,<6.0.0
 and pyinstaller (5.13.1) requires Python <3.13,>=3.7, pyinstaller is forbidden.
So, because py-dl-eye depends on pyinstaller (^5.13.1), version solving failed.

  • Check your dependencies Python requirement: The Python requirement can be specified via the `python` or `markers` properties

    For pyinstaller, a possible solution would be to set the `python` property to ">=3.11,<3.13"

    https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies,
    https://python-poetry.org/docs/dependency-specification/#using-environment-markers

なんじゃ

mbsmbs

pyinstaller の場合、考えられる解決策は、python プロパティを ">=3.11,<3.13" に設定することです。

これをpyproject.tomlに入れるだけで治った!

でも結局 pip でglobalに入れたほうがいいっぽい

mbsmbs
$ ./__init__.exe
Traceback (most recent call last):
  File "py_dl_eye\__init__.py", line 3, in <module>
ModuleNotFoundError: No module named 'watchdog'
[21180] Failed to execute script '__init__' due to unhandled exception!