ingo_weinzierl@23: #!/usr/bin/env python ingo_weinzierl@23: # -*- coding: utf-8 -*- ingo_weinzierl@23: # ingo_weinzierl@23: # (c) 2010 by Ingo Weinzierl 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 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@45: def order_projects(projects): ingo@45: projects = sorted(projects, key=lambda proj: proj.key) ingo@45: ordered = {} ingo@45: for p in projects: ingo@45: c = p.key[0] ingo@45: if c in ordered: ingo@45: ordered[c].add_child(p.key[1:], p) ingo@45: else: ingo@45: ordered[c] = p ingo@45: return ordered ingo@45: ingo@45: 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@45: self.project_view = pv_class(self, order_projects(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): ingo_weinzierl@23: if not entry_nodes: return ingo_weinzierl@23: proj = 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: delete entry '%s' (id = %d, "\ ingo_weinzierl@23: "project id = %d)" ingo_weinzierl@25: % (node.item.desc, node.item.id, ingo_weinzierl@25: node.item.project_id)) ingo_weinzierl@23: ingo_weinzierl@23: if proj is None: ingo_weinzierl@25: proj = self.project_by_id(node.item.project_id) ingo_weinzierl@23: finally: ingo_weinzierl@23: self.backend.delete_entries(entries) ingo_weinzierl@23: proj.entries = self.backend.load_entries(proj.id) ingo_weinzierl@23: self.update_entries(proj) ingo_weinzierl@23: 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: ingo_weinzierl@30: def stop_project(self, desc='-no description-'): 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) ingo_weinzierl@23: self.update_entries(project) ingo_weinzierl@23: self.update_project_list() ingo_weinzierl@23: logger.debug('Still running projects: %r' % self.running) ingo_weinzierl@23: ingo@31: def shutdown(self): ingo_weinzierl@30: for project in self.running: ingo_weinzierl@30: self.stop_project() ingo_weinzierl@30: ingo_weinzierl@23: ingo_weinzierl@23: def main(): ingo_weinzierl@23: config.initialize() ingo_weinzierl@23: global logger ingo_weinzierl@23: ingo_weinzierl@23: if len(sys.argv) > 1: ingo_weinzierl@23: backend = Backend(sys.argv[1]) ingo_weinzierl@23: logging.info("Use database '%s'." % sys.argv[1]) 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() ingo@31: finally: ingo@31: controller.shutdown() ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: if __name__ == '__main__': ingo_weinzierl@23: main()