Implement auto reload on windows

This commit is contained in:
Kovid Goyal 2015-06-05 11:33:01 +05:30
parent 74c50bf11a
commit 6ac01f3560

View File

@ -9,14 +9,34 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, subprocess, signal, time import os, sys, subprocess, signal, time
from threading import Thread from threading import Thread
from calibre.constants import islinux from calibre.constants import islinux, iswindows
class NoAutoReload(EnvironmentError): class NoAutoReload(EnvironmentError):
pass pass
EXTENSIONS_TO_WATCH = frozenset('py pyj css js xml'.split()) EXTENSIONS_TO_WATCH = frozenset('py pyj'.split())
BOUNCE_INTERVAL = 2 # seconds BOUNCE_INTERVAL = 2 # seconds
class WatcherBase(object):
def __init__(self, server, log):
self.server, self.log = server, log
fpath = os.path.abspath(__file__)
d = os.path.dirname
self.base = d(d(d(d(fpath))))
self.last_restart_time = time.time()
def handle_modified(self, modified):
if modified:
if time.time() - self.last_restart_time > BOUNCE_INTERVAL:
modified = {os.path.relpath(x, self.base) if x.startswith(self.base) else x for x in modified if x}
changed = os.pathsep.join(sorted(modified))
self.log('')
self.log('Restarting server because of changed files:', changed)
self.log('')
self.server.restart()
self.last_restart_time = time.time()
if islinux: if islinux:
import select import select
from calibre.utils.inotify import INotifyTreeWatcher from calibre.utils.inotify import INotifyTreeWatcher
@ -24,18 +44,14 @@ if islinux:
def ignore_event(path, name): def ignore_event(path, name):
return name and name.rpartition('.')[-1] not in EXTENSIONS_TO_WATCH return name and name.rpartition('.')[-1] not in EXTENSIONS_TO_WATCH
class Watcher(object): class Watcher(WatcherBase):
def __init__(self, root_dirs, server, log): def __init__(self, root_dirs, server, log):
self.server, self.log = server, log WatcherBase.__init__(self, server, log)
self.fd_map = {} self.fd_map = {}
for d in frozenset(root_dirs): for d in frozenset(root_dirs):
w = INotifyTreeWatcher(d, ignore_event) w = INotifyTreeWatcher(d, ignore_event)
self.fd_map[w._inotify_fd] = w self.fd_map[w._inotify_fd] = w
self.last_restart_time = time.time()
fpath = os.path.abspath(__file__)
d = os.path.dirname
self.base = d(d(d(d(fpath))))
def loop(self): def loop(self):
while True: while True:
@ -44,15 +60,73 @@ if islinux:
for fd in r: for fd in r:
w = self.fd_map[fd] w = self.fd_map[fd]
modified |= w() modified |= w()
if modified: self.handle_modified()
if time.time() - self.last_restart_time > BOUNCE_INTERVAL:
modified = {os.path.relpath(x, self.base) if x.startswith(self.base) else x for x in modified if x} elif iswindows:
changed = os.pathsep.join(sorted(modified)) import win32file, win32con
self.log('') from Queue import Queue
self.log('Restarting server because of changed files:', changed) FILE_LIST_DIRECTORY = 0x0001
self.log('') from calibre.srv.utils import HandleInterrupt
self.server.restart()
self.last_restart_time = time.time() class TreeWatcher(Thread):
daemon = True
def __init__(self, path_to_watch, modified_queue):
Thread.__init__(self, name='TreeWatcher')
self.modified_queue = modified_queue
self.path_to_watch = path_to_watch
self.dir_handle = win32file.CreateFile(
path_to_watch,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None
)
def run(self):
try:
while True:
results = win32file.ReadDirectoryChangesW(
self.dir_handle,
8192, # Buffer size for storing events
True, # Watch sub-directories as well
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_SIZE |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY,
None, None
)
for action, filename in results:
if filename and filename.rpartition('.')[-1] in EXTENSIONS_TO_WATCH:
self.modified_queue.put(os.path.join(self.path_to_watch, filename))
except Exception:
import traceback
traceback.print_exc()
class Watcher(WatcherBase):
def __init__(self, root_dirs, server, log):
WatcherBase.__init__(self, server, log)
self.watchers = []
self.modified_queue = Queue()
for d in frozenset(root_dirs):
self.watchers.append(TreeWatcher(d, self.modified_queue))
def loop(self):
for w in self.watchers:
w.start()
with HandleInterrupt(lambda : self.modified_queue.put(None)):
while True:
path = self.modified_queue.get()
if path is None:
break
modified = {path}
self.handle_modified(modified)
else: else:
Watcher = None Watcher = None