# HG changeset patch # User Sascha L. Teichmann # Date 1217372552 -7200 # Node ID 49aa271aa3d0a2b967e63559d4c8d4cc188773b8 # Parent a3fe8e4e918406d07dc2838296919403c4f1b306 Added +/- time to tasks. diff -r a3fe8e4e9184 -r 49aa271aa3d0 ChangeLog --- a/ChangeLog Tue Jul 29 16:38:40 2008 +0200 +++ b/ChangeLog Wed Jul 30 01:02:32 2008 +0200 @@ -1,3 +1,11 @@ +2008-07-30 Sascha L. Teichmann + + * getan: Added +/- time to tasks. If in pause mode press + or - + to add or subtract time from a task in form of . + key is the effected task seconds is the among of time. seconds + can be postfixed by 's' for seconds, 'm' for minutes, 'h' for + hours and 'd' for days. + 2008-07-29 Sascha L. Teichmann * getan: To exit getan you now can use double BACKSPACE, too. diff -r a3fe8e4e9184 -r 49aa271aa3d0 getan --- a/getan Tue Jul 29 16:38:40 2008 +0200 +++ b/getan Wed Jul 30 01:02:32 2008 +0200 @@ -17,7 +17,7 @@ import traceback import signal -from datetime import datetime, timedelta +from datetime import datetime, timedelta, tzinfo from pysqlite2 import dbapi2 as db @@ -67,6 +67,21 @@ DELETE FROM projects WHERE id = :id ''' +# XXX: This is not very efficent! +LAST_ENTRY = ''' +SELECT id, strftime('%s', start_time), strftime('%s', stop_time) FROM entries +WHERE project_id = :project_id +ORDER by strftime('%s', stop_time) DESC LIMIT 1 +''' + +DELETE_ENTRY = ''' +DELETE FROM entries WHERE id = :id +''' + +UPDATE_STOP_TIME = ''' +UPDATE entries SET stop_time = :stop_time WHERE id = :id +''' + worklog = None stdscr = None @@ -121,6 +136,33 @@ out = "%dd %s" % (delta.days, out) return out +FACTORS = { + 's': 1, + 'm': 60, + 'h' : 60*60, + 'd': 24*60*60} + +def human_seconds(seconds): + factor = FACTORS.get(seconds[-1]) + if factor: seconds = seconds[:-1] + else: factor = 1 + return int(seconds) * factor + + +ZERO = timedelta(0) + +class UTC(tzinfo): + """UTC""" + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + class Project: def __init__(self, id = None, key = None, desc = None, total = 0): @@ -177,10 +219,51 @@ cur.connection.commit() def delete(self, cur): - id = self.getId(cur) - cur.execute(DELETE_PROJECT, { 'id': id }) + pid = self.getId(cur) + cur.execute(DELETE_PROJECT, { 'id': pid }) cur.connection.commit() + def substractTime(self, cur, seconds): + substractTimeed, zero = timedelta(), timedelta() + pid = {'project_id': self.getId(cur)} + utc = UTC() + while seconds > zero: + cur.execute(LAST_ENTRY, pid) + row = cur.fetchone() + if row is None: break + # TODO: evaluate egenix-mx + start_time = datetime.fromtimestamp(float(row[1]), utc) + stop_time = datetime.fromtimestamp(float(row[2]), utc) + runtime = stop_time - start_time + if runtime <= seconds: + cur.execute(DELETE_ENTRY, { 'id': row[0] }) + cur.connection.commit() + seconds -= runtime + substractTimeed += runtime + else: + stop_time -= seconds + cur.execute(UPDATE_STOP_TIME, { + 'id': row[0], + 'stop_time': stop_time}) + cur.connection.commit() + substractTimeed += seconds + break + + self.total -= substractTimeed + return substractTimeed + + def addTime(self, cur, seconds): + now = datetime.now() + cur.execute(WRITE_LOG, { + 'project_id' : self.getId(cur), + 'start_time' : now - seconds, + 'stop_time' : now, + 'description': None + }) + cur.connection.commit() + self.total += seconds + + class Worklog: def __init__(self, database): @@ -249,6 +332,26 @@ finally: tolerantClose(cur) + def addTime(self, key, seconds): + project = self.findProject(key) + if project is None: return + cur = None + try: + cur = self.con.cursor() + project.addTime(cur, seconds) + finally: + tolerantClose(cur) + + def substractTime(self, key, seconds): + project = self.findProject(key) + if project is None: return + cur = None + try: + cur = self.con.cursor() + project.substractTime(cur, seconds) + finally: + tolerantClose(cur) + def isRunning(self): return self.state in (RUNNING, RUNNING_ESC) @@ -309,11 +412,189 @@ finally: tolerantClose(cur) + def pausedState(self, c): + global stdscr + if c in (curses.KEY_DC, curses.KEY_BACKSPACE): + stdscr.erase() + ofs = render_quit(self.render()) + stdscr.refresh() + self.state = PRE_EXIT + + elif c == curses.ascii.ESC: + self.state = PAUSED_ESC + + elif curses.ascii.isascii(c): + if c in (ord('-'), ord('+')): + stdscr.erase() + ofs = self.render() + old_cur = cursor_visible(1) + curses.echo() + stdscr.addstr(ofs + 1, 3, " : ") + key = stdscr.getstr() + curses.noecho() + cursor_visible(old_cur) + key = key.strip() + if key: + parts = SPACE.split(key, 1) + if len(parts) > 1: + key, seconds = parts[0], parts[1] + try: + seconds = human_seconds(seconds) + if seconds > 0: + seconds = timedelta(seconds=seconds) + if c == ord('-'): + self.substractTime(key, seconds) + else: + self.addTime(key, seconds) + except ValueError: + pass + stdscr.erase() + self.render() + stdscr.refresh() + + else: + nproject = self.findProject(chr(c)) + if nproject is None: return + self.current_project = nproject + nproject.start_time = datetime.now() + stdscr.erase() + ofs = self.render() + stdscr.refresh() + self.state = RUNNING + signal.signal(signal.SIGALRM, alarm_handler) + signal.alarm(1) + + def runningState(self, c): + global stdscr + if c == curses.ascii.ESC: + self.state = RUNNING_ESC + + elif c == curses.ascii.NL: + signal.signal(signal.SIGALRM, signal.SIG_IGN) + self.state = PAUSED + stdscr.erase() + ofs = self.render() + old_cur = cursor_visible(1) + curses.echo() + stdscr.addstr(ofs + 1, 3, "Description: ") + description = stdscr.getstr() + curses.noecho() + cursor_visible(old_cur) + self.writeLog(description) + self.current_project = None + stdscr.erase() + ofs = self.render() + stdscr.refresh() + + elif curses.ascii.isascii(c): + nproject = self.findProject(chr(c)) + if nproject is None or nproject == self.current_project: + return + nproject.start_time = self.writeLog() + self.current_project = nproject + stdscr.erase() + ofs = self.render() + stdscr.refresh() + + def pausedEscapeState(self, c): + global stdscr + if curses.ascii.isdigit(c): + pnum = c - ord('0') + nproject = self.findAnonymProject(pnum) + if nproject is None: + nproject = Project() + self.projects.append(nproject) + + nproject.start_time = self.writeLog() + self.current_project = nproject + self.state = RUNNING + stdscr.erase() + ofs = self.render() + stdscr.refresh() + signal.signal(signal.SIGALRM, alarm_handler) + signal.alarm(1) + elif curses.ascii.isalpha(c): + if c == ord('n'): + stdscr.erase() + ofs = self.render() + old_cur = cursor_visible(1) + curses.echo() + stdscr.addstr(ofs + 1, 3, " : ") + stdscr.refresh() + description = stdscr.getstr() + curses.noecho() + cursor_visible(old_cur) + + description = description.strip() + if description: + num, key, description = SPACE.split(description, 2) + try: + num = int(num) + self.renameAnonymProject(num, key, description) + except ValueError: + pass + + stdscr.erase() + ofs = self.render() + stdscr.refresh() + self.state = PAUSED + + elif c == ord('a'): + stdscr.erase() + ofs = self.render() + old_cur = cursor_visible(1) + curses.echo() + stdscr.addstr(ofs + 1, 3, " : ") + stdscr.refresh() + key = stdscr.getstr() + curses.noecho() + cursor_visible(old_cur) + + key = key.strip() + if key: + num, key = SPACE.split(key, 1) + try: + num = int(num) + self.assignLogs(num, key) + except ValueError: + pass + + stdscr.erase() + ofs = self.render() + stdscr.refresh() + self.state = PAUSED + else: + self.state = PAUSED + else: + self.state = PAUSED + + def runningEscapeState(self, c): + global stdscr + if curses.ascii.isdigit(c): + signal.signal(signal.SIGALRM, signal.SIG_IGN) + pnum = c - ord('0') + nproject = self.findAnonymProject(pnum) + if nproject is None: + nproject = Project() + self.projects.append(nproject) + + nproject.start_time = self.writeLog() + self.current_project = nproject + self.state = RUNNING + stdscr.erase() + self.render() + stdscr.refresh() + signal.signal(signal.SIGALRM, alarm_handler) + signal.alarm(1) + else: + self.state = RUNNING + + def run(self): global stdscr stdscr.erase() - ofs = self.render() + self.render() stdscr.refresh() while True: @@ -321,163 +602,26 @@ if c == -1: continue if self.state == PAUSED: - - if c in (curses.KEY_DC, curses.KEY_BACKSPACE): - stdscr.erase() - ofs = render_quit(self.render()) - stdscr.refresh() - self.state = PRE_EXIT - - elif c == curses.ascii.ESC: - self.state = PAUSED_ESC - - elif curses.ascii.isascii(c): - nproject = self.findProject(chr(c)) - if nproject is None: continue - self.current_project = nproject - nproject.start_time = datetime.now() - stdscr.erase() - ofs = self.render() - stdscr.refresh() - self.state = RUNNING - signal.signal(signal.SIGALRM, alarm_handler) - signal.alarm(1) + self.pausedState(c) elif self.state == RUNNING: - if c == curses.ascii.ESC: - self.state = RUNNING_ESC - - elif c == curses.ascii.NL: - signal.signal(signal.SIGALRM, signal.SIG_IGN) - self.state = PAUSED - stdscr.erase() - ofs = self.render() - old_cur = cursor_visible(1) - curses.echo() - stdscr.addstr(ofs + 1, 3, "Description: ") - description = stdscr.getstr() - curses.noecho() - cursor_visible(old_cur) - self.writeLog(description) - self.current_project = None - stdscr.erase() - ofs = self.render() - stdscr.refresh() - - elif curses.ascii.isascii(c): - nproject = self.findProject(chr(c)) - if nproject is None or nproject == self.current_project: - continue - nproject.start_time = self.writeLog() - self.current_project = nproject - stdscr.erase() - ofs = self.render() - stdscr.refresh() + self.runningState(c) elif self.state == PAUSED_ESC: - if curses.ascii.isdigit(c): - pnum = c - ord('0') - nproject = self.findAnonymProject(pnum) - if nproject is None: - nproject = Project() - self.projects.append(nproject) - - nproject.start_time = self.writeLog() - self.current_project = nproject - self.state = RUNNING - stdscr.erase() - ofs = self.render() - stdscr.refresh() - signal.signal(signal.SIGALRM, alarm_handler) - signal.alarm(1) - elif curses.ascii.isalpha(c): - if c == ord('n'): - stdscr.erase() - ofs = self.render() - old_cur = cursor_visible(1) - curses.echo() - stdscr.addstr(ofs + 1, 3, " : ") - stdscr.refresh() - description = stdscr.getstr() - curses.noecho() - cursor_visible(old_cur) - - description = description.strip() - if description: - num, key, description = SPACE.split(description, 2) - try: - num = int(num) - self.renameAnonymProject(num, key, description) - except ValueError: - pass - - stdscr.erase() - ofs = self.render() - stdscr.refresh() - self.state = PAUSED - - elif c == ord('a'): - stdscr.erase() - ofs = self.render() - old_cur = cursor_visible(1) - curses.echo() - stdscr.addstr(ofs + 1, 3, " : ") - stdscr.refresh() - key = stdscr.getstr() - curses.noecho() - cursor_visible(old_cur) - - key = key.strip() - if key: - num, key = SPACE.split(key, 1) - try: - num = int(num) - self.assignLogs(num, key) - except ValueError: - pass - - stdscr.erase() - ofs = self.render() - stdscr.refresh() - self.state = PAUSED - - else: - self.state = PAUSED - pass - else: - self.state = PAUSED + self.pausedEscapeState(c) elif self.state == RUNNING_ESC: - if curses.ascii.isdigit(c): - signal.signal(signal.SIGALRM, signal.SIG_IGN) - pnum = c - ord('0') - nproject = self.findAnonymProject(pnum) - if nproject is None: - nproject = Project() - self.projects.append(nproject) - - nproject.start_time = self.writeLog() - self.current_project = nproject - self.state = RUNNING - stdscr.erase() - ofs = self.render() - stdscr.refresh() - signal.signal(signal.SIGALRM, alarm_handler) - signal.alarm(1) - else: - self.state = RUNNING + self.runningEscapeState(c) elif self.state == PRE_EXIT: if c in (curses.KEY_DC, curses.KEY_BACKSPACE): break else: stdscr.erase() - ofs = self.render() + self.render() stdscr.refresh() self.state = PAUSED - - def alarm_handler(flag, frame): global worklog global stdscr