edbob/edbob/win32.py
2012-11-09 08:59:58 -08:00

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