changeset 2:49aa271aa3d0

Added +/- time to tasks.
author Sascha L. Teichmann <teichmann@intevation.de>
date Wed, 30 Jul 2008 01:02:32 +0200
parents a3fe8e4e9184
children 1513c716eef0
files ChangeLog getan
diffstat 2 files changed, 298 insertions(+), 146 deletions(-) [+]
line wrap: on
line diff
--- 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 <sascha.teichmann@intevation.de>
+
+	* getan: Added +/- time to tasks. If in pause mode press + or -
+	  to add or subtract time from a task in form of <key> <seconds>.
+	  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 <sascha.teichmann@intevation.de>
 
 	* getan: To exit getan you now can use double BACKSPACE, too. 
--- 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> <seconds>: ")
+                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, "<num> <key> <description>: ")
+                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, "<num> <key>: ")
+                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, "<num> <key> <description>: ")
-                        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, "<num> <key>: ")
-                        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
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)