playit-gg 0.0.5

Unofficial Rust Wrapper For PlayIt.GG
Documentation
use crate::consts;
use crate::types::*;
use reqwest::blocking::Client;
use reqwest::blocking::ClientBuilder;
use reqwest::header;
use serde_json::from_str;
use std::any::type_name;
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::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
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> {
  let os: OS = consts::OS;

  let arch: Arch = if consts::ARCH == Arch::Unsupported {
    return Err(String::from("Unsupported Architexture"));
  } else {
    consts::ARCH
  };

  let dir: PathBuf = Path::new(&if 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 os == OS::Windows {
    format!("{}/playit/", var("AppData").unwrap())
  } else {
    format!(
      "{}/Library/Application Support/playit/",
      var("HOME").unwrap()
    )
  })
  .to_owned();

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

    remove_file(&path).ok();

    path
  };

  let destroyed: bool = false;
  let tunnels: Vec<Tunnel> = Vec::new();
  let started: bool = false;
  let used_packets: u8 = 0;
  let free_packets: u8 = 0;
  let connections: Vec<Connection> = Vec::new();
  let version: String = String::from(consts::VERSION);
  let download_urls: Binaries = Binaries {
    win: Url::parse(&format!(
      "https://playit.gg/downloads/playit-win_64-{}.exe",
      version
    ))
    .expect("Failed To Parse Download URL"),
    lin: Url::parse(&format!(
      "https://playit.gg/downloads/playit-linux_64-{}",
      version
    ))
    .expect("Failed To Parse Download URL"),
    mac: Url::parse(&format!(
      "https://playit.gg/downloads/playit-darwin_64-{}.zip",
      version
    ))
    .expect("Failed To Parse Download URL"),
    arm64: Url::parse(&format!(
      "https://playit.gg/downloads/playit-aarch64-{}",
      version
    ))
    .expect("Failed To Parse Download URL"),
    arm: Url::parse(&format!(
      "https://playit.gg/downloads/playit-armv7-{}",
      version
    ))
    .expect("Failed To Parse Download URL"),
  };
  let binary_type: BinaryType = consts::BINARY_TYPE;
  let binary: PathBuf = download(&dir, &download_urls).expect("Failed To Download PlayIt");
  let playit: Child = {
    let mut env = HashMap::new();

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

    if playit_opts.clone().is_some() {
      env.insert(
        String::from("PREFERRED_TUNNEL"),
        playit_opts
          .clone()
          .unwrap()
          .PREFERRED_TUNNEL
          .unwrap_or(String::from("")),
      );
      env.insert(
        String::from("PREFERRED_THRESHOLD"),
        playit_opts
          .clone()
          .unwrap()
          .PREFERRED_THRESHOLD
          .unwrap_or(50)
          .to_string(),
      );
      env.insert(
        String::from("NO_SPECIAL_LAN"),
        playit_opts
          .clone()
          .unwrap()
          .NO_SPECIAL_LAN
          .unwrap_or(false)
          .to_string(),
      );
    }

    exec(String::from(binary.to_str().unwrap()), None, Some(env)).unwrap()
  };
  let output: Vec<String> = Vec::new();
  let stdout: Vec<String> = Vec::new();
  let stderr: Vec<String> = Vec::new();
  let errors: Vec<String> = Vec::new();
  let warnings: Vec<String> = Vec::new();

  let (agent_key, server) = loop {
    let config: Config = from_str::<Config>(
      &read_to_string(&config_file).unwrap_or(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());
    }
  };

  let req_client: Client = {
    let mut headers: header::HeaderMap = header::HeaderMap::new();
    let mut auth: header::HeaderValue =
      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()
  };
  let api_path = Url::parse("https://api.playit.gg/").unwrap();

  return Ok(PlayIt {
    req_client,
    os,
    config_file,
    arch,
    dir,
    destroyed,
    tunnels,
    agent_key,
    started,
    playit,
    server,
    used_packets,
    free_packets,
    connections,
    version,
    download_urls,
    binary,
    binary_type,
    output,
    stdout,
    stderr,
    errors,
    warnings,
    api_path,
  });
}

/// 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 = Command::new(&cmd);
  command
    .args(args.unwrap_or(Vec::new()))
    .envs(envs.unwrap_or(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 = Child(
    command
      .spawn()
      .expect(&format!("Failed To Start Process: {}", &cmd)),
  );

  return Ok(command);
}

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

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

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

  if !file.parent().unwrap().exists() {
    create_dir_all(file.parent().unwrap()).ok();
  }

  let mut _file: OpenOptions = OpenOptions::new();

  _file.create(true).write(true).truncate(true);

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

    _file.mode(0o555);
  }

  let mut _file: File = _file.open(&file).unwrap();

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

  // #[cfg(target_os = "macos")]
  // {
  //   use zip::ZipArchive;

  //   let mut zip: ZipArchive<&File> = ZipArchive::new(&_file).unwrap();

  //   zip
  //     .extract(dir.join(format!("playit-{}-{}", binary_type, VERSION)))
  //     .unwrap();

  //   zip.by_name("playit-darwin_64-").unwrap();
  // }

  return Ok(file);
}

pub fn type_of<T>(_: &T) -> &str {
  return type_name::<T>();
}