Files
weewx/bin/weecfg/extension.py
Tom Keffer ec2c532e78 Extension installer now adds any new weewx services
Adds major comment block before any new top level sections
2015-04-21 14:08:28 -07:00

151 lines
6.7 KiB
Python

#
# Copyright (c) 2009-2015 Tom Keffer <tkeffer@gmail.com> and
# Matthew Wall
#
# See the file LICENSE.txt for your full rights.
#
"""Utilities for installing and removing extensions"""
from __future__ import with_statement
import os
import shutil
import sys
import weecfg
from weecfg import Logger
from weewx.engine import all_service_groups
import weeutil.weeutil
class InstallError(Exception):
"""Exception thrown when installing an extension."""
class ExtensionInstaller(dict):
"""Base class for extension installers."""
class ExtensionEngine(object):
"""Engine that manages extensions."""
# Extension components can be installed to these locations
target_dirs = {
'bin': 'BIN_ROOT',
'skins': 'SKIN_ROOT'}
def __init__(self, config_path, config_dict, tmpdir=None, bin_root=None,
dry_run=None, logger=None):
self.logger = logger or Logger()
self.config_path = config_path
self.config_dict = config_dict
self.tmpdir = tmpdir or '/var/tmp'
# BIN_ROOT does not normally appear in the configuration dictionary. Set a
# default (which could be 'None')
self.config_dict.setdefault('BIN_ROOT', bin_root)
self.dry_run = dry_run
self.root_dict = weecfg.extract_roots(self.config_path, self.config_dict)
self.logger.log("root dictionary: %s" % self.root_dict, 4)
def enumerate_extensions(self):
ext_root = self.root_dict['EXT_ROOT']
try:
exts = os.listdir(ext_root)
if exts:
for f in exts:
self.logger.log(f, level=0)
else:
self.logger.log("Extension cache is '%s'" % ext_root, level=2)
self.logger.log("No extensions installed", level=0)
except OSError:
self.logger.log("No extension cache '%s'" % ext_root, level=2)
self.logger.log("No extensions installed", level=0)
def install_extension(self, extension_path):
self.logger.log("Request to install '%s'" % extension_path)
if os.path.isfile(extension_path):
# It's a file, hopefully a tarball. Extract it, then install
try:
member_names = weecfg.extract_tarball(extension_path, self.tmpdir, self.logger)
extension_reldir = os.path.commonprefix(member_names)
if extension_reldir == '':
raise InstallError("No common path in tarfile '%s'. Unable to install." % extension_path)
extension_dir = os.path.join(self.tmpdir, extension_reldir)
self.install_from_dir(extension_dir)
finally:
shutil.rmtree(extension_dir, ignore_errors=True)
elif os.path.isdir(extension_path):
# It's a directory, presumably containing the extension. Install directly
self.install_from_dir(extension_path)
else:
raise InstallError("Extension '%s' not found or cannot be identified." % extension_path)
self.logger.log("Finished installing extension '%s'" % extension_path)
def install_from_dir(self, extension_dir):
self.logger.log("Request to install extension found in directory %s" % extension_dir, level=2)
old_path = sys.path
try:
# Inject both the location of the extension, and my parent directory (so the extension
# can find setup.py) into the path:
sys.path[0:0] = [extension_dir, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))]
__import__('install')
module = sys.modules['install']
loader = getattr(module, 'loader')
installer = loader()
extension_name = installer.get('name', 'Unknown')
self.logger.log("Found extension with name '%s'" % extension_name, level=2)
# Go through all the files used by the extension:
for file_path in installer['files']:
# For each file, check and see if it's a type we know about
for directory in ExtensionEngine.target_dirs:
# This will be something like 'bin', or 'skins':
source_type = os.path.commonprefix((file_path[0], directory))
if source_type:
# This will be something like 'BIN_ROOT' or 'SKIN_ROOT':
root_type = ExtensionEngine.target_dirs[source_type]
for install_file in file_path[1]:
source = os.path.join(extension_dir, install_file)
destination = os.path.abspath(os.path.join(self.root_dict[root_type], '..', install_file))
self.logger.log("Copying from '%s' to '%s'" % (source, destination), level=3)
if not self.dry_run:
try:
os.makedirs(os.path.dirname(destination))
except OSError:
pass
shutil.copy(source, destination)
break
else:
sys.exit("Unknown destination for file %s" % file_path)
finally:
# Restore the path
sys.path = old_path
needs_save = False
# Find any new top_level sections, so we can include a major comment block with them:
new_top_level = []
if 'config' in installer:
for top_level in installer['config']:
if top_level not in self.config_dict:
new_top_level.append(top_level)
# Inject any new config data into the configuration file
weecfg.conditional_merge(self.config_dict, installer['config'])
# Now include the major comment block for any new top level sections
for new_section in new_top_level:
self.config_dict.comments[new_section] = weecfg.major_comment_block + \
["# Options for extension '%s'" % extension_name]
needs_save = True
for service_group in all_service_groups:
if service_group in installer:
extension_svcs = weeutil.weeutil.option_as_list(installer[service_group])
for svc in extension_svcs:
if svc not in self.config_dict['Engine']['Services'][service_group]:
self.config_dict['Engine']['Services'][service_group].append(svc)
needs_save = True
if needs_save:
weecfg.save_config(self.config_dict, self.config_path)