GFileMonitor

最近 GUI をやっていない。
コマンドラインばかりじゃつまんない。

で、実用的かもしれないディレクトリ内容の変更を監視し出力するウインドウを。
いや、随分前にやって上手くいかなかったのだが原因がやっと解ったので。
GUI は C では面倒すぎなので Python で。

#!/usr/bin/env python3

"""
    $HOME を監視するサンプル
"""

from gi.repository import Gtk, Gio, GLib

class Win(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.view = Gtk.TextView()
        self.view.set_editable(False)
        sw = Gtk.ScrolledWindow()
        sw.add(self.view)
        self.add(sw)
        #
        # ファイル監視も同様、現在存在していない場合でも監視してくれる
        #f = Gio.File.new_for_path(GLib.get_home_dir() + "/test.txt")
        f = Gio.File.new_for_path(GLib.get_home_dir())
        #
        # GFileMonitor は他メソッドで使わなくても必ずアトリビュートにする
        # しないと __init__ 抜けた時点で破棄されてしまう
        #
        self.monitor = f.monitor(Gio.FileMonitorFlags.NONE)
        #self.monitor = f.monitor(Gio.FileMonitorFlags.SEND_MOVED)
        self.monitor.connect("changed", self.on_monitor)
        # self
        self.connect("delete-event", Gtk.main_quit)
        self.resize(300, 200)
        self.show_all()

    def on_monitor(self, monitor, f, other_f, event):
        """
            ディレクトリ監視は隠しファイルの変更も感知します
            ( /.bash.history 等 )
        """
        buf = self.view.get_buffer()
        s = f.get_path()
        if event == Gio.FileMonitorEvent.CHANGED:
            # ファイルの内容が変更された時
            buf.insert_at_cursor("CHANGED: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.CHANGES_DONE_HINT:
            # ファイルの情報が変更された時
            buf.insert_at_cursor("CHANGES_DONE_HINT: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.DELETED:
            # 削除(リネーム、移動含む)された時
            buf.insert_at_cursor("DERETED: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.CREATED:
            # 作成(リネーム、移動含む)された時
            buf.insert_at_cursor("CREATED: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.ATTRIBUTE_CHANGED:
            # パーミッションが変わった時
            buf.insert_at_cursor("ATTRIBUTE_CHANGED: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.PRE_UNMOUNT:
            # Gio.FileMonitorFlags.WATCH_MOUNTS 指定時のみ
            buf.insert_at_cursor("PRE_UNMOUNT: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.UNMOUNTED:
            # Gio.FileMonitorFlags.WATCH_MOUNTS 指定時のみ
            buf.insert_at_cursor("UNMOUNTED: {0}\n".format(s))
        elif event == Gio.FileMonitorEvent.MOVED:
            # Gio.FileMonitorFlags.SEND_MOVED 指定時のみ
            buf.insert_at_cursor("MOVED: {0}\n".format(s))
        # Gio.FileMonitorFlags.WATCH_MOUNTS 指定時に、あれ?
        if other_f:
            buf.insert_at_cursor("other: {0}\n".format(f.get_path()))

Win()
Gtk.main()

gfilemonitor

で、結論。
GFileMonitor のインスタンスは self のアトリビュートにする必要がある。

今迄は他メソッドから参照する必要が無いものは self にはくっつけなかった。
すると全然シグナルが飛んでこない、バグだとずっと思っていた。
しかし今日 self を付けただけであら不思議。

よく考えたらガベージコレクションだから当然である。
GUI 部品なら親ウインドウにくっついているのだから何かしら参照がある。
しかしメインループで動いているだけの部品はどこからも参照されていない。
結果ガベージコレクタの破棄対象に、何かで参照しないといけない。

PyGObject を作っている皆様、コッソリ疑っていてごめんなさい。

もしかしてアレも、と思い付くのがチラホラ。

しかしファイル監視は存在しないファイルを指定してもいいんだね。
新規作成で CREATE と CHANGES_DONE_HINT のシグナルが飛んでくる。
CHANGES_DONE_HINT はちょっとうっとうしい。

それと $HOME を監視したまま gnome-terminal を終了すると解るけど。
~/.bash_history とかの隠しファイルへの書き出しにも反応する。
コレはハンドラ側で見分け等をする、でいいのかな。

gfilemonitor2

SEND_MOVED 指定も試してみた、なるほどそうなるか。
ディレクトリ移動だと結局 DELETED になる、リネームしか意味が無い。
しかし *other_file 引数は何だろう?

それよりリネーム後のファイル名が得られないと困るんだが。
バ…いやいや、コレも手段が悪いだけなのかもしれないし。
というか、使い道が思いつかないしどうでもいいかなと。

おまけで、GDataOutputStream での上書き保存のみ。

#!/usr/bin/env python3
 
from gi.repository import Gio

f = Gio.file_new_for_path("output_stream.txt")
fstream = f.replace("", False, Gio.FileCreateFlags.NONE)
dstream = Gio.DataOutputStream.new(fstream)
dstream.put_string("BKB BKB")
fstream.close()

output_stream

OutputStream ってこんなことをやっていたの!
当然の話だが Gedit の上書き保存もまったく同じ。
いやいや、監視してみると色々面白いことが解るNE!