def isPassphraseValid(self): """Check if user passphrase is valid. ie. : if it can decrypt the check crypto word""" if not self.passphrase: # If no passphrase has been defined, it is definitively not valid ! return False if self.crypto_check: try: int(self._decrypt(self.crypto_check)) return True except ValueError: return False else: # First time that user enter a passphrase. Store the crypto check # for next time usage # We use a long string composed of int that we encrypt check_word = str(Random().getrandbits(KEY_LENGTH * KEY_LENGTH)) check_word = adjustString(check_word, 10 * KEY_LENGTH) self.crypto_check = self._encrypt(check_word) # Save it to database config db.getSession().add( db.Config(name="CRYPTO_CHECK", value=self.crypto_check, system=True, desc="Cryptographic check data of passphrase")) return True
def do_a_edit_command(self, line): """Edit the command of an alias. a_edit_command <alias name>""" session = db.getSession() name = line if name not in self.aliases: raise YokadiException("There is no alias named {}".format(name)) command = tui.editLine(self.aliases[name]) session = db.getSession() db.Alias.setCommand(session, name, command) session.commit() self._updateAliasDict()
def eventLoop(): """Main event loop""" delta = timedelta(hours=float(getConfigKey("ALARM_DELAY"))) suspend = timedelta(hours=float(getConfigKey("ALARM_SUSPEND"))) cmdDelayTemplate = getConfigKey("ALARM_DELAY_CMD") cmdDueTemplate = getConfigKey("ALARM_DUE_CMD") session = db.getSession() # For the two following dict, task id is key, and value is (duedate, triggerdate) triggeredDelayTasks = {} triggeredDueTasks = {} activeTaskFilter = [ Task.status != "done", Task.projectId == Project.id, Project.active == True ] while event[0]: now = datetime.today().replace(microsecond=0) delayTasks = session.query(Task).filter(Task.dueDate < now + delta, Task.dueDate > now, *activeTaskFilter) dueTasks = session.query(Task).filter(Task.dueDate < now, *activeTaskFilter) processTasks(delayTasks, triggeredDelayTasks, cmdDelayTemplate, suspend) processTasks(dueTasks, triggeredDueTasks, cmdDueTemplate, suspend) time.sleep(DELAY)
def run(self): filename = self.options.filename if not filename: filename = basepaths.getDbPath() print("Using default database (%s)" % filename) db.connectDatabase(filename, createIfNeeded=False) session = db.getSession() # Basic tests : if not len(session.query(db.Config).all()) >= 1: print( "Your database seems broken or not initialised properly. Start yokadi command line tool to do it" ) sys.exit(1) # Start ical http handler if self.options.icalserver: yokadiIcalServer = YokadiIcalServer(self.options.tcpPort, self.options.tcpListen) yokadiIcalServer.start() # Start the main event Loop try: while event[1] != "SIGTERM": eventLoop() event[0] = True except KeyboardInterrupt: print("\nExiting...")
def end(self): today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) # Adjust idColumn maxId = db.getSession().query(func.max(Task.id)).one()[0] self.idColumn.width = max(2, len(str(maxId))) # Adjust titleColumn self.titleColumn.width = self.maxTitleWidth totalWidth = sum([x.width for x in self.columns]) + len(self.columns) if totalWidth >= self.termWidth: self.titleColumn.width -= (totalWidth - self.termWidth) + len(self.columns) self.titleColumn.formater = TitleFormater(self.titleColumn.width, self.cryptoMgr) # Print table for sectionName, taskList in self.taskLists: dateSplitters = [(1, "day"), (7, "week"), (30, "month"), (30 * 4, "quarter"), (365, "year")] splitterRange, splitterName = dateSplitters.pop() splitterText = None self._renderTaskListHeader(sectionName) for task in taskList: while self.splitOnDate and task.creationDate > today - timedelta(splitterRange): splitterText = "Last %s" % splitterName if len(dateSplitters) > 0: splitterRange, splitterName = dateSplitters.pop() else: self.splitOnDate = False if splitterText: print(C.GREEN + splitterText.center(totalWidth) + C.RESET, file=self.out) splitterText = None self._renderTaskListRow(task)
def eventLoop(): """Main event loop""" delta = timedelta(hours=float(getConfigKey("ALARM_DELAY"))) suspend = timedelta(hours=float(getConfigKey("ALARM_SUSPEND"))) cmdDelayTemplate = getConfigKey("ALARM_DELAY_CMD") cmdDueTemplate = getConfigKey("ALARM_DUE_CMD") session = db.getSession() # For the two following dict, task id is key, and value is (duedate, triggerdate) triggeredDelayTasks = {} triggeredDueTasks = {} activeTaskFilter = [Task.status != "done", Task.projectId == Project.id, Project.active == True] # noqa def process(now): delayTasks = session.query(Task).filter(Task.dueDate < now + delta, Task.dueDate > now, *activeTaskFilter) dueTasks = session.query(Task).filter(Task.dueDate < now, *activeTaskFilter) processTasks(delayTasks, triggeredDelayTasks, cmdDelayTemplate, suspend) processTasks(dueTasks, triggeredDueTasks, cmdDueTemplate, suspend) nextProcessTime = datetime.today().replace(microsecond=0) while event[0]: now = datetime.today().replace(microsecond=0) if now > nextProcessTime: process(now) nextProcessTime = now + timedelta(seconds=PROCESS_INTERVAL) time.sleep(EVENTLOOP_INTERVAL)
def addTask(projectName, title, keywordDict=None, interactive=True): """Adds a task based on title and keywordDict. @param projectName: name of project as a string @param title: task title as a string @param keywordDict: dictionary of keywords (name : value) @param interactive: Ask user before creating project (this is the default) @type interactive: Bool @returns : Task instance on success, None if cancelled.""" session = db.getSession() if keywordDict is None: keywordDict = {} # Create missing keywords if not createMissingKeywords(keywordDict.keys(), interactive=interactive): return None # Create missing project project = getOrCreateProject(projectName, interactive=interactive) if not project: return None # Create task task = Task(creationDate=datetime.now().replace(second=0, microsecond=0), project=project, title=title, description="", status="new") session.add(task) task.setKeywordDict(keywordDict) session.merge(task) return task
def do_p_set_inactive(self, line): """Desactivate the given project""" session = db.getSession() project = getProjectFromName(line) project.active = False session.merge(project) session.commit()
def _getLock(self): """Retrieve the task lock if it exists (else None)""" try: return db.getSession().query(TaskLock).filter( TaskLock.task == self.task).one() except NoResultFound: return None
def run(self): filename = self.options.filename if not filename: filename = os.path.join(os.path.expandvars("$HOME"), ".yokadi.db") print("Using default database (%s)" % filename) db.connectDatabase(filename, createIfNeeded=False) session = db.getSession() # Basic tests : if not len(session.query(db.Config).all()) >=1: print("Your database seems broken or not initialised properly. Start yokadi command line tool to do it") sys.exit(1) # Start ical http handler if self.options.icalserver: yokadiIcalServer = YokadiIcalServer(self.options.tcpPort, self.options.tcpListen) yokadiIcalServer.start() # Start the main event Loop try: while event[1] != "SIGTERM": eventLoop() event[0] = True except KeyboardInterrupt: print("\nExiting...")
def end(self): today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) # Adjust idColumn maxId = db.getSession().query(func.max(Task.id)).one()[0] self.idColumn.width = max(2, len(str(maxId))) # Adjust titleColumn self.titleColumn.width = self.maxTitleWidth totalWidth = sum([x.width for x in self.columns]) + len(self.columns) - 1 if totalWidth >= self.termWidth: self.titleColumn.width = self.termWidth - (totalWidth - self.titleColumn.width) self.titleColumn.formater = TitleFormater(self.titleColumn.width, self.cryptoMgr) # Print table for sectionName, taskList in self.taskLists: dateSplitters = [(1, "day"), (7, "week"), (30, "month"), (30 * 4, "quarter"), (365, "year")] splitterRange, splitterName = dateSplitters.pop() splitterText = None self._renderTaskListHeader(sectionName) for task in taskList: while self.splitOnDate and task.creationDate > today - timedelta(splitterRange): splitterText = "Last %s" % splitterName if len(dateSplitters) > 0: splitterRange, splitterName = dateSplitters.pop() else: self.splitOnDate = False if splitterText: print(C.GREEN + splitterText.center(totalWidth) + C.RESET, file=self.out) splitterText = None self._renderTaskListRow(task)
def do_p_edit(self, line): """Edit a project. p_edit <project name>""" session = db.getSession() project = dbutils.getOrCreateProject(line, createIfNeeded=False) if not project: raise YokadiException("Project does not exist.") # Create project line projectLine = parseutils.createLine(project.name, "", project.getKeywordDict()) # Edit line = tui.editLine(projectLine) # Update project projectName, garbage, keywordDict = parseutils.parseLine(line) if garbage: raise BadUsageException("Cannot parse line, got garbage (%s)" % garbage) if not dbutils.createMissingKeywords(list(keywordDict.keys())): return try: project.name = projectName project.setKeywordDict(keywordDict) session.merge(project) session.commit() except IntegrityError: session.rollback() raise YokadiException("A project named %s already exists. Please find another name" % projectName)
def getItemPropertiesStartingWith(item, field, text): """Return a list of item.field starting with text @param item: the object item, example : Task, Project, Keyword... @param field: the item's field lookup : Project.q.name, Task.q.title, Keyword.q.name. Don't forget the magic q @param text: The begining of the text as a str @return: list of matching strings""" session = db.getSession() return [x.name for x in session.query(item).filter(field.like(str(text) + "%"))]
def do_k_list(self, line): """List all keywords.""" for name, taskIds in _listKeywords(db.getSession()): if taskIds: tasks = ", ".join([str(x) for x in taskIds]) else: tasks = "none" print("{} (tasks: {})".format(name, tasks))
def createEntriesForProject(project): session = db.getSession() lst = session.query(Task).filter(Task.projectId == project.id, Task.status != 'done') lst = KeywordFilter(NOTE_KEYWORD, negative=True).apply(lst) lst = lst.order_by(desc(Task.urgency)) return [createEntryForTask(x) for x in lst]
def __init__(self, filterLine=None): self.name = "" # Keyword name self.value = "" # Keyword value self.negative = False # Negative filter self.valueOperator = "=" # Operator to compare value self.session = db.getSession() if filterLine: self.parse(filterLine)
def do_a_edit_name(self, line): """Edit the name of an alias. a_edit_name <alias name>""" session = db.getSession() name = line if not name in self.aliases: raise YokadiException("There is no alias named {}".format(name)) newName = tui.editLine(name) newName = parseutils.parseOneWordName(newName) if newName in self.aliases: raise YokadiException("There is already an alias named {}.".format(newName)) session = db.getSession() db.Alias.rename(session, name, newName) session.commit() self._updateAliasDict()
def do_a_edit_name(self, line): """Edit the name of an alias. a_edit_name <alias name>""" session = db.getSession() name = line if name not in self.aliases: raise YokadiException("There is no alias named {}".format(name)) newName = tui.editLine(name) newName = parseOneWordName(newName) if newName in self.aliases: raise YokadiException("There is already an alias named {}.".format(newName)) session = db.getSession() db.Alias.rename(session, name, newName) session.commit() self._updateAliasDict()
def do_p_list(self, line): """List all projects.""" session = db.getSession() for project in session.query(Project).all(): if project.active: active = "" else: active = "(inactive)" print("%s %s %s" % (project.name.ljust(20), str(session.query(Task).filter_by(project=project).count()).rjust(4), active))
def taskIdCompleter(cmd, text, line, begidx, endidx): # TODO: filter on parameter position # TODO: potential performance issue with lots of tasks, find a better way to do it session = db.getSession() tasks = [x for x in session.query(Task).filter(Task.status != 'done') if str(x.id).startswith(text)] print() for task in tasks: # Move that in a renderer class ? print("%s: %s / %s" % (task.id, task.project.name, task.title)) return [str(x.id) for x in tasks]
def warnIfKeywordDoesNotExist(keywordFilters): """Warn user is keyword does not exist @return: True if at least one keyword does not exist, else False""" session = db.getSession() doesNotExist = False for keyword in [k.name for k in keywordFilters]: if session.query(Keyword).filter(Keyword.name.like(keyword)).count() == 0: tui.error("Keyword %s is unknown." % keyword) doesNotExist = True return doesNotExist
def do_a_remove(self, line): """Remove an alias""" if line in self.aliases: session = db.getSession() del self.aliases[line] alias = session.query(db.Alias).filter_by(name=line).one() session.delete(alias) session.commit() else: tui.error("No alias with that name. Use a_list to display all aliases")
def do_p_list(self, line): """List all projects.""" session = db.getSession() for project in session.query(Project).all(): if project.active: active = "" else: active = "(inactive)" taskCount = session.query(Task).filter_by(project=project).count() print("{:20} {:>4} {}".format(project.name, taskCount, active))
def __init__(self): self.lastTaskId = None # Last id created, used self.lastProjectName = None # Last project name used self.lastTaskIds = [] # Last list of ids selected with t_list self.kFilters = [] # Permanent keyword filters (List of KeywordFilter) self.pFilter = "" # Permanent project filter (name of project) self.session = db.getSession() for name in bugutils.PROPERTY_NAMES: dbutils.getOrCreateKeyword(name, interactive=False) dbutils.getOrCreateKeyword(NOTE_KEYWORD, interactive=False) self.session.commit()
def do_k_edit(self, line): """Edit a keyword k_edit @<keyword>""" session = db.getSession() keyword = dbutils.getKeywordFromName(line) oldName = keyword.name newName = tui.editLine(oldName) if newName == "": print("Cancelled") return lst = session.query(Keyword).filter_by(name=newName).all() if len(lst) == 0: # Simple case: newName does not exist, just rename the existing keyword keyword.name = newName session.merge(keyword) session.commit() print("Keyword %s has been renamed to %s" % (oldName, newName)) return # We already have a keyword with this name, we need to merge print("Keyword %s already exists" % newName) if not tui.confirm("Do you want to merge %s and %s" % (oldName, newName)): return # Check we can merge conflictingTasks = [] for task in keyword.tasks: kwDict = task.getKeywordDict() if oldName in kwDict and newName in kwDict and kwDict[ oldName] != kwDict[newName]: conflictingTasks.append(task) if len(conflictingTasks) > 0: # We cannot merge tui.error("Cannot merge keywords %s and %s because they are both" " used with different values in these tasks:" % (oldName, newName)) for task in conflictingTasks: print("- %d, %s" % (task.id, task.title)) print("Edit these tasks and try again") return # Merge for task in keyword.tasks: kwDict = task.getKeywordDict() if newName not in kwDict: kwDict[newName] = kwDict[oldName] del kwDict[oldName] task.setKeywordDict(kwDict) session.delete(keyword) session.commit() print("Keyword %s has been merged with %s" % (oldName, newName))
def do_a_remove(self, line): """Remove an alias""" if line in self.aliases: session = db.getSession() del self.aliases[line] aliases = session.query(db.Config).filter_by(name="ALIASES").one() aliases.value = str(repr(self.aliases)) session.add(aliases) session.commit() else: tui.error("No alias with that name. Use a_list to display all aliases")
def warnIfKeywordDoesNotExist(keywordFilters): """Warn user is keyword does not exist @return: True if at least one keyword does not exist, else False""" session = db.getSession() doesNotExist = False for keyword in [k.name for k in keywordFilters]: if session.query(Keyword).filter( Keyword.name.like(keyword)).count() == 0: tui.error("Keyword %s is unknown." % keyword) doesNotExist = True return doesNotExist
def do_p_merge(self, line): session = db.getSession() parser = self.parser_p_merge() args = parser.parse_args(line) src = getProjectFromName(args.source_project) dst = getProjectFromName(args.destination_project) if not args.force: if not tui.confirm("Merge project '{}' into '{}'".format(src.name, dst.name)): return dst.merge(session, src) print("Project '{}' merged into '{}'".format(src.name, dst.name))
def do_p_remove(self, line): session = db.getSession() parser = self.parser_p_remove() args = parser.parse_args(line) project = getProjectFromName(args.project) nbTasks = len(project.tasks) if not args.force: if not tui.confirm("Remove project '%s' and its %d tasks" % (project.name, nbTasks)): return session.delete(project) session.commit() print("Project removed")
def do_c_get(self, line): parser = self.parser_c_get() args = parser.parse_args(line) key = args.key if not key: key = "%" session = db.getSession() k = session.query(Config).filter(Config.name.like(key)).filter_by(system=args.system).all() fields = [(x.name, "%s (%s)" % (x.value, x.desc)) for x in k] if fields: tui.renderFields(fields) else: raise YokadiException("Configuration key %s does not exist" % line)
def do_p_merge(self, line): session = db.getSession() parser = self.parser_p_merge() args = parser.parse_args(line) src = getProjectFromName(args.source_project) dst = getProjectFromName(args.destination_project) if not args.force: if not tui.confirm("Merge project '{}' into '{}'".format( src.name, dst.name)): return dst.merge(session, src) print("Project '{}' merged into '{}'".format(src.name, dst.name))
def do_k_remove(self, line): """Remove a keyword k_remove @<keyword>""" session = db.getSession() keyword = dbutils.getKeywordFromName(line) if keyword.tasks: print("The keyword %s is used by the following tasks: %s" % (keyword.name, ", ".join(str(task.id) for task in keyword.tasks))) if tui.confirm("Do you really want to remove this keyword"): session.delete(keyword) session.commit() print("Keyword %s has been removed" % keyword.name)
def do_k_edit(self, line): """Edit a keyword k_edit @<keyword>""" session = db.getSession() keyword = dbutils.getKeywordFromName(line) oldName = keyword.name newName = tui.editLine(oldName) if newName == "": print("Cancelled") return lst = session.query(Keyword).filter_by(name=newName).all() if len(lst) == 0: # Simple case: newName does not exist, just rename the existing keyword keyword.name = newName session.merge(keyword) session.commit() print("Keyword %s has been renamed to %s" % (oldName, newName)) return # We already have a keyword with this name, we need to merge print("Keyword %s already exists" % newName) if not tui.confirm("Do you want to merge %s and %s" % (oldName, newName)): return # Check we can merge conflictingTasks = [] for task in keyword.tasks: kwDict = task.getKeywordDict() if oldName in kwDict and newName in kwDict and kwDict[oldName] != kwDict[newName]: conflictingTasks.append(task) if len(conflictingTasks) > 0: # We cannot merge tui.error("Cannot merge keywords %s and %s because they are both" " used with different values in these tasks:" % (oldName, newName)) for task in conflictingTasks: print("- %d, %s" % (task.id, task.title)) print("Edit these tasks and try again") return # Merge for task in keyword.tasks: kwDict = task.getKeywordDict() if newName not in kwDict: kwDict[newName] = kwDict[oldName] del kwDict[oldName] task.setKeywordDict(kwDict) session.delete(keyword) session.commit() print("Keyword %s has been merged with %s" % (oldName, newName))
def getKeywordFromName(name): """Returns a keyword from its name, which may start with "@" raises a YokadiException if not found @param name: the keyword name @return: The keyword""" session = db.getSession() if not name: raise YokadiException("No keyword supplied") if name.startswith("@"): name = name[1:] lst = session.query(Keyword).filter_by(name=name).all() if len(lst) == 0: raise YokadiException("No keyword named '%s' found" % name) return lst[0]
def do_k_remove(self, line): """Remove a keyword k_remove @<keyword>""" session = db.getSession() keyword = dbutils.getKeywordFromName(line) if keyword.tasks: taskList = ", ".join(str(task.id) for task in keyword.tasks) print("The keyword {} is used by the following tasks: {}".format(keyword.name, taskList)) if not tui.confirm("Do you really want to remove this keyword"): return session.delete(keyword) session.commit() print("Keyword {} has been removed".format(keyword.name))
def do_k_add(self, line): """Add a keyword k_add @<keyword1> [@<keyword2>...]""" session = db.getSession() if not line: raise BadUsageException("You must provide at least one keyword name") for keyword in line.split(): try: session.add(Keyword(name=keyword)) session.commit() print("Keyword %s has been created" % keyword) except IntegrityError: session.rollback() print("Keyword %s already exist" % keyword)
def getProjectFromName(name, parameterName="project_name"): """ Helper function which returns a project given its name, or raise a YokadiException if it does not exist. """ name = name.strip() if len(name) == 0: raise BadUsageException("Missing <%s> parameter" % parameterName) try: session = db.getSession() return session.query(Project).filter_by(name=name).one() except NoResultFound: raise YokadiException("Project '%s' not found. Use p_list to see all projects." % name)
def isPassphraseValid(self): """Check if user passphrase is valid. ie. : if it can decrypt the check crypto word""" if not self.passphrase: # If no passphrase has been defined, it is definitively not valid ! return False if self.crypto_check: try: int(self._decrypt(self.crypto_check)) return True except ValueError: return False else: # First time that user enter a passphrase. Store the crypto check # for next time usage # We use a long string composed of int that we encrypt check_word = str(Random().getrandbits(KEY_LENGTH * KEY_LENGTH)) check_word = adjustString(check_word, 10 * KEY_LENGTH) self.crypto_check = self._encrypt(check_word) # Save it to database config db.getSession().add(db.Config(name="CRYPTO_CHECK", value=self.crypto_check, system=True, desc="Cryptographic check data of passphrase")) return True
def do_k_remove(self, line): """Remove a keyword k_remove @<keyword>""" session = db.getSession() keyword = dbutils.getKeywordFromName(line) if keyword.tasks: taskList = ", ".join(str(task.id) for task in keyword.tasks) print("The keyword {} is used by the following tasks: {}".format( keyword.name, taskList)) if not tui.confirm("Do you really want to remove this keyword"): return session.delete(keyword) session.commit() print("Keyword {} has been removed".format(keyword.name))
def do_a_add(self, line): """Add an alias on a command Ex. create an alias 'la' for 't_list -a': a_add la t_list -a""" tokens = line.split() if len(tokens) < 2: raise BadUsageException("You should provide an alias name and a command") name = tokens[0] if name in self.aliases: raise YokadiException("There is already an alias named {}.".format(name)) command = " ".join(tokens[1:]) session = db.getSession() db.Alias.add(session, name, command) session.commit() self._updateAliasDict()
def getOrCreateKeyword(keywordName, interactive=True): """Get a keyword by its name. Create it if needed @param keywordName: keyword name as a string @param interactive: Ask user before creating keyword (this is the default) @type interactive: Bool @return: Keyword instance or None if user cancel creation""" session = db.getSession() try: return session.query(Keyword).filter_by(name=keywordName).one() except (NoResultFound, MultipleResultsFound): if interactive and not tui.confirm("Keyword '%s' does not exist, create it" % keywordName): return None keyword = Keyword(name=keywordName) session.add(keyword) print("Added keyword '%s'" % keywordName) return keyword
def do_p_add(self, line): """Add new project. p_add <projectName>""" if not line: print("Missing project name.") return projectName = parseutils.parseOneWordName(line) session = db.getSession() try: project = Project(name=projectName) session.add(project) session.commit() except IntegrityError: session.rollback() raise YokadiException("A project named %s already exists. Please find another name" % projectName) print("Added project '%s'" % projectName)
def getOrCreateKeyword(keywordName, interactive=True): """Get a keyword by its name. Create it if needed @param keywordName: keyword name as a string @param interactive: Ask user before creating keyword (this is the default) @type interactive: Bool @return: Keyword instance or None if user cancel creation""" session = db.getSession() try: return session.query(Keyword).filter_by(name=keywordName).one() except (NoResultFound, MultipleResultsFound): if interactive and not tui.confirm( "Keyword '%s' does not exist, create it" % keywordName): return None keyword = Keyword(name=keywordName) session.add(keyword) print("Added keyword '%s'" % keywordName) return keyword