Skip to content

Commit

Permalink
refactor autopush to support windows
Browse files Browse the repository at this point in the history
  • Loading branch information
benoitc committed May 8, 2011
1 parent 6d99abc commit 829397a
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 158 deletions.
7 changes: 7 additions & 0 deletions couchapp/autopush/__init__.py
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of couchapp released under the Apache 2 license.
# See the NOTICE for more information.

DEFAULT_UPDATE_DELAY = 5 # update delay in seconds

165 changes: 7 additions & 158 deletions couchapp/autopush/command.py
Expand Up @@ -5,164 +5,18 @@

import logging
import os
import re
import signal
import sys
import time
import traceback

from .pathtools.path import absolute_path
from .watchdog.observers import Observer
from .watchdog.events import FileSystemEventHandler
from .watchdog.utils import has_attribute

from . import DEFAULT_UPDATE_DELAY
from ..errors import AppError
from ..localdoc import document
from ..util import json, remove_comments

log = logging.getLogger(__name__)

DEFAULT_UPDATE_DELAY = 5 # update delay in seconds

class CouchappEventHandler(FileSystemEventHandler):

def __init__(self, doc, dbs, update_delay=DEFAULT_UPDATE_DELAY,
noatomic=False):
super(CouchappEventHandler, self).__init__()

self.update_delay = update_delay
self.doc = doc
self.dbs = dbs
self.noatomic = noatomic
self.last_update = None

ignorefile = os.path.join(doc.docdir, '.couchappignore')
if os.path.exists(ignorefile):
with open(ignorefile, 'r') as f:
self.ignores = json.loads(remove_comments(f.read()))

def check_ignore(self, item):
for ign in self.ignores:
match = re.match(ign, item)
if match:
return True
return False

def maybe_update(self):
if not self.last_update:
return

diff = time.time() - self.last_update
if diff >= self.update_delay:
log.info("synchronize changes")
self.doc.push(self.dbs, noatomic=self.noatomic,
noindex=True)
self.last_update = None

def dispatch(self, ev):
if self.check_ignore(ev.src_path):
return

self.last_update = time.time()
self.maybe_update()

class CouchappWatcher(object):

SIG_QUEUE = []
SIGNALS = map(
lambda x: getattr(signal, "SIG%s" % x),
"QUIT INT TERM".split())

SIG_NAMES = dict(
(getattr(signal, name), name[3:].lower()) \
for name in dir(signal) \
if name[:3] == "SIG" and name[3] != "_")

def __init__(self, doc, dbs, update_delay=DEFAULT_UPDATE_DELAY,
noatomic=False):
self.doc_path = absolute_path(doc.docdir)
self.event_handler = CouchappEventHandler(doc, dbs,
update_delay=update_delay, noatomic=noatomic)
self.observer = Observer()
self.observer.schedule(self.event_handler,
self.doc_path, recursive=True)

def init_signals(self):
"""\
Initialize master signal handling. Most of the signals
are queued. Child signals only wake up the master.
"""
map(lambda s: signal.signal(s, self.signal), self.SIGNALS)
signal.signal(signal.SIGCHLD, self.handle_chld)

def signal(self, sig, frame):
if len(self.SIG_QUEUE) < 5:
self.SIG_QUEUE.append(sig)
else:
log.warn("Dropping signal: %s" % sig)

def handle_chld(self, sig, frame):
return

def handle_quit(self):
raise StopIteration

def handle_int(self):
raise StopIteration

def handle_term(self):
raise StopIteration

def run(self):
log.info("Starting to listen changes in '%s'", self.doc_path)
self.init_signals()
self.observer.start()
while True:
try:
sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None
if sig is None:
self.event_handler.maybe_update()
elif sig in self.SIG_NAMES:
signame = self.SIG_NAMES.get(sig)
handler = getattr(self, "handle_%s" % signame, None)
if not handler:
log.error("Unhandled signal: %s" % signame)
continue
log.info("handling signal: %s" % signame)
handler()
else:
log.info("Ignoring unknown signal: %s" % sig)
time.sleep(1)
except (StopIteration, KeyboardInterrupt):
self.observer.stop()
return 0
except Exception, e:
log.info("unhandled exception in main loop:\n%s" %
traceback.format_exc())
return -1
self.observer.join()

class WinCouchappWatcher(object):
def __init__(self, doc, dbs, update_delay=DEFAULT_UPDATE_DELAY,
noatomic=False):
self.doc_path = absolute_path(doc.docdir)
self.event_handler = CouchappEventHandler(doc, dbs,
update_delay=update_delay, noatomic=noatomic)
self.observer = Observer()
self.observer.schedule(self.event_handler,
self.doc_path, recursive=True)

def run(self):
log.info("Starting to listen changes in '%s'", self.doc_path)
self.observer.start()
try:
while True:
self.event_handler.maybe_update()
time.sleep(1)
except (SystemExit, KeyboardInterrupt):
self.observer.stop()
self.observer.join()
if sys.platform == "win32" or os.name == "nt":
from .winwatcher import WinCouchappWatcher as CouchappWatcher
else:
from .watcher import CouchappWatcher

log = logging.getLogger(__name__)

def autopush(conf, path, *args, **opts):
doc_path = None
Expand All @@ -187,11 +41,6 @@ def autopush(conf, path, *args, **opts):
update_delay = int(opts.get('update_delay', DEFAULT_UPDATE_DELAY))
noatomic = opts.get('no_atomic', False)

