~charles/ftpmusync

5e89694e12dc1dc639ce59e12cd8c29d3e301dca — Charles Daniels 1 year, 5 months ago ebce293 0.0.2 0.0.3
0.0.2 release: fixed some crashes and entry point
5 files changed, 168 insertions(+), 152 deletions(-)

A CHANGELOG
M ftpmusync/constants.py
R ftpmusync/{__main__.py => ftpmusync.py}
M ftpmusync/ftpwrapper.py
M setup.py
A CHANGELOG => CHANGELOG +7 -0
@@ 0,0 1,7 @@
0.0.2:

	* Fixed an issue where the console entry point for the ftpmusync
	  command was not set correctly by setup.py

	* Fixed a bug where providing an incorrect prefix with -e would cause
	  ftp.cwd() to throw an uncaught EOFError.

M ftpmusync/constants.py => ftpmusync/constants.py +1 -1
@@ 17,7 17,7 @@ ftp_retries = 5
# maximum number of IP addresses to scan
max_ips = 1000

version = "0.0.1"
version = "0.0.2"

description = """
A tool for syncing music via FTP to applications such as Foobar2000 which

R ftpmusync/__main__.py => ftpmusync/ftpmusync.py +151 -150
@@ 11,167 11,168 @@ from . import net
from . import sync
from . import util

def main():

parser = argparse.ArgumentParser(description = constants.description)
    parser = argparse.ArgumentParser(description = constants.description)

parser.add_argument('--version', action='version', version=constants.version)
    parser.add_argument('--version', action='version', version=constants.version)


parser.add_argument("--verbose", "-v", default=False, action="store_true",
        help="Display verbose output messages.")
    parser.add_argument("--verbose", "-v", default=False, action="store_true",
            help="Display verbose output messages.")

parser.add_argument("--debug", "-D", default=False, action="store_true",
        help="Display debug output messages. Implies -v.")

parser.add_argument("--port", "-p", default=21,
        help="Specify the port FTP is expected to be running on." +
        " (default: 21)")

parser.add_argument("--ip", "-i", default=None,
        help="Specify an IP address to sync explicitly")

parser.add_argument("--user", "-u", default="anonymous",
        help="Specify FTP user. (default: anonymous)")

parser.add_argument("--password", "-P", default="anonymous",
        help="Specify FTP password. (default: anonymous)")

parser.add_argument("--prefix", "-e", default="./",
        help="Specify prefix on FTP server. (default: ./)")

parser.add_argument("--srcdir", "-d", default=os.path.expanduser("~/Music"),
        help="Specify the source directory. (default: ~/Music)")

parser.add_argument("--allow_scan", "-a", default=False, action="store_true",
        help="Enable features that require scanning the network. " +
        constants.scan_warning)

parser.add_argument("--overwrite", "-w", default=False, action="store_true",
        help="If asserted, any conflicting paths on the remote are " +
        "unconditionally overwritten. By default, only paths where the " +
        "file sizes differ are overwritten.")

parser.add_argument("--token", "-t", default=socket.gethostname(),
        help="Override the token, which is the system hostname by default. " +
        "This is used to check if a given target host is managed by " +
        "this instance of ftpmusync. You may want to override this if you "+
        "wish to sync to the same device from several systems, or if " +
        "your system hostname changes frequently for some reason.")


action = parser.add_mutually_exclusive_group(required=True)

action.add_argument("--scan", "-s", default=False, action="store_true",
        help="Output a list of IP addresses which are managed by ftpmusync " +
        "on the current LAN network. Requires --allow_scan to be asserted.")

action.add_argument("--scan_all", "-S", default=False, action="store_true",
        help="Output a list of IP addresses which have open FTP ports " +
        "on the current LAN network. Requires --allow_scan to be asserted." )

action.add_argument("--enroll", "-n", default=False, action="store_true",
        help="Enroll an host to be managed by this ftpmusync instance. " +
        "you must specify --ip to use this action.")

action.add_argument("--sync", "-y", default=False, action="store_true",
        help="Sync music files now. If --allow_scan is asserted and no " +
        "--ip is provided, all results that would be returned by --scan " +
        "are used as sync targets.")

args = parser.parse_args()

if args.debug:
    util.setup_logging(logging.DEBUG)
elif args.verbose:
    util.setup_logging(logging.INFO)
else:
    util.setup_logging(logging.WARNING)

# validate IP address if any
if args.ip is not None:
    try:
        args.ip = ipaddress.ip_address(args.ip)
    except Exception as e:
        logging.error("invalid IP address")
        util.log_exception(e)
        exit(1)

params = ftpwrapper.FTParams(args.ip,
        args.port,
        args.user,
        args.password,
        args.prefix)

if args.scan_all:
    if not args.allow_scan:
        logging.error("--scan_all cannot be used without asserting " +
                "--allow_scan. This is a safety feature, as the requested " +
                "operation involves scanning and connecting to every host " +
                "on the subnet you are connected to. This is likely to trip " +
                "intrusion detection systems, and is not something you " +
                "should do on networks you don't control or have permission " +
                "to access in this way.")
        sys.exit(1)

    for ip in net.scan_subnets(net.get_subnet(), args.port):
        print(ip)

elif args.scan:
    if not args.allow_scan:
        logging.error("--scan cannot be used without asserting " +
                "--allow_scan. This is a safety feature, as the requested " +
                "operation involves scanning and connecting to every host " +
                "on the subnet you are connected to. This is likely to trip " +
                "intrusion detection systems, and is not something you " +
                "should do on networks you don't control or have permission " +
                "to access in this way.")
        sys.exit(1)
    for ip in net.get_enrolled(params, net.scan_subnets(net.get_subnet()), 
            args.token):
        print(ip)

elif args.enroll:
    if args.ip is None:
        logging.error("cannot enroll without specifying --ip")
        sys.exit(1)

    net.enroll_host(params, args.token)

elif args.sync:
    if args.ip is None and not args.allow_scan:
        logging.error("--sync cannot be used without asserting " +
                "--ip (if you know the device you want to sync to), or " +
                "--allow_scan. This is a safety feature, as the requested " +
                "operation involves scanning and connecting to every host " +
                "on the subnet you are connected to. This is likely to trip " +
                "intrusion detection systems, and is not something you " +
                "should do on networks you don't control or have permission " +
                "to access in this way.")
        sys.exit(1)

    elif args.ip is not None:
        logging.info("syncing to target {}".format(params))
        if not net.check_host(params, args.token):
            logging.error("host {} not enrolled, skipping it."
                    .format(params))
            sys.exit(1)
        sync.sync_dir(args.srcdir, "./", params, args.overwrite)
    parser.add_argument("--debug", "-D", default=False, action="store_true",
            help="Display debug output messages. Implies -v.")

    parser.add_argument("--port", "-p", default=21,
            help="Specify the port FTP is expected to be running on." +
            " (default: 21)")

    parser.add_argument("--ip", "-i", default=None,
            help="Specify an IP address to sync explicitly")

    parser.add_argument("--user", "-u", default="anonymous",
            help="Specify FTP user. (default: anonymous)")

    parser.add_argument("--password", "-P", default="anonymous",
            help="Specify FTP password. (default: anonymous)")

    parser.add_argument("--prefix", "-e", default="./",
            help="Specify prefix on FTP server. (default: ./)")

    parser.add_argument("--srcdir", "-d", default=os.path.expanduser("~/Music"),
            help="Specify the source directory. (default: ~/Music)")

    parser.add_argument("--allow_scan", "-a", default=False, action="store_true",
            help="Enable features that require scanning the network. " +
            constants.scan_warning)

    parser.add_argument("--overwrite", "-w", default=False, action="store_true",
            help="If asserted, any conflicting paths on the remote are " +
            "unconditionally overwritten. By default, only paths where the " +
            "file sizes differ are overwritten.")

    parser.add_argument("--token", "-t", default=socket.gethostname(),
            help="Override the token, which is the system hostname by default. " +
            "This is used to check if a given target host is managed by " +
            "this instance of ftpmusync. You may want to override this if you "+
            "wish to sync to the same device from several systems, or if " +
            "your system hostname changes frequently for some reason.")


    action = parser.add_mutually_exclusive_group(required=True)

    action.add_argument("--scan", "-s", default=False, action="store_true",
            help="Output a list of IP addresses which are managed by ftpmusync " +
            "on the current LAN network. Requires --allow_scan to be asserted.")

    action.add_argument("--scan_all", "-S", default=False, action="store_true",
            help="Output a list of IP addresses which have open FTP ports " +
            "on the current LAN network. Requires --allow_scan to be asserted." )

    action.add_argument("--enroll", "-n", default=False, action="store_true",
            help="Enroll an host to be managed by this ftpmusync instance. " +
            "you must specify --ip to use this action.")

    action.add_argument("--sync", "-y", default=False, action="store_true",
            help="Sync music files now. If --allow_scan is asserted and no " +
            "--ip is provided, all results that would be returned by --scan " +
            "are used as sync targets.")

    args = parser.parse_args()

    if args.debug:
        util.setup_logging(logging.DEBUG)
    elif args.verbose:
        util.setup_logging(logging.INFO)
    else:
        logging.info("scanning for enrolled devices... ")
        enrolled = net.get_enrolled(params,
                net.scan_subnets(net.get_subnet()), args.token)
        logging.info("found {} enrolled devices".format(len(enrolled)))
        util.setup_logging(logging.WARNING)

    # validate IP address if any
    if args.ip is not None:
        try:
            args.ip = ipaddress.ip_address(args.ip)
        except Exception as e:
            logging.error("invalid IP address")
            util.log_exception(e)
            exit(1)

    params = ftpwrapper.FTParams(args.ip,
            args.port,
            args.user,
            args.password,
            args.prefix)

    if args.scan_all:
        if not args.allow_scan:
            logging.error("--scan_all cannot be used without asserting " +
                    "--allow_scan. This is a safety feature, as the requested " +
                    "operation involves scanning and connecting to every host " +
                    "on the subnet you are connected to. This is likely to trip " +
                    "intrusion detection systems, and is not something you " +
                    "should do on networks you don't control or have permission " +
                    "to access in this way.")
            sys.exit(1)

        for ip in net.scan_subnets(net.get_subnet(), args.port):
            print(ip)

    elif args.scan:
        if not args.allow_scan:
            logging.error("--scan cannot be used without asserting " +
                    "--allow_scan. This is a safety feature, as the requested " +
                    "operation involves scanning and connecting to every host " +
                    "on the subnet you are connected to. This is likely to trip " +
                    "intrusion detection systems, and is not something you " +
                    "should do on networks you don't control or have permission " +
                    "to access in this way.")
            sys.exit(1)
        for ip in net.get_enrolled(params, net.scan_subnets(net.get_subnet()), 
                args.token):
            print(ip)

        for ip in enrolled:
    elif args.enroll:
        if args.ip is None:
            logging.error("cannot enroll without specifying --ip")
            sys.exit(1)

            params.ip = str(ip)
            logging.info("syncing to target {}".format(params))
        net.enroll_host(params, args.token)

    elif args.sync:
        if args.ip is None and not args.allow_scan:
            logging.error("--sync cannot be used without asserting " +
                    "--ip (if you know the device you want to sync to), or " +
                    "--allow_scan. This is a safety feature, as the requested " +
                    "operation involves scanning and connecting to every host " +
                    "on the subnet you are connected to. This is likely to trip " +
                    "intrusion detection systems, and is not something you " +
                    "should do on networks you don't control or have permission " +
                    "to access in this way.")
            sys.exit(1)

        elif args.ip is not None:
            logging.info("syncing to target {}".format(params))
            if not net.check_host(params, args.token):
                logging.warning("host {} not enrolled, skipping it."
                logging.error("host {} not enrolled, skipping it."
                        .format(params))
                continue

                sys.exit(1)
            sync.sync_dir(args.srcdir, "./", params, args.overwrite)

        else:
            logging.info("scanning for enrolled devices... ")
            enrolled = net.get_enrolled(params,
                    net.scan_subnets(net.get_subnet()), args.token)
            logging.info("found {} enrolled devices".format(len(enrolled)))

            for ip in enrolled:

                params.ip = str(ip)
                logging.info("syncing to target {}".format(params))

                if not net.check_host(params, args.token):
                    logging.warning("host {} not enrolled, skipping it."
                            .format(params))
                    continue

                sync.sync_dir(args.srcdir, "./", params, args.overwrite)


M ftpmusync/ftpwrapper.py => ftpmusync/ftpwrapper.py +8 -0
@@ 45,6 45,14 @@ def setup_connection(ftp, params):
        logging.warning("temporary error with {}: {}".format(params, e))
        util.log_exception(e, logging.debug)
        return None
    except EOFError as e:
        logging.warning("EOF error with {}: {}".format(params, e))
        logging.warning("HINT: you probably have the wrong prefix!")
        util.log_exception(e, logging.debug)
    except Exception as e:
        logging.warning("unahndled error with {}: {}".format(params, e))
        util.log_exception(e, logging.debug)
        return None

    return ftp


M setup.py => setup.py +1 -1
@@ 26,7 26,7 @@ setup(name="ftpmusync",
      keywords='',
      packages=find_packages(),
      entry_points={'console_scripts':
                    ['ftpmusync=ftpmusync.__main__:main']},
                    ['ftpmusync=ftpmusync.ftpmusync:main']},
      package_dir={'ftpmusync': 'ftpmusync'},
      platforms=['POSIX']
      )