Commit ac982b13 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'watchdog'

parents 7cdcce98 b4f281a6
...@@ -20,6 +20,8 @@ def find_version(*file_paths): ...@@ -20,6 +20,8 @@ def find_version(*file_paths):
return version_match.group(1) return version_match.group(1)
raise RuntimeError("Unable to find version string.") raise RuntimeError("Unable to find version string.")
WATCHDOG = 'watchdog >= 0.8.3'
setup( setup(
name = "topydo", name = "topydo",
packages = find_packages(exclude=["test"]), packages = find_packages(exclude=["test"]),
...@@ -34,9 +36,9 @@ setup( ...@@ -34,9 +36,9 @@ setup(
extras_require = { extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'], ':sys_platform=="win32"': ['colorama>=0.2.5'],
':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'], ':python_version=="3.2"': ['backports.shutil_get_terminal_size>=1.0.0'],
'columns': ['urwid >= 1.3.0'], 'columns': ['urwid >= 1.3.0', WATCHDOG],
'ical': ['icalendar'], 'ical': ['icalendar'],
'prompt': ['prompt_toolkit >= 0.53'], 'prompt': ['prompt_toolkit >= 0.53', WATCHDOG],
'test': ['coverage', 'freezegun', 'green', ], 'test': ['coverage', 'freezegun', 'green', ],
'test:python_version=="3.2"': ['mock'], 'test:python_version=="3.2"': ['mock'],
}, },
......
...@@ -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,42 @@ class TodoFile(object): ...@@ -27,8 +28,42 @@ 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)
self.write_lock = False
if p_on_update:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileCreatedEvent
class EventHandler(FileSystemEventHandler):
"""
Event handler to catch modifications (or creations) of the
current todo.txt file.
"""
def __init__(self, p_file):
super().__init__()
self.file = p_file
def _handle(self, p_event):
right_type = isinstance(p_event, FileModifiedEvent) or isinstance(p_event, FileCreatedEvent)
if not self.file.write_lock and right_type and p_event.src_path == self.file.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(self), 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. """
...@@ -49,6 +84,11 @@ class TodoFile(object): ...@@ -49,6 +84,11 @@ class TodoFile(object):
p_todos can be a list of todo items, or a string that is just written p_todos can be a list of todo items, or a string that is just written
to the file. to the file.
""" """
# make sure not to reread the todo file because this instance is
# actually writing it
self.write_lock = True
todofile = codecs.open(self.path, 'w', encoding="utf-8") todofile = codecs.open(self.path, 'w', encoding="utf-8")
if p_todos is list: if p_todos is list:
...@@ -60,3 +100,4 @@ class TodoFile(object): ...@@ -60,3 +100,4 @@ class TodoFile(object):
todofile.write("\n") todofile.write("\n")
todofile.close() todofile.close()
self.write_lock = False
...@@ -108,8 +108,14 @@ class UIApplication(CLIApplicationBase): ...@@ -108,8 +108,14 @@ 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._redraw()
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 = []
...@@ -553,13 +559,16 @@ class UIApplication(CLIApplicationBase): ...@@ -553,13 +559,16 @@ class UIApplication(CLIApplicationBase):
self._console_visible = True self._console_visible = True
self.console.print_text(p_text) self.console.print_text(p_text)
def _redraw(self):
self.mainloop.draw_screen()
def _input(self, p_question): def _input(self, p_question):
self._print_to_console(p_question) self._print_to_console(p_question)
# don't wait for the event loop to enter idle, there is a command # don't wait for the event loop to enter idle, there is a command
# waiting for input right now, so already go ahead and draw the # waiting for input right now, so already go ahead and draw the
# question on screen. # question on screen.
self.mainloop.draw_screen() self._redraw()
user_input = self.mainloop.screen.get_input() user_input = self.mainloop.screen.get_input()
self._console_visible = False self._console_visible = False
......
...@@ -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.completer = TopydoCompleter(self.todolist)
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
self.mtime = current_mtime
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