diff --git a/edbob/commands.py b/edbob/commands.py index 9d19cc1..f9a3701 100644 --- a/edbob/commands.py +++ b/edbob/commands.py @@ -386,12 +386,15 @@ class DatabaseCommand(Subcommand): class FileMonitorCommand(Subcommand): """ - Interacts with the file monitor Windows service; called as ``edbob - filemon``. This command expects a subcommand; one of the following: + Interacts with the file monitor service; called as ``edbob filemon``. This + command expects a subcommand; one of the following: - * ``edbob filemon install`` * ``edbob filemon start`` * ``edbob filemon stop`` + + On Windows platforms, the following additional subcommands are available: + + * ``edbob filemon install`` * ``edbob filemon uninstall`` .. note:: @@ -399,28 +402,46 @@ class FileMonitorCommand(Subcommand): ``cmd.exe`` as an Administrator in order to have sufficient rights to run the above commands. + .. todo:: + Verify the previous statement... (Maybe test with/out UAC?) + See :doc:`howto.use_filemon` for more information. """ name = 'filemon' - description = "Manage the file monitor service on Windows" + description = "Manage the file monitor service" def add_parser_args(self, parser): subparsers = parser.add_subparsers(title='subcommands') - install = subparsers.add_parser('install', - help="Install (register) service") - install.set_defaults(subcommand='install') - uninstall = subparsers.add_parser('uninstall', - help="Uninstall (unregister) service") - uninstall.set_defaults(subcommand='remove') + start = subparsers.add_parser('start', help="Start service") start.set_defaults(subcommand='start') stop = subparsers.add_parser('stop', help="Stop service") stop.set_defaults(subcommand='stop') + if sys.platform == 'win32': + install = subparsers.add_parser('install', + help="Install (register) service") + install.set_defaults(subcommand='install') + uninstall = subparsers.add_parser('uninstall', + help="Uninstall (unregister) service") + uninstall.set_defaults(subcommand='remove') + def run(self, args): - from edbob.filemon import exec_server_command - exec_server_command(args.subcommand) + if sys.platform == 'linux2': + if args.subcommand == 'start': + from edbob.filemon.linux import start_daemon + start_daemon() + elif args.subcommand == 'stop': + from edbob.filemon.linux import stop_daemon + stop_daemon() + + elif sys.platform == 'win32': + from edbob.filemon.win32 import exec_server_command + exec_server_command(args.subcommand) + + else: + print "Sorry, file monitor is not supported on platform %s." % sys.platform class ShellCommand(Subcommand): diff --git a/edbob/filemon/__init__.py b/edbob/filemon/__init__.py new file mode 100644 index 0000000..c3293b9 --- /dev/null +++ b/edbob/filemon/__init__.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +################################################################################ +# +# edbob -- Pythonic Software Framework +# Copyright © 2010-2012 Lance Edgar +# +# This file is part of edbob. +# +# edbob is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# edbob is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with edbob. If not, see . +# +################################################################################ + +""" +``edbob.filemon`` -- File Monitoring Service +""" + +import edbob +from edbob.exceptions import ConfigError + + +class MonitorProfile(object): + """ + This is a simple profile class, used to represent configuration of the file + monitor service. + """ + + def __init__(self, key): + self.key = key + self.dirs = eval(edbob.config.require('edbob.filemon', '%s.dirs' % key)) + if not self.dirs: + raise ConfigError('edbob.filemon', '%s.dirs' % key) + self.actions = eval(edbob.config.require('edbob.filemon', '%s.actions' % key)) + if not self.actions: + raise ConfigError('edbob.filemon', '%s.actions' % key) diff --git a/edbob/filemon/linux.py b/edbob/filemon/linux.py new file mode 100644 index 0000000..4715a58 --- /dev/null +++ b/edbob/filemon/linux.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +################################################################################ +# +# edbob -- Pythonic Software Framework +# Copyright © 2010-2012 Lance Edgar +# +# This file is part of edbob. +# +# edbob is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# edbob is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with edbob. If not, see . +# +################################################################################ + +""" +``edbob.filemon.linux`` -- File Monitor for Linux +""" + +import sys +import os +import os.path +import signal +import pyinotify + +import edbob +from edbob.filemon import MonitorProfile + + +class EventHandler(pyinotify.ProcessEvent): + """ + Event processor for file monitor daemon. + """ + + def my_init(self, actions=[], **kwargs): + self.actions = actions + + def process_IN_CREATE(self, event): + self.perform_actions(event.pathname) + + def process_IN_MOVED_TO(self, event): + self.perform_actions(event.pathname) + + def perform_actions(self, path): + for action in self.actions: + if isinstance(action, tuple): + func = action[0] + args = action[1:] + else: + func = action + args = [] + func = edbob.load_spec(func) + func(path, *args) + + +def get_pid_path(): + """ + Returns the path to the PID file for the file monitor daemon. + """ + + basename = os.path.basename(sys.argv[0]) + return '/tmp/%s_filemon.pid' % basename + + +def start_daemon(): + """ + Starts the file monitor daemon. + """ + + pid_path = get_pid_path() + if os.path.exists(pid_path): + print "File monitor is already running" + return + + wm = pyinotify.WatchManager() + notifier = pyinotify.Notifier(wm) + + monitored = {} + keys = edbob.config.require('edbob.filemon', 'monitored') + keys = keys.split(',') + for key in keys: + key = key.strip() + monitored[key] = MonitorProfile(key) + + mask = pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO + for profile in monitored.itervalues(): + for path in profile.dirs: + wm.add_watch(path, mask, proc_fun=EventHandler(actions=profile.actions)) + + notifier.loop(daemonize=True, pid_file=pid_path) + + +def stop_daemon(): + """ + Stops the file monitor daemon. + """ + + pid_path = get_pid_path() + if not os.path.exists(pid_path): + print "File monitor is not running" + return + + f = open(pid_path) + pid = f.read().strip() + f.close() + if not pid.isdigit(): + print "Hm, found bogus PID:", pid + return + + os.kill(int(pid), signal.SIGKILL) + os.remove(pid_path) diff --git a/edbob/filemon.py b/edbob/filemon/win32.py similarity index 89% rename from edbob/filemon.py rename to edbob/filemon/win32.py index 38f1d1c..0b063b3 100644 --- a/edbob/filemon.py +++ b/edbob/filemon/win32.py @@ -23,7 +23,7 @@ ################################################################################ """ -``edbob.filemon`` -- File Monitoring Service +``edbob.filemon.win32`` -- File Monitor for Windows """ # Much of the Windows monitoring code below was borrowed from Tim Golden: @@ -67,22 +67,6 @@ def exec_server_command(command): subprocess.call([sys.executable, server_path, command]) -class MonitorProfile(object): - """ - This is a simple profile class, used to represent configuration of the file - monitor service. - """ - - def __init__(self, key): - self.key = key - self.dirs = eval(edbob.config.require('edbob.filemon', '%s.dirs' % key)) - if not self.dirs: - raise ConfigError('edbob.filemon', '%s.dirs' % key) - self.actions = eval(edbob.config.require('edbob.filemon', '%s.actions' % key)) - if not self.actions: - raise ConfigError('edbob.filemon', '%s.actions' % key) - - def monitor_win32(path, include_subdirs=False): """ This is the workhorse of file monitoring on the Windows platform. It is a diff --git a/edbob/filemon_server.py b/edbob/filemon/win32_server.py similarity index 97% rename from edbob/filemon_server.py rename to edbob/filemon/win32_server.py index 8071a51..8e5c150 100644 --- a/edbob/filemon_server.py +++ b/edbob/filemon/win32_server.py @@ -32,9 +32,9 @@ import time import Queue import edbob +from edbob.filemon import MonitorProfile +from edbob.filemon.win32 import WatcherWin32, ACTION_CREATE, ACTION_UPDATE from edbob.win32 import file_is_free -from edbob.filemon import (MonitorProfile, WatcherWin32, - ACTION_CREATE, ACTION_UPDATE) import sys if sys.platform == 'win32': # docs should build for everyone