def synchronize_times(self): """ Synchronize the local times of FTP client and server. This is necessary to let `upload_if_newer` and `download_if_newer` work correctly. If `synchronize_times` isn't applicable (see below), the time shift can still be set explicitly with `set_time_shift`. This implementation of `synchronize_times` requires _all_ of the following: - The connection between server and client is established. - The client has write access to the directory that is current when `synchronize_times` is called. The common usage pattern of `synchronize_times` is to call it directly after the connection is established. (As can be concluded from the points above, this requires write access to the login directory.) If `synchronize_times` fails, it raises a `TimeShiftError`. """ helper_file_name = "_ftputil_sync_" # Open a dummy file for writing in the current directory # on the FTP host, then close it. try: # May raise `FTPIOError` if directory isn't writable file_ = self.file(helper_file_name, 'w') file_.close() except ftp_error.FTPIOError: raise ftp_error.TimeShiftError( '''couldn't write helper file in directory "%s"''' % self.getcwd()) # If everything worked up to here it should be possible to stat # and then remove the just-written file. try: server_time = self.path.getmtime(helper_file_name) self.unlink(helper_file_name) except ftp_error.FTPOSError: # If we got a `TimeShiftError` exception above, we should't # come here: if we did not get a `TimeShiftError` above, # deletion should be possible. The only reason for an exception # I can think of here is a race condition by removing write # permission from the directory or helper file after it has been # written to. raise ftp_error.TimeShiftError( "could write helper file but not unlink it") # Calculate the difference between server and client. time_shift = server_time - time.time() # Do some sanity checks. self.__assert_valid_time_shift(time_shift) # If tests passed, store the time difference as time shift value. self.set_time_shift(self.__rounded_time_shift(time_shift))
def __assert_valid_time_shift(self, time_shift): """ Perform sanity checks on the time shift value (given in seconds). If the value is invalid, raise a `TimeShiftError`, else simply return `None`. """ minute = 60.0 hour = 60.0 * minute absolute_rounded_time_shift = abs(self.__rounded_time_shift(time_shift)) # test 1: fail if the absolute time shift is greater than # a full day (24 hours) if absolute_rounded_time_shift > 24 * hour: raise ftp_error.TimeShiftError( "time shift (%.2f s) > 1 day" % time_shift) # test 2: fail if the deviation between given time shift and # full hours is greater than a certain limit (e. g. five minutes) maximum_deviation = 5 * minute if abs(time_shift - self.__rounded_time_shift(time_shift)) > \ maximum_deviation: raise ftp_error.TimeShiftError( "time shift (%.2f s) deviates more than %d s from full hours" % (time_shift, maximum_deviation))
def synchronize_times(self, prepend=''): """ Synchronize the local times of FTP client and server. This is necessary to let `upload_if_newer` and `download_if_newer` work correctly. If `synchronize_times` isn't applicable (see below), the time shift can still be set explicitly with `set_time_shift`. This implementation of `synchronize_times` requires _all_ of the following: - The connection between server and client is established. - The client has write access to the directory that is current when `synchronize_times` is called. The common usage pattern of `synchronize_times` is to call it directly after the connection is established. (As can be concluded from the points above, this requires write access to the login directory.) If `synchronize_times` fails, it raises a `TimeShiftError`. """ helper_file_name = "%s_ftputil_sync_" % prepend # Open a dummy file for writing in the current directory # on the FTP host, then close it. try: # May raise `FTPIOError` if directory isn't writable. file_ = self.file(helper_file_name, 'w') file_.close() except ftp_error.FTPIOError: raise ftp_error.TimeShiftError( '''couldn't write helper file %s in directory "%s"''' % (helper_file_name, self.getcwd())) # If everything worked up to here it should be possible to stat # and then remove the just-written file. try: server_time = self.path.getmtime(helper_file_name) self.unlink(helper_file_name) except ftp_error.FTPOSError: # If we got a `TimeShiftError` exception above, we should't # come here: if we did not get a `TimeShiftError` above, # deletion should be possible. The only reason for an exception # I can think of here is a race condition by removing write # permission from the directory or helper file after it has been # written to. raise ftp_error.TimeShiftError( "could write helper file but not unlink it") # Calculate the difference between server and client. now = time.time() time_shift = server_time - now # As the time shift for this host instance isn't set yet, the # directory parser will calculate times one year in the past if # the time zone of the server is east from ours. Thus the time # shift will be off by a year as well (see ticket #55). if time_shift < -360 * 24 * 60 * 60: # Re-add one year and re-calculate the time shift. We don't # know how many days made up that year (it might have been # a leap year), so go the route via `time.localtime` and # `time.mktime`. server_time_struct = time.localtime(server_time) server_time_struct = (server_time_struct.tm_year+1,) + \ server_time_struct[1:] server_time = time.mktime(server_time_struct) time_shift = server_time - now # Do some sanity checks. self.__assert_valid_time_shift(time_shift) # If tests passed, store the time difference as time shift value. self.set_time_shift(self.__rounded_time_shift(time_shift))