Well then, let's try setting the PROT setting, even though the data will
not be secure. Try this version.

On Fri, May 29, 2020 at 6:15 AM Richard Cropper <richardcropper...@gmail.com>
wrote:

> I'm on v 4.0.0 - it can't have been more than a week since I installed it
>
> Yes I did try setting secure_data to false. With the previous version of
> ftpupload.py I then get
>
> May 29 14:10:56 raspberrypi weewx[4780] ERROR weeutil.ftpupload: Attempt
> #1. Failed uploading /public_html/weewx/weekwind.png to
> polaris.servers.prgn.misp.co.uk. Reason: 521 Data connection cannot be
> opened with this PROT setting.
>
> May 29 14:10:57 raspberrypi weewx[4780] ERROR weeutil.ftpupload: Attempt
> #2. Failed uploading /public_html/weewx/weekwind.png to
> polaris.servers.prgn.misp.co.uk. Reason: 521 Data connection cannot be
> opened with this PROT setting.
>
> May 29 14:10:57 raspberrypi weewx[4780] ERROR weeutil.ftpupload: Attempt
> #3. Failed uploading /public_html/weewx/weekwind.png to
> polaris.servers.prgn.misp.co.uk. Reason: 521 Data connection cannot be
> opened with this PROT setting.
>
> May 29 14:10:57 raspberrypi weewx[4780] ERROR weeutil.ftpupload: Failed to
> upload file /public_html/weewx/weekwind.png
>
>
> Regards
>
> Richard
> On Friday, May 29, 2020 at 12:40:44 PM UTC+1, Tom Keffer wrote:
>>
>> The file I posted was to fix a problem introduced in V4.1, which I had
>> assumed you were using. Apparently not. Disregard the file. Sorry for any
>> confusion.
>>
>> Did you try setting secure_data to false?
>>
>> -tk
>>
>> On Fri, May 29, 2020 at 4:22 AM Richard Cropper <richardc...@gmail.com>
>> wrote:
>>
>>> Incidentally, I've been nowhere near reportengine.py
>>>
>>> On Friday, May 29, 2020 at 12:19:38 PM UTC+1, Richard Cropper wrote:
>>>>
>>>> Hi Tom
>>>>
>>>> This what I get with the new ftpupload.py
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] INFO weewx.reportengine: Copied
>>>> 5 files to /var/www/html/weewx
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>> Caught unrecoverable exception in generator
>>>> 'weewx.reportengine.FtpGenerator'
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****  __init__() got an unexpected keyword argument 'max_tries'
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****  Traceback (most recent call last):
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****    File "/usr/share/weewx/weewx/reportengine.py", line 197, in run
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****      obj.start()
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****    File "/usr/share/weewx/weewx/reportengine.py", line 280, in start
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****      self.run()
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****    File "/usr/share/weewx/weewx/reportengine.py", line 320, in run
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****      secure_data=to_bool(self.skin_dict.get('secure_data', True)))
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****  TypeError: __init__() got an unexpected keyword argument 
>>>> 'max_tries'
>>>>
>>>> May 29 12:01:40 raspberrypi weewx[2431] ERROR weewx.reportengine:
>>>>   ****  Generator terminated
>>>>
>>>> I get the same message whether secure_data is set to True or False.
>>>>
>>>> Regards
>>>>
>>>> Richard
>>>>
>>>>
>>>>
>>>>
>>>> On Thursday, May 28, 2020 at 9:28:49 PM UTC+1, Tom Keffer wrote:
>>>>>
>>>>> I just noticed that in refactoring the ftp uploader, the function
>>>>> _make_remote_dir() never gets called! Try this version of
>>>>> weeutil/ftpupload.py.
>>>>>
>>>>> -tk
>>>>>
>>>>>
>>>>>
>>>>> On Thu, May 28, 2020 at 12:50 PM Tom Keffer <tke...@gmail.com> wrote:
>>>>>
>>>>>> Hard to say, but I think you're getting hit by issue #284
>>>>>> <https://github.com/weewx/weewx/issues/284>. Try adding an option
>>>>>> secure_data and setting it to false:
>>>>>>
>>>>>> [[FTP]]
>>>>>>    ...
>>>>>>    secure_ftp = True
>>>>>>    secure_data = False
>>>>>>
>>>>>> -tk
>>>>>>
>>>>>> On Thu, May 28, 2020 at 9:36 AM Richard Cropper <
>>>>>> richardc...@gmail.com> wrote:
>>>>>>
>>>>>>> Thanks Tom
>>>>>>>
>>>>>>> I had in fact tried those steps but did so again, without success.
>>>>>>>
>>>>>>> Suspecting that the directory might be the cause even though its
>>>>>>> permissions looked good, I deleted the weewx directory and then created 
>>>>>>> it
>>>>>>> again.
>>>>>>>
>>>>>>> This had made a difference but not solved the problem.
>>>>>>>
>>>>>>> Here's an extract from the log:
>>>>>>>
>>>>>>> May 28 17:24:35 raspberrypi weewx[723] ERROR weeutil.ftpupload:
>>>>>>> Attempt #1. Failed uploading /public_html/weewx/daytempfeel.png to
>>>>>>> polaris.servers.prgn.misp.co.uk. Reason: [Errno 32] Broken pipe
>>>>>>>
>>>>>>> May 28 17:24:35 raspberrypi weewx[723] ERROR weeutil.ftpupload:
>>>>>>> Attempt #2. Failed uploading /public_html/weewx/daytempfeel.png to
>>>>>>> polaris.servers.prgn.misp.co.uk. Reason: [Errno 32] Broken pipe
>>>>>>>
>>>>>>> May 28 17:24:35 raspberrypi weewx[723] ERROR weeutil.ftpupload:
>>>>>>> Attempt #3. Failed uploading /public_html/weewx/daytempfeel.png to
>>>>>>> polaris.servers.prgn.misp.co.uk. Reason: [Errno 32] Broken pipe
>>>>>>>
>>>>>>> May 28 17:24:35 raspberrypi weewx[723] ERROR weeutil.ftpupload:
>>>>>>> Failed to upload file /public_html/weewx/daytempfeel.png
>>>>>>>
>>>>>>> I have tried all possible file paths again, as you suggest but I
>>>>>>> always get this broken pipe message. If I extend the file path any 
>>>>>>> higher,
>>>>>>> I get the 'cannot open this file' message.
>>>>>>>
>>>>>>> I have been able to upload the index file from /var/www/html/weewx
>>>>>>> to the weewx directory on my server using curl.
>>>>>>>
>>>>>>> Richard
>>>>>>>
>>>>>>> On Wednesday, May 27, 2020 at 7:56:28 PM UTC+1, Tom Keffer wrote:
>>>>>>>>
>>>>>>>> Two things to try:
>>>>>>>>
>>>>>>>> 1. Make sure the FTP username has write permissions on the server.
>>>>>>>>
>>>>>>>> 2. Try different variations on the FTP path. For example,
>>>>>>>>
>>>>>>>> path = public_html/weewx/    # What you have
>>>>>>>> path = public_html/weewx
>>>>>>>> path = /public_html/weewx/
>>>>>>>> path = /public_html/weewx
>>>>>>>> path = weewx
>>>>>>>> path = /weewx
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Wed, May 27, 2020 at 10:02 AM Richard Cropper <
>>>>>>>> richardc...@gmail.com> wrote:
>>>>>>>>
>>>>>>>>> Thanks Tom
>>>>>>>>>
>>>>>>>>> Yes all the files are in /var/www/html/weewx
>>>>>>>>>
>>>>>>>>> If I understand you correctly, this has more to do with the file
>>>>>>>>> path of the remote host server.
>>>>>>>>>
>>>>>>>>> I am at a loss to understand what is going on at that end.
>>>>>>>>>
>>>>>>>>> I can at least connect but I either get a message as you have seen
>>>>>>>>> or a 'broken pipe' message if I play around with the file path.
>>>>>>>>>
>>>>>>>>> I guess this is more to do with the remote server than weewx but
>>>>>>>>> thanks for your advice.
>>>>>>>>>
>>>>>>>>> Regards
>>>>>>>>>
>>>>>>>>> Richard
>>>>>>>>> On Wednesday, May 27, 2020 at 3:17:35 PM UTC+1, Tom Keffer wrote:
>>>>>>>>>>
>>>>>>>>>> Hello, Richard
>>>>>>>>>>
>>>>>>>>>> Unfortunately, the error message is misleading (and should be
>>>>>>>>>> changed). When it says
>>>>>>>>>>
>>>>>>>>>> Failed uploading public_html/weewx/monthtempfeel.png to
>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>> file or directory
>>>>>>>>>>
>>>>>>>>>> what it means is it could not upload to the *target*
>>>>>>>>>> public_html/weewx/monthtempfeel.png*.* The unfortunate part is
>>>>>>>>>> that the error message doesn't give the source.
>>>>>>>>>>
>>>>>>>>>> Take a look in /var/www/html/weewx and make sure the files you
>>>>>>>>>> expect to be in there are actually in there.
>>>>>>>>>>
>>>>>>>>>> -tk
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Wed, May 27, 2020 at 5:50 AM Richard Cropper <
>>>>>>>>>> richardc...@gmail.com> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi
>>>>>>>>>>> Having secured a connection, I still don't  seem to be able to
>>>>>>>>>>> upload files
>>>>>>>>>>>
>>>>>>>>>>> I get the following error message on the log
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:36 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #1. Failed uploading public_html/weewx/monthtempfeel.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:36 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #2. Failed uploading public_html/weewx/monthtempfeel.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:37 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #3. Failed uploading public_html/weewx/monthtempfeel.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:37 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Failed to upload file public_html/weewx/monthtempfeel.png
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:37 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #1. Failed uploading public_html/weewx/yearwind.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:38 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #2. Failed uploading public_html/weewx/yearwind.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:38 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #3. Failed uploading public_html/weewx/yearwind.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:38 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Failed to upload file public_html/weewx/yearwind.png
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:39 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #1. Failed uploading public_html/weewx/statistics.html to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:39 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #2. Failed uploading public_html/weewx/statistics.html to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:40 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #3. Failed uploading public_html/weewx/statistics.html to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:40 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Failed to upload file public_html/weewx/statistics.html
>>>>>>>>>>>
>>>>>>>>>>> May 27 13:30:41 raspberrypi weewx[899] ERROR weeutil.ftpupload:
>>>>>>>>>>> Attempt #1. Failed uploading public_html/weewx/weekbarometer.png to
>>>>>>>>>>> ftp.tq7weather.uk. Reason: 553 Can't open that file: No such
>>>>>>>>>>> file or directory
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Here's the relevant bit of my weewx.conf:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>  [[FTP]]
>>>>>>>>>>>
>>>>>>>>>>>         # FTP'ing the results to a webserver is treated as just
>>>>>>>>>>> another report,
>>>>>>>>>>>
>>>>>>>>>>>         # albeit one with an unusual report generator!
>>>>>>>>>>>
>>>>>>>>>>>         skin = Ftp
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>         # If you wish to use FTP, set "enable" to "true", then
>>>>>>>>>>>
>>>>>>>>>>>         # fill out the next four lines.
>>>>>>>>>>>
>>>>>>>>>>>         # Use quotes around passwords to guard against parsing
>>>>>>>>>>> errors.
>>>>>>>>>>>
>>>>>>>>>>>         enable = true
>>>>>>>>>>>
>>>>>>>>>>>         user =[user name]
>>>>>>>>>>>
>>>>>>>>>>>         password = [password]
>>>>>>>>>>>
>>>>>>>>>>>         server =  [server name]    # The ftp server name, e.g,
>>>>>>>>>>> www.myserver.org
>>>>>>>>>>>
>>>>>>>>>>>         path = public_html/weewx/    # The destination
>>>>>>>>>>> directory, e.g., /weather
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>         # Set to True for an FTP over TLS (FTPS) connection. Not
>>>>>>>>>>> all servers
>>>>>>>>>>>
>>>>>>>>>>>         # support this.
>>>>>>>>>>>
>>>>>>>>>>>         secure_ftp = True
>>>>>>>>>>>
>>>>>>>>>>>         secure_data = True
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>        # To upload files from something other than what
>>>>>>>>>>> HTML_ROOT is set
>>>>>>>>>>>
>>>>>>>>>>>         # to above, specify a different HTML_ROOT here.
>>>>>>>>>>>
>>>>>>>>>>>          HTML_ROOT = /var/www/html/weewx/
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>         # Most FTP servers use port 21
>>>>>>>>>>>
>>>>>>>>>>>         port = 21
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>         # Set to 1 to use passive mode, zero for active mode
>>>>>>>>>>>
>>>>>>>>>>>         passive = 1
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> The connection is FTP over TLS.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> My weather data is from an Aercus Weather Sleuth using
>>>>>>>>>>> interceptor on a Raspberry pi.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> It's as if FTP is looking for the wrong path on my  pi but maybe
>>>>>>>>>>> I misunderstand the error message. That's why I tried removing the 
>>>>>>>>>>> hash
>>>>>>>>>>> before HTML_ROOT but I got the same result with the hash.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Otherwise very pleased with weewx  which I can view on my pi
>>>>>>>>>>> using nginx
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Any help gratefully received.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Richard
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> --
>>>>>>>>>>> You received this message because you are subscribed to the
>>>>>>>>>>> Google Groups "weewx-user" group.
>>>>>>>>>>> To unsubscribe from this group and stop receiving emails from
>>>>>>>>>>> it, send an email to weewx...@googlegroups.com.
>>>>>>>>>>> To view this discussion on the web visit
>>>>>>>>>>> https://groups.google.com/d/msgid/weewx-user/35d77ba3-e9a1-4919-9b2e-bb0680490b8a%40googlegroups.com
>>>>>>>>>>> <https://groups.google.com/d/msgid/weewx-user/35d77ba3-e9a1-4919-9b2e-bb0680490b8a%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>>>>>>>> .
>>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>> You received this message because you are subscribed to the Google
>>>>>>>>> Groups "weewx-user" group.
>>>>>>>>> To unsubscribe from this group and stop receiving emails from it,
>>>>>>>>> send an email to weewx...@googlegroups.com.
>>>>>>>>> To view this discussion on the web visit
>>>>>>>>> https://groups.google.com/d/msgid/weewx-user/65f68c3f-31e6-4e6f-95aa-0268a0a82f78%40googlegroups.com
>>>>>>>>> <https://groups.google.com/d/msgid/weewx-user/65f68c3f-31e6-4e6f-95aa-0268a0a82f78%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>>>>>> .
>>>>>>>>>
>>>>>>>> --
>>>>>>> You received this message because you are subscribed to the Google
>>>>>>> Groups "weewx-user" group.
>>>>>>> To unsubscribe from this group and stop receiving emails from it,
>>>>>>> send an email to weewx...@googlegroups.com.
>>>>>>> To view this discussion on the web visit
>>>>>>> https://groups.google.com/d/msgid/weewx-user/cc51fa47-91a9-4a06-838b-18a3ed7b3c2f%40googlegroups.com
>>>>>>> <https://groups.google.com/d/msgid/weewx-user/cc51fa47-91a9-4a06-838b-18a3ed7b3c2f%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>>>> .
>>>>>>>
>>>>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "weewx-user" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to weewx...@googlegroups.com.
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/weewx-user/c53bbd3e-00ee-4291-872d-6de13557e08c%40googlegroups.com
>>> <https://groups.google.com/d/msgid/weewx-user/c53bbd3e-00ee-4291-872d-6de13557e08c%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>> --
> You received this message because you are subscribed to the Google Groups
> "weewx-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to weewx-user+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/weewx-user/67c65342-a58e-440b-a92a-8ed9dd6b3395%40googlegroups.com
> <https://groups.google.com/d/msgid/weewx-user/67c65342-a58e-440b-a92a-8ed9dd6b3395%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to weewx-user+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/weewx-user/CAPq0zEBQEky58SX8V4CaE8ot7ecu25rGHkYFSts%2BF-bk7Bdy_g%40mail.gmail.com.
#
#    Copyright (c) 2009-2020 Tom Keffer <tkef...@gmail.com>
#
#    See the file LICENSE.txt for your full rights.
#
"""For uploading files to a remove server via FTP"""

