comparison classic/getan.py @ 115:32dad62909c3

Convert classic getan into a module to be able to install it
author Björn Ricks <bjoern.ricks@intevation.de>
date Mon, 12 Dec 2011 09:36:21 +0100
parents classic/getan@9c4e8ba3c4fa
children
comparison
equal deleted inserted replaced
114:6df408534f3f 115:32dad62909c3
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # getan
5 # -----
6 # (c) 2008 by Sascha L. Teichmann <sascha.teichmann@intevation.de>
7 #
8 # A python worklog-alike to log what you have 'getan' (done).
9 #
10 # This is Free Software licensed under the terms of GPLv3 or later.
11 # For details see LICENSE coming with the source of 'getan'.
12 #
13 import sys
14 import re
15 import curses
16 import curses.ascii
17 import traceback
18 import signal
19
20 from datetime import datetime, timedelta, tzinfo
21
22 from pysqlite2 import dbapi2 as db
23
24 PAUSED = 0
25 RUNNING = 1
26 PRE_EXIT = 2
27 PAUSED_ESC = 3
28 RUNNING_ESC = 4
29
30 SPACE = re.compile("[\\t ]+")
31
32 DEFAULT_DATABASE = "time.db"
33
34 LOAD_ACTIVE_PROJECTS = '''
35 SELECT id, key, description, total
36 FROM projects LEFT JOIN
37 (SELECT
38 project_id,
39 sum(strftime('%s', stop_time) - strftime('%s', start_time)) AS total
40 FROM entries
41 GROUP BY project_id) ON project_id = id
42 WHERE active
43 '''
44
45 WRITE_LOG = '''
46 INSERT INTO entries (project_id, start_time, stop_time, description)
47 VALUES(:project_id, :start_time, :stop_time, :description)
48 '''
49
50 CREATE_PROJECT = '''
51 INSERT INTO projects (key, description) VALUES (:key, :description)
52 '''
53
54 LAST_PROJECT_ID = '''
55 SELECT last_insert_rowid()
56 '''
57
58 RENAME_PROJECT = '''
59 UPDATE projects set key = :key, description = :description WHERE id = :id
60 '''
61
62 ASSIGN_LOGS = '''
63 UPDATE entries SET project_id = :new_id WHERE project_id = :old_id
64 '''
65
66 DELETE_PROJECT = '''
67 DELETE FROM projects WHERE id = :id
68 '''
69
70 # XXX: This is not very efficent!
71 LAST_ENTRY = '''
72 SELECT id, strftime('%s', start_time), strftime('%s', stop_time) FROM entries
73 WHERE project_id = :project_id
74 ORDER by strftime('%s', stop_time) DESC LIMIT 1
75 '''
76
77 DELETE_ENTRY = '''
78 DELETE FROM entries WHERE id = :id
79 '''
80
81 UPDATE_STOP_TIME = '''
82 UPDATE entries SET stop_time = :stop_time WHERE id = :id
83 '''
84
85 worklog = None
86 stdscr = None
87
88 orig_vis = None
89
90 def cursor_visible(flag):
91 global orig_vis
92 try:
93 old = curses.curs_set(flag)
94 if orig_vis is None: orig_vis = old
95 return old
96 except:
97 pass
98 return 1
99
100 def restore_cursor():
101 global orig_vis
102 if not orig_vis is None:
103 curses.curs_set(orig_vis)
104
105 def render_header(ofs=0):
106 global stdscr
107 stdscr.attron(curses.A_BOLD)
108 stdscr.addstr(ofs, 5, "getan v0.1")
109 stdscr.addstr(ofs+1, 3, "--------------")
110 stdscr.attroff(curses.A_BOLD)
111 return ofs + 2
112
113 def render_quit(ofs=0):
114 global stdscr
115 stdscr.addstr(ofs + 2, 3, "Press DEL once more to quit")
116 return ofs + 3
117
118 def tolerantClose(cur):
119 if cur:
120 try: cur.close()
121 except: pass
122
123 def ifNull(v, d):
124 if v is None: return d
125 return v
126
127 def human_time(delta):
128 seconds = delta.seconds
129 s = seconds % 60
130 if delta.microseconds >= 500000: s += 1
131 seconds /= 60
132 m = seconds % 60
133 seconds /= 60
134 out = "%02d:%02d:%02d" % (seconds, m, s)
135 if delta.days:
136 out = "%dd %s" % (delta.days, out)
137 return out
138
139 FACTORS = {
140 's': 1,
141 'm': 60,
142 'h' : 60*60,
143 'd': 24*60*60}
144
145 def human_seconds(timespec):
146 """Translate human input to seconds, default factor is minutes"""
147 total = 0
148 for v in timespec.split(':'):
149 factor = FACTORS.get(v[-1])
150 if factor: v = v[:-1]
151 else: factor = 60
152 total += int(v) * factor
153 return total
154
155 ESC_MAP = {
156 curses.KEY_F1 : ord('1'),
157 curses.KEY_F2 : ord('2'),
158 curses.KEY_F3 : ord('3'),
159 curses.KEY_F4 : ord('4'),
160 curses.KEY_F5 : ord('5'),
161 curses.KEY_F6 : ord('6'),
162 curses.KEY_F7 : ord('7'),
163 curses.KEY_F8 : ord('8'),
164 curses.KEY_F9 : ord('9'),
165 curses.KEY_F10: ord('0'),
166 }
167
168 ZERO = timedelta(0)
169
170 class UTC(tzinfo):
171 """UTC"""
172
173 def utcoffset(self, dt):
174 return ZERO
175
176 def tzname(self, dt):
177 return "UTC"
178
179 def dst(self, dt):
180 return ZERO
181
182 class Project:
183
184 def __init__(self, id = None, key = None, desc = None, total = 0):
185 self.id = id
186 self.key = key
187 self.desc = desc
188 self.total = timedelta(seconds = ifNull(total, 0))
189 self.start_time = None
190
191 def checkExistence(self, cur):
192 if self.id is None:
193 cur.execute(CREATE_PROJECT, {
194 'key' : self.key,
195 'description': self.desc})
196 cur.execute(LAST_PROJECT_ID)
197 row = cur.fetchone()
198 cur.connection.commit()
199 self.id = row[0]
200
201 def writeLog(self, cur, description = None):
202 if self.start_time is None: return
203 self.checkExistence(cur)
204 now = datetime.now()
205 cur.execute(WRITE_LOG, {
206 'project_id' : self.id,
207 'start_time' : self.start_time,
208 'stop_time' : now,
209 'description': description})
210 self.total += now-self.start_time
211 return now
212
213 def getId(self, cur):
214 self.checkExistence(cur)
215 return self.id
216
217 def rename(self, cur, key, desc):
218 self.key = key
219 self.desc = desc
220 self.checkExistence(cur)
221 cur.execute(RENAME_PROJECT, {
222 'key' : key,
223 'description': desc,
224 'id' : self.id })
225 cur.connection.commit()
226
227 def assignLogs(self, cur, anon):
228 self.total += anon.total
229 anon.total = timedelta(seconds=0)
230 old_id = anon.getId(cur)
231 new_id = self.getId(cur)
232 cur.execute(ASSIGN_LOGS, {
233 'new_id': new_id,
234 'old_id': old_id})
235 cur.connection.commit()
236
237 def delete(self, cur):
238 pid = self.getId(cur)
239 cur.execute(DELETE_PROJECT, { 'id': pid })
240 cur.connection.commit()
241
242 def subtractTime(self, cur, seconds):
243 subtractTimeed, zero = timedelta(), timedelta()
244 pid = {'project_id': self.getId(cur)}
245 utc = UTC()
246 while seconds > zero:
247 cur.execute(LAST_ENTRY, pid)
248 row = cur.fetchone()
249 if row is None: break
250 # TODO: evaluate egenix-mx
251 start_time = datetime.fromtimestamp(float(row[1]), utc)
252 stop_time = datetime.fromtimestamp(float(row[2]), utc)
253 runtime = stop_time - start_time
254 if runtime <= seconds:
255 cur.execute(DELETE_ENTRY, { 'id': row[0] })
256 cur.connection.commit()
257 seconds -= runtime
258 subtractTimeed += runtime
259 else:
260 stop_time -= seconds
261 cur.execute(UPDATE_STOP_TIME, {
262 'id': row[0],
263 'stop_time': stop_time})
264 cur.connection.commit()
265 subtractTimeed += seconds
266 break
267
268 self.total -= subtractTimeed
269 return subtractTimeed
270
271 def addTime(self, cur, seconds, description):
272 now = datetime.now()
273 cur.execute(WRITE_LOG, {
274 'project_id' : self.getId(cur),
275 'start_time' : now - seconds,
276 'stop_time' : now,
277 'description': description
278 })
279 cur.connection.commit()
280 self.total += seconds
281
282 def build_tree(project, depth):
283 if len(project.key) == depth+1:
284 return ProjectNode(project, project.key[depth])
285 node = ProjectNode(None, project.key[depth])
286 node.children.append(build_tree(project, depth+1))
287 return node
288
289 class ProjectNode:
290
291 def __init__(self, project = None, key = None):
292 self.children = []
293 self.project = project
294 self.key = key
295
296 def insertProject(self, project, depth = 0):
297
298 if not project.key: # anonym -> end
299 node = ProjectNode(project)
300 self.children.append(node)
301 return
302
303 for i, child in enumerate(self.children):
304 if not child.key: # before anonym projects
305 self.children.insert(i, build_tree(project, depth))
306 return
307 if child.key == project.key[depth]:
308 child.insertProject(project, depth+1)
309 return
310 self.children.append(build_tree(project, depth))
311
312 def removeProject(self, project):
313
314 if self.isLeaf(): return
315 stack = [self]
316 while stack:
317 parent = stack.pop()
318 for child in parent.children:
319 if not child.isLeaf():
320 stack.append(child)
321 continue
322 if child.project == project:
323 parent.children.remove(child)
324 return
325
326 def isLeaf(self):
327 return not self.project is None
328
329 def findProject(self, key):
330 l, lower = key.lower(), None
331 for child in self.children:
332 if child.key == key:
333 return child
334 if child.key and child.key.lower() == l:
335 lower = child
336 return lower
337
338 def dump(self, depth = 0):
339 out = []
340 indent = " " * depth
341 out.append("%skey: %s" % (indent, self.key))
342 if self.project:
343 out.append("%sdescription: %s" % (indent, self.project.desc))
344 for child in self.children:
345 out.append(child.dump(depth+1))
346 return "\n".join(out)
347
348 class Worklog:
349
350 def __init__(self, database):
351 self.initDB(database)
352 self.projects = []
353 self.tree = ProjectNode()
354 self.state = PAUSED
355 self.current_project = None
356 self.selection = self.tree
357 self.stack = []
358 self.loadProjects()
359
360 def initDB(self, database):
361 self.con = db.connect(database)
362
363 def loadProjects(self):
364 cur = None
365 try:
366 cur = self.con.cursor()
367 cur.execute(LOAD_ACTIVE_PROJECTS)
368 while True:
369 row = cur.fetchone()
370 if not row: break
371 project = Project(*row)
372 self.projects.append(project)
373 self.tree.insertProject(project)
374 finally:
375 tolerantClose(cur)
376
377 def shutdown(self):
378 self.con.close()
379
380 def fetchStack(self):
381 cut = ''.join([chr(i) for i in self.stack])
382 self.stack = []
383 return cut
384
385 def findProject(self, key):
386 key_lower = key.lower()
387 lower = None
388
389 for p in self.projects:
390 if p.key == key:
391 return p
392 if p.key and p.key.lower() == key_lower:
393 lower = p
394
395 return lower
396
397 def findAnonymProject(self, num):
398 count = 0
399 for p in self.projects:
400 if p.key is None:
401 if count == num:
402 return p
403 count += 1
404 return None
405
406 def renameAnonymProject(self, num, key, description):
407 project = self.findAnonymProject(num)
408 if project:
409 cur = None
410 try:
411 cur = self.con.cursor()
412 project.rename(cur, key, description)
413 finally:
414 tolerantClose(cur)
415 self.tree.removeProject(project)
416 self.tree.insertProject(project)
417
418 def assignLogs(self, num, key):
419 anon = self.findAnonymProject(num)
420 if anon is None: return
421 project = self.findProject(key)
422 if project is None: return
423 cur = None
424 try:
425 cur = self.con.cursor()
426 project.assignLogs(cur, anon)
427 self.projects.remove(anon)
428 anon.delete(cur)
429 finally:
430 tolerantClose(cur)
431
432 def addTime(self, key, seconds, description = None):
433 project = self.findProject(key)
434 if project is None: return
435 cur = None
436 try:
437 cur = self.con.cursor()
438 project.addTime(cur, seconds, description)
439 finally:
440 tolerantClose(cur)
441
442 def subtractTime(self, key, seconds):
443 project = self.findProject(key)
444 if project is None: return
445 cur = None
446 try:
447 cur = self.con.cursor()
448 project.subtractTime(cur, seconds)
449 finally:
450 tolerantClose(cur)
451
452 def isRunning(self):
453 return self.state in (RUNNING, RUNNING_ESC)
454
455 def totalTime(self):
456 sum = timedelta()
457 for p in self.projects:
458 sum += p.total
459 return sum
460
461 def render(self, ofs=0):
462 ofs = render_header(ofs)
463 ml = max([len(p.desc and p.desc or "unknown") for p in self.projects])
464 unknown = 0
465
466 if self.current_project and self.current_project.start_time:
467 current_delta = datetime.now() - self.current_project.start_time
468 current_time_str = "%s " % human_time(current_delta)
469 current_time_space = " " * len(current_time_str)
470 else:
471 current_delta = timedelta()
472 current_time_str = ""
473 current_time_space = ""
474
475 for project in self.projects:
476 is_current = project == self.current_project
477 pref = is_current and " -> " or " "
478 if project.key is None:
479 key = "^%d" % unknown
480 unknown += 1
481 else:
482 key = " %s" % project.key
483 desc = project.desc is None and "unknown" or project.desc
484 stdscr.attron(curses.A_BOLD)
485 stdscr.addstr(ofs, 0, "%s%s" % (pref, key))
486 stdscr.attroff(curses.A_BOLD)
487 stdscr.addstr(" %s" % desc)
488
489 diff = ml - len(desc) + 1
490 stdscr.addstr(" " * diff)
491 if is_current: stdscr.attron(curses.A_UNDERLINE)
492
493 if is_current:
494 stdscr.addstr("%s(%s)" % (
495 current_time_str,
496 human_time(project.total + current_delta)))
497 else:
498 stdscr.addstr("%s(%s)" % (
499 current_time_space,
500 human_time(project.total)))
501
502 if is_current: stdscr.attroff(curses.A_UNDERLINE)
503 ofs += 1
504
505 total_str = "(%s)" % human_time(self.totalTime() + current_delta)
506 total_x_pos = ml + 8 + len(current_time_space)
507
508 stdscr.addstr(ofs, total_x_pos, "=" * len(total_str))
509 ofs += 1
510 stdscr.addstr(ofs, total_x_pos, total_str)
511 ofs += 1
512
513 return ofs
514
515 def writeLog(self, description = None):
516 if self.current_project is None:
517 return datetime.now()
518 cur = None
519 try:
520 cur = self.con.cursor()
521 now = self.current_project.writeLog(cur, description)
522 self.con.commit()
523 return now
524 finally:
525 tolerantClose(cur)
526
527 def pausedState(self, c):
528 c2 = ESC_MAP.get(c)
529 if c2:
530 self.pausedEscapeState(c2)
531 return
532
533 global stdscr
534 if c in (curses.KEY_DC, curses.KEY_BACKSPACE):
535 stdscr.erase()
536 ofs = render_quit(self.render())
537 stdscr.refresh()
538 self.state = PRE_EXIT
539
540 elif c == curses.ascii.ESC:
541 self.state = PAUSED_ESC
542
543 elif curses.ascii.isascii(c):
544 if c == ord('-'):
545 self.selection = self.tree
546 stdscr.erase()
547 ofs = self.render()
548 old_cur = cursor_visible(1)
549 curses.echo()
550 stdscr.addstr(ofs + 1, 3, "<key> <minutes>: ")
551 key = stdscr.getstr()
552 curses.noecho()
553 cursor_visible(old_cur)
554 key = key.strip()
555 if key:
556 parts = SPACE.split(key, 1)
557 if len(parts) > 1:
558 key, timespec = parts[0], parts[1]
559 try:
560 seconds = human_seconds(timespec)
561 if seconds > 0:
562 seconds = timedelta(seconds=seconds)
563 self.subtractTime(key, seconds)
564 except ValueError:
565 pass
566 stdscr.erase()
567 self.render()
568 stdscr.refresh()
569
570 elif c == ord('+'):
571 self.selection = self.tree
572 stdscr.erase()
573 ofs = self.render()
574 old_cur = cursor_visible(1)
575 curses.echo()
576 stdscr.addstr(ofs + 1, 3, "<key> <minutes> [<description>]: ")
577 key = stdscr.getstr()
578 curses.noecho()
579 cursor_visible(old_cur)
580 key = key.strip()
581 if key:
582 parts = SPACE.split(key, 2)
583 if len(parts) > 1:
584 key, timespec = parts[0], parts[1]
585 if len(parts) > 2: desc = parts[2]
586 else: desc = None
587 try:
588 seconds = human_seconds(timespec)
589 if seconds > 0:
590 seconds = timedelta(seconds=seconds)
591 self.addTime(key, seconds, desc)
592 except ValueError:
593 pass
594 stdscr.erase()
595 self.render()
596 stdscr.refresh()
597
598 else:
599 node = self.selection.findProject(chr(c))
600 if not node:
601 self.selection = self.tree
602 return
603 if node.isLeaf():
604 self.selection = self.tree
605 nproject = node.project
606 self.current_project = nproject
607 nproject.start_time = datetime.now()
608 stdscr.erase()
609 ofs = self.render()
610 stdscr.refresh()
611 self.state = RUNNING
612 signal.signal(signal.SIGALRM, alarm_handler)
613 signal.alarm(1)
614 else:
615 self.selection = node
616
617 def runningState(self, c):
618 global stdscr
619 c2 = ESC_MAP.get(c)
620 if c2:
621 self.runningEscapeState(c2)
622 return
623
624 if c == curses.ascii.ESC:
625 self.state = RUNNING_ESC
626
627 elif c == curses.ascii.NL:
628 signal.signal(signal.SIGALRM, signal.SIG_IGN)
629 self.state = PAUSED
630 stdscr.erase()
631 ofs = self.render()
632 old_cur = cursor_visible(1)
633 curses.echo()
634 stdscr.addstr(ofs + 1, 3, "Description: ")
635 description = stdscr.getstr()
636 curses.noecho()
637 cursor_visible(old_cur)
638 self.writeLog(description)
639 self.current_project = None
640 stdscr.erase()
641 ofs = self.render()
642 stdscr.refresh()
643 signal.signal(signal.SIGALRM, alarm_handler)
644 signal.alarm(1)
645 elif c == ord('+'):
646 signal.signal(signal.SIGALRM, signal.SIG_IGN)
647 stdscr.erase()
648 ofs = self.render()
649 if self.stack:
650 timespec = self.fetchStack()
651 else:
652 old_cur = cursor_visible(1)
653 curses.echo()
654 stdscr.addstr(ofs + 1, 3, "Enter time to add: ")
655 timespec = stdscr.getstr()
656 curses.noecho()
657 cursor_visible(old_cur)
658 stdscr.erase()
659 ofs = self.render()
660 try:
661 seconds = human_seconds(timespec)
662 if seconds > 0:
663 seconds = timedelta(seconds=seconds)
664 self.current_project.start_time -= seconds
665 stdscr.addstr(ofs + 1, 3, "added %s" % human_time(seconds))
666 except (ValueError, IndexError):
667 pass
668 stdscr.refresh()
669 signal.signal(signal.SIGALRM, alarm_handler)
670 signal.alarm(1)
671 elif c == ord('-'):
672 signal.signal(signal.SIGALRM, signal.SIG_IGN)
673 stdscr.erase()
674 ofs = self.render()
675 if self.stack:
676 timespec = self.fetchStack()
677 else:
678 old_cur = cursor_visible(1)
679 curses.echo()
680 stdscr.addstr(ofs + 1, 3, "Enter time to subtract: ")
681 timespec = stdscr.getstr()
682 curses.noecho()
683 cursor_visible(old_cur)
684 stdscr.erase()
685 ofs = self.render()
686 try:
687 seconds = human_seconds(timespec)
688 if seconds > 0:
689 now = datetime.now()
690 seconds = timedelta(seconds=seconds)
691 self.current_project.start_time += seconds
692 stdscr.addstr(ofs + 1, 3, "subtracted %s" % human_time(seconds))
693 if self.current_project.start_time > now:
694 seconds = self.current_project.start_time - now
695 self.current_project.start_time = now
696 cur = None
697 try:
698 cur = self.con.cursor()
699 self.current_project.subtractTime(cur, seconds)
700 finally:
701 tolerantClose(cur)
702 except (ValueError, IndexError):
703 pass
704 stdscr.refresh()
705 signal.signal(signal.SIGALRM, alarm_handler)
706 signal.alarm(1)
707 elif self.stack or curses.ascii.isdigit(c):
708 self.stack.append(c)
709 elif curses.ascii.isascii(c):
710 project_node = self.selection.findProject(chr(c))
711 if project_node is None:
712 self.selection = self.tree
713 return
714
715 if project_node.isLeaf():
716 self.selection = self.tree
717 nproject = project_node.project
718 if nproject == self.current_project:
719 return
720 nproject.start_time = self.writeLog()
721 self.current_project = nproject
722 stdscr.erase()
723 ofs = self.render()
724 stdscr.refresh()
725 else:
726 self.selection = project_node
727
728 def pausedEscapeState(self, c):
729 global stdscr
730 if curses.ascii.isdigit(c):
731 pnum = c - ord('0')
732 nproject = self.findAnonymProject(pnum)
733 if nproject is None:
734 nproject = Project()
735 self.projects.append(nproject)
736
737 nproject.start_time = self.writeLog()
738 self.current_project = nproject
739 self.state = RUNNING
740 stdscr.erase()
741 ofs = self.render()
742 stdscr.refresh()
743 signal.signal(signal.SIGALRM, alarm_handler)
744 signal.alarm(1)
745 elif curses.ascii.isalpha(c):
746 if c == ord('n'):
747 stdscr.erase()
748 ofs = self.render()
749 old_cur = cursor_visible(1)
750 curses.echo()
751 stdscr.addstr(ofs + 1, 3, "<num> <key> <description>: ")
752 stdscr.refresh()
753 description = stdscr.getstr()
754 curses.noecho()
755 cursor_visible(old_cur)
756
757 description = description.strip()
758 if description:
759 num, key, description = SPACE.split(description, 2)
760 try:
761 num = int(num)
762 self.renameAnonymProject(num, key, description)
763 except ValueError:
764 pass
765
766 stdscr.erase()
767 ofs = self.render()
768 stdscr.refresh()
769 self.state = PAUSED
770
771 elif c == ord('a'):
772 stdscr.erase()
773 ofs = self.render()
774 old_cur = cursor_visible(1)
775 curses.echo()
776 stdscr.addstr(ofs + 1, 3, "<num> <key>: ")
777 stdscr.refresh()
778 key = stdscr.getstr()
779 curses.noecho()
780 cursor_visible(old_cur)
781
782 key = key.strip()
783 if key:
784 num, key = SPACE.split(key, 1)
785 try:
786 num = int(num)
787 self.assignLogs(num, key)
788 except ValueError:
789 pass
790
791 stdscr.erase()
792 ofs = self.render()
793 stdscr.refresh()
794 self.state = PAUSED
795 else:
796 self.state = PAUSED
797 else:
798 self.state = PAUSED
799
800 def runningEscapeState(self, c):
801 global stdscr
802 if curses.ascii.isdigit(c):
803 signal.signal(signal.SIGALRM, signal.SIG_IGN)
804 pnum = c - ord('0')
805 nproject = self.findAnonymProject(pnum)
806 if nproject is None:
807 nproject = Project()
808 self.projects.append(nproject)
809
810 nproject.start_time = self.writeLog()
811 self.current_project = nproject
812 self.state = RUNNING
813 stdscr.erase()
814 self.render()
815 stdscr.refresh()
816 signal.signal(signal.SIGALRM, alarm_handler)
817 signal.alarm(1)
818 else:
819 self.state = RUNNING
820
821
822 def run(self):
823 global stdscr
824
825 stdscr.erase()
826 self.render()
827 stdscr.refresh()
828
829 while True:
830 c = stdscr.getch()
831 if c == -1: continue
832
833 if self.state == PAUSED:
834 self.pausedState(c)
835
836 elif self.state == RUNNING:
837 self.runningState(c)
838
839 elif self.state == PAUSED_ESC:
840 self.pausedEscapeState(c)
841
842 elif self.state == RUNNING_ESC:
843 self.runningEscapeState(c)
844
845 elif self.state == PRE_EXIT:
846 if c in (curses.KEY_DC, curses.KEY_BACKSPACE):
847 break
848 else:
849 stdscr.erase()
850 self.render()
851 stdscr.refresh()
852 self.state = PAUSED
853
854 def alarm_handler(flag, frame):
855 global worklog
856 global stdscr
857
858 stdscr.erase()
859 worklog.render()
860 stdscr.refresh()
861 if worklog.isRunning():
862 signal.alarm(1)
863
864 def exit_handler(flag, frame):
865 exit_code = 0
866 global worklog
867 try:
868 worklog.shutdown()
869 except:
870 traceback.print_exc(file=sys.stderr)
871 exit_code = 1
872
873 restore_cursor()
874 curses.nocbreak()
875 stdscr.keypad(0)
876 curses.echo()
877 curses.endwin()
878 sys.exit(exit_code)
879
880 def main():
881
882 database = len(sys.argv) < 2 and DEFAULT_DATABASE or sys.argv[1]
883 # TODO: create database file if it does not exist.
884
885 global worklog
886 try:
887 worklog = Worklog(database)
888 except:
889 traceback.print_exc(file=sys.stderr)
890 sys.exit(1)
891
892 global stdscr
893 stdscr = curses.initscr()
894 curses.noecho()
895 curses.cbreak()
896 stdscr.keypad(1)
897 cursor_visible(0)
898
899 signal.signal(signal.SIGHUP, exit_handler)
900 signal.signal(signal.SIGINT, exit_handler)
901 signal.signal(signal.SIGQUIT, exit_handler)
902 signal.signal(signal.SIGTERM, exit_handler)
903
904 try:
905 try:
906 worklog.run()
907 except:
908 traceback.print_exc(file=sys.stderr)
909 finally:
910 exit_handler(0, None)
911
912 if __name__ == '__main__':
913 main()
914
915 # vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)