def get_smtp_no_tls(config): no_tls = False config_path = ["app", "smtp", "no_tls"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: no_tls is not found in {config_path_to_string(config_path)}" ) else: no_tls = get_config_value(config=config, config_path=config_path) return no_tls
def get_smtp_port(config): port = None config_path = ["app", "smtp", "port"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: port is not found in {config_path_to_string(config_path)}" ) else: port = get_config_value(config=config, config_path=config_path) return port
def get_photos_sync_interval(config): sync_interval = DEFAULT_SYNC_INTERVAL_SEC config_path = ["photos", "sync_interval"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"sync_interval is not found in {config_path_to_string(config_path=config_path)}." + f" Using default sync_interval: {sync_interval} seconds ...") else: sync_interval = get_config_value(config=config, config_path=config_path) LOGGER.info(f"Syncing photos every {sync_interval} seconds.") return sync_interval
def get_retry_login_interval(config): retry_login_interval = DEFAULT_RETRY_LOGIN_INTERVAL_SEC config_path = ["app", "credentials", "retry_login_interval"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"retry_login_interval not found in {config_path_to_string(config_path=config_path)}." + f" Using default {retry_login_interval} seconds ...") else: retry_login_interval = get_config_value(config=config, config_path=config_path) LOGGER.info(f"Retrying login every {retry_login_interval} seconds.") return retry_login_interval
def prepare_root_destination(config): LOGGER.debug("Checking root destination ...") root_destination = DEFAULT_ROOT_DESTINATION config_path = ["app", "root"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: root destination is missing in {config_path_to_string(config_path)}." + f" Using default root destination: {root_destination}", ) else: root_destination = get_config_value(config=config, config_path=config_path) root_destination_path = os.path.abspath(root_destination) os.makedirs(root_destination_path, exist_ok=True) return root_destination_path
def get_photos_remove_obsolete(config): photos_remove_obsolete = False config_path = ["photos", "remove_obsolete"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: remove_obsolete is not found in {config_path_to_string(config_path)}." + " Not removing the obsolete photos.") else: photos_remove_obsolete = get_config_value(config=config, config_path=config_path) LOGGER.debug( f"{'R' if photos_remove_obsolete else 'Not R'}emoving obsolete photos ..." ) return photos_remove_obsolete
def get_drive_remove_obsolete(config): drive_remove_obsolete = False config_path = ["drive", "remove_obsolete"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: remove_obsolete is not found in {config_path_to_string(config_path)}." + " Not removing the obsolete files and folders.") else: drive_remove_obsolete = get_config_value(config=config, config_path=config_path) LOGGER.debug( f"{'R' if drive_remove_obsolete else 'Not R'}emoving obsolete files and folders ..." ) return drive_remove_obsolete
def prepare_drive_destination(config): LOGGER.debug("Checking drive destination ...") config_path = ["drive", "destination"] drive_destination = DEFAULT_DRIVE_DESTINATION if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: destination is missing in {config_path_to_string(config_path)}." + f" Using default drive destination: {drive_destination}.") else: drive_destination = get_config_value(config=config, config_path=config_path) drive_destination_path = os.path.abspath( os.path.join(prepare_root_destination(config=config), drive_destination)) os.makedirs(drive_destination_path, exist_ok=True) return drive_destination_path
def prepare_photos_destination(config): LOGGER.debug("Checking photos destination ...") config_path = ["photos", "destination"] photos_destination = DEFAULT_PHOTOS_DESTINATION if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"Warning: destination is missing in {photos_destination}." + f" Using default photos destination: {config_path_to_string(config_path)}" ) else: photos_destination = get_config_value(config=config, config_path=config_path) photos_destination_path = os.path.abspath( os.path.join(prepare_root_destination(config=config), photos_destination)) os.makedirs(photos_destination_path, exist_ok=True) return photos_destination_path
def send(config, last_send=None, dry_run=False): sent_on = None email = config_parser.get_smtp_email(config=config) to_email = config_parser.get_smtp_to_email(config=config) host = config_parser.get_smtp_host(config=config) port = config_parser.get_smtp_port(config=config) no_tls = config_parser.get_smtp_no_tls(config=config) password = config_parser.get_smtp_password(config=config) if last_send and last_send > datetime.datetime.now() - datetime.timedelta(hours=24): LOGGER.info("Throttling email to once a day") sent_on = last_send elif email and host and port: try: sent_on = datetime.datetime.now() if not dry_run: smtp = smtplib.SMTP(host, port) smtp.set_debuglevel(0) smtp.connect(host, port) if not no_tls: smtp.starttls() if password: smtp.login(email, password) msg = build_message(email) smtp.sendmail(from_addr=email, to_addrs=to_email, msg=msg.as_string()) smtp.quit() except (Exception) as e: sent_on = None LOGGER.error(f"Failed to send email: {str(e)}.") else: LOGGER.warning("Not sending 2FA notification because SMTP is not configured") return sent_on
def get_photos_filters(config): photos_filters = {"albums": None, "file_sizes": ["original"]} valid_file_sizes = ["original", "medium", "thumb"] config_path = ["photos", "filters"] if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"{config_path_to_string(config_path=config_path)} not found. Downloading all albums with original size ..." ) else: config_path.append("albums") if (not traverse_config_path(config=config, config_path=config_path) or not get_config_value(config=config, config_path=config_path) or len(get_config_value(config=config, config_path=config_path)) == 0): LOGGER.warning( f"{config_path_to_string(config_path=config_path)} not found. Downloading all albums ..." ) else: photos_filters["albums"] = get_config_value( config=config, config_path=config_path) config_path[2] = "file_sizes" if not traverse_config_path(config=config, config_path=config_path): LOGGER.warning( f"{config_path_to_string(config_path=config_path)} not found. Downloading original size photos ..." ) else: file_sizes = get_config_value(config=config, config_path=config_path) for file_size in file_sizes: if not file_size in valid_file_sizes: LOGGER.warning( f"Skipping the invalid file size {file_size}, " + f"valid file sizes are {','.join(valid_file_sizes)}.") file_sizes.remove(file_size) if len(file_sizes) == 0: file_sizes = ["original"] photos_filters["file_sizes"] = file_sizes return photos_filters
def sync(): last_send = None enable_sync_drive = True enable_sync_photos = True drive_sync_interval = 0 photos_sync_interval = 0 sleep_for = 10 while True: config = read_config() username = config_parser.get_username(config=config) if username: try: if ENV_ICLOUD_PASSWORD_KEY in os.environ: password = os.environ.get(ENV_ICLOUD_PASSWORD_KEY) utils.store_password_in_keyring(username=username, password=password) else: password = utils.get_password_from_keyring( username=username) api = ICloudPyService( apple_id=username, password=password, cookie_directory=DEFAULT_COOKIE_DIRECTORY, ) if not api.requires_2sa: if "drive" in config and enable_sync_drive: sync_drive.sync_drive(config=config, drive=api.drive) drive_sync_interval = config_parser.get_drive_sync_interval( config=config) if "photos" in config and enable_sync_photos: sync_photos.sync_photos(config=config, photos=api.photos) photos_sync_interval = config_parser.get_photos_sync_interval( config=config) if "drive" not in config and "photos" not in config: LOGGER.warning( "Nothing to sync. Please add drive: and/or photos: section in config.yaml file." ) else: LOGGER.error("Error: 2FA is required. Please log in.") # Retry again sleep_for = config_parser.get_retry_login_interval( config=config) next_sync = ( datetime.datetime.now() + datetime.timedelta(seconds=sleep_for)).strftime("%c") LOGGER.info(f"Retrying login at {next_sync} ...") last_send = notify.send(config, last_send) sleep(sleep_for) continue except exceptions.ICloudPyNoStoredPasswordAvailableException: LOGGER.error( "Password is not stored in keyring. Please save the password in keyring." ) sleep_for = config_parser.get_retry_login_interval( config=config) next_sync = ( datetime.datetime.now() + datetime.timedelta(seconds=sleep_for)).strftime("%c") LOGGER.info(f"Retrying login at {next_sync} ...") last_send = notify.send(config, last_send) sleep(sleep_for) continue if "drive" not in config and "photos" in config: sleep_for = photos_sync_interval enable_sync_drive = False enable_sync_photos = True elif "drive" in config and "photos" not in config: sleep_for = drive_sync_interval enable_sync_drive = True enable_sync_photos = False elif ("drive" in config and "photos" in config and drive_sync_interval <= photos_sync_interval): sleep_for = photos_sync_interval - drive_sync_interval photos_sync_interval -= drive_sync_interval enable_sync_drive = True enable_sync_photos = False else: sleep_for = drive_sync_interval - photos_sync_interval drive_sync_interval -= photos_sync_interval enable_sync_drive = False enable_sync_photos = True next_sync = (datetime.datetime.now() + datetime.timedelta(seconds=sleep_for)).strftime("%c") LOGGER.info(f"Resyncing at {next_sync} ...") if (config_parser.get_drive_sync_interval(config=config) < 0 if "drive" in config else True and config_parser.get_photos_sync_interval( config=config) < 0 if "photos" in config else True): break sleep(sleep_for)