Commit 4d31c149 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Trigger updates when the todo.txt file changes

This is applicable for prompt mode and column mode. Changes are
immediately read whenever todo.txt is changed.

Current issues:

* Writes caused by the current topydo instance also trigger the file
  update: needless overhead.
* Column mode: items are not immediately redrawn on update

This mostly addresses issue #142.
parent 0c87872f
...@@ -19,6 +19,7 @@ This module deals with todo.txt files. ...@@ -19,6 +19,7 @@ This module deals with todo.txt files.
""" """
import codecs import codecs
import os.path
class TodoFile(object): class TodoFile(object):
...@@ -27,8 +28,33 @@ class TodoFile(object): ...@@ -27,8 +28,33 @@ class TodoFile(object):
to. to.
""" """
def __init__(self, p_path): def __init__(self, p_path, p_on_update=None):
self.path = p_path self.path = os.path.abspath(p_path)
if p_on_update:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileCreatedEvent
class EventHandler(FileSystemEventHandler):
def _handle(_, p_event):
right_type = isinstance(p_event, FileCreatedEvent) or isinstance(p_event, FileModifiedEvent)
if right_type and p_event.src_path == self.path:
p_on_update()
def on_created(self, p_event):
"""
Because vim deletes and creates a file on buffer save, also
catch a creation event.
"""
self._handle(p_event)
def on_modified(self, p_event):
self._handle(p_event)
observer = Observer()
observer.schedule(EventHandler(), os.path.dirname(self.path))
observer.start()
def read(self): def read(self):
""" Reads the todo.txt file and returns a list of todo items. """ """ Reads the todo.txt file and returns a list of todo items. """
......
...@@ -108,8 +108,13 @@ class UIApplication(CLIApplicationBase): ...@@ -108,8 +108,13 @@ class UIApplication(CLIApplicationBase):
if opt == "-l": if opt == "-l":
self.alt_layout_path = value self.alt_layout_path = value
def callback():
self.todolist.erase()
self.todolist.add_list(self.todofile.read())
self._update_all_columns()
self.column_width = config().column_width() self.column_width = config().column_width()
self.todofile = TodoFile.TodoFile(config().todotxt()) self.todofile = TodoFile.TodoFile(config().todotxt(), callback)
self.todolist = TodoList.TodoList(self.todofile.read()) self.todolist = TodoList.TodoList(self.todofile.read())
self.marked_todos = [] self.marked_todos = []
......
...@@ -41,17 +41,6 @@ from topydo.lib import TodoFile ...@@ -41,17 +41,6 @@ from topydo.lib import TodoFile
from topydo.lib import TodoList from topydo.lib import TodoList
def _todotxt_mtime():
"""
Returns the mtime for the configured todo.txt file.
"""
try:
return os.path.getmtime(config().todotxt())
except os.error:
# file not found
return None
class PromptApplication(CLIApplicationBase): class PromptApplication(CLIApplicationBase):
""" """
This class implements a variant of topydo's CLI showing a shell and This class implements a variant of topydo's CLI showing a shell and
...@@ -62,33 +51,25 @@ class PromptApplication(CLIApplicationBase): ...@@ -62,33 +51,25 @@ class PromptApplication(CLIApplicationBase):
super().__init__() super().__init__()
self._process_flags() self._process_flags()
self.mtime = None
self.completer = None self.completer = None
self.todofile = TodoFile.TodoFile(config().todotxt(), self._load_file)
def _load_file(self): def _load_file(self):
""" """
Reads the configured todo.txt file and loads it into the todo list Reads the configured todo.txt file and loads it into the todo list
instance. instance.
If the modification time of the todo.txt file is equal to the last time
it was checked, nothing will be done.
""" """
current_mtime = _todotxt_mtime() self.todolist.erase()
self.todolist.add_list(self.todofile.read())
if not self.todofile or self.mtime != current_mtime:
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
self.mtime = current_mtime
self.completer = TopydoCompleter(self.todolist) self.completer = TopydoCompleter(self.todolist)
def run(self): def run(self):
""" Main entry function. """ """ Main entry function. """
history = InMemoryHistory() history = InMemoryHistory()
self._load_file()
while True: while True:
# (re)load the todo.txt file (only if it has been modified) # (re)load the todo.txt file (only if it has been modified)
self._load_file()
try: try:
user_input = prompt(u'topydo> ', history=history, user_input = prompt(u'topydo> ', history=history,
...@@ -103,20 +84,12 @@ class PromptApplication(CLIApplicationBase): ...@@ -103,20 +84,12 @@ class PromptApplication(CLIApplicationBase):
error('Error: ' + str(verr)) error('Error: ' + str(verr))
continue continue
mtime_after = _todotxt_mtime()
try: try:
(subcommand, args) = get_subcommand(user_input) (subcommand, args) = get_subcommand(user_input)
except ConfigError as ce: except ConfigError as ce:
error('Error: ' + str(ce) + '. Check your aliases configuration') error('Error: ' + str(ce) + '. Check your aliases configuration')
continue continue
# refuse to perform operations such as 'del' and 'do' if the
# todo.txt file has been changed in the background.
if subcommand and not self.is_read_only(subcommand) and self.mtime != mtime_after:
error("WARNING: todo.txt file was modified by another application.\nTo prevent unintended changes, this operation was not executed.")
continue
try: try:
if self._execute(subcommand, args) != False: if self._execute(subcommand, args) != False:
self._post_execute() self._post_execute()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment