Synchronizes 2 directory trees.

def rsync(src_dir, dst_dir, mirror, dry_run, print_func, recursed, sync_hidden):
    """Synchronizes 2 directory trees."""
    # This test is a hack to avoid errors when accessing /flash. When the
    # cache synchronisation issue is solved it should be removed
    if not isinstance(src_dir, str) or not len(src_dir):
        return

    sstat = auto(get_stat, src_dir)
    smode = stat_mode(sstat)
    if mode_isfile(smode):
        print_err('Source is a file not a directory.')
        return

    d_src = {}  # Look up stat tuple from name in current directory
    src_files = auto(listdir_stat, src_dir, show_hidden=sync_hidden)
    if src_files is None:
        print_err('Source directory {} does not exist.'.format(src_dir))
        return
    for name, stat in src_files:
        d_src[name] = stat

    d_dst = {}
    dst_files = auto(listdir_stat, dst_dir, show_hidden=sync_hidden)
    if dst_files is None: # Directory does not exist
        if not make_dir(dst_dir, dry_run, print_func, recursed):
            return
    else: # dest exists
        for name, stat in dst_files:
            d_dst[name] = stat

    set_dst = set(d_dst.keys())
    set_src = set(d_src.keys())
    to_add = set_src - set_dst  # Files to copy to dest
    to_del = set_dst - set_src  # To delete from dest
    to_upd = set_dst.intersection(set_src) # In both: may need updating

    for src_basename in to_add:  # Name in source but absent from destination
        src_filename = src_dir + '/' + src_basename
        dst_filename = dst_dir + '/' + src_basename
        print_func("Adding %s" % dst_filename)
        src_stat = d_src[src_basename]
        src_mode = stat_mode(src_stat)
        if not dry_run:
            if not mode_isdir(src_mode):
                cp(src_filename, dst_filename)
        if mode_isdir(src_mode):
            rsync(src_filename, dst_filename, mirror=mirror, dry_run=dry_run,
                  print_func=print_func, recursed=True, sync_hidden=sync_hidden)

    if mirror:  # May delete
        for dst_basename in to_del:  # In dest but not in source
            dst_filename = dst_dir + '/' + dst_basename
            print_func("Removing %s" % dst_filename)
            if not dry_run:
                rm(dst_filename, recursive=True, force=True)

    for src_basename in to_upd:  # Names are identical
        src_stat = d_src[src_basename]
        dst_stat = d_dst[src_basename]
        src_filename = src_dir + '/' + src_basename
        dst_filename = dst_dir + '/' + src_basename
        src_mode = stat_mode(src_stat)
        dst_mode = stat_mode(dst_stat)
        if mode_isdir(src_mode):
            if mode_isdir(dst_mode):
                # src and dst are both directories - recurse
                rsync(src_filename, dst_filename, mirror=mirror, dry_run=dry_run,
                      print_func=print_func, recursed=True, sync_hidden=sync_hidden)
            else:
                msg = "Source '{}' is a directory and destination " \
                      "'{}' is a file. Ignoring"
                print_err(msg.format(src_filename, dst_filename))
        else:
            if mode_isdir(dst_mode):
                msg = "Source '{}' is a file and destination " \
                      "'{}' is a directory. Ignoring"
                print_err(msg.format(src_filename, dst_filename))
            else:
                if stat_mtime(src_stat) > stat_mtime(dst_stat):
                    msg = "{} is newer than {} - copying"
                    print_func(msg.format(src_filename, dst_filename))
                    if not dry_run:
                        cp(src_filename, dst_filename)