diff --git a/CHANGES.txt b/CHANGES.txt index 8346cb69..bc86621d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ CHANGE HISTORY -------------------------------- -1.6.0 03/24/10 +1.6.0 03/25/10 Now supports skins. A skin is a collection of templates, under the control of a 'skin configuration file.' Presentation layer specific @@ -21,20 +21,16 @@ FTP is treated as just another report, albeit with an unusual report generator. You can have multiple FTP sessions, each to a different server, or uploading to or from a different area. +Rewrote the FTP upload package so that it allows more than one FTP +session to be active in the same local directory. This version also does +fewer hits on the server, so it might be a bit faster. + Data files used in reports (such as weewx.css) are copied over to the HTML directory on program startup. Moved the URL for the radar image from the configuration file to the template index.html.tmpl. -setup.py no longer deletes public_html/#upstream.last as part of the -install process. - -Now requires configobj v4.6 (instead of V4.5). As this version was -released nearly a year ago, this should not be a problem for most -users. If it is the following command will take care of it: - easy_install --upgrade configobj - 1.5.0 03/07/10 Added support for other units besides the U.S. Customary. Plots and diff --git a/MANIFEST b/MANIFEST index f67e29e8..e2a5e117 100644 --- a/MANIFEST +++ b/MANIFEST @@ -6,7 +6,6 @@ configure.py daemon.py setup.cfg setup.py -upload.py weewx.conf weewxd.py docs/customizing.htm @@ -40,13 +39,13 @@ weeutil/Almanac.py weeutil/Moon.py weeutil/Sun.py weeutil/__init__.py +weeutil/ftpupload.py weeutil/weeutil.py weewx/VantagePro.py weewx/__init__.py weewx/archive.py weewx/crc16.py weewx/formatter.py -weewx/ftpdata.py weewx/genfiles.py weewx/genimages.py weewx/reportengine.py diff --git a/setup.py b/setup.py index 7d8563e8..113c9e69 100755 --- a/setup.py +++ b/setup.py @@ -89,6 +89,12 @@ class My_install_data(install_data): # Run the superclass's run(): install_data.run(self) + # 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_ROOT/readme.htm exists, delete it. It's # the old readme (since replaced with docs/readme.htm) try: @@ -217,7 +223,7 @@ setup(name='weewx', author_email='tkeffer@gmail.com', url='http://www.weewx.com', packages = ['weewx', 'weeplot', 'weeutil', 'examples'], - py_modules = ['upload', 'daemon'], + py_modules = ['daemon'], scripts = ['configure.py', 'weewxd.py'], data_files = [('', ['CHANGES.txt', 'LICENSE.txt', 'README', 'weewx.conf']), ('docs', ['docs/customizing.htm', 'docs/readme.htm', @@ -229,7 +235,7 @@ setup(name='weewx', 'skins/Standard/skin.conf', 'skins/Standard/week.html.tmpl', 'skins/Standard/weewx.css', 'skins/Standard/year.html.tmpl']), ('start_scripts', ['start_scripts/Debian/weewx', 'start_scripts/SuSE/weewx'])], - requires = ['configobj(>=4.6)', 'pyserial(>=1.35)', 'Cheetah(>=2.0)', 'pysqlite(>=2.5)', 'PIL(>=1.1.6)'], + requires = ['configobj(>=4.5)', 'pyserial(>=1.35)', 'Cheetah(>=2.0)', 'pysqlite(>=2.5)', 'PIL(>=1.1.6)'], cmdclass = {"install_data" : My_install_data, "sdist" : My_sdist} ) diff --git a/upload.py b/upload.py deleted file mode 100644 index 3a73a48f..00000000 --- a/upload.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/python -# -# upload.py -- a script to upload files to FTP server only as-needed -# -# Copyright (c) 2002, Silverback Software, LLC -# -# Brian St. Pierre, -# -# Permission to use, copy, modify, and distribute this software and its -# documentation for any purpose, without fee, and without a written agreement -# is hereby granted, provided that the above copyright notice and this -# paragraph and the following two paragraphs appear in all copies. -# -# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, -# INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST -# PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, -# EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" -# BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, -# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -# - -# $Revision$ -# $Author$ -# $Date$ - -# HISTORY: -# -# 2002-12-20 -- Original -# 2004-05-25 -- Fixed bug creating new dirs where parent did not exist -# 2004-06-15 -- Put ".svn" in default ignore list. -# 2006-09-24 -- Handle FTP error during 'quit'. -# 2009-04-23 -- Modified the member function 'upstream' to return the number -# of files uploaded. -Tom Keffer -# 2009-08-25 -- Added an optional passive mode. -Tom Keffer -# 2009-10-12 -- Allows up to max_retries attempts at uploading a file to the -# server before giving up. Logs a failed transfer to syslog. -Tom Keffer -# - -#### -# -# How this works: -# -# 1. There is a file called #upstream.xml in the root directory. -# This file contains information about how and where to put files -# on the FTP server. -# We don't look for #upstream.xml in subdirectories -- there is no -# way to override the root file. -# #upstream.xml is based on Radio UserLand's #upstream.xml, except -# that we store passwords in plaintext in the element. -# This is insecure, but it works. -# -# 2. We look for #upstream.last in the root directory. -# This is a data file that contains the time each file was last -# uploaded. -# -# 3. We compare every file's timestamp to the info in -# .upload-last. If the file is newer, it needs to be uploaded. -# If the .upload-last does not exist, all files need to be -# uploaded. -# -# 4. We gather all the files to upload and then send everything at the -# end when we've had a chance to walk the entire directory tree. -# -# 5. We skip the following: -# - anything ending with ~ (emacs backups) -# - anything begginning with # (control files) -# - directories named CVS (don't examine them at all) -# - this script (which we assume is named upload.py) -# -# From http://bstpierre.org/Projects/upload.py - -import ftplib -import os -import os.path -import pickle -import socket -import sys -import time -import syslog -from xml.dom import minidom - -class UpstreamerFactory: - def create(self): - self.doc = minidom.parse(open('#upstream.xml', 'r')) - upstream = self.doc.getElementsByTagName('upstream')[0] - self.type = upstream.getAttribute('type') - server = self._get_simple_text('server') - user = self._get_simple_text('username') - password = self._get_simple_text('password') - path = self._get_simple_text('path') - url = self._get_simple_text('url') - if self.type == 'ftp': - u = Upstreamer(server, user, password, path) - elif self.type == 'copy': - u = LocalUpstreamer(path) - - return u - - def _get_simple_text(self, tag): - elems = self.doc.getElementsByTagName(tag) - if len(elems) > 0 and len(elems[0].childNodes) > 0: - return elems[0].childNodes[0].data - return '' - -class Upstreamer: - def __init__(self, server, user, password, path, passive=False, max_retries = 3): - self.server = server - self.user = user - self.password = password - self.path = path - self.passive = passive - self.max_retries = max_retries - self._normalize_path() - return - - def _normalize_path(self): - self.path = self.path.replace('\\', '/') - if self.path[-1] == '/': - self.path = self.path[:-1] - return - - def get_dest_dir(self, destfile): - return destfile[:destfile.rfind('/')] - - def check_dir_and_create(self, ftp, dir): - dir = dir.replace(self.path, '') - path = self.path - for d in dir.split('/'): - if d == '': - continue - cur = path + '/' + d - try: - ftp.cwd(cur) - path = cur - except ftplib.error_perm: -# print 'MKD', cur - ftp.mkd(cur) - ftp.cwd(cur) - path = cur - try: - ftp.cwd(self.path) - except ftplib.error_perm: -# print 'MKD', self.path - ftp.mkd(self.path) - ftp.cwd(self.path) - return - - def upstream(self, files): - n_uploaded = 0 - if len(files) == 0: - return n_uploaded - ftp = ftplib.FTP(self.server) - #ftp.set_debuglevel(1) - ftp.login(self.user, self.password) - ftp.set_pasv(self.passive) - self.check_dir_and_create(ftp, self.path) - for file in files: - if file[0] == '.': - destfile = file[1:].replace('\\', '/') - if destfile[0] == '/': - destfile = destfile[1:] - destfile = self.path + '/' + destfile - cmd = 'STOR ' + destfile - f = open(file, 'r') - - for _count in range(self.max_retries): - try: - self.check_dir_and_create(ftp, - self.get_dest_dir(destfile)) - except (socket.error, ftplib.Error), e: - syslog.syslog(syslog.LOG_ERR, "upload: attempt #%d; got exception while changing/making directory. Reason: %s" % (_count+1, e,)) - if _count == self.max_retries-1: - syslog.syslog(syslog.LOG_ERR, "upload: max retries (%d) exceeded while changing/making directory. Giving up." % (self.max_retries,)) - raise - else: - break - - for _count in range(self.max_retries): - try: - ftp.storbinary(cmd, f) - except socket.error, e: - syslog.syslog(syslog.LOG_ERR, "upload: attempt #%d. failed uploading %s. Reason: %s" % (_count+1, destfile, e)) - else: - n_uploaded += 1 - syslog.syslog(syslog.LOG_DEBUG, "upload: attempt #%d. uploaded file %s." % (_count+1, destfile)) - break - else: - syslog.syslog(syslog.LOG_ERR, - "upload: max retries (%d) exceeded while ftp'ing file %s. Giving up." % (self.max_retries,file)) - try: - ftp.quit() - except socket.error: - # My FTP server started causing "connection reset" - # errors. I think it is closing the far end socket too - # quickly. - pass - return n_uploaded - -class LocalUpstreamer(Upstreamer): - """This Upstreamer uses a local file copy rather than FTP.""" - def __init__(self, path): - Upstreamer.__init__(self, '', '', '', path) - return - - def check_dir_and_create(self, ftp, dir): - if not os.path.isdir(dir): - print 'mkdir', dir - os.makedirs(dir) - return - - def upstream(self, files): - if len(files) == 0: - return - self.check_dir_and_create(None, self.path) - for file in files: - if file[0] == '.': - destfile = file[1:].replace('\\', '/') - if destfile[0] == '/': - destfile = destfile[1:] - destfile = os.path.join(self.path, destfile) - self.check_dir_and_create(None, - self.get_dest_dir(destfile)) - print 'copy %s' % (file, ) - destfd = open(destfile, 'w') - srcfd = open(file, 'r') - destfd.write(srcfd.read()) - srcfd.close() - destfd.close() - return - -class Finder: - def __init__(self): - if os.access('#upstream.last', os.R_OK): - self.last = pickle.load(open('#upstream.last', 'r')) - else: - self.last = {} - self.now = {} - self.find_all_files() - return - - def _os_walk_callback(self, dummy, dir, names): - if dir[-3:] == 'CVS' or dir.find('.svn') != -1: - return - if dir.find('/#') != -1: - return - for name in names: - filename = os.path.join(dir, name) - if name in ['CVS', '.svn']: - continue - elif name[-1] == '~': - continue - elif name[0] == '#': - continue - elif name == 'upload.py': - continue - elif os.path.isdir(filename): - continue - else: - stamp = os.stat(filename).st_mtime - self.now[filename] = stamp - return - - def find_all_files(self): - os.path.walk('.', self._os_walk_callback, None) - return - - def get_new_files(self): - new = [] - for file in self.now.keys(): - if not self.last.has_key(file): - new.append(file) - elif self.last[file] < self.now[file]: - new.append(file) - return new - - def reset_stamp(self, when): - for file in self.now.keys(): - self.now[file] = when - return - - def save(self): - pickle.dump(self.now, open('#upstream.last', 'w')) - return - - -if __name__ == '__main__': - f = UpstreamerFactory() - upstr = f.create() - finder = Finder() - finder.find_all_files() - if len(sys.argv) == 2: - if sys.argv[1] == '-t': ## test - print finder.get_new_files() - if sys.argv[1] == '-f': ## fake - print finder.get_new_files() - finder.reset_stamp(time.time()) - finder.save() - print "No upload: timestamps reset." - else: - upstr.upstream(finder.get_new_files()) - finder.reset_stamp(time.time()) - finder.save() diff --git a/weeutil/ftpupload.py b/weeutil/ftpupload.py new file mode 100644 index 00000000..fbb36a72 --- /dev/null +++ b/weeutil/ftpupload.py @@ -0,0 +1,229 @@ +# +# Copyright (c) 2009, 2010 Tom Keffer +# +# See the file LICENSE.txt for your full rights. +# +# $Revision$ +# $Author$ +# $Date$ +# +"""For uploading files to a remove server via FTP""" + +import os +import sys +import ftplib +import cPickle +import time +import syslog + +class FtpUpload: + """Uploads a directory and all its descendents to a remote server. + + Keeps track of when a file was last uploaded, so it is uploaded only + if its modification time is newer.""" + + def __init__(self, server, + user, password, + local_root, remote_root, + name = "FTP", + passive = True, + max_tries = 3): + """Initialize an instance of FtpUpload. + + After initializing, call method run() to perform the upload. + + server: The remote server to which the files are to be uploaded. + + user, + password : The user name and password that are to be used. + + name: A unique name to be given for this FTP session. This allows more + than one session to be uploading from the same local directory. [Optional. + Default is 'FTP'.] + + passive: True to use passive mode; False to use active mode. [Optional. + Default is True (passive mode)] + + max_tries: How many times to try creating a directory or uploading + a file before giving up [Optional. Default is 3] + """ + self.server = server + self.user = user + self.password = password + self.local_root = os.path.normpath(local_root) + self.remote_root = os.path.normpath(remote_root) + self.name = name + self.passive = passive + self.max_tries = max_tries + + def run(self): + """Perform the actual upload. + + returns: the number of files uploaded.""" + + # Get the timestamp and members of the last upload: + (timestamp, fileset) = self.getLastUpload() + + n_uploaded = 0 + ftp_server = ftplib.FTP(self.server) + #ftp_server.set_debuglevel(1) + ftp_server.login(self.user, self.password) + ftp_server.set_pasv(self.passive) + + # Walk the local directory structure + for (dirpath, dirnames, filenames) in os.walk(self.local_root): + + # Strip out the common local root directory. What is left + # will be the relative directory both locally and remotely. + local_rel_dir_path = dirpath.replace(self.local_root, '.') + if self._skipThisDir(local_rel_dir_path): + continue + # This is the absolute path to the remote directory: + remote_dir_path = os.path.normpath(os.path.join(self.remote_root, local_rel_dir_path)) + + # Make the remote directory if necessary: + self._make_remote_dir(ftp_server, remote_dir_path) + + # Now iterate over all members of the local directory: + for filename in filenames: + + full_local_path = os.path.join(dirpath, filename) + # See if this file can be skipped: + if self._skipThisFile(timestamp, fileset, full_local_path): + continue + + full_remote_path = os.path.join(remote_dir_path, filename) + STOR_cmd = "STOR %s" % full_remote_path + fd = open(full_local_path, "r") + for count in range(self.max_tries): + try: + ftp_server.storbinary(STOR_cmd, fd) + except ftplib.all_errors, e: + syslog.syslog(syslog.LOG_ERR, "ftpupload: attempt #%d. Failed uploading %s. Reason: %s" % (count+1, full_remote_path, e)) + if count >= self.max_tries -1 : + syslog.syslog(syslog.LOG_ERR, "ftpupload: Failed to upload file %s" % full_remote_path) + raise + else: + fd.close() + n_uploaded += 1 + syslog.syslog(syslog.LOG_DEBUG, "ftpupload: Uploaded file %s" % full_remote_path) + break + fileset.add(full_local_path) + try: + ftp_server.quit() + except socket.error: + pass + + timestamp = time.time() + self.saveLastUpload(timestamp, fileset) + return n_uploaded + + def getLastUpload(self): + """Reads the time and members of the last upload from the local root""" + + timeStampFile = os.path.join(self.local_root, "#%s.last" % self.name ) + try: + f = open(timeStampFile, "r") + timestamp = cPickle.load(f) + fileset = cPickle.load(f) + f.close() + except IOError: + timestamp = 0 + fileset = set() + return (timestamp, fileset) + + def saveLastUpload(self, timestamp, fileset): + """Saves the time and members of the last upload in the local root.""" + timeStampFile = os.path.join(self.local_root, "#%s.last" % self.name ) + try: + f = open(timeStampFile, "w") + cPickle.dump(timestamp, f) + cPickle.dump(fileset, f) + f.close() + except IOError: + pass + + def _make_remote_dir(self, ftp_server, remote_dir_path): + """Make a remote directory if necessary.""" + # Try to make the remote directory up max_tries times, then give up. + for count in range(self.max_tries): + try: + ftp_server.mkd(remote_dir_path) + except ftplib.all_errors, e: + # Got an exception. It might be because the remote directory already exists: + if sys.exc_info()[0] is ftplib.error_perm and str(e).strip().startswith('550'): + # Directory already exists + return + syslog.syslog(syslog.LOG_ERR, "ftpupload: Got error while attempting to make remote directory %s" % remote_dir_path) + syslog.syslog(syslog.LOG_ERR, " **** Error:" % e) + if count >= self.max_tries - 1: + syslog.syslog(syslog.LOG_ERR, "ftpupload: Unable to create remote directory %s" % remote_dir_path) + raise + else: + syslog.syslog(syslog.LOG_DEBUG, "ftpupload: Made directory %s" % remote_dir_path) + return + + def _skipThisDir(self, local_dir): + + return os.path.basename(local_dir) in ('.svn', 'CVS') + + def _skipThisFile(self, timestamp, fileset, full_local_path): + + filename = os.path.basename(full_local_path) + if filename[-1] == '~' or filename[0] == '#' : + return True + + if full_local_path not in fileset: + return False + + if os.stat(full_local_path).st_mtime > timestamp: + return False + + # Filename is in the set, and is up to date. + return True + + +if __name__ == '__main__': + + import weewx + import syslog + import socket + import configobj + + weewx.debug = 1 + syslog.openlog('reportengine', syslog.LOG_PID|syslog.LOG_CONS) + syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) + + if len(sys.argv) < 2 : + print """Usage: ftpupload.py path-to-configuration-file [path-to-be-ftp'd]""" + exit() + + try : + config_dict = configobj.ConfigObj(sys.argv[1], file_error=True) + except IOError: + print "Unable to open configuration file ", sys.argv[1] + raise + + if len(sys.argv) == 2: + try: + ftp_dir = os.path.join(config_dict['Station']['WEEWX_ROOT'], + config_dict['Reports']['HTML_ROOT']) + except KeyError: + print "No HTML_ROOT in configuration dictionary." + exit() + else: + ftp_dir = sys.argv[2] + + + socket.setdefaulttimeout(10) + + + ftp_upload = FtpUpload(config_dict['Reports']['FTP']['server'], + config_dict['Reports']['FTP']['user'], + config_dict['Reports']['FTP']['password'], + ftp_dir, + config_dict['Reports']['FTP']['path'], + 'FTP', + config_dict['Reports']['FTP'].as_bool('passive'), + config_dict['Reports']['FTP'].as_int('max_tries')) + ftp_upload.run() \ No newline at end of file diff --git a/weewx.conf b/weewx.conf index cad22049..20483083 100644 --- a/weewx.conf +++ b/weewx.conf @@ -29,7 +29,7 @@ debug = 0 socket_timeout = 20 # Current version -version = 1.6.0a3 +version = 1.6.0a4 ############################################################################################ @@ -223,7 +223,7 @@ version = 1.6.0a3 passive = 1 # How many times to try to transfer a file before giving up: - max_retries = 3 + max_tries = 3 # If you wish to upload files from something other than what HTML_ROOT is set to # above, then reset it here: diff --git a/weewx/__init__.py b/weewx/__init__.py index 06229d91..faa1cd2e 100644 --- a/weewx/__init__.py +++ b/weewx/__init__.py @@ -12,7 +12,7 @@ """ import time -__version__="1.6.0a3" +__version__="1.6.0a4" # Holds the program launch time in unix epoch seconds: # Useful for calculating 'uptime.' diff --git a/weewx/ftpdata.py b/weewx/ftpdata.py deleted file mode 100644 index 4685cc4e..00000000 --- a/weewx/ftpdata.py +++ /dev/null @@ -1,77 +0,0 @@ -# -# Copyright (c) 2009 Tom Keffer -# -# See the file LICENSE.txt for your full rights. -# -# $Revision$ -# $Author$ -# $Date$ -# - -import os -import time -import syslog -import socket - -import upload - -class FtpData(object): - """Synchronize local files with the web server""" - - def __init__(self, source_dir, server, user, password, path, passive = 1, max_retries = 3): - self.source_dir = source_dir - self.server = server - self.user = user - self.password = password - self.path = path - self.passive = int(passive) - self.max_retries= int(max_retries) - - def ftpData(self): - """FTP the contents of the HTML directory to your web server. It uses - module 'upload' to do the heavy lifting. - """ - t1 = time.time() - - # While 'upload' is a nice simple utility, it assumes that the - # root directory is the current directory. So, change directory to there: - os.chdir(self.source_dir) - - # Now we can use the utility - upstr = upload.Upstreamer(self.server, self.user, self.password, self.path, - self.passive, self.max_retries) - finder = upload.Finder() - finder.find_all_files() - try: - n_uploaded = upstr.upstream(finder.get_new_files()) - finder.reset_stamp(time.time()) - finder.save() - - t2=time.time() - syslog.syslog(syslog.LOG_INFO, - "ftp: uploaded %d files in %0.2f seconds" % (n_uploaded, (t2-t1))) - except socket.error, e: - syslog.syslog(syslog.LOG_ERR, "ftp: FTP failed. Reason: %s" % e) - -if __name__ == '__main__': - import configobj - - def test(config_path): - - try : - config_dict = configobj.ConfigObj(config_path, file_error=True) - except IOError: - print "Unable to open configuration file ", config_path - exit() - - ftp_dict = config_dict.get('FTP') - if ftp_dict: - html_dir = os.path.join(config_dict['Station']['WEEWX_ROOT'], - config_dict['HTML']['html_root']) - ftpData = FtpData(source_dir = html_dir, **ftp_dict) - ftpData.ftpData() - else: - print "No FTP section in configuration file. Nothing done." - - test('/home/weewx/weewx.conf') - diff --git a/weewx/reportengine.py b/weewx/reportengine.py index 6e96c696..f6c49fd7 100644 --- a/weewx/reportengine.py +++ b/weewx/reportengine.py @@ -14,13 +14,14 @@ import glob import shutil import syslog import threading +import time import configobj import weewx.archive import weewx.genfiles import weewx.genimages -import weewx.ftpdata +import weeutil.ftpupload import weeutil.weeutil class StdReportEngine(threading.Thread): @@ -97,6 +98,9 @@ class StdReportEngine(threading.Thread): # Now inject any overrides for this specific report: skin_dict.merge(self.config_dict['Reports'][report]) + # Finally, add the report name: + skin_dict['REPORT_NAME'] = report + # If this is the first time the report engine has been run, then # run the 'singleton list' of generators. if self.first_run and skin_dict.has_key('singleton_list'): @@ -190,9 +194,6 @@ class Ftp(ReportGenerator): This will ftp everything in the public_html subdirectory to a webserver.""" def run(self): - f = open("/home/weewx/ftp.dict", "w") - self.skin_dict.write(f) - f.close() # Check to see that all necessary options are present. # If so, FTP the data up to a server. @@ -200,13 +201,21 @@ class Ftp(ReportGenerator): self.skin_dict.has_key('password') and self.skin_dict.has_key('user') and self.skin_dict.has_key('path')): - ftpData = weewx.ftpdata.FtpData(source_dir = os.path.join(self.config_dict['Station']['WEEWX_ROOT'], - self.config_dict['Reports']['HTML_ROOT']), - server = self.skin_dict['server'], - user = self.skin_dict['user'], - password = self.skin_dict['password'], - path = self.skin_dict['path']) - ftpData.ftpData() + + t1 = time.time() + ftpData = weeutil.ftpupload.FtpUpload(server = self.skin_dict['server'], + user = self.skin_dict['user'], + password = self.skin_dict['password'], + local_root = os.path.join(self.config_dict['Station']['WEEWX_ROOT'], + self.config_dict['Reports']['HTML_ROOT']), + remote_root = self.skin_dict['path'], + name = self.skin_dict['REPORT_NAME'], + passive = bool(self.skin_dict.get('passive', True)), + max_tries = int(self.skin_dict.get('max_tries', 3))) + N = ftpData.run() + t2= time.time() + syslog.syslog(syslog.LOG_INFO, "reportengine: uploaded %d files in %0.1f seconds" % (N, (t2-t1))) + class Copy(ReportGenerator): """Class for managing the 'copy generator.'