1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import gtk
18 import os
19 import re
20 import subprocess
21 import pynotify
22 import base64
23 import tempfile
24
25 from GTG import _
26
27
28
29
31 PLUGIN_NAME = 'reminder'
32 VERSION = open(os.path.join(os.path.dirname(__file__), 'version')).readline()
33 NOTIFY = os.path.join(os.path.dirname(__file__), 'notify.py')
34 DEFAULT_PREFERENCES = {
35 'ask_on_task_close': False,
36 'command_open': '/usr/bin/mplayer',
37 'command_at': '/usr/bin/at',
38 'command_crontab': '/usr/bin/crontab'
39 }
40
41 kn = gtk.gdk.keyval_from_name
42 key_Return = kn("Return")
43 key_Escape = kn("Escape")
44
52
54 self.plugin_api = plugin_api
55 self.logger = self.plugin_api.get_logger()
56
57 self.preference_init()
58 self.logger.debug('the plugin v' + self.VERSION + ' is initialized')
59
61 self.textview = plugin_api.get_textview()
62 self.logger.debug('a task was opened')
63
64
66 textview = plugin_api.get_textview()
67
68 textview_start = textview.buff.get_start_iter()
69 textview_end = textview.buff.get_end_iter()
70 texte = textview.buff.serialize(textview.buff, 'application/x-gtg-task', textview_start, textview_end)
71 texte = re.sub(r'<[^>]+>', '', texte)
72 tags = map((lambda x: x.get_name().lstrip('@')), plugin_api.get_tags())
73 alarms = list(set(tags) & set(self.get_tag_names()))
74 if (len(alarms) == 0):
75 return
76 alarms_parsed = []
77 success_at = []
78 unsuccess_at = []
79 success_cron = []
80 unsuccess_cron = []
81 for alarm in alarms:
82 arr = texte.split('\n@' + alarm + ' ')
83 arr.pop(0)
84 for i in range(len(arr)):
85 m = re.search('\s*(#*[a-zA-Z+-:,\* /,-?#]+).*', arr[i])
86 if (m != None):
87 arr[i] = m.group(1)
88 for time in arr:
89 if (plugin_api.get_task().get_uuid() + time.strip() in self.reminders):
90 self.logger.debug('reminder "' + time.strip() + '" already exists for task ' + plugin_api.get_task().get_uuid())
91 continue
92 if (time[0] == '#'):
93
94 (flag, message) = self.add_cron_job(plugin_api.get_task(), alarm, time[1:].strip())
95 if (flag):
96 success_cron.append([alarm, message])
97 self.reminders[plugin_api.get_task().get_uuid() + time.strip()] = True
98 else:
99 unsuccess_cron.append([alarm, message])
100 else:
101
102 (flag, message) = self.add_at_job(plugin_api.get_task(), alarm, time.strip())
103 if (flag):
104 success_at.append([alarm, message])
105 self.reminders[plugin_api.get_task().get_uuid() + time.strip()] = True
106 else:
107 unsuccess_at.append([alarm, message])
108
109 notice_message = ''
110 error = False
111 if (len(success_at) > 0 or len(success_cron) > 0):
112 notice_message = _('Notification successfully updated.')
113 if (len(unsuccess_at) > 0 or len(unsuccess_cron) > 0):
114 error = True
115 notice_message = _('Some notification successfully updated.')
116 if (len(success_at) == 0 and len(success_cron) == 0 and (len(unsuccess_at) > 0 or len(unsuccess_cron) > 0)):
117 error = True
118 notice_message = _('Notification failed.')
119 if (len(success_at) > 0 or len(unsuccess_at) > 0):
120 notice_message = notice_message + _('\n ─── AT (atq for view) ───')
121 for job in success_at:
122 notice_message = notice_message + '\n@' + job[0] + ' at ' + job[1]
123 for job in unsuccess_at:
124 notice_message = notice_message + '\n@' + job[0] + ' - ' + job[1]
125 if (len(success_cron) > 0 or len(unsuccess_cron) > 0):
126 notice_message = notice_message + _('\n ─── CRON (crontab -l for view) ───')
127 for job in success_cron:
128 notice_message = notice_message + '\n@' + job[0] + ' at ' + job[1]
129 for job in unsuccess_cron:
130 notice_message = notice_message + '\n@' + job[0] + ' - ' + job[1]
131
132 if (notice_message != ''):
133 icon = '/usr/share/icons/gnome/48x48/status/stock_appointment-reminder.png'
134 notice = pynotify.Notification(plugin_api.get_task().get_title(), notice_message, icon)
135 if (error):
136 notice.set_timeout(0)
137 notice.show()
138 self.plugin_api.save_configuration_object(self.PLUGIN_NAME, 'reminders', self.reminders)
139 self.logger.debug('a task was closed')
140
142 self.logger.debug('the plugin was deactivated')
143
145 self.logger.debug('add cron job for task ' + task.get_uuid() + ' for alarm @' + alarm + ' at <' + time + '>')
146
147 p = subprocess.Popen(self.command_crontab + ' -l', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
148 output = ''
149 output_err = ''
150 while True:
151 stdout, stderr = p.communicate()
152 output += stdout
153 output_err += stderr
154 rc = p.poll()
155 if rc is not None:
156 break
157 if (rc == 0):
158 crontab = output
159 else:
160 self.logger.debug(output_err)
161 crontab = ''
162
163 command = ''
164 title = ' '
165 message = ' '
166 icon = ' '
167 timeout = -1
168 urgency = -1
169 if self.alarmtags[alarm][1] == 0:
170
171 if (self.alarmtags[alarm][2].strip() != ''):
172 title = task.get_title()
173 message = self.alarmtags[alarm][2]
174 else:
175 message = task.get_title()
176 elif self.alarmtags[alarm][1] == 1:
177
178 message = task.get_title()
179 command = self.command_open + ' ' + self.alarmtags[alarm][2] + ' &\n'
180 elif self.alarmtags[alarm][1] == 2:
181
182 message = task.get_title()
183 command = self.alarmtags[alarm][2] + '\n'
184 if (title == ''):
185 title = ' '
186 if (message == ''):
187 message = ' '
188 if (icon == ''):
189 icon = ' '
190 arg = 'python ' + self.NOTIFY + ' 1 ' + \
191 task.get_uuid() + ' ' + \
192 base64.b64encode(title) + ' ' + \
193 base64.b64encode(message) + ' ' + \
194 base64.b64encode(icon) + ' ' + \
195 str(timeout) + ' ' + str(urgency) + ' ' + base64.b64encode(command)
196 crontab = crontab + '\n# gtg tag @' + alarm + ' task ' + task.get_uuid() + ' ' + task.get_title()
197 crontab = crontab + '\n' + time + ' ' + arg + '\n'
198 f = tempfile.NamedTemporaryFile(delete=False)
199 f.write(crontab)
200 f.close()
201
202 p = subprocess.Popen(self.command_crontab + ' ' + f.name, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
203 output = ''
204 while True:
205 stdout, stderr = p.communicate()
206 output += stdout
207 output += stderr
208 rc = p.poll()
209 if rc is not None:
210 break
211 if (rc != 0):
212 self.logger.debug(output)
213 else:
214 output = time
215 return ((rc == 0), output)
216
218 self.logger.debug('add at job for task ' + task.get_uuid() + ' for alarm @' + alarm + ' at <' + time + '>')
219 p = subprocess.Popen(self.command_at + ' -v ' + time, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
220 command = ''
221 title = ' '
222 message = ' '
223 icon = ' '
224 timeout = 0
225 urgency = -1
226 if self.alarmtags[alarm][1] == 0:
227
228 if (self.alarmtags[alarm][2].strip() != ''):
229 title = task.get_title()
230 message = self.alarmtags[alarm][2]
231 else:
232 message = task.get_title()
233 elif self.alarmtags[alarm][1] == 1:
234
235 message = task.get_title()
236 command = self.command_open + ' ' + self.alarmtags[alarm][2] + ' &\n'
237 elif self.alarmtags[alarm][1] == 2:
238
239 message = task.get_title()
240 command = self.alarmtags[alarm][2] + '\n'
241 if (title == ''):
242 title = ' '
243 if (message == ''):
244 message = ' '
245 if (icon == ''):
246 icon = ' '
247 arg = 'python ' + self.NOTIFY + ' 0 ' + \
248 task.get_uuid() + ' ' + \
249 base64.b64encode(title) + ' ' + \
250 base64.b64encode(message) + ' ' + \
251 base64.b64encode(icon) + ' ' + \
252 str(timeout) + ' ' + str(urgency) + ' ' + base64.b64encode(command)
253 output = ''
254 while True:
255 stdout, stderr = p.communicate(arg)
256 output += stdout
257 output += stderr
258 rc = p.poll()
259 if rc is not None:
260 break
261 self.logger.debug('execute at for tag @' + alarm + ' with rc:' + str(rc) + ' and result: ' + output)
262 return ((rc == 0), output[:output.index('\n')])
263
265 '''A configurable plugin should have this method and return True'''
266 return True
267
273
274
275
276
278 self.preferences_load()
279 self.builder = gtk.Builder()
280 self.builder.add_from_file(os.path.dirname(os.path.abspath(__file__)) + '/reminder.ui')
281 self.preferences_dialog = self.builder.get_object('preferences_dialog')
282 self.treeview = self.builder.get_object('treeview')
283 self.liststore = self.builder.get_object('liststore')
284 self.liststoretype = self.builder.get_object('liststoretype')
285 self.button_apply = self.builder.get_object('button2')
286 self.button_add = self.builder.get_object('add')
287 self.button_delete = self.builder.get_object('delete')
288 self.button_find = self.builder.get_object('find')
289 self.button_cancel = self.builder.get_object('button1')
290 self.button_link = self.builder.get_object('linkbutton1')
291 self.button_command_open = self.builder.get_object('filechooserbutton1')
292 self.accelgroup = self.builder.get_object('accelgroup1')
293 self.builder.get_object('typecol').set_cell_data_func(self.builder.get_object('typeimage'), self.set_grid_status_icon)
294 SIGNAL_CONNECTIONS_DIC = {
295 'on_preferences_dialog_delete_event':
296 self.on_toolbar_cancel,
297 'on_btn_preferences_cancel_clicked':
298 self.on_toolbar_cancel,
299 'on_btn_preferences_ok_clicked':
300 self.on_toolbar_ok,
301 'on_btn_preferences_add_clicked':
302 self.on_toolbar_add,
303 'on_btn_preferences_del_clicked':
304 self.on_toolbar_del,
305 'on_btn_preferences_find_clicked':
306 self.on_toolbar_find,
307 'on_tag_name_changed':
308 self.on_grid_name_changed,
309 'on_tag_name_changing':
310 self.on_grid_name_changing,
311 'on_tag_type_changed':
312 self.on_grid_type_changed,
313 'on_tag_type_changing':
314 self.on_grid_type_changing,
315 'on_tag_arg_changed':
316 self.on_grid_arg_changed,
317 'on_tag_arg_changing':
318 self.on_grid_arg_changing,
319 'on_grid_stop_editing':
320 self.on_grid_stop_editing
321 }
322 self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
323 (key, mod) = gtk.accelerator_parse("Escape")
324 self.accelgroup.connect_group(key, mod, gtk.ACCEL_VISIBLE, self.on_accel_cancel)
325 (key, mod) = gtk.accelerator_parse("Return")
326 self.accelgroup.connect_group(key, mod, gtk.ACCEL_VISIBLE, self.on_accel_apply)
327 if self.preferences.has_key('alarmtags'):
328 self.liststore.clear()
329 for row in self.preferences['alarmtags']:
330 self.liststore.append(row)
331 self.preferences_apply()
332 self.button_command_open.set_filename(self.command_open)
333
335 self.ask_on_task_close = self.preferences['ask_on_task_close']
336 self.command_open = self.preferences['command_open']
337 self.command_at = self.preferences['command_at']
338 self.command_crontab = self.preferences['command_crontab']
339 self.alarmtags = {}
340 self.preferences['alarmtags'] = []
341 for row in self.liststore:
342 (tag_name, tag_type, tag_arg) = row
343 self.alarmtags[row[0]] = [tag_name, tag_type, tag_arg]
344 self.preferences['alarmtags'].append([tag_name, tag_type, tag_arg])
345
347 data = self.plugin_api.load_configuration_object(self.PLUGIN_NAME, 'preferences')
348 if data == None or type(data) != type (dict()):
349 self.preferences = self.DEFAULT_PREFERENCES
350 else:
351 self.preferences = data
352 for key in self.DEFAULT_PREFERENCES:
353 if not key in self.preferences:
354 self.preferences[key] = self.DEFAULT_PREFERENCES[key]
355 if self.preferences.has_key('alarmtags'):
356 self.alarmtags = {}
357 for row in self.preferences['alarmtags']:
358 (tag_name, tag_type, tag_arg) = row
359 self.alarmtags[row[0]] = [tag_name, tag_type, tag_arg]
360 self.reminders = self.plugin_api.load_configuration_object(self.PLUGIN_NAME, 'reminders')
361 if self.reminders == None or type(self.reminders) != type (dict()):
362 self.reminders = {}
363
365 self.plugin_api.save_configuration_object(self.PLUGIN_NAME, 'preferences', self.preferences)
366
367
368
370 value = model.get_value(iter, 1)
371 if value == 0:
372 cell.set_property ('stock-id', gtk.STOCK_INFO)
373 elif value == 1:
374 cell.set_property ('stock-id', gtk.STOCK_FILE)
375 elif value == 2:
376 cell.set_property ('stock-id', gtk.STOCK_EXECUTE)
377
379 self.editing = True
380 self.button_apply.set_sensitive(False)
381 self.button_add.set_sensitive(False)
382 self.button_delete.set_sensitive(False)
383 self.button_find.set_sensitive(False)
384 self.button_cancel.set_sensitive(False)
385 self.button_link.set_sensitive(False)
386
388 self.editing = False
389 self.button_apply.set_sensitive(True)
390 self.button_add.set_sensitive(True)
391 self.button_delete.set_sensitive(True)
392 self.button_find.set_sensitive(True)
393 self.button_cancel.set_sensitive(True)
394 self.button_link.set_sensitive(True)
395
398
400 self.on_grid_stop_editing()
401 treeiter = self.liststore.get_iter(path)
402 old_name = self.liststore.get_value(treeiter, 0)
403 if new_name == old_name:
404 return
405 collections = [r[0] for r in self.liststore]
406 if not new_name in collections:
407 if new_name.strip != '':
408 self.liststore.set(treeiter, 0, new_name)
409 else:
410 md = gtk.MessageDialog(self.preferences_dialog, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, 'An entity with the same tag alreaty exists')
411 md.run()
412 md.destroy()
413
416
418 self.on_grid_stop_editing()
419 value = self.liststoretype.get_value(new_iter, 0)
420 treeiter = self.liststore.get_iter(path)
421 if value == 'message':
422 self.liststore.set(treeiter, 1, 0)
423 elif value == 'resource':
424 self.liststore.set(treeiter, 1, 1)
425 elif value == 'command':
426 self.liststore.set(treeiter, 1, 2)
427
430
432 self.on_grid_stop_editing()
433 treeiter = self.liststore.get_iter(path)
434 old_arg = self.liststore.get_value(treeiter, 2)
435 if new_arg == old_arg:
436 return
437 self.liststore.set(treeiter, 2, new_arg)
438
439
440
446
453
464
483
510
511
512 - def on_accel_cancel(self, accelgroup, acceleratable, accel_key, accel_mods):
517
518 - def on_accel_apply(self, accelgroup, acceleratable, accel_key, accel_mods):
523
525 '''Return a list of the first-column treeview row values.'''
526 return [r[0] for r in self.preferences['alarmtags']]
527
528
529