def test_download_fs_fs(self): # Download files from local to remote (which is empty) local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) remote = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "local")) opts = {"force": False, "delete": False, "dry_run": False} s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["local_dirs"], 0) self.assertEqual(stats["local_files"], 0) self.assertEqual(stats["remote_dirs"], 2) self.assertEqual(stats["remote_files"], 4) # currently files are not counted, when inside a *new* folder self.assertEqual(stats["files_written"], 6) self.assertEqual(stats["dirs_created"], 2) self.assertEqual(stats["bytes_written"], 16403) # Again: nothing to do s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["local_dirs"], 2) self.assertEqual(stats["local_files"], 6) self.assertEqual(stats["remote_dirs"], 2) self.assertEqual(stats["remote_files"], 6) self.assertEqual(stats["files_written"], 0) self.assertEqual(stats["dirs_created"], 0) self.assertEqual(stats["bytes_written"], 0) # file times are preserved self.assertEqual(_get_test_file_date("local/file1.txt"), STAMP_20140101_120000) self.assertEqual(_get_test_file_date("remote/file1.txt"), STAMP_20140101_120000)
def test_issue_24(self): if not self.use_ftp_target: raise SkipTest("Only FTP targets.") # empty_folder() empty_folder(os.path.join(PYFTPSYNC_TEST_FOLDER, "local")) empty_folder(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) local_target = make_target(self.local_url) remote_target = make_target(self.remote_url) SIZE = 1000 * 1000 write_test_file("local/large1.txt", size=SIZE) write_test_file("remote/large2.txt", size=SIZE) opts = { "verbose": 5, "match": "large*.txt", } synchronizer = DownloadSynchronizer(local_target, remote_target, opts) assert is_test_file("local/large1.txt") assert is_test_file("remote/large2.txt") assert not is_test_file("remote/large1.txt") assert not is_test_file("local/large2.txt") synchronizer.run() # Expect that large2 was downloaded assert not is_test_file("remote/large1.txt") assert get_test_file_size("local/large2.txt") == SIZE
def sync_from_server(self): """Sync today's readings from FTP server Note: this recreates the download/upload synchronisers each time to avoid issues with flags set in these classes (bug in pyftpsync; see https://github.com/mar10/pyftpsync/issues/20) """ # current date and time now = datetime.datetime.utcnow() # FTP download options download_opts = {"force": True, "resolve": "remote", "verbose": 3, # match today's filename (wildcard required) "match": "*" + self.filename_from_date(now)} # create downloader ftp_downloader = DownloadSynchronizer(self.local_target, self.remote_target, download_opts) # download ftp_downloader.run()
def test_issue_24(self): if not self.use_ftp_target: raise SkipTest("Only FTP targets.") # empty_folder() empty_folder(os.path.join(PYFTPSYNC_TEST_FOLDER, "local")) empty_folder(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) local_target = make_target(self.local_url) remote_target = make_target(self.remote_url) SIZE = 1000 * 1000 write_test_file("local/large1.txt", size=SIZE) write_test_file("remote/large2.txt", size=SIZE) opts = {"verbose": 5, "match": "large*.txt"} synchronizer = DownloadSynchronizer(local_target, remote_target, opts) assert is_test_file("local/large1.txt") assert is_test_file("remote/large2.txt") assert not is_test_file("remote/large1.txt") assert not is_test_file("local/large2.txt") synchronizer.run() # Expect that large2 was downloaded assert not is_test_file("remote/large1.txt") assert get_test_file_size("local/large2.txt") == SIZE
def test_issue_5(self): """issue #5: Unable to navigate to working directory '' (Windows)""" if not on_windows: raise SkipTest("Windows only") local = targets.FsTarget("c:/temp") remote = FtpTarget("/", "www.wwwendt.de", None, self.username, self.password) opts = {"resolve": "remote", "verbose": 3, "dry_run": True} s = DownloadSynchronizer(local, remote, opts) s.run()
def test_download_fs_fs(self): # Download files from local to remote (which is empty) local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) remote = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "local")) opts = {"force": False, "delete": False, "verbose": self.verbose} s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["local_dirs"], 0) self.assertEqual(stats["local_files"], 0) self.assertEqual(stats["remote_dirs"], 2) # currently files are not counted, when inside a *new* folder: self.assertEqual(stats["remote_files"], 4) self.assertEqual(stats["files_written"], 6) self.assertEqual(stats["dirs_created"], 2) self.assertEqual(stats["bytes_written"], 16403) # Again: nothing to do s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["local_dirs"], 2) self.assertEqual(stats["local_files"], 6) self.assertEqual(stats["remote_dirs"], 2) self.assertEqual(stats["remote_files"], 6) self.assertEqual(stats["files_written"], 0) self.assertEqual(stats["dirs_created"], 0) self.assertEqual(stats["bytes_written"], 0) # file times are preserved self.assertEqual(get_test_file_date("local/file1.txt"), STAMP_20140101_120000) self.assertEqual(get_test_file_date("remote/file1.txt"), STAMP_20140101_120000)
def test_issue_20(self): opts = {"verbose": 5} local_target = make_target(self.local_url) remote_target = make_target(self.remote_url) ftp_downloader = DownloadSynchronizer(local_target, remote_target, opts) ftp_uploader = UploadSynchronizer(local_target, remote_target, opts) write_test_file("local/large1.txt", size=10 * 1000) write_test_file("remote/large2.txt", size=10 * 1000) ftp_downloader.run() ftp_uploader.run()
def download_prolog(): localpath = path_config.local_ftp_path # 本地保存的ftp目录 os.makedirs(localpath, 0o777) # 创建本地日志文件夹 local = FsTarget(localpath) remote = FtpTarget(path_config.ftp_remotepath, path_config.ftp_server, username=path_config.ftp_username, password=path_config.ftp_password) opts = {"resolve": "skip", "verbose": 1} try: s = DownloadSynchronizer(local, remote, opts) # 下载ftp日志文件夹 s.run() except SyntaxError: pass
def _transfer_files(self, count, size): temp1_path = os.path.join(PYFTPSYNC_TEST_FOLDER, "temp1") _empty_folder(temp1_path) # remove standard test files local = FsTarget(temp1_path) remote = self.remote for i in range(count): _write_test_file("temp1/file_%s.txt" % i, size=size) # Upload all of /temp1 to remote opts = { "force": False, "delete": False, "verbose": 3, "dry_run": False } s = UploadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["files_written"], count) self.assertEqual(stats["bytes_written"], count * size) # pprint(stats) print("Upload %s x %s bytes took %s: %s" % (count, size, stats["upload_write_time"], stats["upload_rate_str"]), file=sys.stderr) # Download all of remote to /temp2 local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "temp2")) opts = {"force": False, "delete": True, "verbose": 3, "dry_run": False} s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["files_written"], count) self.assertEqual(stats["bytes_written"], count * size) # pprint(stats) print("Download %s x %s bytes took %s: %s" % (count, size, stats["download_write_time"], stats["download_rate_str"]), file=sys.stderr)
def _transfer_files(self, count, size): temp1_path = os.path.join(PYFTPSYNC_TEST_FOLDER, "local") empty_folder(temp1_path) # remove standard test files local = FsTarget(temp1_path) remote = self.remote for i in range(count): write_test_file("local/file_{}.txt".format(i), size=size) # Upload all of temp/local to remote opts = {"force": False, "delete": False, "verbose": 3} s = UploadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["files_written"], count) self.assertEqual(stats["bytes_written"], count * size) # pprint(stats) print( "Upload {} x {} bytes took {}: {}".format( count, size, stats["upload_write_time"], stats["upload_rate_str"]), file=sys.stderr, ) # Download all of remote to temp/remote local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) opts = {"force": False, "delete": True, "verbose": 3} s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["files_written"], count) self.assertEqual(stats["bytes_written"], count * size) # pprint(stats) print( "Download {} x {} bytes took {}: {}".format( count, size, stats["download_write_time"], stats["download_rate_str"]), file=sys.stderr, )
def test_sync_fs_ftp(self): local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "local")) remote = self.remote # Upload all of temp/local to remote opts = {"force": False, "delete": True, "verbose": 3} s = UploadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["local_dirs"], 2) # currently files are not counted, when inside a *new* folder: self.assertEqual(stats["local_files"], 4) self.assertEqual(stats["remote_dirs"], 0) self.assertEqual(stats["remote_files"], 0) self.assertEqual(stats["files_written"], 6) self.assertEqual(stats["dirs_created"], 2) self.assertEqual(stats["bytes_written"], 16403) # Change one file and upload again touch_test_file("local/file1.txt") opts = {"force": False, "delete": True, "verbose": 3} s = UploadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) # assert False self.assertEqual(stats["entries_seen"], 18) # ??? self.assertEqual(stats["entries_touched"], 1) self.assertEqual(stats["files_created"], 0) self.assertEqual(stats["files_deleted"], 0) self.assertEqual(stats["files_written"], 1) self.assertEqual(stats["dirs_created"], 0) self.assertEqual(stats["download_files_written"], 0) self.assertEqual(stats["upload_files_written"], 1) self.assertEqual(stats["conflict_files"], 0) self.assertEqual(stats["bytes_written"], 3) # Download all from remote to temp/remote local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) opts = {"force": False, "delete": True, "verbose": 3} s = DownloadSynchronizer(local, remote, opts) s.run() stats = s.get_stats() # pprint(stats) self.assertEqual(stats["entries_seen"], 8) self.assertEqual(stats["entries_touched"], 8) # self.assertEqual(stats["files_created"], 6) self.assertEqual(stats["files_deleted"], 0) self.assertEqual(stats["files_written"], 6) self.assertEqual(stats["dirs_created"], 2) self.assertEqual(stats["download_files_written"], 6) self.assertEqual(stats["upload_files_written"], 0) self.assertEqual(stats["conflict_files"], 0) self.assertEqual(stats["bytes_written"], 16403) # Original file times are preserved, even when retrieved from FTP self.assertNotEqual(get_test_file_date("local/file1.txt"), STAMP_20140101_120000) self.assertEqual( get_test_file_date("local/file1.txt"), get_test_file_date("local//file1.txt"), ) self.assertEqual(get_test_file_date("local/file2.txt"), STAMP_20140101_120000) self.assertEqual(get_test_file_date("remote//file2.txt"), STAMP_20140101_120000) # Synchronize temp/local <=> remote : nothing to do local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "local")) opts = {"verbose": 3} s = BiDirSynchronizer(local, remote, opts) s.run() stats = s.get_stats() pprint(stats) self.assertEqual(stats["entries_touched"], 0) self.assertEqual(stats["conflict_files"], 0) self.assertEqual(stats["bytes_written"], 0) # Synchronize temp/remote <=> remote : nothing to do local = FsTarget(os.path.join(PYFTPSYNC_TEST_FOLDER, "remote")) opts = {"verbose": 3} s = BiDirSynchronizer(local, remote, opts) s.run() stats = s.get_stats() pprint(stats) self.assertEqual(stats["entries_touched"], 0) self.assertEqual(stats["conflict_files"], 0) self.assertEqual(stats["bytes_written"], 0)
def run(): """CLI main entry point.""" # Use print() instead of logging when running in CLI mode: set_pyftpsync_logger(None) parser = argparse.ArgumentParser( description="Synchronize folders over FTP.", epilog="See also https://github.com/mar10/pyftpsync", parents=[verbose_parser], ) # Note: we want to allow --version to be combined with --verbose. However # on Py2, argparse makes sub-commands mandatory, unless `action="version"` is used. if check_cli_verbose(3) > 3: version_info = "pyftpsync/{} Python/{} {}".format( __version__, PYTHON_VERSION, platform.platform() ) else: version_info = "{}".format(__version__) parser.add_argument("-V", "--version", action="version", version=version_info) subparsers = parser.add_subparsers(help="sub-command help") # --- Create the parser for the "upload" command --------------------------- sp = subparsers.add_parser( "upload", parents=[verbose_parser, common_parser, matcher_parser, creds_parser], help="copy new and modified files to remote folder", ) sp.add_argument( "local", metavar="LOCAL", default=".", help="path to local folder (default: %(default)s)", ) sp.add_argument("remote", metavar="REMOTE", help="path to remote folder") sp.add_argument( "--force", action="store_true", help="overwrite remote files, even if the target is newer " "(but no conflict was detected)", ) sp.add_argument( "--resolve", default="ask", choices=["local", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')", ) sp.add_argument( "--delete", action="store_true", help="remove remote files if they don't exist locally", ) sp.add_argument( "--delete-unmatched", action="store_true", help="remove remote files if they don't exist locally " "or don't match the current filter (implies '--delete' option)", ) sp.set_defaults(command="upload") # --- Create the parser for the "download" command ------------------------- sp = subparsers.add_parser( "download", parents=[verbose_parser, common_parser, matcher_parser, creds_parser], help="copy new and modified files from remote folder to local target", ) sp.add_argument( "local", metavar="LOCAL", default=".", help="path to local folder (default: %(default)s)", ) sp.add_argument("remote", metavar="REMOTE", help="path to remote folder") sp.add_argument( "--force", action="store_true", help="overwrite local files, even if the target is newer " "(but no conflict was detected)", ) sp.add_argument( "--resolve", default="ask", choices=["remote", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')", ) sp.add_argument( "--delete", action="store_true", help="remove local files if they don't exist on remote target", ) sp.add_argument( "--delete-unmatched", action="store_true", help="remove local files if they don't exist on remote target " "or don't match the current filter (implies '--delete' option)", ) sp.set_defaults(command="download") # --- Create the parser for the "sync" command ----------------------------- sp = subparsers.add_parser( "sync", parents=[verbose_parser, common_parser, matcher_parser, creds_parser], help="synchronize new and modified files between remote folder and local target", ) sp.add_argument( "local", metavar="LOCAL", default=".", help="path to local folder (default: %(default)s)", ) sp.add_argument("remote", metavar="REMOTE", help="path to remote folder") sp.add_argument( "--resolve", default="ask", choices=["old", "new", "local", "remote", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')", ) sp.set_defaults(command="sync") # --- Create the parser for the "run" command ----------------------------- add_run_parser(subparsers) # --- Create the parser for the "scan" command ----------------------------- add_scan_parser(subparsers) # --- Create the parser for the "tree" command ----------------------------- add_tree_parser(subparsers) # --- Parse command line --------------------------------------------------- args = parser.parse_args() args.verbose -= args.quiet del args.quiet # print("verbose", args.verbose) ftp_debug = 0 if args.verbose >= 6: ftp_debug = 1 if args.debug: if args.verbose < 4: parser.error("'--debug' requires verbose level >= 4") DEBUG_FLAGS.update(args.debug) # Modify the `args` from the `pyftpsync.yaml` config: if getattr(args, "command", None) == "run": handle_run_command(parser, args) if callable(getattr(args, "command", None)): # scan_handler try: return args.command(parser, args) except KeyboardInterrupt: print("\nAborted by user.", file=sys.stderr) sys.exit(3) elif not hasattr(args, "command"): parser.error( "missing command (choose from 'upload', 'download', 'run', 'sync', 'scan')" ) # Post-process and check arguments if hasattr(args, "delete_unmatched") and args.delete_unmatched: args.delete = True args.local_target = make_target(args.local, {"ftp_debug": ftp_debug}) if args.remote == ".": parser.error("'.' is expected to be the local target (not remote)") args.remote_target = make_target(args.remote, {"ftp_debug": ftp_debug}) if not isinstance(args.local_target, FsTarget) and isinstance( args.remote_target, FsTarget ): parser.error("a file system target is expected to be local") # Let the command handler do its thing opts = namespace_to_dict(args) if args.command == "upload": s = UploadSynchronizer(args.local_target, args.remote_target, opts) elif args.command == "download": s = DownloadSynchronizer(args.local_target, args.remote_target, opts) elif args.command == "sync": s = BiDirSynchronizer(args.local_target, args.remote_target, opts) else: parser.error("unknown command '{}'".format(args.command)) s.is_script = True try: s.run() except KeyboardInterrupt: print("\nAborted by user.", file=sys.stderr) sys.exit(3) finally: # Prevent sporadic exceptions in ftplib, when closing in __del__ s.local.close() s.remote.close() stats = s.get_stats() if args.verbose >= 5: pprint(stats) elif args.verbose >= 1: if args.dry_run: print("(DRY-RUN) ", end="") print( "Wrote {}/{} files in {} directories, skipped: {}.".format( stats["files_written"], stats["local_files"], stats["local_dirs"], stats["conflict_files_skipped"], ), end="", ) if stats["interactive_ask"]: print() else: print(" Elap: {}.".format(stats["elap_str"])) return
def run(): """CLI main entry point.""" # Use print() instead of logging when running in CLI mode: set_pyftpsync_logger(None) parser = argparse.ArgumentParser( description="Synchronize folders over FTP.", epilog="See also https://github.com/mar10/pyftpsync") parser.add_argument("-V", "--version", action="version", version="{}".format(__version__)) subparsers = parser.add_subparsers(help="sub-command help") # --- Create the parser for the "upload" command --------------------------- sp = subparsers.add_parser( "upload", help="copy new and modified files to remote folder") sp.add_argument("--force", action="store_true", help="overwrite remote files, even if the target is newer " "(but no conflict was detected)") sp.add_argument( "--resolve", default="ask", choices=["local", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')") sp.add_argument("--delete", action="store_true", help="remove remote files if they don't exist locally") sp.add_argument( "--delete-unmatched", action="store_true", help="remove remote files if they don't exist locally " "or don't match the current filter (implies '--delete' option)") add_common_sub_args(sp) sp.set_defaults(command="upload") # --- Create the parser for the "download" command ------------------------- sp = subparsers.add_parser( "download", help="copy new and modified files from remote folder to local target") sp.add_argument("--force", action="store_true", help="overwrite local files, even if the target is newer " "(but no conflict was detected)") sp.add_argument( "--resolve", default="ask", choices=["remote", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')") sp.add_argument( "--delete", action="store_true", help="remove local files if they don't exist on remote target") sp.add_argument( "--delete-unmatched", action="store_true", help="remove local files if they don't exist on remote target " "or don't match the current filter (implies '--delete' option)") add_common_sub_args(sp) sp.set_defaults(command="download") # --- Create the parser for the "sync" command ----------------------------- sp = subparsers.add_parser( "sync", help= "synchronize new and modified files between remote folder and local target" ) sp.add_argument( "--resolve", default="ask", choices=["old", "new", "local", "remote", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')") add_common_sub_args(sp) sp.set_defaults(command="synchronize") # --- Create the parser for the "scan" command ----------------------------- add_scan_parser(subparsers) # --- Parse command line --------------------------------------------------- args = parser.parse_args() args.verbose -= args.quiet del args.quiet ftp_debug = 0 if args.verbose >= 6: ftp_debug = 1 if callable(getattr(args, "command", None)): try: return getattr(args, "command")(args) except KeyboardInterrupt: print("\nAborted by user.", file=sys.stderr) sys.exit(3) elif not hasattr(args, "command"): parser.error( "missing command (choose from 'upload', 'download', 'sync', 'scan')" ) # Post-process and check arguments if hasattr(args, "delete_unmatched") and args.delete_unmatched: args.delete = True args.local_target = make_target(args.local, {"ftp_debug": ftp_debug}) if args.remote == ".": parser.error("'.' is expected to be the local target (not remote)") args.remote_target = make_target(args.remote, {"ftp_debug": ftp_debug}) if not isinstance(args.local_target, FsTarget) and isinstance( args.remote_target, FsTarget): parser.error("a file system target is expected to be local") # Let the command handler do its thing opts = namespace_to_dict(args) if args.command == "upload": s = UploadSynchronizer(args.local_target, args.remote_target, opts) elif args.command == "download": s = DownloadSynchronizer(args.local_target, args.remote_target, opts) elif args.command == "synchronize": s = BiDirSynchronizer(args.local_target, args.remote_target, opts) else: parser.error("unknown command {}".format(args.command)) s.is_script = True try: s.run() except KeyboardInterrupt: print("\nAborted by user.", file=sys.stderr) sys.exit(3) finally: # Prevent sporadic exceptions in ftplib, when closing in __del__ s.local.close() s.remote.close() stats = s.get_stats() if args.verbose >= 5: pprint(stats) elif args.verbose >= 1: if args.dry_run: print("(DRY-RUN) ", end="") print("Wrote {}/{} files in {} directories, skipped: {}.".format( stats["files_written"], stats["local_files"], stats["local_dirs"], stats["conflict_files_skipped"]), end="") if stats["interactive_ask"]: print() else: print(" Elap: {}.".format(stats["elap_str"])) return
def run(): parser = argparse.ArgumentParser( description="Synchronize folders over FTP.", epilog="See also https://github.com/mar10/pyftpsync") qv_group = parser.add_mutually_exclusive_group() qv_group.add_argument( "--verbose", "-v", action="count", default=3, help="increment verbosity by one (default: %(default)s, range: 0..5)") qv_group.add_argument("--quiet", "-q", action="count", default=0, help="decrement verbosity by one") parser.add_argument("-V", "--version", action="version", version="%s" % __version__) parser.add_argument( "--progress", "-p", action="store_true", default=False, help="show progress info, even if redirected or verbose < 3") subparsers = parser.add_subparsers(help="sub-command help") def __add_common_sub_args(parser): parser.add_argument( "local", metavar="LOCAL", # required=True, default=".", help="path to local folder (default: %(default)s)") parser.add_argument("remote", metavar="REMOTE", help="path to remote folder") parser.add_argument( "--dry-run", action="store_true", help="just simulate and log results, but don't change anything") # parser.add_argument("-x", "--execute", # action="store_false", dest="dry_run", default=True, # help="turn off the dry-run mode (which is ON by default), " # "that would just print status messages but does " # "not change anything") parser.add_argument("-f", "--include-files", help="wildcard for file names (default: all, " "separate multiple values with ',')") parser.add_argument( "-o", "--omit", help= "wildcard of files and directories to exclude (applied after --include)" ) parser.add_argument("--store-password", action="store_true", help="save password to keyring if login succeeds") parser.add_argument("--no-prompt", action="store_true", help="prevent prompting for missing credentials") parser.add_argument("--no-color", action="store_true", help="prevent use of ansi terminal color codes") # --- Create the parser for the "upload" command --------------------------- upload_parser = subparsers.add_parser( "upload", help="copy new and modified files to remote folder") __add_common_sub_args(upload_parser) upload_parser.add_argument( "--force", action="store_true", help= "overwrite remote files, even if the target is newer (but no conflict was detected)" ) upload_parser.add_argument( "--resolve", default="skip", choices=["local", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')") upload_parser.add_argument( "--delete", action="store_true", help="remove remote files if they don't exist locally") upload_parser.add_argument( "--delete-unmatched", action="store_true", help="remove remote files if they don't exist locally " "or don't match the current filter (implies '--delete' option)") upload_parser.set_defaults(command="upload") # --- Create the parser for the "download" command ------------------------- download_parser = subparsers.add_parser( "download", help="copy new and modified files from remote folder to local target") __add_common_sub_args(download_parser) download_parser.add_argument( "--force", action="store_true", help= "overwrite local files, even if the target is newer (but no conflict was detected)" ) download_parser.add_argument( "--resolve", default="skip", choices=["remote", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')") download_parser.add_argument( "--delete", action="store_true", help="remove local files if they don't exist on remote target") download_parser.add_argument( "--delete-unmatched", action="store_true", help="remove local files if they don't exist on remote target " "or don't match the current filter (implies '--delete' option)") download_parser.set_defaults(command="download") # --- Create the parser for the "sync" command ----------------------------- sync_parser = subparsers.add_parser( "sync", help= "synchronize new and modified files between remote folder and local target" ) __add_common_sub_args(sync_parser) sync_parser.add_argument( "--resolve", default="ask", choices=["old", "new", "local", "remote", "skip", "ask"], help="conflict resolving strategy (default: '%(default)s')") sync_parser.set_defaults(command="synchronize") # --- Create the parser for the "scan" command ----------------------------- scan_parser = add_scan_parser(subparsers) # --- Parse command line --------------------------------------------------- args = parser.parse_args() args.verbose -= args.quiet del args.quiet ftp_debug = 0 if args.verbose >= 5: ftp_debug = 1 if callable(getattr(args, "command", None)): return getattr(args, "command")(args) elif not hasattr(args, "command"): parser.error( "missing command (choose from 'upload', 'download', 'sync')") # Post-process and check arguments if hasattr(args, "delete_unmatched") and args.delete_unmatched: args.delete = True args.local_target = make_target(args.local, {"ftp_debug": ftp_debug}) if args.remote == ".": parser.error("'.' is expected to be the local target (not remote)") args.remote_target = make_target(args.remote, {"ftp_debug": ftp_debug}) if not isinstance(args.local_target, FsTarget) and isinstance( args.remote_target, FsTarget): parser.error("a file system target is expected to be local") # Let the command handler do its thing opts = namespace_to_dict(args) if args.command == "upload": s = UploadSynchronizer(args.local_target, args.remote_target, opts) elif args.command == "download": s = DownloadSynchronizer(args.local_target, args.remote_target, opts) elif args.command == "synchronize": s = BiDirSynchronizer(args.local_target, args.remote_target, opts) else: parser.error("unknown command %s" % args.command) try: s.run() except KeyboardInterrupt: print("\nAborted by user.") return finally: # prevent sporadic exceptions in ftplib, when closing in __del__ s.local.close() s.remote.close() stats = s.get_stats() if args.verbose >= 4: pprint(stats) elif args.verbose >= 1: if args.dry_run: print("(DRY-RUN) ", end="") print("Wrote %s/%s files in %s dirs. Elap: %s" % (stats["files_written"], stats["local_files"], stats["local_dirs"], stats["elap_str"])) return