from __future__ import absolute_import
from __future__ import print_function
from __future__ import with_statement

import ftplib
import logging
import os
import sys
import time

from six.moves import cPickle

log = logging.getLogger(__name__)


class FtpUpload(object):
    """Uploads a directory and all its descendants 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,
                 port=21,
                 name="FTP",
                 passive=True,
                 secure=False,
                 debug=0,
                 secure_data=True,
                 reuse_ssl=False):
        """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)]
        
        secure: Set to True to attempt an FTP over TLS (FTPS) session.
        
        debug: Set to 1 for extra debug information, 0 otherwise.
        
        secure_data: If a secure session is requested (option secure=True),
        should we attempt a secure data connection as well? This option is useful
        due to a bug in the Python FTP client library. See Issue #284. 
        [Optional. Default is True]

        reuse_ssl: Work around a bug in the Python library that closes ssl sockets that should
        be reused. See https://bit.ly/3dKq4JY [Optional. Default is False]
        """
        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.port = port
        self.name = name
        self.passive = passive
        self.secure = secure
        self.debug = debug
        self.secure_data = secure_data
        self.reuse_ssl = reuse_ssl

        if self.reuse_ssl and sys.version < '3.6':
            raise ValueError("Reusing an SSL connection requires Python version 3.6 or greater")

    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.get_last_upload()

        n_uploaded = 0

        try:
            if self.secure:
                log.debug("Attempting secure connection to %s" % self.server)
                if self.reuse_ssl:
                    # Activate the workaround for the Python ftplib library.
                    from ssl import SSLSocket

                    class ReusedSslSocket(SSLSocket):
                        def unwrap(self):
                            pass

                    class WeeFTPTLS(ftplib.FTP_TLS):
                        """Explicit FTPS, with shared TLS session"""

                        def ntransfercmd(self, cmd, rest=None):
                            conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
                            if self._prot_p:
                                conn = self.context.wrap_socket(conn,
                                                                server_hostname=self.host,
                                                                session=self.sock.session)
                                conn.__class__ = ReusedSslSocket
                            return conn, size
                    log.debug("Reusing SSL connections.")
                    ftp_server = WeeFTPTLS()
                else:
                    ftp_server = ftplib.FTP_TLS()
            else:
                log.debug("Attempting connection to %s" % self.server)
                ftp_server = ftplib.FTP()

            if self.debug:
                ftp_server.set_debuglevel(self.debug)

            ftp_server.set_pasv(self.passive)
            ftp_server.connect(self.server, self.port)
            ftp_server.login(self.user, self.password)
            if self.secure:
                ftp_server.prot_p()
                log.debug("Secure data connection to %s" % self.server)
            else:
                log.debug("Connected to %s" % self.server)

            # Walk the local directory structure
            for (dirpath, unused_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._skip_this_dir(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:
                _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._skip_this_file(timestamp, fileset, full_local_path):
                        continue

                    full_remote_path = os.path.join(remote_dir_path, filename)
                    stor_cmd = "STOR %s" % full_remote_path

                    with open(full_local_path, 'rb') as fd:
                        try:
                            ftp_server.storbinary(stor_cmd, fd)
                        except ftplib.all_errors as e:
                            # Unsuccessful. Log it, then reraise the exception
                            log.error("Failed uploading %s to server %s. Reason: '%s'"
                                      % (full_local_path, self.server, e))
                            raise
                    # Success.
                    n_uploaded += 1
                    fileset.add(full_local_path)
                    log.debug("Uploaded file %s to %s" % (full_local_path, full_remote_path))
        finally:
            try:
                ftp_server.quit()
            except Exception:
                pass

        timestamp = time.time()
        self.save_last_upload(timestamp, fileset)
        return n_uploaded

    def get_last_upload(self):
        """Reads the time and members of the last upload from the local root"""

        timestamp_file_path = os.path.join(self.local_root, "#%s.last" % self.name)

        # If the file does not exist, an IOError exception will be raised. 
        # If the file exists, but is truncated, an EOFError will be raised.
        # Either way, be prepared to catch it.
        try:
            with open(timestamp_file_path, "rb") as f:
                timestamp = cPickle.load(f)
                fileset = cPickle.load(f)
        except (IOError, EOFError, cPickle.PickleError, AttributeError):
            timestamp = 0
            fileset = set()
            # Either the file does not exist, or it is garbled.
            # Either way, it's safe to remove it.
            try:
                os.remove(timestamp_file_path)
            except OSError:
                pass

        return timestamp, fileset

    def save_last_upload(self, timestamp, fileset):
        """Saves the time and members of the last upload in the local root."""
        timestamp_file_path = os.path.join(self.local_root, "#%s.last" % self.name)
        with open(timestamp_file_path, "wb") as f:
            cPickle.dump(timestamp, f)
            cPickle.dump(fileset, f)

    def _skip_this_dir(self, local_dir):
        """Determine whether to skip a directory."""

        return os.path.basename(local_dir) in ('.svn', 'CVS')

    def _skip_this_file(self, timestamp, fileset, full_local_path):
        """Determine whether to skip a specific file."""

        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


def _make_remote_dir(ftp_server, remote_dir_path):
    """Make a remote directory if necessary."""

    try:
        ftp_server.mkd(remote_dir_path)
    except ftplib.all_errors as e:
        # Got an exception. It might be because the remote directory already exists:
        if sys.exc_info()[0] is ftplib.error_perm:
            msg = str(e).strip()
            # If a directory already exists, some servers respond with a '550' ("Requested
            # action not taken") code, others with a '521' ("Access denied" or "Pathname
            # already exists") code.
            if msg.startswith('550') or msg.startswith('521'):
                # Directory already exists
                return
        # It's a real error. Re-raise the exception.
        raise

    log.debug("Made directory %s" % remote_dir_path)
    return

Reply via email to