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):
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
WATCHDOG = 'watchdog >= 0.8.3'
setup(
name = "topydo",
packages = find_packages(exclude=["test"]),
......@@ -34,9 +36,9 @@ setup(
extras_require = {
':sys_platform=="win32"': ['colorama>=0.2.5'],
':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'],
'prompt': ['prompt_toolkit >= 0.53'],
'prompt': ['prompt_toolkit >= 0.53', WATCHDOG],
'test': ['coverage', 'freezegun', 'green', ],
'test:python_version=="3.2"': ['mock'],
},
......
......@@ -19,6 +19,7 @@ This module deals with todo.txt files.
"""
import codecs
import os.path
class TodoFile(object):
......@@ -27,8 +28,42 @@ class TodoFile(object):
to.
"""
def __init__(self, p_path):
self.path = p_path
def __init__(self, p_path, p_on_update=None):
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):
""" Reads the todo.txt file and returns a list of todo items. """
......@@ -49,6 +84,11 @@ class TodoFile(object):
p_todos can be a list of todo items, or a string that is just written
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")
if p_todos is list:
......@@ -60,3 +100,4 @@ class TodoFile(object):
todofile.write("\n")
todofile.close()
self.write_lock = False
......@@ -108,8 +108,14 @@ class UIApplication(CLIApplicationBase):
if opt == "-l":
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.todofile = TodoFile.TodoFile(config().todotxt())
self.todofile = TodoFile.TodoFile(config().todotxt(), callback)
self.todolist = TodoList.TodoList(self.todofile.read())
self.marked_todos = []
......@@ -553,13 +559,16 @@ class UIApplication(CLIApplicationBase):
self._console_visible = True
self.console.print_text(p_text)
def _redraw(self):
self.mainloop.draw_screen()
def _input(self, p_question):
self._print_to_console(p_question)
# 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
# question on screen.
self.mainloop.draw_screen()
self._redraw()
user_input = self.mainloop.screen.get_input()
self._console_visible = False
......
......@@ -41,17 +41,6 @@ from topydo.lib import TodoFile
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):
"""
This class implements a variant of topydo's CLI showing a shell and
......@@ -62,33 +51,25 @@ class PromptApplication(CLIApplicationBase):
super().__init__()
self._process_flags()
self.mtime = None
self.completer = None
self.todofile = TodoFile.TodoFile(config().todotxt(), self._load_file)
def _load_file(self):
"""
Reads the configured todo.txt file and loads it into the todo list
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()
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.todolist.erase()
self.todolist.add_list(self.todofile.read())
self.completer = TopydoCompleter(self.todolist)
def run(self):
""" Main entry function. """
history = InMemoryHistory()
self._load_file()
while True:
# (re)load the todo.txt file (only if it has been modified)
self._load_file()
try:
user_input = prompt(u'topydo> ', history=history,
......@@ -103,20 +84,12 @@ class PromptApplication(CLIApplicationBase):
error('Error: ' + str(verr))
continue
mtime_after = _todotxt_mtime()
try:
(subcommand, args) = get_subcommand(user_input)
except ConfigError as ce:
error('Error: ' + str(ce) + '. Check your aliases configuration')
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:
if self._execute(subcommand, args) != False:
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