%PDF- %PDF-
| Direktori : /proc/self/root/lib/python3/dist-packages/livereload/ |
| Current File : //proc/self/root/lib/python3/dist-packages/livereload/watcher.py |
# -*- coding: utf-8 -*-
"""
livereload.watcher
~~~~~~~~~~~~~~~~~~
A file watch management for LiveReload Server.
:copyright: (c) 2013 - 2015 by Hsiaoming Yang
:license: BSD, see LICENSE for more details.
"""
import glob
import logging
import os
import time
import sys
if sys.version_info.major < 3:
import inspect
else:
from inspect import signature
try:
import pyinotify
except ImportError:
pyinotify = None
logger = logging.getLogger('livereload')
class Watcher(object):
"""A file watcher registry."""
def __init__(self):
self._tasks = {}
# modification time of filepaths for each task,
# before and after checking for changes
self._task_mtimes = {}
self._new_mtimes = {}
# setting changes
self._changes = []
# filepath that is changed
self.filepath = None
self._start = time.time()
# list of ignored dirs
self.ignored_dirs = ['.git', '.hg', '.svn', '.cvs']
def ignore_dirs(self, *args):
self.ignored_dirs.extend(args)
def remove_dirs_from_ignore(self, *args):
for a in args:
self.ignored_dirs.remove(a)
def ignore(self, filename):
"""Ignore a given filename or not."""
_, ext = os.path.splitext(filename)
return ext in ['.pyc', '.pyo', '.o', '.swp']
def watch(self, path, func=None, delay=0, ignore=None):
"""Add a task to watcher.
:param path: a filepath or directory path or glob pattern
:param func: the function to be executed when file changed
:param delay: Delay sending the reload message. Use 'forever' to
not send it. This is useful to compile sass files to
css, but reload on changed css files then only.
:param ignore: A function return True to ignore a certain pattern of
filepath.
"""
self._tasks[path] = {
'func': func,
'delay': delay,
'ignore': ignore,
'mtimes': {},
}
def start(self, callback):
"""Start the watcher running, calling callback when changes are
observed. If this returns False, regular polling will be used."""
return False
def examine(self):
"""Check if there are changes. If so, run the given task.
Returns a tuple of modified filepath and reload delay.
"""
if self._changes:
return self._changes.pop()
# clean filepath
self.filepath = None
delays = set()
for path in self._tasks:
item = self._tasks[path]
self._task_mtimes = item['mtimes']
changed = self.is_changed(path, item['ignore'])
if changed:
func = item['func']
delay = item['delay']
if delay and isinstance(delay, float):
delays.add(delay)
if func:
name = getattr(func, 'name', None)
if not name:
name = getattr(func, '__name__', 'anonymous')
logger.info(
"Running task: {} (delay: {})".format(name, delay))
if sys.version_info.major < 3:
sig_len = len(inspect.getargspec(func)[0])
else:
sig_len = len(signature(func).parameters)
if sig_len > 0 and isinstance(changed, list):
func(changed)
else:
func()
if delays:
delay = max(delays)
else:
delay = None
return self.filepath, delay
def is_changed(self, path, ignore=None):
"""Check if any filepaths have been added, modified, or removed.
Updates filepath modification times in self._task_mtimes.
"""
self._new_mtimes = {}
changed = False
if os.path.isfile(path):
changed = self.is_file_changed(path, ignore)
elif os.path.isdir(path):
changed = self.is_folder_changed(path, ignore)
else:
changed = self.get_changed_glob_files(path, ignore)
if not changed:
changed = self.is_file_removed()
self._task_mtimes.update(self._new_mtimes)
return changed
def is_file_removed(self):
"""Check if any filepaths have been removed since last check.
Deletes removed paths from self._task_mtimes.
Sets self.filepath to one of the removed paths.
"""
removed_paths = set(self._task_mtimes) - set(self._new_mtimes)
if not removed_paths:
return False
for path in removed_paths:
self._task_mtimes.pop(path)
# self.filepath seems purely informational, so setting one
# of several removed files seems sufficient
self.filepath = path
return True
def is_file_changed(self, path, ignore=None):
"""Check if filepath has been added or modified since last check.
Updates filepath modification times in self._new_mtimes.
Sets self.filepath to changed path.
"""
if not os.path.isfile(path):
return False
if self.ignore(path):
return False
if ignore and ignore(path):
return False
mtime = os.path.getmtime(path)
if path not in self._task_mtimes:
self._new_mtimes[path] = mtime
self.filepath = path
return mtime > self._start
if self._task_mtimes[path] != mtime:
self._new_mtimes[path] = mtime
self.filepath = path
return True
self._new_mtimes[path] = mtime
return False
def is_folder_changed(self, path, ignore=None):
"""Check if directory path has any changed filepaths."""
for root, dirs, files in os.walk(path, followlinks=True):
for d in self.ignored_dirs:
if d in dirs:
dirs.remove(d)
for f in files:
if self.is_file_changed(os.path.join(root, f), ignore):
return True
return False
def get_changed_glob_files(self, path, ignore=None):
"""Check if glob path has any changed filepaths."""
if sys.version_info[0] >=3 and sys.version_info[1] >=5:
files = glob.glob(path, recursive=True)
else:
files = glob.glob(path)
changed_files = [f for f in files if self.is_file_changed(f, ignore)]
return changed_files
class INotifyWatcher(Watcher):
def __init__(self):
Watcher.__init__(self)
self.wm = pyinotify.WatchManager()
self.notifier = None
self.callback = None
def watch(self, path, func=None, delay=None, ignore=None):
flag = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY
self.wm.add_watch(path, flag, rec=True, do_glob=True, auto_add=True)
Watcher.watch(self, path, func, delay, ignore)
def inotify_event(self, event):
self.callback()
def start(self, callback):
if not self.notifier:
self.callback = callback
from tornado import ioloop
self.notifier = pyinotify.TornadoAsyncNotifier(
self.wm, ioloop.IOLoop.instance(),
default_proc_fun=self.inotify_event
)
callback()
return True
def get_watcher_class():
if pyinotify is None or not hasattr(pyinotify, 'TornadoAsyncNotifier'):
return Watcher
return INotifyWatcher