283 lines
8.5 KiB
Python
283 lines
8.5 KiB
Python
#!/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.win32`` -- Stuff for Microsoft Windows
|
|
"""
|
|
|
|
import sys
|
|
import subprocess
|
|
import logging
|
|
|
|
if sys.platform == 'win32': # docs should build for everyone
|
|
import pywintypes
|
|
import win32api
|
|
import win32con
|
|
import win32event
|
|
import win32file
|
|
import win32print
|
|
import win32service
|
|
import win32serviceutil
|
|
import winerror
|
|
|
|
import edbob
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Service(win32serviceutil.ServiceFramework):
|
|
"""
|
|
Base class for Windows service implementations.
|
|
"""
|
|
|
|
appname = 'edbob'
|
|
|
|
def __init__(self, args):
|
|
"""
|
|
Constructor.
|
|
"""
|
|
|
|
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, service=self._svc_name_)
|
|
|
|
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! (Initialization failed.)"
|
|
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 RegDeleteTree(key, subkey):
|
|
"""
|
|
This is a clone of ``win32api.RegDeleteTree()``, since that apparently
|
|
requires Vista or later.
|
|
"""
|
|
|
|
def delete_contents(key):
|
|
subkeys = []
|
|
for name, reserved, class_, mtime in win32api.RegEnumKeyEx(key):
|
|
subkeys.append(name)
|
|
for subkey_name in subkeys:
|
|
subkey = win32api.RegOpenKeyEx(key, subkey_name, 0, win32con.KEY_ALL_ACCESS)
|
|
delete_contents(subkey)
|
|
win32api.RegCloseKey(subkey)
|
|
win32api.RegDeleteKey(key, subkey_name)
|
|
values = []
|
|
i = 0
|
|
while True:
|
|
try:
|
|
name, value, type_ = win32api.RegEnumValue(key, i)
|
|
except pywintypes.error, e:
|
|
if e[0] == winerror.ERROR_NO_MORE_ITEMS:
|
|
break
|
|
values.append(name)
|
|
i += 1
|
|
for value in values:
|
|
win32api.RegDeleteValue(key, value)
|
|
|
|
orig_key = key
|
|
try:
|
|
key = win32api.RegOpenKeyEx(orig_key, subkey, 0, win32con.KEY_ALL_ACCESS)
|
|
except pywintypes.error, e:
|
|
if e[0] != winerror.ERROR_FILE_NOT_FOUND:
|
|
raise
|
|
else:
|
|
delete_contents(key)
|
|
win32api.RegCloseKey(key)
|
|
try:
|
|
win32api.RegDeleteKey(orig_key, subkey)
|
|
except pywintypes.error, e:
|
|
if e[0] == winerror.ERROR_FILE_NOT_FOUND:
|
|
pass
|
|
|
|
|
|
def capture_output(command):
|
|
"""
|
|
Runs ``command`` and returns any output it produces.
|
|
"""
|
|
|
|
# We *need* to pipe ``stdout`` because that's how we capture the output of
|
|
# the ``hg`` command. However, we must pipe *all* handles in order to
|
|
# prevent issues when running as a GUI but *from* the Windows console.
|
|
# See also: http://bugs.python.org/issue3905
|
|
kwargs = dict(stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
output = subprocess.Popen(command, **kwargs).communicate()[0]
|
|
return output
|
|
|
|
|
|
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
|
|
currently tied up in any way by another process.
|
|
"""
|
|
|
|
# This code was borrowed from Nikita Nemkin:
|
|
# http://stackoverflow.com/a/2848266
|
|
|
|
handle = None
|
|
try:
|
|
handle = win32file.CreateFile(
|
|
path,
|
|
win32file.GENERIC_WRITE,
|
|
0,
|
|
None,
|
|
win32file.OPEN_EXISTING,
|
|
win32file.FILE_ATTRIBUTE_NORMAL,
|
|
None)
|
|
return True
|
|
except pywintypes.error, e:
|
|
if e[0] == winerror.ERROR_SHARING_VIOLATION:
|
|
return False
|
|
raise
|
|
finally:
|
|
if handle:
|
|
win32file.CloseHandle(handle)
|
|
|
|
|
|
def send_data_to_printer(data, printer_name, job_name):
|
|
"""
|
|
Create and submit a new print job (named ``job_name``) which sends ``data``
|
|
directly to the Windows printer identified by ``printer_name``. Returns
|
|
the number of bytes actually written to the printer port.
|
|
|
|
This is designed for sending command strings to Zebra label printers, but
|
|
could potentially be useful for other situations as well.
|
|
"""
|
|
|
|
printer = win32print.OpenPrinter(printer_name)
|
|
assert printer
|
|
assert win32print.StartDocPrinter(printer, 1, (job_name, None, None))
|
|
win32print.StartPagePrinter(printer)
|
|
num_bytes = win32print.WritePrinter(printer, data)
|
|
win32print.EndPagePrinter(printer)
|
|
win32print.EndDocPrinter(printer)
|
|
win32print.ClosePrinter(printer)
|
|
return num_bytes
|