apkeep 0.14.0

A command-line tool for downloading APK files from various sources
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;

use futures_util::StreamExt;
use gpapi::error::ErrorKind as GpapiErrorKind;
use gpapi::Gpapi;
use tokio::time::{sleep, Duration as TokioDuration};

pub async fn download_apps(
    apps: Vec<(String, Option<String>)>,
    parallel: usize,
    sleep_duration: u64,
    username: &str,
    password: &str,
    outpath: &Path,
    mut options: HashMap<&str, &str>,
) {
    let locale = options.remove("locale").unwrap_or("en_US");
    let timezone = options.remove("timezone").unwrap_or("UTC");
    let device = options.remove("device").unwrap_or("hero2lte");
    let split_apk = match options.remove("split_apk") {
        Some(val) if val == "1" || val.to_lowercase() == "true" => true,
        _ => false,
    };
    let include_additional_files = match options.remove("include_additional_files") {
        Some(val) if val == "1" || val.to_lowercase() == "true" => true,
        _ => false,
    };
    let mut gpa = Gpapi::new(locale, timezone, device);

    if let Err(err) = gpa.login(username, password).await {
        match err.kind() {
            GpapiErrorKind::SecurityCheck | GpapiErrorKind::EncryptLogin => println!("{}", err),
            _ => println!("Could not log in to Google Play.  Please check your credentials and try again later."),
        }
        std::process::exit(1);
    }
    let gpa = Rc::new(gpa);

    futures_util::stream::iter(
        apps.into_iter().map(|app| {
            let (app_id, app_version) = app;
            let gpa = Rc::clone(&gpa);
            async move {
                if app_version.is_none() {
                    println!("Downloading {}...", app_id);
                    if sleep_duration > 0 {
                        sleep(TokioDuration::from_millis(sleep_duration)).await;
                    }
                    match gpa.download(&app_id, None, split_apk, include_additional_files, Path::new(outpath)).await {
                        Ok(_) => println!("{} downloaded successfully!", app_id),
                        Err(err) if matches!(err.kind(), GpapiErrorKind::FileExists) => {
                            println!("File already exists for {}. Skipping...", app_id);
                        }
                        Err(err) if matches!(err.kind(), GpapiErrorKind::DirectoryExists) => {
                            println!("Split APK directory already exists for {}. Skipping...", app_id);
                        }
                        Err(err) if matches!(err.kind(), GpapiErrorKind::InvalidApp) => {
                            println!("Invalid app response for {}. Skipping...", app_id);
                        }
                        Err(err) if matches!(err.kind(), GpapiErrorKind::PermissionDenied) => {
                            println!("Permission denied when attempting to write file for {}. Skipping...", app_id);
                        }
                        Err(_) => {
                            println!("An error has occurred attempting to download {}.  Retry #1...", app_id);
                            match gpa.download(&app_id, None, split_apk, include_additional_files, Path::new(outpath)).await {
                                Ok(_) => println!("{} downloaded successfully!", app_id),
                                Err(_) => {
                                    println!("An error has occurred attempting to download {}.  Retry #2...", app_id);
                                    match gpa.download(&app_id, None, split_apk, include_additional_files, Path::new(outpath)).await {
                                        Ok(_) => println!("{} downloaded successfully!", app_id),
                                        Err(_) => {
                                            println!("An error has occurred attempting to download {}. Skipping...", app_id);
                                        }
                                    }
                                }
                            }
                        }
                    }
                } else {
                    println!("Specific versions can not be downloaded from Google Play ({}@{}). Skipping...", app_id, app_version.unwrap());
                }
            }
        })
    ).buffer_unordered(parallel).collect::<Vec<()>>().await;
}

pub fn list_versions(apps: Vec<(String, Option<String>)>) {
    for app in apps {
        let (app_id, _) = app;
        println!("Versions available for {} on Google Play:", app_id);
        println!("| Google Play does not make old versions of apps available.");
    }
}