mirror of
https://github.com/weewx/weewx.git
synced 2026-04-17 08:06:58 -04:00
476 lines
19 KiB
Python
Executable File
476 lines
19 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# weewx --- A simple, high-performance weather station server
|
|
#
|
|
# Copyright (c) 2009, 2010, 2011, 2012 Tom Keffer <tkeffer@gmail.com>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# $Revision$
|
|
# $Author$
|
|
# $Date$
|
|
#
|
|
""" Customized weewx setup script.
|
|
|
|
In addition to the normal setup script duties, this script does the
|
|
following:
|
|
|
|
1. When building a source distribution ('sdist') it checks for
|
|
password information in the configuration file weewx.conf and
|
|
that US units are the standard units.
|
|
|
|
2. It merges any existing weewx.conf configuration files into the new, thus
|
|
preserving any user changes.
|
|
|
|
3. It installs the skins subdirectory only if the user doesn't already
|
|
have one.
|
|
|
|
4. It sets the option ['WEEWX_ROOT'] in weewx.conf to reflect
|
|
the actual installation directory (as set in setup.cfg or specified
|
|
in the command line to setup.py install)
|
|
|
|
5. In a similar manner, it sets WEEWX_ROOT in the daemon startup script.
|
|
|
|
6. It backs up any pre-existing bin subdirectory.
|
|
|
|
7. It conserves the ./bin/user subdirectory.
|
|
"""
|
|
|
|
import os.path
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import configobj
|
|
|
|
from distutils.core import setup
|
|
from distutils.command.install_data import install_data
|
|
from distutils.command.install_lib import install_lib
|
|
from distutils.command.sdist import sdist
|
|
import distutils.dir_util
|
|
|
|
# Find the install bin subdirectory:
|
|
this_file = os.path.join(os.getcwd(), __file__)
|
|
bin_dir = os.path.abspath(os.path.join(os.path.dirname(this_file), 'bin'))
|
|
|
|
# Get the version:
|
|
save_path = list(sys.path)
|
|
sys.path.insert(0, bin_dir)
|
|
import weewx
|
|
VERSION = weewx.__version__
|
|
del weewx
|
|
sys.path = save_path
|
|
|
|
#===============================================================================
|
|
# weewx_install_lib
|
|
#===============================================================================
|
|
|
|
class weewx_install_lib(install_lib):
|
|
"""Specialized version of install_lib
|
|
|
|
This version:
|
|
|
|
- Backs up the old ./bin subdirectory before installing.
|
|
- Conserves the ./bin/user subdirectory
|
|
"""
|
|
|
|
def run(self):
|
|
# Back up any existing 'bin' subdirectory:
|
|
if os.path.exists(self.install_dir):
|
|
bin_backupdir = backup(self.install_dir)
|
|
print "Backed up bin subdirectory to %s" % bin_backupdir
|
|
else:
|
|
bin_backupdir = None
|
|
|
|
# Determine whether the user is still using an old-style schema
|
|
schema_type = self._check_schema_type()
|
|
|
|
# Run the superclass's version. This will install all incoming files.
|
|
install_lib.run(self)
|
|
|
|
# If the bin subdirectory previously existed, and if it included
|
|
# a 'user' subsubdirectory, then restore it
|
|
if bin_backupdir:
|
|
user_backupdir = os.path.join(bin_backupdir, 'user')
|
|
if os.path.exists(user_backupdir):
|
|
user_dir = os.path.join(self.install_dir, 'user')
|
|
distutils.dir_util.copy_tree(user_backupdir, user_dir)
|
|
|
|
# But, there is one exception: if the old user subdirectory included an
|
|
# old-style schema, then it should be overwritten with the new version.
|
|
if schema_type == 'old':
|
|
incoming_schema_path = os.path.join(bin_dir, 'user/schemas.py')
|
|
target_path = os.path.join(self.install_dir, 'user/schemas.py')
|
|
distutils.file_util.copy_file(incoming_schema_path, target_path)
|
|
|
|
# Remove weeutil/Almanac, which is no longer in the distribution:
|
|
try:
|
|
os.remove(os.path.join(self.install_dir, 'weeutil/Almanac.py'))
|
|
os.remove(os.path.join(self.install_dir, 'weeutil/Almanac.pyc'))
|
|
except OSError:
|
|
pass
|
|
|
|
def _check_schema_type(self):
|
|
"""If a schema type exists in the install directory, check whether it is
|
|
an old-style schema, or a new one."""
|
|
save_path = list(sys.path)
|
|
sys.path.insert(0, self.install_dir)
|
|
|
|
try:
|
|
import user.schemas
|
|
except ImportError:
|
|
# There is no existing schema at all.
|
|
result = 'none'
|
|
else:
|
|
# There is a schema. Determine if it is old-style or new-style
|
|
try:
|
|
# Try the old style 'drop_list'. If it fails, it must be a new-style schema
|
|
drop_list = user.schemas.drop_list # @UnusedVariable @UndefinedVariable
|
|
except AttributeError:
|
|
# New style schema
|
|
result = 'new'
|
|
else:
|
|
# It did not fail. Must be an old-style schema
|
|
result = 'old'
|
|
finally:
|
|
del user.schemas
|
|
|
|
# Restore the path
|
|
sys.path = save_path
|
|
|
|
return result
|
|
|
|
#===============================================================================
|
|
# install_data
|
|
#===============================================================================
|
|
|
|
class weewx_install_data(install_data):
|
|
"""Specialized version of install_data
|
|
|
|
This version:
|
|
|
|
- Sets WEEWX_ROOT in the configuration file to reflect the
|
|
the location of the install directory;
|
|
- Merges an old week.conf configuration file into a new,
|
|
thus preserving any changes made by the user;
|
|
- Massages the daemon start up script to reflect the choice
|
|
of WEEWX_ROOT;
|
|
- Installs the skins subdirectory only if the user doesn't
|
|
already have one.
|
|
"""
|
|
|
|
def copy_file(self, f, install_dir, **kwargs):
|
|
rv = None
|
|
|
|
# If this is the configuration file, then merge it instead
|
|
# of copying it
|
|
if f == 'weewx.conf':
|
|
rv = self.massageWeewxConfigFile(f, install_dir, **kwargs)
|
|
elif f in start_scripts:
|
|
rv = self.massageStartFile(f, install_dir, **kwargs)
|
|
else:
|
|
rv = install_data.copy_file(self, f, install_dir, **kwargs)
|
|
return rv
|
|
|
|
def run(self):
|
|
# If there is an existing skins subdirectory, do not overwrite it.
|
|
if os.path.exists(os.path.join(self.install_dir, 'skins')):
|
|
# Do this by filtering it out of the list of subdirectories to be installed:
|
|
self.data_files = filter(lambda dat : not dat[0].startswith('skins/'), self.data_files)
|
|
|
|
# If the file #upstream.last exists, delete it, as it is no longer used.
|
|
try:
|
|
os.remove(os.path.join(self.install_dir, 'public_html/#upstream.last'))
|
|
except:
|
|
pass
|
|
|
|
# If the file $WEEWX_INSTALL/readme.htm exists, delete it. It's
|
|
# the old readme (since replaced with docs/readme.htm)
|
|
try:
|
|
os.remove(os.path.join(self.install_dir, 'readme.htm'))
|
|
except:
|
|
pass
|
|
|
|
# If the file $WEEWX_INSTALL/CHANGES.txt exists, delete it. It's
|
|
# been moved to the docs subdirectory and renamed
|
|
try:
|
|
os.remove(os.path.join(self.install_dir, 'CHANGES.txt'))
|
|
except:
|
|
pass
|
|
|
|
# Clean up after a bad install from earlier versions of setup.py:
|
|
try:
|
|
os.remove(os.path.join(self.install_dir, 'start_scripts/weewx'))
|
|
except:
|
|
pass
|
|
|
|
# Run the superclass's run():
|
|
install_data.run(self)
|
|
|
|
|
|
def massageWeewxConfigFile(self, f, install_dir, **kwargs):
|
|
"""Merges any old config file into the new one, and sets WEEWX_ROOT
|
|
|
|
If an old configuration file exists, it will merge the contents
|
|
into the new file. It also sets variable ['WEEWX_ROOT']
|
|
to reflect the installation directory"""
|
|
|
|
# The path name of the weewx.conf configuration file:
|
|
config_path = os.path.join(install_dir, os.path.basename(f))
|
|
|
|
# Create a ConfigObj using the new contents:
|
|
new_config = configobj.ConfigObj(f)
|
|
new_config.indent_type = ' '
|
|
new_version_number = VERSION.split('.')
|
|
if len(new_version_number[1]) < 2:
|
|
new_version_number[1] = '0'+new_version_number[1]
|
|
|
|
# Sometimes I forget to turn the debug flag off:
|
|
new_config['debug'] = 0
|
|
|
|
# And forget that while my rain year starts in October,
|
|
# for most people it starts in January!
|
|
new_config['Station']['rain_year_start'] = 1
|
|
|
|
# The default target conversion units should be 'US':
|
|
new_config['StdConvert']['target_unit'] = 'US'
|
|
|
|
# Check to see if there is an existing config file.
|
|
# If so, merge its contents with the new one
|
|
if os.path.exists(config_path):
|
|
old_config = configobj.ConfigObj(config_path)
|
|
old_version = old_config.get('version')
|
|
# If the version number does not appear at all, then
|
|
# assume a very old version:
|
|
if not old_version: old_version = '1.0.0'
|
|
old_version_number = old_version.split('.')
|
|
# Take care of the collation problem when comparing things like
|
|
# version '1.9' to '1.10' by prepending a '0' to the former:
|
|
if len(old_version_number[1]) < 2:
|
|
old_version_number[1] = '0'+old_version_number[1]
|
|
|
|
# I don't know how to merge older, V1.X configuration files, only
|
|
# newer V2.X ones.
|
|
if old_version_number[0:2] >= ['2','00']:
|
|
# Merge the old configuration file into the new file, thus
|
|
# saving any user modifications.
|
|
# First, turn interpolation off:
|
|
old_config.interpolation = False
|
|
# Now do the merge:
|
|
new_config.merge(old_config)
|
|
# Option stats_types has been moved to bin/user/schemas.py
|
|
new_config['StdArchive'].pop('stats_types', None)
|
|
|
|
# Make sure WEEWX_ROOT reflects the choice made in setup.cfg:
|
|
new_config['WEEWX_ROOT'] = self.install_dir
|
|
# Add the version:
|
|
new_config['version'] = VERSION
|
|
|
|
# Get a temporary file:
|
|
tmpfile = tempfile.NamedTemporaryFile("w", 1)
|
|
|
|
# Write the new configuration file to it:
|
|
new_config.write(tmpfile)
|
|
|
|
# Back up the old config file if it exists:
|
|
if os.path.exists(config_path):
|
|
backup_path = backup(config_path)
|
|
print "Backed up old configuration file as %s" % backup_path
|
|
|
|
# Now install the temporary file (holding the merged config data)
|
|
# into the proper place:
|
|
rv = install_data.copy_file(self, tmpfile.name, config_path, **kwargs)
|
|
|
|
# Set the permission bits unless this is a dry run:
|
|
if not self.dry_run:
|
|
shutil.copymode(f, config_path)
|
|
|
|
return rv
|
|
|
|
def massageStartFile(self, f, install_dir, **kwargs):
|
|
|
|
outname = os.path.join(install_dir, os.path.basename(f))
|
|
sre = re.compile(r"WEEWX_ROOT\s*=")
|
|
|
|
infile = open(f, "r")
|
|
tmpfile = tempfile.NamedTemporaryFile("w", 1)
|
|
|
|
for line in infile:
|
|
if sre.match(line):
|
|
tmpfile.writelines("WEEWX_ROOT=%s\n" % self.install_dir)
|
|
else:
|
|
tmpfile.writelines(line)
|
|
|
|
rv = install_data.copy_file(self, tmpfile.name, outname, **kwargs)
|
|
|
|
# Set the permission bits unless this is a dry run:
|
|
if not self.dry_run:
|
|
shutil.copymode(f, outname)
|
|
|
|
return rv
|
|
|
|
#===============================================================================
|
|
# sdist
|
|
#===============================================================================
|
|
|
|
class weewx_sdist(sdist):
|
|
"""Specialized version of sdist which checks for password information in distribution
|
|
|
|
See http://epydoc.sourceforge.net/stdlib/distutils.command.sdist.sdist-class.html
|
|
for possible sdist instance methods."""
|
|
|
|
def copy_file(self, f, install_dir, **kwargs):
|
|
"""Specialized version of copy_file.
|
|
|
|
Return a tuple (dest_name, copied): 'dest_name' is the actual name of
|
|
the output file, and 'copied' is true if the file was copied (or would
|
|
have been copied, if 'dry_run' true)."""
|
|
# If this is the configuration file, then massage it to eliminate
|
|
# the password info
|
|
if f == 'weewx.conf':
|
|
config = configobj.ConfigObj(f)
|
|
|
|
# If we're working with the configuration file, make sure it doesn't
|
|
# have any private data in it.
|
|
|
|
if config.has_key('StdReport') and config['StdReport'].has_key('FTP') and config['StdReport']['FTP'].has_key('password'):
|
|
sys.stderr.write("\n*** FTP password found in configuration file. Aborting ***\n\n")
|
|
exit()
|
|
|
|
rest_dict = config['StdRESTful']
|
|
if rest_dict.has_key('Wunderground') and rest_dict['Wunderground'].has_key('password'):
|
|
sys.stderr.write("\n*** Wunderground password found in configuration file. Aborting ***\n\n")
|
|
exit()
|
|
if rest_dict.has_key('PWSweather') and rest_dict['PWSweather'].has_key('password'):
|
|
sys.stderr.write("\n*** PWSweather password found in configuration file. Aborting ***\n\n")
|
|
exit()
|
|
|
|
# Pass on to my superclass:
|
|
return sdist.copy_file(self, f, install_dir, **kwargs)
|
|
|
|
#===============================================================================
|
|
# utility functions
|
|
#===============================================================================
|
|
def backup(filepath):
|
|
# Sometimes the target has a trailing '/'. This will take care of it:
|
|
filepath = os.path.normpath(filepath)
|
|
newpath = filepath + time.strftime(".%Y%m%d%H%M%S")
|
|
if os.path.isdir(filepath):
|
|
distutils.dir_util.copy_tree(filepath, newpath)
|
|
else:
|
|
distutils.file_util.copy_file(filepath, newpath)
|
|
return newpath
|
|
|
|
start_scripts = (
|
|
'util/init.d/weewx.bsd',
|
|
'util/init.d/weewx.debian',
|
|
'util/init.d/weewx.redhat',
|
|
'util/init.d/weewx.suse',
|
|
);
|
|
|
|
setup(name='weewx',
|
|
version=VERSION,
|
|
description='weather software',
|
|
long_description="""weewx interacts with a weather station to produce graphs, reports, and HTML pages. weewx can upload data to the WeatherUnderground, PWSweather.com, or CWOP.""",
|
|
author='Tom Keffer',
|
|
author_email='tkeffer@gmail.com',
|
|
url='http://www.weewx.com',
|
|
license = 'GPLv3',
|
|
classifiers = ['Development Status :: 5 - Production/Stable',
|
|
'Intended Audience :: End Users/Desktop',
|
|
'License :: GPLv3',
|
|
'Operating System :: OS Independent',
|
|
'Programming Language :: Python',
|
|
'Programming Language :: Python :: 2',
|
|
],
|
|
platforms = ['any'],
|
|
package_dir = {'' : 'bin'},
|
|
packages = ['weedb',
|
|
'examples',
|
|
'user',
|
|
'weeplot',
|
|
'weeutil',
|
|
'weewx'],
|
|
py_modules = ['daemon'],
|
|
scripts = ['bin/config_database.py',
|
|
'bin/config_fousb.py',
|
|
'bin/config_vp.py',
|
|
'bin/weewxd.py',
|
|
'bin/runreports.py'],
|
|
data_files = [('',
|
|
['LICENSE.txt',
|
|
'README',
|
|
'weewx.conf']),
|
|
('docs',
|
|
['docs/changes.txt',
|
|
'docs/customizing.htm',
|
|
'docs/daytemp_with_avg.png',
|
|
'docs/debian.htm',
|
|
'docs/readme.htm',
|
|
'docs/samaxesjs.toc-1.5.min.js',
|
|
'docs/sheeva.htm',
|
|
'docs/upgrading.htm',
|
|
'docs/usersguide.htm',
|
|
'docs/weekgustoverlay.png',
|
|
'docs/weewx_docs.css']),
|
|
('skins/Ftp',
|
|
['skins/Ftp/skin.conf']),
|
|
('skins/Rsync',
|
|
['skins/Rsync/skin.conf']),
|
|
('skins/Standard/backgrounds',
|
|
['skins/Standard/backgrounds/band.gif']),
|
|
('skins/Standard/NOAA',
|
|
['skins/Standard/NOAA/NOAA-YYYY.txt.tmpl',
|
|
'skins/Standard/NOAA/NOAA-YYYY-MM.txt.tmpl']),
|
|
('skins/Standard/RSS',
|
|
['skins/Standard/RSS/weewx_rss.xml.tmpl']),
|
|
('skins/Standard/smartphone',
|
|
['skins/Standard/smartphone/barometer.html.tmpl',
|
|
'skins/Standard/smartphone/custom.js',
|
|
'skins/Standard/smartphone/humidity.html.tmpl',
|
|
'skins/Standard/smartphone/index.html.tmpl',
|
|
'skins/Standard/smartphone/radar.html.tmpl',
|
|
'skins/Standard/smartphone/rain.html.tmpl',
|
|
'skins/Standard/smartphone/temp_outside.html.tmpl',
|
|
'skins/Standard/smartphone/wind.html.tmpl']),
|
|
('skins/Standard/smartphone/icons',
|
|
['skins/Standard/smartphone/icons/icon_ipad_x1.png',
|
|
'skins/Standard/smartphone/icons/icon_ipad_x2.png',
|
|
'skins/Standard/smartphone/icons/icon_iphone_x1.png',
|
|
'skins/Standard/smartphone/icons/icon_iphone_x2.png']),
|
|
('skins/Standard',
|
|
['skins/Standard/favicon.ico',
|
|
'skins/Standard/mobile.css',
|
|
'skins/Standard/mobile.html.tmpl',
|
|
'skins/Standard/index.html.tmpl',
|
|
'skins/Standard/month.html.tmpl',
|
|
'skins/Standard/skin.conf',
|
|
'skins/Standard/week.html.tmpl',
|
|
'skins/Standard/weewx.css',
|
|
'skins/Standard/year.html.tmpl']),
|
|
('start_scripts/Debian',
|
|
['start_scripts/Debian/weewx']),
|
|
('start_scripts/SuSE',
|
|
['start_scripts/SuSE/weewx'])],
|
|
requires = ['configobj(>=4.5)',
|
|
'serial(>=2.3)',
|
|
'Cheetah(>=2.0)',
|
|
'sqlite3(>=2.5)',
|
|
'PIL(>=1.1.6)'],
|
|
cmdclass = {"install_data" : weewx_install_data,
|
|
"install_lib" : weewx_install_lib,
|
|
"sdist" : weewx_sdist}
|
|
)
|