1#!/usr/bin/env python
2
3import _gamin
4import os.path
5
6has_debug_api = 0
7if _gamin.__dict__.has_key("MonitorDebug"):
8    has_debug_api = 1
9
10#
11# the type of events provided in the callbacks.
12#
13GAMChanged=1
14GAMDeleted=2
15GAMStartExecuting=3
16GAMStopExecuting=4
17GAMCreated=5
18GAMMoved=6
19GAMAcknowledge=7
20GAMExists=8
21GAMEndExist=9
22
23#
24# The Gamin Errno values
25GAM_OK =     0
26GAM_ARG=     1 # Bad arguments
27GAM_FILE=    2 # Bad filename
28GAM_CONNECT= 3 # Connection failure
29GAM_AUTH=    4 # Authentication failure
30GAM_MEM=     5 # Memory allocation
31GAM_UNIMPLEM=6 # Unimplemented
32GAM_INTR=    7 # Interrupted system call
33
34def GaminErrno():
35    return _gamin.Errno()
36
37def GaminErrmsg(err = None):
38    if err == None:
39	err = _gamin.Errno()
40    if err == GAM_ARG:
41        msg = "bad argument error"
42    elif err == GAM_FILE:
43        msg = "filename error"
44    elif err == GAM_CONNECT:
45        msg = "connection error"
46    elif err == GAM_AUTH:
47        msg = "authentication error"
48    elif err == GAM_MEM:
49        msg = "memory allocation error"
50    elif err == GAM_UNIMPLEM:
51        msg = "unimplemented part error"
52    elif err == GAM_INTR:
53        msg = "interrupted system call"
54    else:
55        msg = ""
56    return msg
57
58class GaminException(Exception):
59    def __init__(self, value):
60        Exception.__init__(self)
61	self.value = value
62	self.errno = GaminErrno()
63
64    def __str__(self):
65        str = GaminErrmsg(self.errno)
66	if str != "":
67            return repr(self.value) + ': ' + str
68        return repr(self.value)
69
70class WatchMonitor:
71    """This is a wrapper for a FAM connection. It uses a single connection
72       to the gamin server, over a socket. Use get_fd() to get the file
73       descriptor which allows to plug it in an usual event loop. The
74       watch_directory(), watch_file() and stop_watch() are direct mapping
75       to the FAM API. The event raised are also a direct mapping of the
76       FAM API events."""
77
78    class WatchObject:
79	def __init__ (self, monitor, mon_no, path, dir, callback, data=None):
80	    self.monitor = monitor
81	    self.callback = callback
82	    self.data = data
83	    self.path = path
84	    self.__mon_no = mon_no
85	    if dir == 1:
86		ret = _gamin.MonitorDirectory(self.__mon_no, path, self);
87		if ret < 0:
88		    raise(GaminException("Failed to monitor directory %s" %
89					 (path)))
90	    elif dir == 0:
91		ret = _gamin.MonitorFile(self.__mon_no, path, self);
92		if ret < 0:
93		    raise(GaminException("Failed to monitor file %s" %
94					 (path)))
95	    elif dir == -1:
96		ret = _gamin.MonitorDebug(self.__mon_no, path, self);
97		if ret < 0:
98		    raise(GaminException("Failed to debug %s" %
99					 (path)))
100	    self.__req_no = ret
101
102	def _internal_callback(self, path, event):
103	    # it is very important here to catch all exception which may
104	    # arise in the client callback code.
105	    try:
106		if self.data != None:
107		    self.callback (path, event, self.data)
108		else:
109		    self.callback (path, event)
110	    except:
111		import traceback
112		traceback.print_exc()
113
114	    if event == GAMAcknowledge:
115	        try:
116		    self.monitor.cancelled.remove(self)
117		except:
118		    print "gamin failed to remove from cancelled"
119		    pass
120
121	def cancel(self):
122	    ret = _gamin.MonitorCancel(self.__mon_no, self.__req_no);
123	    if ret < 0:
124		raise(GaminException("Failed to stop monitor on %s" %
125				     (self.path)))
126	    try:
127		self.monitor.cancelled.append(self)
128	    except:
129	        print "gamin cancel() failed to add to cancelled"
130
131    def __init__ (self):
132        self.__no = _gamin.MonitorConnect()
133	if self.__no < 0:
134	    raise(GaminException("Failed to connect to gam_server"))
135	self.objects = {}
136	self.__fd = _gamin.GetFd(self.__no)
137	if self.__fd < 0:
138	    _gamin.MonitorClose(self.__no)
139	    raise(GaminException("Failed to get file descriptor"))
140	self.cancelled = []
141
142    def __del__ (self):
143        self.disconnect()
144
145    def __raise_disconnected():
146	raise(GaminException("Already disconnected"))
147
148    def _debug_object(self, value, callback, data = None):
149        if has_debug_api == 0:
150	    return;
151
152        if (self.__no < 0):
153	    self.__raise_disconnected();
154        obj = self.WatchObject(self, self.__no, value, -1, callback, data)
155	# persistency need to be insured
156	self.objects["debug"] = obj
157	return obj
158
159    def disconnect(self):
160        if (self.__no >= 0):
161	    _gamin.MonitorClose(self.__no)
162	self.__no = -1;
163
164    def watch_directory(self, directory, callback, data = None):
165        if (self.__no < 0):
166	    self.__raise_disconnected();
167        directory = os.path.abspath(directory)
168
169        obj = self.WatchObject(self, self.__no, directory, 1, callback, data)
170        if self.objects.has_key(directory):
171	    self.objects[directory].append(obj)
172	else:
173	    self.objects[directory] = [obj]
174	return obj
175
176    def watch_file(self, file, callback, data = None):
177        if (self.__no < 0):
178	    self.__raise_disconnected();
179        file = os.path.abspath(file)
180
181        obj = self.WatchObject(self, self.__no, file, 0, callback, data)
182        if self.objects.has_key(file):
183	    self.objects[file].append(obj)
184	else:
185	    self.objects[file] = [obj]
186	return obj
187
188    def no_exists(self):
189        if (self.__no < 0):
190	    return
191	ret = _gamin.MonitorNoExists(self.__no)
192	return ret
193
194    def stop_watch(self, path):
195        if (self.__no < 0):
196	    return
197        path = os.path.abspath(path)
198	try:
199	    list = self.objects[path]
200	except:
201	    raise(GaminException("Resource %s is not monitored" % (path)))
202	for obj in list:
203	    obj.cancel()
204	self.objects[path] = []
205
206    def get_fd(self):
207        if (self.__no < 0):
208	    self.__raise_disconnected();
209        return self.__fd
210
211    def event_pending(self):
212        if (self.__no < 0):
213	    self.__raise_disconnected();
214        ret = _gamin.EventPending(self.__no);
215	if ret < 0:
216	    raise(GaminException("Failed to check pending events"))
217	return ret
218
219    def handle_one_event(self):
220        if (self.__no < 0):
221	    self.__raise_disconnected();
222        ret = _gamin.ProcessOneEvent(self.__no);
223	if ret < 0:
224	    raise(GaminException("Failed to process one event"))
225	return ret
226
227    def handle_events(self):
228        if (self.__no < 0):
229	    self.__raise_disconnected();
230        ret = _gamin.ProcessEvents(self.__no);
231	if ret < 0:
232	    raise(GaminException("Failed to process events"))
233	return ret
234
235def run_unit_tests():
236    def callback(path, event):
237        print "Got callback: %s, %s" % (path, event)
238    mon = WatchMonitor()
239    print "watching current directory"
240    mon.watch_directory(".", callback)
241    import time
242    time.sleep(1)
243    print "fd: ", mon.get_fd()
244    ret = mon.event_pending()
245    print "pending: ", ret
246    if ret > 0:
247        ret = mon.handle_one_event()
248	print "processed %d event" % (ret)
249	ret = mon.handle_events()
250	print "processed %d remaining events" % (ret)
251    print "stop watching current directory"
252    mon.stop_watch(".")
253    print "disconnecting"
254    del mon
255
256if __name__ == '__main__':
257    run_unit_tests()
258