ingo_weinzierl@23: #!/usr/bin/env python ingo_weinzierl@23: # -*- coding: utf-8 -*- ingo_weinzierl@23: # ingo_weinzierl@23: # (c) 2010 by Ingo Weinzierl bjoern@97: # (c) 2011 by Björn Ricks ingo_weinzierl@23: # ingo_weinzierl@23: # A python worklog-alike to log what you have 'getan' (done). ingo_weinzierl@23: # ingo_weinzierl@23: # This is Free Software licensed under the terms of GPLv3 or later. ingo_weinzierl@23: # For details see LICENSE coming with the source of 'getan'. ingo_weinzierl@23: # ingo_weinzierl@23: ingo_weinzierl@23: import logging ingo_weinzierl@23: import sys ingo_weinzierl@23: from datetime import datetime bjoern@67: from optparse import OptionParser ingo_weinzierl@23: ingo_weinzierl@23: import getan.config as config ingo_weinzierl@23: from getan.backend import * ingo_weinzierl@23: from getan.view import * ingo_weinzierl@23: from getan.utils import format_time ingo_weinzierl@23: ingo_weinzierl@23: logger = logging.getLogger() ingo_weinzierl@23: ingo_weinzierl@23: class GetanController: ingo_weinzierl@23: def __init__(self, backend, pv_class, ev_class): ingo_weinzierl@23: self.ev_class = ev_class ingo_weinzierl@23: self.pv_class = pv_class ingo_weinzierl@23: ingo_weinzierl@23: self.projects = backend.load_projects() teichmann@41: if self.projects: teichmann@41: entries = backend.load_entries(self.projects[0].id) teichmann@41: else: teichmann@41: entries = [] ingo_weinzierl@23: self.running = [] ingo_weinzierl@23: ingo_weinzierl@23: self.backend = backend ingo_weinzierl@23: self.project_view = pv_class(self, self.projects) ingo_weinzierl@23: self.entries_view = ev_class(entries) ingo_weinzierl@23: ingo_weinzierl@23: self.view = GetanView(self, self.project_view, self.entries_view) ingo_weinzierl@23: self.state = PausedProjectsState(self, self.project_view) ingo_weinzierl@23: ingo_weinzierl@23: def main(self): ingo_weinzierl@23: self.view.run() ingo_weinzierl@23: ingo_weinzierl@23: def unhandled_keypress(self, key): ingo_weinzierl@23: self.state = self.state.keypress(key) ingo_weinzierl@23: ingo_weinzierl@23: def input_filter(self, input, raw_input): ingo_weinzierl@26: if 'window resize' in input: ingo_weinzierl@26: self.view.loop.screen_size = None ingo_weinzierl@26: self.view.loop.draw_screen() ingo_weinzierl@26: else: ingo_weinzierl@26: self.state = self.state.keypress(input) ingo_weinzierl@23: ingo_weinzierl@23: def update_entries(self, project): ingo_weinzierl@23: logger.debug("GetanController: update entries.") teichmann@42: if project: entries = self.backend.load_entries(project.id) teichmann@42: else: entries = [] teichmann@42: self.entries_view.set_rows(entries) ingo_weinzierl@23: self.view.update_view() ingo_weinzierl@23: ingo_weinzierl@23: def move_selected_entries(self, project): ingo_weinzierl@23: old_project = None ingo_weinzierl@23: entries = [] ingo_weinzierl@23: try: ingo@33: while self.entries_view.selection: ingo_weinzierl@23: node = self.entries_view.selection.pop() ingo_weinzierl@23: if node.selected: node.select() ingo_weinzierl@25: entries.append(node.item) ingo_weinzierl@23: logger.info("GetanController: move entry '%s' (id = %d, "\ ingo_weinzierl@23: "project id = %d) to project '%s'" ingo_weinzierl@25: % (node.item.desc, node.item.id, ingo_weinzierl@25: node.item.project_id, project.desc)) ingo_weinzierl@23: ingo_weinzierl@23: if not old_project: ingo_weinzierl@25: old_project = self.project_by_id(node.item.project_id) ingo_weinzierl@23: finally: ingo_weinzierl@23: self.backend.move_entries(entries, project.id) ingo_weinzierl@23: if not old_project: return ingo_weinzierl@23: project.entries = self.backend.load_entries(project.id) ingo_weinzierl@23: old_project.entries = self.backend.load_entries(old_project.id) ingo_weinzierl@23: self.update_entries(old_project) ingo_weinzierl@23: self.project_view.update_all() ingo_weinzierl@23: ingo_weinzierl@23: def delete_entries(self, entry_nodes): bjoern@99: if not entry_nodes: bjoern@99: return bjoern@99: proj = self.project_by_id(entry_nodes[0].project_id) bjoern@99: entries = entry_nodes bjoern@99: self.backend.delete_entries(entries) bjoern@99: proj.entries = self.backend.load_entries(proj.id) bjoern@99: self.update_entries(proj) bjoern@99: self.project_view.update() ingo_weinzierl@23: ingo_weinzierl@23: def update_project_list(self): ingo_weinzierl@23: self.project_view.update() ingo_weinzierl@23: self.view.update_view() ingo_weinzierl@23: ingo_weinzierl@23: def exit(self): ingo_weinzierl@23: self.view.exit() ingo_weinzierl@23: ingo_weinzierl@23: def project_by_key(self, key): ingo_weinzierl@23: for proj in self.projects: ingo_weinzierl@23: if proj.key == key: ingo_weinzierl@23: return proj ingo_weinzierl@23: return None ingo_weinzierl@23: ingo_weinzierl@23: def project_by_id(self, id): ingo_weinzierl@23: for proj in self.projects: ingo_weinzierl@23: if proj.id == id: ingo_weinzierl@23: return proj ingo_weinzierl@23: return None ingo_weinzierl@23: ingo_weinzierl@23: def start_project(self, project): teichmann@41: if not project: return ingo_weinzierl@23: self.running.append(project) ingo_weinzierl@23: project.start = datetime.now() ingo_weinzierl@23: logger.info("Start project '%s' at %s." ingo_weinzierl@23: % (project.desc, format_time(datetime.now()))) ingo_weinzierl@23: self.view.set_footer_text(" Running on '%s'" % project.desc, 'running') ingo_weinzierl@23: logger.debug('All running projects: %r' % self.running) ingo_weinzierl@23: bjoern@90: def stop_project(self, desc='-no description-', display=True): teichmann@42: if not self.running: return ingo_weinzierl@23: project = self.running.pop() teichmann@41: if not project: return ingo_weinzierl@23: logger.info("Stop project '%s' at %s." ingo_weinzierl@23: % (project.desc, format_time(datetime.now()))) ingo_weinzierl@23: project.stop = datetime.now() ingo_weinzierl@23: self.backend.insert_project_entry(project, datetime.now(), desc) bjoern@90: if display: bjoern@90: self.update_entries(project) bjoern@90: self.update_project_list() ingo_weinzierl@23: logger.debug('Still running projects: %r' % self.running) ingo_weinzierl@23: bjoern@53: def add_project(self, key, description): bjoern@53: if not key or not description: bjoern@53: return bjoern@53: self.backend.insert_project(key, description) bjoern@53: self.update_project_list() bjoern@53: bjoern@69: def update_entry(self, entry): bjoern@69: self.backend.update_entry(entry) bjoern@69: ingo@31: def shutdown(self): ingo_weinzierl@30: for project in self.running: bjoern@90: self.stop_project(display=False) ingo_weinzierl@30: ingo_weinzierl@23: ingo_weinzierl@23: def main(): bjoern@67: bjoern@67: usage = "usage: %prog [options] [databasefile (default: " + \ bjoern@67: DEFAULT_DATABASE + ")]" bjoern@67: parser = OptionParser(usage=usage) bjoern@67: parser.add_option("-d", "--debug", action="store_true", dest="debug", bjoern@67: help="Set verbosity to debug") bjoern@67: parser.add_option("-l", "--logfile", dest="logfile", metavar="FILE", bjoern@67: help="Write log information to FILE [default: %default]", bjoern@67: default="getan.log") bjoern@67: (options, args) = parser.parse_args() bjoern@67: logargs = dict() bjoern@67: if options.debug: bjoern@67: logargs["level"] = logging.DEBUG bjoern@67: if options.logfile: bjoern@67: logargs["filename"] = options.logfile bjoern@67: config.initialize(**logargs) ingo_weinzierl@23: global logger ingo_weinzierl@23: bjoern@67: if len(args) > 0: bjoern@67: backend = Backend(args[0]) bjoern@67: logging.info("Use database '%s'." % args[0]) ingo_weinzierl@23: else: ingo_weinzierl@23: backend = Backend() ingo_weinzierl@23: logging.info("Use database '%s'." % DEFAULT_DATABASE) ingo_weinzierl@23: ingo@31: controller = GetanController(backend, ProjectList, EntryList) ingo@31: ingo@31: try: ingo_weinzierl@30: controller.main() bjoern@90: except KeyboardInterrupt: bjoern@90: pass ingo@31: finally: ingo@31: controller.shutdown() ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: if __name__ == '__main__': ingo_weinzierl@23: main()