if sys.platform == "win32" or os.name == "nt":
watcher_class = WinCouchappWatcher
else:
watcher_class = CouchappWatcher

watcher = watcher_class(doc, dbs, update_delay=update_delay,
watcher = CouchappWatcher(doc, dbs, update_delay=update_delay,
noatomic=noatomic)
watcher.run()
58 changes: 58 additions & 0 deletions couchapp/autopush/handler.py
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
#
# This file is part of couchapp released under the Apache 2 license.
# See the NOTICE for more information.

import logging
import os
import re
import time

from . import DEFAULT_UPDATE_DELAY
from .watchdog.events import FileSystemEventHandler
from ..util import json, remove_comments


log = logging.getLogger(__name__)

class CouchappEventHandler(FileSystemEventHandler):

def __init__(self, doc, dbs, update_delay=DEFAULT_UPDATE_DELAY,
noatomic=False):
super(CouchappEventHandler, self).__init__()

self.update_delay = update_delay
self.doc = doc
self.dbs = dbs
self.noatomic = noatomic
self.last_update = None

ignorefile = os.path.join(doc.docdir, '.couchappignore')
if os.path.exists(ignorefile):
with open(ignorefile, 'r') as f:
self.ignores = json.loads(remove_comments(f.read()))

def check_ignore(self, item):
for ign in self.ignores:
match = re.match(ign, item)
if match:
return True
return False

def maybe_update(self):
if not self.last_update:
return

diff = time.time() - self.last_update
if diff >= self.update_delay:
log.info("synchronize changes")
self.doc.push(self.dbs, noatomic=self.noatomic,
noindex=True)
self.last_update = None

def dispatch(self, ev):
if self.check_ignore(ev.src_path):
return

self.last_update = time.time()
self.maybe_update()
94 changes: 94 additions & 0 deletions couchapp/autopush/watcher.py
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
#
# This file is part of couchapp released under the Apache 2 license.
# See the NOTICE for more information.

import logging
import signal
import time
import traceback

from . import DEFAULT_UPDATE_DELAY
from .handler import CouchappEventHandler
from .pathtools.path import absolute_path
from .watchdog.observers import Observer


log = logging.getLogger(__name__)


class CouchappWatcher(object):

SIG_QUEUE = []
SIGNALS = map(
lambda x: getattr(signal, "SIG%s" % x),
"QUIT INT TERM".split())

SIG_NAMES = dict(
(getattr(signal, name), name[3:].lower()) \
for name in dir(signal) \
if name[:3] == "SIG" and name[3] != "_")

def __init__(self, doc, dbs, update_delay=DEFAULT_UPDATE_DELAY,
noatomic=False):
self.doc_path = absolute_path(doc.docdir)
self.event_handler = CouchappEventHandler(doc, dbs,
update_delay=update_delay, noatomic=noatomic)
self.observer = Observer()
self.observer.schedule(self.event_handler,
self.doc_path, recursive=True)

def init_signals(self):
"""\
Initialize master signal handling. Most of the signals
are queued. Child signals only wake up the master.
"""
map(lambda s: signal.signal(s, self.signal), self.SIGNALS)
signal.signal(signal.SIGCHLD, self.handle_chld)

def signal(self, sig, frame):
if len(self.SIG_QUEUE) < 5:
self.SIG_QUEUE.append(sig)
else:
log.warn("Dropping signal: %s" % sig)

def handle_chld(self, sig, frame):
return

def handle_quit(self):
raise StopIteration

def handle_int(self):
raise StopIteration

def handle_term(self):
raise StopIteration

def run(self):
log.info("Starting to listen changes in '%s'", self.doc_path)
self.init_signals()
self.observer.start()
while True:
try:
sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None
if sig is None:
self.event_handler.maybe_update()
elif sig in self.SIG_NAMES:
signame = self.SIG_NAMES.get(sig)
handler = getattr(self, "handle_%s" % signame, None)
if not handler:
log.error("Unhandled signal: %s" % signame)
continue
log.info("handling signal: %s" % signame)
handler()
else:
log.info("Ignoring unknown signal: %s" % sig)
time.sleep(1)
except (StopIteration, KeyboardInterrupt):
self.observer.stop()
return 0
except Exception, e:
log.info("unhandled exception in main loop:\n%s" %
traceback.format_exc())
return -1
self.observer.join()
35 changes: 35 additions & 0 deletions couchapp/autopush/winwatcher.py
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#
# This file is part of couchapp released under the Apache 2 license.
# See the NOTICE for more information.

import logging
import time

from . import DEFAULT_UPDATE_DELAY
from .pathtools.path import absolute_path
from .watchdog.observers import Observer

log = logging.getLogger(__name__)

class WinCouchappWatcher(object):
def __init__(self, doc, dbs, update_delay=DEFAULT_UPDATE_DELAY,
noatomic=False):
self.doc_path = absolute_path(doc.docdir)
self.event_handler = CouchappEventHandler(doc, dbs,
update_delay=update_delay, noatomic=noatomic)
self.observer = Observer()
self.observer.schedule(self.event_handler,
self.doc_path, recursive=True)

def run(self):
log.info("Starting to listen changes in '%s'", self.doc_path)
self.observer.start()
try:
while True:
self.event_handler.maybe_update()
time.sleep(1)
except (SystemExit, KeyboardInterrupt):
self.observer.stop()
self.observer.join()

0 comments on commit 829397a

Please sign in to comment.