overhaul filemon.win32 service
This commit is contained in:
parent
3adf289652
commit
1d8403a67e
6 changed files with 350 additions and 285 deletions
|
@ -32,6 +32,7 @@ import sys
|
|||
import argparse
|
||||
import subprocess
|
||||
import logging
|
||||
import platform
|
||||
|
||||
import edbob
|
||||
from edbob.util import entry_point_map, requires_impl
|
||||
|
@ -420,32 +421,58 @@ class FileMonitorCommand(Subcommand):
|
|||
stop.set_defaults(subcommand='stop')
|
||||
|
||||
if sys.platform == 'win32':
|
||||
install = subparsers.add_parser('install',
|
||||
help="Install (register) service")
|
||||
install = subparsers.add_parser('install', help="Install service")
|
||||
install.set_defaults(subcommand='install')
|
||||
uninstall = subparsers.add_parser('uninstall',
|
||||
help="Uninstall (unregister) service")
|
||||
install.add_argument('-a', '--auto-start', action='store_true',
|
||||
help="Configure service to start automatically")
|
||||
remove = subparsers.add_parser('remove', help="Uninstall (remove) service")
|
||||
remove.set_defaults(subcommand='remove')
|
||||
uninstall = subparsers.add_parser('uninstall', help="Uninstall (remove) service")
|
||||
uninstall.set_defaults(subcommand='remove')
|
||||
|
||||
def manage_service(self, args, win32):
|
||||
def get_win32_module(self):
|
||||
from edbob.filemon import win32
|
||||
return win32
|
||||
|
||||
def get_win32_service(self):
|
||||
from edbob.filemon.win32 import FileMonitorService
|
||||
return FileMonitorService
|
||||
|
||||
def get_win32_service_name(self):
|
||||
service = self.get_win32_service()
|
||||
return service._svc_name_
|
||||
|
||||
def run(self, args):
|
||||
if sys.platform == 'linux2':
|
||||
from edbob.filemon import linux as filemon
|
||||
|
||||
if args.subcommand == 'start':
|
||||
from edbob.filemon.linux import start_daemon
|
||||
start_daemon()
|
||||
filemon.start_daemon()
|
||||
|
||||
elif args.subcommand == 'stop':
|
||||
from edbob.filemon.linux import stop_daemon
|
||||
stop_daemon()
|
||||
filemon.stop_daemon()
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
win32.exec_server_command(args.subcommand)
|
||||
from edbob import win32
|
||||
|
||||
filemon = self.get_win32_module()
|
||||
|
||||
# Execute typical service command.
|
||||
options = []
|
||||
if args.subcommand == 'install' and args.auto_start:
|
||||
options = ['--startup', 'auto']
|
||||
win32.execute_service_command(filemon, args.subcommand, *options)
|
||||
|
||||
# If installing auto-start service on Windows 7, we should update
|
||||
# its startup type to be "Automatic (Delayed Start)".
|
||||
if args.subcommand == 'install' and args.auto_start:
|
||||
if platform.release() == '7':
|
||||
name = self.get_win32_service_name()
|
||||
win32.delayed_auto_start_service(name)
|
||||
|
||||
else:
|
||||
print "Sorry, file monitor is not supported on platform %s." % sys.platform
|
||||
|
||||
def run(self, args):
|
||||
from edbob.filemon import win32
|
||||
self.manage_service(args, win32)
|
||||
|
||||
|
||||
class ShellCommand(Subcommand):
|
||||
"""
|
||||
|
|
|
@ -52,11 +52,14 @@ def init(config):
|
|||
sys.excepthook = excepthook
|
||||
|
||||
|
||||
def email_exception(type, value, traceback):
|
||||
def email_exception(type=None, value=None, traceback=None):
|
||||
"""
|
||||
Sends an email containing a traceback to the configured recipient(s).
|
||||
"""
|
||||
|
||||
if not (type and value and traceback):
|
||||
type, value, traceback = sys.exc_info()
|
||||
|
||||
body = StringIO()
|
||||
|
||||
hostname = socket.gethostname()
|
||||
|
|
|
@ -26,8 +26,13 @@
|
|||
``edbob.filemon`` -- File Monitoring Service
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import logging
|
||||
|
||||
import edbob
|
||||
from edbob.exceptions import ConfigError
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonitorProfile(object):
|
||||
|
@ -36,11 +41,69 @@ class MonitorProfile(object):
|
|||
monitor service.
|
||||
"""
|
||||
|
||||
def __init__(self, key):
|
||||
def __init__(self, appname, key):
|
||||
self.appname = appname
|
||||
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)
|
||||
|
||||
self.dirs = edbob.config.require('%s.filemon' % appname, '%s.dirs' % key)
|
||||
self.dirs = eval(self.dirs)
|
||||
|
||||
actions = edbob.config.require('%s.filemon' % appname, '%s.actions' % key)
|
||||
actions = eval(actions)
|
||||
|
||||
self.actions = []
|
||||
for action in actions:
|
||||
if isinstance(action, tuple):
|
||||
spec = action[0]
|
||||
args = list(action[1:])
|
||||
else:
|
||||
spec = action
|
||||
args = []
|
||||
func = edbob.load_spec(spec)
|
||||
self.actions.append((spec, func, args))
|
||||
|
||||
|
||||
def get_monitor_profiles(appname):
|
||||
"""
|
||||
Convenience function to load monitor profiles from config.
|
||||
"""
|
||||
|
||||
monitored = {}
|
||||
|
||||
# Read monitor profile(s) from config.
|
||||
keys = edbob.config.require('%s.filemon' % appname, 'monitored')
|
||||
keys = keys.split(',')
|
||||
for key in keys:
|
||||
key = key.strip()
|
||||
profile = MonitorProfile(appname, key)
|
||||
monitored[key] = profile
|
||||
for path in profile.dirs[:]:
|
||||
|
||||
# Ensure the monitored path exists.
|
||||
if not os.path.exists(path):
|
||||
log.warning("get_monitor_profiles: Profile '%s' has nonexistent "
|
||||
"path, which will be pruned: %s" % (key, path))
|
||||
profile.dirs.remove(path)
|
||||
|
||||
# Ensure the monitored path is a folder.
|
||||
elif not os.path.isdir(path):
|
||||
log.warning("get_monitor_profiles: Profile '%s' has non-folder "
|
||||
"path, which will be pruned: %s" % (key, path))
|
||||
profile.dirs.remove(path)
|
||||
|
||||
for key in monitored.keys():
|
||||
profile = monitored[key]
|
||||
|
||||
# Prune any profiles with no valid folders to monitor.
|
||||
if not profile.dirs:
|
||||
log.warning("get_monitor_profiles: Profile '%s' has no folders to "
|
||||
"monitor, and will be pruned." % key)
|
||||
del monitored[key]
|
||||
|
||||
# Prune any profiles with no valid actions to perform.
|
||||
elif not profile.actions:
|
||||
log.warning("get_monitor_profiles: Profile '%s' has no actions to "
|
||||
"perform, and will be pruned." % key)
|
||||
del monitored[key]
|
||||
|
||||
return monitored
|
||||
|
|
|
@ -23,136 +23,213 @@
|
|||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.filemon.win32`` -- File Monitor for Windows
|
||||
``edbob.filemon.win32`` -- File Monitoring Service for Windows
|
||||
"""
|
||||
|
||||
# Much of the Windows monitoring code below was borrowed from Tim Golden:
|
||||
# http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import threading
|
||||
import sys
|
||||
import Queue
|
||||
import logging
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
import edbob
|
||||
from edbob.exceptions import ConfigError
|
||||
from edbob.errors import email_exception
|
||||
from edbob.filemon import get_monitor_profiles
|
||||
from edbob.win32 import file_is_free
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import win32file
|
||||
if sys.platform == 'win32': # docs should build for everyone
|
||||
import win32api
|
||||
import win32con
|
||||
import win32event
|
||||
import win32file
|
||||
import win32service
|
||||
import win32serviceutil
|
||||
import winnt
|
||||
|
||||
|
||||
FILE_LIST_DIRECTORY = 0x0001
|
||||
|
||||
ACTION_CREATE = 1
|
||||
ACTION_DELETE = 2
|
||||
ACTION_UPDATE = 3
|
||||
ACTION_RENAME_TO = 4
|
||||
ACTION_RENAME_FROM = 5
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def exec_server_command(command):
|
||||
class FileMonitorService(win32serviceutil.ServiceFramework):
|
||||
"""
|
||||
Executes ``command`` against the file monitor Windows service, i.e. one of:
|
||||
|
||||
* ``'install'``
|
||||
* ``'start'``
|
||||
* ``'stop'``
|
||||
* ``'remove'``
|
||||
Implements edbob's file monitor Windows service.
|
||||
"""
|
||||
server_path = os.path.join(os.path.dirname(__file__), 'filemon_server.py')
|
||||
subprocess.call([sys.executable, server_path, command])
|
||||
|
||||
_svc_name_ = "Edbob File Monitor"
|
||||
_svc_display_name_ = "Edbob : File Monitoring Service"
|
||||
_svc_description_ = ("Monitors one or more folders for incoming files, "
|
||||
"and performs configured actions as new files arrive.")
|
||||
|
||||
appname = 'edbob'
|
||||
|
||||
def __init__(self, args):
|
||||
"""
|
||||
Constructor.
|
||||
"""
|
||||
|
||||
# super(FileMonitorService, self).__init__(args)
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
|
||||
# Create "wait stop" event, for main worker loop.
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
|
||||
def Initialize(self):
|
||||
"""
|
||||
Service initialization.
|
||||
"""
|
||||
|
||||
# Read configuration file(s).
|
||||
edbob.init(self.appname)
|
||||
|
||||
# Read monitor profile(s) from config.
|
||||
self.monitored = get_monitor_profiles(self.appname)
|
||||
|
||||
# Make sure we have something to do.
|
||||
if not self.monitored:
|
||||
return False
|
||||
|
||||
# Create monitor and action threads for each profile.
|
||||
for key, profile in self.monitored.iteritems():
|
||||
|
||||
# Create a file queue for the profile.
|
||||
queue = Queue.Queue()
|
||||
|
||||
# Create a monitor thread for each folder in profile.
|
||||
for i, path in enumerate(profile.dirs, 1):
|
||||
name = 'monitor-%s-%u' % (key, i)
|
||||
log.debug("Initialize: Starting '%s' thread for folder: %s" %
|
||||
(name, path))
|
||||
thread = threading.Thread(
|
||||
target=monitor_files,
|
||||
name=name,
|
||||
args=(queue, path, profile))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
# Create an action thread for the profile.
|
||||
name = 'actions-%s' % key
|
||||
log.debug("Initialize: Starting '%s' thread" % name)
|
||||
thread = threading.Thread(
|
||||
target=perform_actions,
|
||||
name=name,
|
||||
args=(queue, profile))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return True
|
||||
|
||||
def SvcDoRun(self):
|
||||
"""
|
||||
This method is invoked when the service starts.
|
||||
"""
|
||||
|
||||
import servicemanager
|
||||
|
||||
# Write start occurrence to Windows Event Log.
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
# Figure out what we're supposed to be doing.
|
||||
if self.Initialize():
|
||||
|
||||
# Wait infinitely for stop request, while threads do their thing.
|
||||
log.info("SvcDoRun: All threads started; waiting for stop request.")
|
||||
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
|
||||
log.info("SvcDoRun: Stop request received.")
|
||||
|
||||
else: # Nothing to be done...
|
||||
msg = "Nothing to do! No valid monitor profiles found in config."
|
||||
servicemanager.LogWarningMsg(msg)
|
||||
log.warning("SvcDoRun: %s" % msg)
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
|
||||
# Write stop occurrence to Windows Event Log.
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STOPPED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
def SvcStop(self):
|
||||
"""
|
||||
This method is invoked when the service is requested to stop itself.
|
||||
"""
|
||||
|
||||
# Let the SCM know we're trying to stop.
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
|
||||
# Let worker loop know its job is done.
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
|
||||
def monitor_win32(path, include_subdirs=False):
|
||||
"""
|
||||
This is the workhorse of file monitoring on the Windows platform. It is a
|
||||
generator function which yields a ``(file_type, file_path, action)`` tuple
|
||||
whenever changes occur in the monitored folder. ``file_type`` will be one
|
||||
of:
|
||||
|
||||
* ``'file'``
|
||||
* ``'folder'``
|
||||
* ``'<deleted>'``
|
||||
|
||||
``file_path`` will be the path to the changed object; and ``action`` will
|
||||
be one of:
|
||||
|
||||
* ``ACTION_CREATE``
|
||||
* ``ACTION_DELETE``
|
||||
* ``ACTION_UPDATE``
|
||||
* ``ACTION_RENAME_TO``
|
||||
* ``ACTION_RENAME_FROM``
|
||||
|
||||
(The above are "constants" importable from ``edbob.filemon``.)
|
||||
|
||||
This function leverages the ``ReadDirectoryChangesW()`` Windows API
|
||||
function. This is nice because the OS now reports changes to us so that we
|
||||
needn't poll to find them.
|
||||
|
||||
However ``ReadDirectoryChangesW()`` is a blocking call, so in practice this
|
||||
means that if, say, you're using this function in a python shell, you will
|
||||
not be able to stop the process with a keyboard interrupt. (Actually you
|
||||
sort of can, as long as you send the interrupt and then perform some file
|
||||
operation which will trigger the monitoring function to return.)
|
||||
|
||||
Fortunately the primary need for this function is by the Windows service,
|
||||
which is of course "disconnected" from the user's desktop and never really
|
||||
needs to close under normal circumstances.
|
||||
def monitor_files(queue, path, profile):
|
||||
"""
|
||||
Callable target for file monitor threads.
|
||||
"""
|
||||
|
||||
hDir = win32file.CreateFile(
|
||||
path,
|
||||
FILE_LIST_DIRECTORY,
|
||||
winnt.FILE_LIST_DIRECTORY,
|
||||
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
|
||||
None,
|
||||
win32con.OPEN_EXISTING,
|
||||
win32con.FILE_FLAG_BACKUP_SEMANTICS,
|
||||
None)
|
||||
|
||||
if hDir == win32file.INVALID_HANDLE_VALUE:
|
||||
log.warning("monitor_files: Can't open directory with CreateFile(): %s" % path)
|
||||
return
|
||||
|
||||
while True:
|
||||
results = win32file.ReadDirectoryChangesW (
|
||||
results = win32file.ReadDirectoryChangesW(
|
||||
hDir,
|
||||
1024,
|
||||
include_subdirs,
|
||||
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)
|
||||
False,
|
||||
win32con.FILE_NOTIFY_CHANGE_FILE_NAME)
|
||||
|
||||
for action, fn in results:
|
||||
fpath = os.path.join(path, fn)
|
||||
if not os.path.exists(fpath):
|
||||
ftype = "<deleted>"
|
||||
elif os.path.isdir(fpath):
|
||||
ftype = "folder"
|
||||
else:
|
||||
ftype = "file"
|
||||
yield ftype, fpath, action
|
||||
log.debug("monitor_files: ReadDirectoryChangesW() results: %s" % results)
|
||||
for action, fname in results:
|
||||
fpath = os.path.join(path, fname)
|
||||
if action in (winnt.FILE_ACTION_ADDED,
|
||||
winnt.FILE_ACTION_RENAMED_NEW_NAME):
|
||||
log.debug("monitor_files: Queueing '%s' file: %s" %
|
||||
(profile.key, fpath))
|
||||
queue.put(fpath)
|
||||
|
||||
|
||||
class WatcherWin32(threading.Thread):
|
||||
def perform_actions(queue, profile):
|
||||
"""
|
||||
A ``threading.Thread`` subclass which is responsible for monitoring a
|
||||
particular folder (on Windows platforms).
|
||||
Callable target for action threads.
|
||||
"""
|
||||
|
||||
def __init__(self, key, path, queue, include_subdirs=False, **kwargs):
|
||||
threading.Thread.__init__(self, **kwargs)
|
||||
self.setDaemon(1)
|
||||
self.key = key
|
||||
self.path = path
|
||||
self.queue = queue
|
||||
self.include_subdirs = include_subdirs
|
||||
self.start()
|
||||
while True:
|
||||
|
||||
def run(self):
|
||||
for result in monitor_win32(self.path,
|
||||
include_subdirs=self.include_subdirs):
|
||||
self.queue.put((self.key,) + result)
|
||||
try:
|
||||
path = queue.get_nowait()
|
||||
except Queue.Empty:
|
||||
pass
|
||||
else:
|
||||
|
||||
while not file_is_free(path):
|
||||
win32api.Sleep(0)
|
||||
|
||||
for spec, func, args in profile.actions:
|
||||
|
||||
log.info("perform_actions: Calling function '%s' on file: %s" %
|
||||
(spec, path))
|
||||
|
||||
try:
|
||||
func(path, *args)
|
||||
|
||||
except:
|
||||
log.exception("perform_actions: An exception occurred "
|
||||
"while processing file: %s" % path)
|
||||
email_exception()
|
||||
|
||||
# This file probably shouldn't be processed any further.
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
win32serviceutil.HandleCommandLine(FileMonitorService)
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.filemon_server`` -- File Monitoring Service for Windows
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import socket
|
||||
import time
|
||||
import Queue
|
||||
import logging
|
||||
from traceback import format_exception
|
||||
|
||||
import edbob
|
||||
from edbob.filemon import MonitorProfile
|
||||
from edbob.filemon.win32 import WatcherWin32, ACTION_CREATE, ACTION_UPDATE
|
||||
from edbob.win32 import file_is_free
|
||||
|
||||
if sys.platform == 'win32': # docs should build for everyone
|
||||
import win32serviceutil
|
||||
import win32service
|
||||
import win32event
|
||||
import servicemanager
|
||||
import win32api
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileMonitorService(win32serviceutil.ServiceFramework):
|
||||
"""
|
||||
Implements edbob's file monitor Windows service.
|
||||
"""
|
||||
|
||||
_svc_name_ = "Edbob File Monitor"
|
||||
_svc_display_name_ = "Edbob : File Monitoring Service"
|
||||
_svc_description_ = ("Monitors one or more folders for incoming files, "
|
||||
"and performs configured actions as new files arrive.")
|
||||
|
||||
appname = 'edbob'
|
||||
|
||||
def __init__(self, *args):
|
||||
win32serviceutil.ServiceFramework.__init__(self, *args)
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
socket.setdefaulttimeout(60)
|
||||
self.stop_requested = False
|
||||
|
||||
def SvcStop(self):
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
self.stop_requested = True
|
||||
|
||||
def SvcDoRun(self):
|
||||
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ''))
|
||||
edbob.init(self.appname)
|
||||
self.main()
|
||||
|
||||
def main(self):
|
||||
self.monitored = {}
|
||||
monitored = edbob.config.require('edbob.filemon', 'monitored')
|
||||
monitored = monitored.split(',')
|
||||
for key in monitored:
|
||||
key = key.strip()
|
||||
profile = MonitorProfile(key)
|
||||
self.monitored[key] = profile
|
||||
log.debug("Monitoring profile '%s': %s" % (key, profile.dirs))
|
||||
for path in profile.dirs:
|
||||
if not os.path.exists(path):
|
||||
log.warning("Path does not exist: %s" % path)
|
||||
queue = Queue.Queue()
|
||||
|
||||
for key in self.monitored:
|
||||
for d in self.monitored[key].dirs:
|
||||
WatcherWin32(key, d, queue)
|
||||
|
||||
while not self.stop_requested:
|
||||
try:
|
||||
key, ftype, fpath, action = queue.get_nowait()
|
||||
except Queue.Empty:
|
||||
pass
|
||||
else:
|
||||
log.debug("Got notification: %s, %s, %s" % (key, ftype, fpath))
|
||||
# if ftype == 'file' and action in (
|
||||
# ACTION_CREATE, ACTION_UPDATE):
|
||||
if ftype == 'file' and action == ACTION_CREATE:
|
||||
self.do_actions(key, fpath)
|
||||
win32api.SleepEx(250, True)
|
||||
|
||||
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STOPPED,
|
||||
(self._svc_name_, ''))
|
||||
|
||||
def do_actions(self, key, path):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
while not file_is_free(path):
|
||||
# TODO: Add configurable timeout so long-open files can't hijack
|
||||
# our prcessing.
|
||||
win32api.SleepEx(250, True)
|
||||
for action in self.monitored[key].actions:
|
||||
if isinstance(action, tuple):
|
||||
func = action[0]
|
||||
args = action[1:]
|
||||
else:
|
||||
func = action
|
||||
args = []
|
||||
func = edbob.load_spec(func)
|
||||
|
||||
try:
|
||||
func(path, *args)
|
||||
except:
|
||||
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
# Call the system exception hook in case anything special has
|
||||
# been registered there, e.g. if edbob.errors.init() has
|
||||
# happened. Note that this is especially necessary since
|
||||
# PythonService.exe doesn't seem to honor sys.excepthook.
|
||||
sys.excepthook(*exc_info)
|
||||
|
||||
# Go ahead and write exception info to the Windows Event Log
|
||||
# while we're at it.
|
||||
msg = "File monitor action failed.\n"
|
||||
msg += "\n"
|
||||
msg += "Profile: %s\n" % key
|
||||
msg += "Action: %s\n" % action
|
||||
msg += "File Path: %s\n" % path
|
||||
msg += "\n"
|
||||
msg += ''.join(format_exception(*exc_info))
|
||||
servicemanager.LogErrorMsg(msg)
|
||||
|
||||
# Don't re-raise the exception since the service should
|
||||
# continue running despite any problems it encounters. But
|
||||
# this file probably shouldn't be processed any further.
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
win32serviceutil.HandleCommandLine(FileMonitorService)
|
|
@ -27,13 +27,16 @@
|
|||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if sys.platform == 'win32': # docs should build for everyone
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32con
|
||||
import pywintypes
|
||||
import win32file
|
||||
import winerror
|
||||
import win32print
|
||||
import win32service
|
||||
import winerror
|
||||
|
||||
|
||||
def RegDeleteTree(key, subkey):
|
||||
|
@ -80,6 +83,62 @@ def RegDeleteTree(key, subkey):
|
|||
pass
|
||||
|
||||
|
||||
def delayed_auto_start_service(name):
|
||||
"""
|
||||
Configures the Windows service named ``name`` such that its startup type is
|
||||
"Automatic (Delayed Start)".
|
||||
|
||||
.. note::
|
||||
It is assumed that the service is already configured to start
|
||||
automatically. This function only modifies the service so that its
|
||||
automatic startup is delayed.
|
||||
"""
|
||||
|
||||
hSCM = win32service.OpenSCManager(
|
||||
None,
|
||||
None,
|
||||
win32service.SC_MANAGER_ENUMERATE_SERVICE)
|
||||
|
||||
hService = win32service.OpenService(
|
||||
hSCM,
|
||||
name,
|
||||
win32service.SERVICE_CHANGE_CONFIG)
|
||||
|
||||
win32service.ChangeServiceConfig2(
|
||||
hService,
|
||||
win32service.SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
|
||||
True)
|
||||
|
||||
win32service.CloseServiceHandle(hService)
|
||||
win32service.CloseServiceHandle(hSCM)
|
||||
|
||||
|
||||
def execute_service_command(module, command, *args):
|
||||
"""
|
||||
Executes ``command`` against the Windows service contained in ``module``.
|
||||
|
||||
``module`` must be a proper module object, which is assumed to implement a
|
||||
command line interface when invoked directly by the Python interpreter, a
|
||||
la ``win32serviceutil.HandleCommandLine()``.
|
||||
|
||||
``command`` may be anything supported by ``HandleCommandLine()``, e.g.:
|
||||
|
||||
* ``'install'``
|
||||
* ``'remove'``
|
||||
* ``'start'``
|
||||
* ``'stop'``
|
||||
* ``'restart'``
|
||||
|
||||
``args``, if present, are assumed to be "option" arguments and will precede
|
||||
``command`` when the command line is constructed.
|
||||
"""
|
||||
|
||||
command = [command]
|
||||
if args:
|
||||
command = list(args) + command
|
||||
subprocess.call([sys.executable, module.__file__] + command)
|
||||
|
||||
|
||||
def file_is_free(path):
|
||||
"""
|
||||
Returns boolean indicating whether or not the file located at ``path`` is
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue