playit-gg 0.0.7

Unofficial Rust wrapper for https://playit.gg
Documentation
use crate::*;
use reqwest::blocking::get;
use reqwest::blocking::ClientBuilder;
use reqwest::header;
use serde_json::from_str;
use std::collections::HashMap;
use std::env::var;
use std::fs::create_dir_all;
use std::fs::read_to_string;
use std::fs::remove_file;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use url::Url;

pub fn get_defaults(playit_opts: Option<PlayItOpts>) -> Result<PlayIt, String> {
  if consts::ARCH == Arch::Unsupported {
    return Err(String::from("Unsupported Architexture"));
  }

  let dir = Path::new(&if consts::OS == OS::Linux {
    if var("XDG_CONFIG_HOME").is_err() {
      format!("{}/.config/playit/", var("HOME").unwrap())
    } else {
      format!("{}/playit/", var("XDG_CONFIG_HOME").unwrap())
    }
  } else if consts::OS == OS::Windows {
    format!(r"{}\playit\", var("AppData").unwrap())
  } else {
    format!(
      "{}/Library/Application Support/playit/",
      var("HOME").unwrap()
    )
  })
  .to_owned();

  let config_file = {
    let path = dir.join("config.json");

    remove_file(&path).ok();

    path
  };
  let connections = Vec::new();
  let download_urls = Binaries {
    win: Url::parse(&format!(
      "https://playit.gg/downloads/playit-win_64-{}.exe",
      consts::VERSION
    ))
    .expect("Failed To Parse Download URL"),
    lin: Url::parse(&format!(
      "https://playit.gg/downloads/playit-linux_64-{}",
      consts::VERSION
    ))
    .expect("Failed To Parse Download URL"),
    mac: Url::parse(&format!(
      "https://playit.gg/downloads/playit-darwin_64-{}.zip",
      consts::VERSION
    ))
    .expect("Failed To Parse Download URL"),
    arm64: Url::parse(&format!(
      "https://playit.gg/downloads/playit-aarch64-{}",
      consts::VERSION
    ))
    .expect("Failed To Parse Download URL"),
    arm: Url::parse(&format!(
      "https://playit.gg/downloads/playit-armv7-{}",
      consts::VERSION
    ))
    .expect("Failed To Parse Download URL"),
  };
  let binary = download(&dir, &download_urls).expect("Failed To Download PlayIt");
  let playit = {
    let mut env = HashMap::new();

    env.insert(String::from("NO_BROWSER"), String::from("true"));

    if let Some(opts) = playit_opts {
      env.insert(
        String::from("PREFERRED_TUNNEL"),
        opts.PREFERRED_TUNNEL.unwrap_or_else(|| String::from("")),
      );
      env.insert(
        String::from("PREFERRED_THRESHOLD"),
        opts.PREFERRED_THRESHOLD.unwrap_or(50).to_string(),
      );
      env.insert(
        String::from("NO_SPECIAL_LAN"),
        opts.NO_SPECIAL_LAN.unwrap_or(false).to_string(),
      );
    }

    exec(String::from(binary.to_str().unwrap()), None, Some(env)).unwrap()
  };
  let (agent_key, server) = loop {
    let config =
      from_str::<Config>(&read_to_string(&config_file).unwrap_or_else(|_| String::from("{}")))
        .unwrap_or(Config {
          agent_key: None,
          preferred_tunnel: None,
        });

    if config.agent_key.is_some() && config.preferred_tunnel.is_some() {
      break (config.agent_key.unwrap(), config.preferred_tunnel.unwrap());
    }
  };

  Ok(PlayIt {
    req_client: {
      let mut headers = header::HeaderMap::new();
      let mut auth = header::HeaderValue::from_str(&format!("agent {}", agent_key)).unwrap();
      auth.set_sensitive(true);
      headers.insert(header::AUTHORIZATION, auth);
      ClientBuilder::new()
        .default_headers(headers)
        .build()
        .unwrap()
    },
    os: consts::OS,
    config_file,
    arch: consts::ARCH,
    dir,
    destroyed: false,
    tunnels: Vec::new(),
    agent_key,
    started: false,
    playit,
    server,
    used_packets: 0,
    free_packets: 0,
    connections,
    version: String::from(consts::VERSION),
    download_urls,
    binary,
    binary_type: consts::BINARY_TYPE,
    output: Vec::new(),
    stdout: Vec::new(),
    stderr: Vec::new(),
    errors: Vec::new(),
    warnings: Vec::new(),
    api_path: Url::parse("https://api.playit.gg/").unwrap(),
  })
}

/// Small wrapper for `command`
pub fn exec(
  cmd: String,
  args: Option<Vec<String>>,
  envs: Option<HashMap<String, String>>,
) -> Result<Child, String> {
  let mut command = Command::new(&cmd);
  command
    .args(args.unwrap_or_else(Vec::new))
    .envs(envs.unwrap_or_else(HashMap::new))
    .stdin(Stdio::piped())
    .stderr(Stdio::piped())
    .stdout(Stdio::piped());

  #[cfg(target_os = "windows")]
  {
    use std::os::windows::process::CommandExt;
    command.creation_flags(0x08000000);
  }

  let command = Child(
    command
      .spawn()
      .unwrap_or_else(|_| panic!("Failed To Start Process: {}", &cmd)),
  );

  Ok(command)
}

pub fn trim_newlines(string: &str) -> String {
  return string.trim_end().replace('\n', " ").replace('\r', "");
}

fn download(dir: &Path, download_urls: &Binaries) -> Result<PathBuf, String> {
  let file = dir.join(&format!(
    "playit-{:#?}-{}.{}",
    if consts::BINARY_TYPE == BinaryType::Unsupported {
      return Err(String::from("Unsupported Architexture"));
    } else {
      consts::BINARY_TYPE
    },
    consts::VERSION,
    if consts::OS == OS::Windows {
      "exe"
    } else if consts::OS == OS::Linux {
      "bin"
    } else {
      "zip"
    }
  ));

  if file.exists() {
    return Ok(file);
  }

  if !dir.exists() {
    create_dir_all(dir).unwrap();
  }

  let mut _file = OpenOptions::new()
    .create(true)
    .read(true)
    .write(true)
    .truncate(true)
    .open(&file)
    .unwrap();

  _file
    .write_all(
      &get(download_urls[consts::BINARY_TYPE].clone())
        .unwrap()
        .bytes()
        .unwrap(),
    )
    .expect("Failed To Download PlayIt");

  #[cfg(target_os = "macos")]
  {
    use std::fs::copy;
    use std::fs::remove_dir_all;
    use std::fs::File;
    use zip::ZipArchive;

    let mut zip = ZipArchive::new(_file).unwrap();

    zip.extract(dir).unwrap();

    let file = dir.join(format!(
      "playit-{:#?}-{}.bin",
      BinaryType::MacOS,
      consts::VERSION,
    ));

    copy(
      dir.join(format!(
        "playit-darwin_64-{}/playit-darwin_64-{}",
        consts::VERSION,
        consts::VERSION
      )),
      &file,
    )
    .unwrap();

    remove_dir_all(dir.join(format!("playit-darwin_64-{}", consts::VERSION))).unwrap();

    _file = File::open(file).unwrap();
  }

  #[cfg(any(
    target_os = "linux",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "openbsd",
    target_os = "netbsd"
  ))]
  {
    use std::os::unix::fs::PermissionsExt;

    _file.metadata().permissions().set_mode(0o777);
  }

  Ok(file)
}