pythonでなんかGUI作ってみる
簡単なファイル監視常駐ツールが欲しくなった
tauriとかelectoron...うーん、あ!そうだpythonでよくね?の流れ
正直バージョン管理入れるのすらだるいので直インストールする
Add python.exe to PATH にチェックを入れないと手動でパス通す必要が出てくる
pythonでGUIアプリ作る
・tkinter/PySimpleGUI:GUIフレームワーク
・pystray:タスクトレイ常駐
・pyinstaller:ビルド(マルチOS対応)
・plyer:通知とかOS機能のアクセス
・watchdoc:フォルダ監視
※ただしウイルス判定されやすい
python 対話モードの抜け方は ctrl+ZからのEnter
$ python --version
Python 3.11.1
$ pip --version
pip 22.3.1 from (python 3.11)
npm に該当するものはデフォルトでは無いらしく、今回はpoetry というものを入れる
$ curl -sSL https://install.python-poetry.org | python -
in your PATH で環境変数に入れろって言われる
結局手動かーーーい
$ poetry --version
Poetry (version 1.6.0)
常駐するやつ作ってみる
$ poetry new <project-name>
もしかするとハイフンよりアンダーバーが良い?
なんかこんな感じのプロジェクトが立ち上がる
いいね
パッケージの追加は add
これをすると pyproject.toml に追記される
$ poetry add pystray schedule
実行する
$ poetry run python py_dl_eye/__init__.py
ほーん?
めちゃ簡単やな
フォルダ監視は watchdog というものを使うらしい
上記コードのrunProgramに差し込んで見る
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)
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()
...
すごいいいいい簡単
次、windowsの通知機能
Plyer ってのを使うといいらしい
おや icon じゃないとだめみたい
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'
)
できてる!!!!簡単すぎる
あとなんだろ、ファイルのコピー機能かな?
全てのメタデータをコピーするには copy2() ってのを使うみたい
copy2() はファイルのデータとパーミッションに加え、ファイルの作成時間や変更時間などのメタデータもコピーします。
コピーで追加したとき:
・created
・modified
新規に作成したとき:
・created
名前をつけたあとに
・moved
・modified
リネームしたとき:
・moved
中身を書き換えたとき:
・modified
削除したとき:
・deleted
移動したとき:
・deleted
※
なるほど、modifiedは中身に関係があるときに発火
created deleted に関しては、このフォルダに増えたか減ったで見ている
同じファイルで名前が変わったときだけmovedが発火する
mac だと コピーや新規のときに modified が発火しないみたい
今回はダウンロードフォルダの監視 and 置き換えなので
- ★
created
だけを監視すれば良いはず - ★ 監視するのは、再帰処理した全ファイル(拡張によってはフォルダを掘ることもあるので)
- ★ byte 0 のものは対象外としたい(一時ファイルの拡張子を変えるとうまく行かないので)
- ★ まずはwindowsのみを想定する
- 末尾に
.crdownload
が付くものは、(1) (2) が付いているかを監視- windows以外の場合どうなるかを確認する
- もしついている場合、重複している通知を発火する
- 上記以外のファイルに関して監視対象とする
- もしイベントが走ったら通知?(うるさそうなので検討)
- 別の場所にファイルを移動する
- このとき、フォルダ階層は維持する
- 元の場所にbyte 0のダミーファイルを作成する
これだけ
名前をつけて保存のとき、一旦.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>
ファイルサイズ
pythonのクラスメソッドは第一引数にselfを入れないといけない
罠すぎる。。。
True / False も気持ち悪いな...
catch は except
notification.notify(
title='ファイルの保存',
message=str(event),
app_name='アプリ名だよ',
app_icon='icon.ico'
)
なんで~
拡張でディレクトリ保存したときのイベント
<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>
on_created より on_moved が適切かも
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
できてる~~~
何がしたかったかというと、ダウンロードフォルダ、自動移動するけど、(1) とかは維持したい仕組み
つまりdownloadフォルダは履歴リストになる感じ
ディレクトリが維持できてないな...
あるねぇ relpath
最後にビルドやってみる
pyinstaller
$ 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
なんじゃ
pyinstaller の場合、考えられる解決策は、python
プロパティを ">=3.11,<3.13" に設定することです。
これをpyproject.tomlに入れるだけで治った!
でも結局 pip でglobalに入れたほうがいいっぽい
https://qiita.com/Lily/items/b2d55965e417f2997324
$ ./__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!