Use own style reloader
This commit is contained in:
@@ -53,7 +53,7 @@ class ClientScript(Plugin):
|
||||
],
|
||||
}
|
||||
|
||||
watcher = None
|
||||
watches = {}
|
||||
|
||||
original_paths = {'style': {}, 'script': {}}
|
||||
paths = {'style': {}, 'script': {}}
|
||||
@@ -82,17 +82,7 @@ class ClientScript(Plugin):
|
||||
def livereload(self):
|
||||
|
||||
if Env.get('dev'):
|
||||
from livereload import Server
|
||||
from livereload.watcher import Watcher
|
||||
|
||||
self.livereload_server = Server()
|
||||
self.livereload_server.watch('%s/minified/*.css' % Env.get('cache_dir'))
|
||||
self.livereload_server.watch('%s/*.css' % os.path.join(Env.get('app_dir'), 'couchpotato', 'static', 'style'))
|
||||
|
||||
self.watcher = Watcher()
|
||||
fireEvent('schedule.interval', 'livereload.watcher', self.watcher.examine, seconds = .5)
|
||||
|
||||
self.livereload_server.serve(port = 35729)
|
||||
fireEvent('schedule.interval', 'livereload.watcher', self.watcher, seconds = .5)
|
||||
|
||||
def addCore(self):
|
||||
|
||||
@@ -106,6 +96,39 @@ class ClientScript(Plugin):
|
||||
else:
|
||||
self.registerStyle(core_url, file_path, position = 'front')
|
||||
|
||||
def watcher(self):
|
||||
|
||||
changed = []
|
||||
|
||||
for file_path in self.watches:
|
||||
info = self.watches[file_path]
|
||||
old_time = info['file_time']
|
||||
file_time = os.path.getmtime(file_path)
|
||||
if file_time > old_time:
|
||||
changed.append(info['api_path'])
|
||||
|
||||
if info['compiled_path']:
|
||||
compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)])
|
||||
f = open(file_path, 'r').read()
|
||||
f = compiler.compile(f)
|
||||
|
||||
self.createFile(info['compiled_path'], f.strip())
|
||||
|
||||
|
||||
# Add file to watchlist again, with current filetime
|
||||
self.watches[file_path]['file_time'] = file_time
|
||||
|
||||
# Notify fronted with changes
|
||||
if changed:
|
||||
fireEvent('notify.frontend', type = 'watcher.changed', data = changed)
|
||||
|
||||
def watchFile(self, file_path, api_path, compiled_path = False):
|
||||
self.watches[file_path] = {
|
||||
'file_time': os.path.getmtime(file_path),
|
||||
'api_path': api_path,
|
||||
'compiled_path': compiled_path,
|
||||
}
|
||||
|
||||
def compile(self):
|
||||
|
||||
# Create cache dir
|
||||
@@ -146,7 +169,6 @@ class ClientScript(Plugin):
|
||||
|
||||
# Reload watcher
|
||||
if Env.get('dev'):
|
||||
self.watcher.watch(file_path, self.compile)
|
||||
|
||||
compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0]
|
||||
compiled_file_path = os.path.join(minified_dir, compiled_file_name)
|
||||
@@ -155,6 +177,8 @@ class ClientScript(Plugin):
|
||||
# Remove scss path
|
||||
x = (file_path, 'minified/%s?%s' % (compiled_file_name, tryInt(time.time())))
|
||||
|
||||
self.watchFile(file_path, x[1], compiled_path = compiled_file_path)
|
||||
|
||||
if not Env.get('dev'):
|
||||
|
||||
if file_type == 'script':
|
||||
@@ -199,6 +223,8 @@ class ClientScript(Plugin):
|
||||
|
||||
def register(self, api_path, file_path, type, location):
|
||||
|
||||
self.watchFile(file_path, api_path)
|
||||
|
||||
api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path)))
|
||||
|
||||
if not self.original_paths[type].get(location):
|
||||
|
||||
@@ -5,7 +5,7 @@ body, html {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #111;
|
||||
@@ -75,6 +75,11 @@ a {
|
||||
border-radius: 3px 0 0 3px;
|
||||
overflow: hidden;
|
||||
|
||||
h1, h2, h3 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
widows: 100%;
|
||||
@@ -91,6 +96,7 @@ a {
|
||||
.page {
|
||||
|
||||
display: none;
|
||||
padding: 20px;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
|
||||
@@ -8,18 +8,22 @@
|
||||
|
||||
|
||||
{% for url in fireEvent('clientscript.get_styles', location = 'front', single = True) %}
|
||||
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||
<link data-url="{{ url }}" rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||
{% for url in fireEvent('clientscript.get_scripts', location = 'front', single = True) %}
|
||||
<script type="text/javascript" src="{{ Env.get('web_base') }}{{ url }}"></script>{% end %}
|
||||
|
||||
{% for url in fireEvent('clientscript.get_scripts', location = 'head', single = True) %}
|
||||
<script type="text/javascript" src="{{ Env.get('web_base') }}{{ url }}"></script>{% end %}
|
||||
{% for url in fireEvent('clientscript.get_styles', location = 'head', single = True) %}
|
||||
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||
<link data-url="{{ url }}" rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||
|
||||
<link href="{{ Env.get('static_path') }}images/favicon.ico" rel="icon" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon" href="{{ Env.get('static_path') }}images/homescreen.png" />
|
||||
|
||||
{% if Env.get('dev') %}
|
||||
<script type="text/javascript" src="{{ Env.get('static_path') }}scripts/reloader.js"></script>
|
||||
{% end %}
|
||||
|
||||
<script type="text/javascript" src="https://www.youtube.com/player_api" defer="defer"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
"""
|
||||
livereload
|
||||
~~~~~~~~~~
|
||||
|
||||
A python version of livereload.
|
||||
|
||||
:copyright: (c) 2013 by Hsiaoming Yang
|
||||
"""
|
||||
|
||||
__version__ = '2.2.0'
|
||||
__author__ = 'Hsiaoming Yang <me@lepture.com>'
|
||||
__homepage__ = 'https://github.com/lepture/python-livereload'
|
||||
|
||||
from .server import Server, shell
|
||||
|
||||
__all__ = ('Server', 'shell')
|
||||
@@ -1,43 +0,0 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
livereload._compat
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Compatible module for python2 and python3.
|
||||
|
||||
:copyright: (c) 2013 by Hsiaoming Yang
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
unicode_type = str
|
||||
bytes_type = bytes
|
||||
text_types = (str,)
|
||||
else:
|
||||
unicode_type = unicode
|
||||
bytes_type = str
|
||||
text_types = (str, unicode)
|
||||
|
||||
|
||||
def to_unicode(value, encoding='utf-8'):
|
||||
"""Convert different types of objects to unicode."""
|
||||
if isinstance(value, unicode_type):
|
||||
return value
|
||||
|
||||
if isinstance(value, bytes_type):
|
||||
return unicode_type(value, encoding=encoding)
|
||||
|
||||
if isinstance(value, int):
|
||||
return unicode_type(str(value))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def to_bytes(value, encoding='utf-8'):
|
||||
"""Convert different types of objects to bytes."""
|
||||
if isinstance(value, bytes_type):
|
||||
return value
|
||||
return value.encode(encoding)
|
||||
@@ -1,209 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
livereload.handlers
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
HTTP and WebSocket handlers for livereload.
|
||||
|
||||
:copyright: (c) 2013 by Hsiaoming Yang
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import hashlib
|
||||
import logging
|
||||
import mimetypes
|
||||
from tornado import ioloop
|
||||
from tornado import escape
|
||||
from tornado.websocket import WebSocketHandler
|
||||
from tornado.web import RequestHandler
|
||||
from tornado.util import ObjectDict
|
||||
from ._compat import to_bytes
|
||||
|
||||
|
||||
class LiveReloadHandler(WebSocketHandler):
|
||||
waiters = set()
|
||||
watcher = None
|
||||
_last_reload_time = None
|
||||
|
||||
def allow_draft76(self):
|
||||
return True
|
||||
|
||||
def on_close(self):
|
||||
if self in LiveReloadHandler.waiters:
|
||||
LiveReloadHandler.waiters.remove(self)
|
||||
|
||||
def send_message(self, message):
|
||||
if isinstance(message, dict):
|
||||
message = escape.json_encode(message)
|
||||
|
||||
try:
|
||||
self.write_message(message)
|
||||
except:
|
||||
logging.error('Error sending message', exc_info=True)
|
||||
|
||||
def poll_tasks(self):
|
||||
filepath = self.watcher.examine()
|
||||
if not filepath:
|
||||
return
|
||||
logging.info('File %s changed', filepath)
|
||||
self.watch_tasks()
|
||||
|
||||
def watch_tasks(self):
|
||||
if time.time() - self._last_reload_time < 3:
|
||||
# if you changed lot of files in one time
|
||||
# it will refresh too many times
|
||||
logging.info('ignore this reload action')
|
||||
return
|
||||
|
||||
logging.info('Reload %s waiters', len(self.waiters))
|
||||
|
||||
msg = {
|
||||
'command': 'reload',
|
||||
'path': self.watcher.filepath or '*',
|
||||
'liveCSS': True
|
||||
}
|
||||
|
||||
self._last_reload_time = time.time()
|
||||
for waiter in LiveReloadHandler.waiters:
|
||||
try:
|
||||
waiter.write_message(msg)
|
||||
except:
|
||||
logging.error('Error sending message', exc_info=True)
|
||||
LiveReloadHandler.waiters.remove(waiter)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Handshake with livereload.js
|
||||
|
||||
1. client send 'hello'
|
||||
2. server reply 'hello'
|
||||
3. client send 'info'
|
||||
|
||||
http://feedback.livereload.com/knowledgebase/articles/86174-livereload-protocol
|
||||
"""
|
||||
message = ObjectDict(escape.json_decode(message))
|
||||
if message.command == 'hello':
|
||||
handshake = {}
|
||||
handshake['command'] = 'hello'
|
||||
handshake['protocols'] = [
|
||||
'http://livereload.com/protocols/official-7',
|
||||
'http://livereload.com/protocols/official-8',
|
||||
'http://livereload.com/protocols/official-9',
|
||||
'http://livereload.com/protocols/2.x-origin-version-negotiation',
|
||||
'http://livereload.com/protocols/2.x-remote-control'
|
||||
]
|
||||
handshake['serverName'] = 'livereload-tornado'
|
||||
self.send_message(handshake)
|
||||
|
||||
if message.command == 'info' and 'url' in message:
|
||||
logging.info('Browser Connected: %s' % message.url)
|
||||
LiveReloadHandler.waiters.add(self)
|
||||
|
||||
if not LiveReloadHandler._last_reload_time:
|
||||
if not self.watcher._tasks:
|
||||
logging.info('Watch current working directory')
|
||||
self.watcher.watch(os.getcwd())
|
||||
|
||||
LiveReloadHandler._last_reload_time = time.time()
|
||||
logging.info('Start watching changes')
|
||||
if not self.watcher.start(self.poll_tasks):
|
||||
ioloop.PeriodicCallback(self.poll_tasks, 800).start()
|
||||
|
||||
|
||||
class LiveReloadJSHandler(RequestHandler):
|
||||
def initialize(self, port):
|
||||
self._port = port
|
||||
|
||||
def get(self):
|
||||
js = os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)), 'livereload.js',
|
||||
)
|
||||
self.set_header('Content-Type', 'application/javascript')
|
||||
with open(js, 'r') as f:
|
||||
content = f.read()
|
||||
content = content.replace('{{port}}', str(self._port))
|
||||
self.write(content)
|
||||
|
||||
|
||||
class ForceReloadHandler(RequestHandler):
|
||||
def get(self):
|
||||
msg = {
|
||||
'command': 'reload',
|
||||
'path': self.get_argument('path', default=None) or '*',
|
||||
'liveCSS': True,
|
||||
'liveImg': True
|
||||
}
|
||||
for waiter in LiveReloadHandler.waiters:
|
||||
try:
|
||||
waiter.write_message(msg)
|
||||
except:
|
||||
logging.error('Error sending message', exc_info=True)
|
||||
LiveReloadHandler.waiters.remove(waiter)
|
||||
self.write('ok')
|
||||
|
||||
|
||||
class StaticHandler(RequestHandler):
|
||||
def initialize(self, root, fallback=None):
|
||||
self._root = os.path.abspath(root)
|
||||
self._fallback = fallback
|
||||
|
||||
def filepath(self, url):
|
||||
url = url.lstrip('/')
|
||||
url = os.path.join(self._root, url)
|
||||
|
||||
if url.endswith('/'):
|
||||
url += 'index.html'
|
||||
elif not os.path.exists(url) and not url.endswith('.html'):
|
||||
url += '.html'
|
||||
|
||||
if not os.path.isfile(url):
|
||||
return None
|
||||
return url
|
||||
|
||||
def get(self, path='/'):
|
||||
filepath = self.filepath(path)
|
||||
if not filepath and path.endswith('/'):
|
||||
rootdir = os.path.join(self._root, path.lstrip('/'))
|
||||
return self.create_index(rootdir)
|
||||
|
||||
if not filepath:
|
||||
if self._fallback:
|
||||
self._fallback(self.request)
|
||||
self._finished = True
|
||||
return
|
||||
return self.send_error(404)
|
||||
|
||||
mime_type, encoding = mimetypes.guess_type(filepath)
|
||||
if not mime_type:
|
||||
mime_type = 'text/html'
|
||||
|
||||
self.mime_type = mime_type
|
||||
self.set_header('Content-Type', mime_type)
|
||||
|
||||
with open(filepath, 'r') as f:
|
||||
data = f.read()
|
||||
|
||||
hasher = hashlib.sha1()
|
||||
hasher.update(to_bytes(data))
|
||||
self.set_header('Etag', '"%s"' % hasher.hexdigest())
|
||||
|
||||
ua = self.request.headers.get('User-Agent', 'bot').lower()
|
||||
if mime_type == 'text/html' and 'msie' not in ua:
|
||||
data = data.replace(
|
||||
'</head>',
|
||||
'<script src="/livereload.js"></script></head>'
|
||||
)
|
||||
self.write(data)
|
||||
|
||||
def create_index(self, root):
|
||||
files = os.listdir(root)
|
||||
self.write('<ul>')
|
||||
for f in files:
|
||||
path = os.path.join(root, f)
|
||||
self.write('<li>')
|
||||
if os.path.isdir(path):
|
||||
self.write('<a href="%s/">%s</a>' % (f, f))
|
||||
else:
|
||||
self.write('<a href="%s">%s</a>' % (f, f))
|
||||
self.write('</li>')
|
||||
self.write('</ul>')
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,224 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
livereload.server
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
WSGI app server for livereload.
|
||||
|
||||
:copyright: (c) 2013 by Hsiaoming Yang
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from subprocess import Popen, PIPE
|
||||
import time
|
||||
import threading
|
||||
import webbrowser
|
||||
|
||||
from tornado import escape
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import Application, FallbackHandler
|
||||
from .handlers import LiveReloadHandler, LiveReloadJSHandler
|
||||
from .handlers import ForceReloadHandler, StaticHandler
|
||||
from .watcher import Watcher
|
||||
from ._compat import text_types
|
||||
from tornado.log import enable_pretty_logging
|
||||
enable_pretty_logging()
|
||||
|
||||
|
||||
def shell(command, output=None, mode='w'):
|
||||
"""Command shell command.
|
||||
|
||||
You can add a shell command::
|
||||
|
||||
server.watch(
|
||||
'style.less', shell('lessc style.less', output='style.css')
|
||||
)
|
||||
|
||||
:param command: a shell command, string or list
|
||||
:param output: output stdout to the given file
|
||||
:param mode: only works with output, mode ``w`` means write,
|
||||
mode ``a`` means append
|
||||
"""
|
||||
if not output:
|
||||
output = os.devnull
|
||||
else:
|
||||
folder = os.path.dirname(output)
|
||||
if folder and not os.path.isdir(folder):
|
||||
os.makedirs(folder)
|
||||
|
||||
if isinstance(command, (list, tuple)):
|
||||
cmd = command
|
||||
else:
|
||||
cmd = command.split()
|
||||
|
||||
def run_shell():
|
||||
try:
|
||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
except OSError as e:
|
||||
logging.error(e)
|
||||
if e.errno == os.errno.ENOENT: # file (command) not found
|
||||
logging.error("maybe you haven't installed %s", cmd[0])
|
||||
return e
|
||||
stdout, stderr = p.communicate()
|
||||
if stderr:
|
||||
logging.error(stderr)
|
||||
return stderr
|
||||
#: stdout is bytes, decode for python3
|
||||
code = stdout.decode()
|
||||
with open(output, mode) as f:
|
||||
f.write(code)
|
||||
|
||||
return run_shell
|
||||
|
||||
|
||||
class WSGIWrapper(WSGIContainer):
|
||||
"""Insert livereload scripts into response body."""
|
||||
|
||||
def __call__(self, request):
|
||||
data = {}
|
||||
response = []
|
||||
|
||||
def start_response(status, response_headers, exc_info=None):
|
||||
data["status"] = status
|
||||
data["headers"] = response_headers
|
||||
return response.append
|
||||
app_response = self.wsgi_application(
|
||||
WSGIContainer.environ(request), start_response)
|
||||
try:
|
||||
response.extend(app_response)
|
||||
body = b"".join(response)
|
||||
finally:
|
||||
if hasattr(app_response, "close"):
|
||||
app_response.close()
|
||||
if not data:
|
||||
raise Exception("WSGI app did not call start_response")
|
||||
|
||||
status_code = int(data["status"].split()[0])
|
||||
headers = data["headers"]
|
||||
header_set = set(k.lower() for (k, v) in headers)
|
||||
body = escape.utf8(body)
|
||||
body = body.replace(
|
||||
b'</head>',
|
||||
b'<script src="/livereload.js"></script></head>'
|
||||
)
|
||||
|
||||
if status_code != 304:
|
||||
if "content-length" not in header_set:
|
||||
headers.append(("Content-Length", str(len(body))))
|
||||
if "content-type" not in header_set:
|
||||
headers.append(("Content-Type", "text/html; charset=UTF-8"))
|
||||
if "server" not in header_set:
|
||||
headers.append(("Server", "livereload-tornado"))
|
||||
|
||||
parts = [escape.utf8("HTTP/1.1 " + data["status"] + "\r\n")]
|
||||
for key, value in headers:
|
||||
if key.lower() == 'content-length':
|
||||
value = str(len(body))
|
||||
parts.append(
|
||||
escape.utf8(key) + b": " + escape.utf8(value) + b"\r\n"
|
||||
)
|
||||
parts.append(b"\r\n")
|
||||
parts.append(body)
|
||||
request.write(b"".join(parts))
|
||||
request.finish()
|
||||
self._log(status_code, request)
|
||||
|
||||
|
||||
class Server(object):
|
||||
"""Livereload server interface.
|
||||
|
||||
Initialize a server and watch file changes::
|
||||
|
||||
server = Server(wsgi_app)
|
||||
server.serve()
|
||||
|
||||
:param app: a wsgi application instance
|
||||
:param watcher: A Watcher instance, you don't have to initialize
|
||||
it by yourself. Under Linux, you will want to install
|
||||
pyinotify and use INotifyWatcher() to avoid wasted
|
||||
CPU usage.
|
||||
"""
|
||||
def __init__(self, app=None, watcher=None):
|
||||
self.app = app
|
||||
self.port = 5500
|
||||
self.root = None
|
||||
if not watcher:
|
||||
watcher = Watcher()
|
||||
self.watcher = watcher
|
||||
|
||||
def watch(self, filepath, func=None):
|
||||
"""Add the given filepath for watcher list.
|
||||
|
||||
Once you have intialized a server, watch file changes before
|
||||
serve the server::
|
||||
|
||||
server.watch('static/*.stylus', 'make static')
|
||||
def alert():
|
||||
print('foo')
|
||||
server.watch('foo.txt', alert)
|
||||
server.serve()
|
||||
|
||||
:param filepath: files to be watched, it can be a filepath,
|
||||
a directory, or a glob pattern
|
||||
:param func: the function to be called, it can be a string of
|
||||
shell command, or any callable object without
|
||||
parameters
|
||||
"""
|
||||
if isinstance(func, text_types):
|
||||
func = shell(func)
|
||||
|
||||
self.watcher.watch(filepath, func)
|
||||
|
||||
def application(self, debug=True):
|
||||
LiveReloadHandler.watcher = self.watcher
|
||||
handlers = [
|
||||
(r'/livereload', LiveReloadHandler),
|
||||
(r'/forcereload', ForceReloadHandler),
|
||||
(r'/livereload.js', LiveReloadJSHandler, dict(port=self.port)),
|
||||
]
|
||||
|
||||
if self.app:
|
||||
self.app = WSGIWrapper(self.app)
|
||||
handlers.append(
|
||||
(r'.*', FallbackHandler, dict(fallback=self.app))
|
||||
)
|
||||
else:
|
||||
handlers.append(
|
||||
(r'(.*)', StaticHandler, dict(root=self.root or '.')),
|
||||
)
|
||||
return Application(handlers=handlers, debug=debug)
|
||||
|
||||
def serve(self, port=None, host=None, root=None, debug=True, open_url=False):
|
||||
"""Start serve the server with the given port.
|
||||
|
||||
:param port: serve on this port, default is 5500
|
||||
:param host: serve on this hostname, default is 0.0.0.0
|
||||
:param root: serve static on this root directory
|
||||
:param open_url: open system browser
|
||||
"""
|
||||
if root:
|
||||
self.root = root
|
||||
if port:
|
||||
self.port = port
|
||||
if host is None:
|
||||
host = ''
|
||||
|
||||
self.application(debug=debug).listen(self.port, address=host)
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
host = host or '127.0.0.1'
|
||||
print('Serving on %s:%s' % (host, self.port))
|
||||
|
||||
# Async open web browser after 5 sec timeout
|
||||
if open_url:
|
||||
def opener():
|
||||
time.sleep(5)
|
||||
webbrowser.open('http://%s:%s' % (host, self.port))
|
||||
threading.Thread(target=opener).start()
|
||||
|
||||
try:
|
||||
IOLoop.instance().start()
|
||||
except KeyboardInterrupt:
|
||||
print('Shutting down...')
|
||||
@@ -1,132 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
livereload.watcher
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A file watch management for LiveReload Server.
|
||||
|
||||
:copyright: (c) 2013 by Hsiaoming Yang
|
||||
"""
|
||||
|
||||
import os
|
||||
import glob
|
||||
import time
|
||||
|
||||
|
||||
class Watcher(object):
|
||||
"""A file watcher registery."""
|
||||
def __init__(self):
|
||||
self._tasks = {}
|
||||
self._mtimes = {}
|
||||
|
||||
# filepath that is changed
|
||||
self.filepath = None
|
||||
self._start = time.time()
|
||||
|
||||
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):
|
||||
"""Add a task to watcher."""
|
||||
self._tasks[path] = func
|
||||
|
||||
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 true, run the given task."""
|
||||
# clean filepath
|
||||
self.filepath = None
|
||||
for path in self._tasks:
|
||||
if self.is_changed(path):
|
||||
func = self._tasks[path]
|
||||
# run function
|
||||
func and func()
|
||||
return self.filepath
|
||||
|
||||
def is_changed(self, path):
|
||||
if os.path.isfile(path):
|
||||
return self.is_file_changed(path)
|
||||
elif os.path.isdir(path):
|
||||
return self.is_folder_changed(path)
|
||||
return self.is_glob_changed(path)
|
||||
|
||||
def is_file_changed(self, path):
|
||||
if not os.path.isfile(path):
|
||||
return False
|
||||
|
||||
if self.ignore(path):
|
||||
return False
|
||||
|
||||
mtime = os.path.getmtime(path)
|
||||
|
||||
if path not in self._mtimes:
|
||||
self._mtimes[path] = mtime
|
||||
self.filepath = path
|
||||
return mtime > self._start
|
||||
|
||||
if self._mtimes[path] != mtime:
|
||||
self._mtimes[path] = mtime
|
||||
self.filepath = path
|
||||
return True
|
||||
|
||||
self._mtimes[path] = mtime
|
||||
return False
|
||||
|
||||
def is_folder_changed(self, path):
|
||||
for root, dirs, files in os.walk(path, followlinks=True):
|
||||
if '.git' in dirs:
|
||||
dirs.remove('.git')
|
||||
if '.hg' in dirs:
|
||||
dirs.remove('.hg')
|
||||
if '.svn' in dirs:
|
||||
dirs.remove('.svn')
|
||||
if '.cvs' in dirs:
|
||||
dirs.remove('.cvs')
|
||||
|
||||
for f in files:
|
||||
if self.is_file_changed(os.path.join(root, f)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_glob_changed(self, path):
|
||||
for f in glob.glob(path):
|
||||
if self.is_file_changed(f):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class INotifyWatcher(Watcher):
|
||||
def __init__(self):
|
||||
Watcher.__init__(self)
|
||||
|
||||
import pyinotify
|
||||
self.wm = pyinotify.WatchManager()
|
||||
self.notifier = None
|
||||
self.callback = None
|
||||
|
||||
def watch(self, path, func=None):
|
||||
import pyinotify
|
||||
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)
|
||||
|
||||
def inotify_event(self, event):
|
||||
self.callback()
|
||||
|
||||
def start(self, callback):
|
||||
if not self.notifier:
|
||||
self.callback = callback
|
||||
|
||||
import pyinotify
|
||||
from tornado import ioloop
|
||||
self.notifier = pyinotify.TornadoAsyncNotifier(
|
||||
self.wm, ioloop.IOLoop.instance(),
|
||||
default_proc_fun=self.inotify_event
|
||||
)
|
||||
callback()
|
||||
return True
|
||||
Reference in New Issue
Block a user