1 # Copyright (c) 2008 David Aguilar
2 """Provides an inotify plugin for Linux and other systems with pyinotify"""
9 from pyinotify
import ProcessEvent
10 from pyinotify
import WatchManager
11 from pyinotify
import Notifier
12 from pyinotify
import EventsCodes
19 from PyQt4
import QtCore
22 from cola
import signals
23 from cola
import utils
24 from cola
.compat
import set
26 INOTIFY_EVENT
= QtCore
.QEvent
.User
+ 0
33 if not utils
.is_linux():
35 msg
= ('inotify: disabled\n'
36 'Note: install python-pyinotify to enable inotify.\n')
39 msg
+= ('On Debian systems '
40 'try: sudo aptitude install python-pyinotify')
41 cola
.notifier().broadcast(signals
.log_cmd
, 0, msg
)
44 # Start the notification thread
45 _thread
= GitNotifier()
47 msg
= 'inotify support: enabled'
48 cola
.notifier().broadcast(signals
.log_cmd
, 0, msg
)
49 cola
.notifier().broadcast(signals
.inotify
, True)
55 _thread
.set_abort(True)
61 """Return True if pyinotify is available."""
62 return AVAILABLE
and _thread
and _thread
.isRunning()
65 class EventReceiver(QtCore
.QObject
):
67 """Overrides event() to handle custom inotify events."""
70 if msg
.type() == INOTIFY_EVENT
:
71 cola
.notifier().broadcast(signals
.rescan
)
77 class FileSysEvent(ProcessEvent
):
78 """Generated by GitNotifier in response to inotify events"""
80 def __init__(self
, parent
):
81 """Keep a reference to the QThread parent and maintain event state"""
82 ProcessEvent
.__init
__(self
)
85 ## Timer used to prevents notification floods
86 self
._last
_event
_time
= time
.time()
88 def process_default(self
, event
):
89 """Notifies the Qt parent when actions occur"""
90 ## Limit the notification frequency to 1 per second
91 if time
.time() - self
._last
_event
_time
> 1.0:
93 self
._last
_event
_time
= time
.time()
96 class GitNotifier(QtCore
.QThread
):
97 """Polls inotify for changes and generates FileSysEvents"""
99 def __init__(self
, timeout
=250):
100 """Set up the pyinotify thread"""
101 QtCore
.QThread
.__init
__(self
)
102 self
.setTerminationEnabled(True)
104 ## QApplication receiver of Qt events
105 self
._receiver
= EventReceiver()
106 ## Git command object
107 self
._git
= cola
.model().git
109 self
._timeout
= timeout
111 self
._path
= self
._git
.worktree()
112 ## Signals thread termination
114 ## Directories to watching
115 self
._dirs
_seen
= set()
116 ## The inotify watch manager instantiated in run()
119 self
._mask
= (EventsCodes
.ALL_FLAGS
['IN_CREATE'] |
120 EventsCodes
.ALL_FLAGS
['IN_DELETE'] |
121 EventsCodes
.ALL_FLAGS
['IN_MODIFY'] |
122 EventsCodes
.ALL_FLAGS
['IN_MOVED_TO'])
124 def set_abort(self
, abort
):
125 """Tells the GitNotifier to abort"""
129 """Post a Qt event in response to inotify updates"""
131 event_type
= QtCore
.QEvent
.Type(INOTIFY_EVENT
)
132 event
= QtCore
.QEvent(event_type
)
133 QtCore
.QCoreApplication
.postEvent(self
._receiver
, event
)
135 def _watch_directory(self
, directory
):
136 """Set up a directory for monitoring by inotify"""
139 directory
= os
.path
.realpath(directory
)
140 if not os
.path
.exists(directory
):
142 if directory
not in self
._dirs
_seen
:
143 self
._wmgr
.add_watch(directory
, self
._mask
)
144 self
._dirs
_seen
.add(directory
)
146 def _is_pyinotify_08x(self
):
147 """Is this pyinotify 0.8.x?
149 The pyinotify API changed between 0.7.x and 0.8.x.
150 This allows us to maintain backwards compatibility.
152 if hasattr(pyinotify
, '__version__'):
153 if pyinotify
.__version
__[:3] == '0.8':
158 """Create the inotify WatchManager and generate FileSysEvents"""
159 # Only capture events that git cares about
160 self
._wmgr
= WatchManager()
161 if self
._is
_pyinotify
_08x
():
162 notifier
= Notifier(self
._wmgr
, FileSysEvent(self
),
163 timeout
=self
._timeout
)
165 notifier
= Notifier(self
._wmgr
, FileSysEvent(self
))
168 # self._abort signals app termination. The timeout is a tradeoff
169 # between fast notification response and waiting too long to exit.
170 while not self
._abort
:
172 self
._watch
_directory
(self
._path
)
173 # Register files/directories known to git
174 for filename
in self
._git
.ls_files().splitlines():
175 directory
= os
.path
.dirname(filename
)
176 self
._watch
_directory
(directory
)
178 notifier
.process_events()
179 if self
._is
_pyinotify
_08x
():
180 check
= notifier
.check_events()
182 check
= notifier
.check_events(timeout
=self
._timeout
)
184 notifier
.read_events()