| | 95 | class _TransferredFile(object): |
| | 96 | """ |
| | 97 | Represent a file on the local or remote side which is to |
| | 98 | be transferred or is already transferred. |
| | 99 | """ |
| | 100 | |
| | 101 | def __init__(self, host, name, mode): |
| | 102 | self._host = host |
| | 103 | self._is_local = (host is None) |
| | 104 | if self._is_local: |
| | 105 | self._path = os.path |
| | 106 | else: |
| | 107 | self._path = host.path |
| | 108 | self.name = self._path.abspath(name) |
| | 109 | self.mode = mode |
| | 110 | |
| | 111 | def exists(self): |
| | 112 | return self._path.exists(self.name) |
| | 113 | |
| | 114 | def mtime(self): |
| | 115 | mtime_ = self._path.getmtime(self.name) |
| | 116 | if not self._is_local: |
| | 117 | # convert to client time zone; see definition of time |
| | 118 | # shift in docstring of `FTPHost.set_time_shift` |
| | 119 | mtime_ -= self._host.time_shift() |
| | 120 | return mtime_ |
| | 121 | |
| | 122 | def fobj(self): |
| | 123 | if self._is_local: |
| | 124 | return open(self.name, self.mode) |
| | 125 | else: |
| | 126 | return self._host.file(self.name, self.mode) |
| | 127 | |
| | 128 | |
| 416 | | def __copy_file(self, source, target, mode, source_open, target_open): |
| 417 | | """ |
| 418 | | Copy a file from source to target. Which of both is a local |
| 419 | | or a remote file, respectively, is determined by the arguments. |
| | 453 | def __copy_file(self, source_file, target_file, conditional): |
| | 454 | """ |
| | 455 | Copy a file from `source_file` to `target_file`. |
| | 456 | |
| | 457 | Both of these are `_TransferredFile` objects. Which of them is |
| | 458 | a local or a remote file, respectively, is determined by the |
| | 459 | arguments. If `conditional` is true, the file is only copied |
| | 460 | if the target doesn't exist or is older than the source. If |
| | 461 | `conditional` is false, the file is copied unconditionally. |
| | 462 | """ |
| | 463 | if conditional: |
| | 464 | # evaluate condition: the target file either doesn't exist or is |
| | 465 | # older than the source file; use >= in the comparison, that is |
| | 466 | # if in doubt (due to imprecise timestamps) transfer |
| | 467 | #FIXME we probably need a more complex comparison, depending |
| | 468 | # on the precision of the timestamp on the server! |
| | 469 | condition = not target_file.exists() or \ |
| | 470 | source_file.mtime() > target_file.mtime() |
| | 471 | if not condition: |
| | 472 | # we didn't transfer |
| | 473 | return False |
| | 474 | source_fobj = source_file.fobj() |
| | 475 | try: |
| | 476 | target_fobj = target_file.fobj() |
| | 477 | try: |
| | 478 | self.copyfileobj(source_fobj, target_fobj) |
| | 479 | finally: |
| | 480 | target_fobj.close() |
| | 481 | finally: |
| | 482 | source_fobj.close() |
| | 483 | # transfer accomplished |
| | 484 | return True |
| | 485 | |
| | 486 | def _upload(self, source, target, mode, conditional): |
| | 487 | """ |
| | 488 | Upload from `source` to `target` which are `_TransferredFile` |
| | 489 | objects. The string `mode` may be "" or "b". If `conditional` |
| | 490 | is true, check if file should be copied at all. See the |
| | 491 | docstring of `__copy_file` for more. |
| 422 | | source = source_open(source, source_mode) |
| 423 | | try: |
| 424 | | target = target_open(target, target_mode) |
| 425 | | try: |
| 426 | | self.copyfileobj(source, target) |
| 427 | | finally: |
| 428 | | target.close() |
| 429 | | finally: |
| 430 | | source.close() |
| 431 | | |
| 432 | | def upload(self, source, target, mode=''): |
| 433 | | """ |
| 434 | | Upload a file from the local source (name) to the remote |
| 435 | | target (name). The argument mode is an empty string or 'a' for |
| 436 | | text copies, or 'b' for binary copies. |
| 437 | | """ |
| 438 | | self.__copy_file(source, target, mode, open, self.file) |
| | 494 | source_file = _TransferredFile(None, source, source_mode) |
| | 495 | target_file = _TransferredFile(self, target, target_mode) |
| | 498 | return self.__copy_file(source_file, target_file, conditional) |
| | 499 | |
| | 500 | def upload(self, source, target, mode=''): |
| | 501 | #FIXME should we support mode "a" at all? We don't support |
| | 502 | # appending! |
| | 503 | """ |
| | 504 | Upload a file from the local source (name) to the remote |
| | 505 | target (name). The argument `mode` is an empty string or 'a' for |
| | 506 | text copies, or 'b' for binary copies. |
| | 507 | """ |
| | 508 | self._upload(source, target, mode, conditional=False) |
| | 509 | |
| | 510 | def upload_if_newer(self, source, target, mode=''): |
| | 511 | """ |
| | 512 | Upload a file only if it's newer than the target on the |
| | 513 | remote host or if the target file does not exist. See the |
| | 514 | method `upload` for the meaning of the parameters. |
| | 515 | |
| | 516 | If an upload was necessary, return `True`, else return |
| | 517 | `False`. |
| | 518 | """ |
| | 519 | return self._upload(source, target, mode, conditional=True) |
| | 520 | |
| | 521 | def _download(self, source, target, mode, conditional): |
| | 522 | """ |
| | 523 | Download from `source` to `target` which are `_TransferredFile` |
| | 524 | objects. The string `mode` may be "" or "b". If `conditional` |
| | 525 | is true, check if file should be copied at all. See the |
| | 526 | docstring of `__copy_file` for more. |
| | 527 | """ |
| | 528 | source_mode, target_mode = self.__get_modes(mode) |
| | 529 | source_file = _TransferredFile(None, source, source_mode) |
| | 530 | target_file = _TransferredFile(self, target, target_mode) |
| | 531 | return self.__copy_file(source_file, target_file, conditional) |
| 448 | | self.__copy_file(source, target, mode, self.file, open) |
| 449 | | |
| 450 | | #XXX the use of the `copy_method` seems less-than-ideal |
| 451 | | # factoring; can we handle it in another way? |
| 452 | | |
| 453 | | def __copy_file_if_newer(self, source, target, mode, |
| 454 | | source_mtime, target_mtime, target_exists, copy_method): |
| 455 | | """ |
| 456 | | Copy a source file only if it's newer than the target. The |
| 457 | | direction of the copy operation is determined by the |
| 458 | | arguments. See methods `upload_if_newer` and |
| 459 | | `download_if_newer` for examples. |
| 460 | | |
| 461 | | If the copy was necessary, return `True`, else return `False`. |
| 462 | | """ |
| 463 | | source_timestamp = source_mtime(source) |
| 464 | | if target_exists(target): |
| 465 | | target_timestamp = target_mtime(target) |
| 466 | | else: |
| 467 | | # every timestamp is newer than this one |
| 468 | | target_timestamp = 0.0 |
| 469 | | if source_timestamp > target_timestamp: |
| 470 | | copy_method(source, target, mode) |
| 471 | | return True |
| 472 | | else: |
| 473 | | return False |
| 474 | | |
| 475 | | def __shifted_local_mtime(self, file_name): |
| 476 | | """ |
| 477 | | Return last modification of a local file, corrected with |
| 478 | | respect to the time shift between client and server. |
| 479 | | """ |
| 480 | | local_mtime = os.path.getmtime(file_name) |
| 481 | | # transform to server time |
| 482 | | return local_mtime + self.time_shift() |
| 483 | | |
| 484 | | def upload_if_newer(self, source, target, mode=''): |
| 485 | | """ |
| 486 | | Upload a file only if it's newer than the target on the |
| 487 | | remote host or if the target file does not exist. See the |
| 488 | | method `upload` for the meaning of the parameters. |
| 489 | | |
| 490 | | If an upload was necessary, return `True`, else return |
| 491 | | `False`. |
| 492 | | """ |
| 493 | | return self.__copy_file_if_newer(source, target, mode, |
| 494 | | self.__shifted_local_mtime, self.path.getmtime, |
| 495 | | self.path.exists, self.upload) |
| | 541 | self._download(source, target, mode, conditional=False) |