use anyhow::{anyhow, Result};
#[allow(unused_imports)]
use log::{debug, info, trace};
use reqwest::Url;
use serde_derive::{Deserialize, Serialize};
use serde_yaml;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use trauma::download::Download;
use crate::lib::api::dryad::DataDryadAPI;
use crate::lib::api::figshare::FigShareAPI;
use crate::lib::api::zenodo::ZenodoAPI;
use crate::lib::data::{DataFile, MergedFile};
use crate::lib::project::LocalMetadata;
const AUTHKEYS: &str = ".scidataflow_authkeys.yml";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RemoteFile {
pub name: String,
pub md5: Option<String>,
pub size: Option<u64>,
pub remote_service: String,
pub url: Option<String>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum RemoteStatusCode {
Current, MessyLocal, Different, NotExists, Exists, NoLocal, DeletedLocal, Invalid,
}
impl RemoteFile {
pub fn set_md5(&mut self, md5: String) {
self.md5 = Some(md5);
}
pub fn get_md5(&self) -> Option<String> {
let md5 = self.md5.clone();
md5.filter(|digest| !digest.is_empty())
}
pub fn set_size(&mut self, size: u64) {
self.size = Some(size);
}
}
#[derive(Serialize, Deserialize, Default, PartialEq, Debug)]
pub struct AuthKeys {
keys: HashMap<String, String>,
}
impl AuthKeys {
pub fn new() -> Self {
let home_dir = env::var("HOME").expect("Could not infer home directory");
let path = Path::new(&home_dir).join(AUTHKEYS);
let keys = match path.exists() {
true => {
let mut contents = String::new();
File::open(path)
.unwrap()
.read_to_string(&mut contents)
.unwrap();
serde_yaml::from_str(&contents)
.unwrap_or_else(|_| panic!("Cannot load {}!", AUTHKEYS))
}
false => {
let keys: HashMap<String, String> = HashMap::new();
keys
}
};
debug!("auth_keys: {:?}", keys);
AuthKeys { keys }
}
pub fn add(&mut self, service: &str, key: &str) {
let service = service.to_lowercase();
self.keys.insert(service, key.to_owned());
self.save();
}
pub fn temporary_add(&mut self, service: &str, key: &str) {
let service = service.to_lowercase();
self.keys.insert(service, key.to_owned());
}
pub fn get(&self, service: String) -> Result<String> {
match self.keys.get(&service) {
None => Err(anyhow!("no key found for service '{}'", service)),
Some(key) => Ok(key.to_string()),
}
}
pub fn save(&self) {
let serialized_keys =
serde_yaml::to_string(&self.keys).expect("Cannot serialize authentication keys!");
let home_dir = env::var("HOME").expect("Could not infer home directory");
let path = Path::new(&home_dir).join(AUTHKEYS);
fs::write(path, serialized_keys)
.unwrap_or_else(|_| panic!("Cound not write {}!", AUTHKEYS));
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub enum Remote {
FigShareAPI(FigShareAPI),
DataDryadAPI(DataDryadAPI),
ZenodoAPI(ZenodoAPI),
}
macro_rules! service_not_implemented {
($service:expr) => {
Err(anyhow!("{} not implemented yet.", $service))
};
}
impl Remote {
pub fn name(&self) -> &str {
match self {
Remote::FigShareAPI(_) => "FigShare",
Remote::DataDryadAPI(_) => "Dryad",
Remote::ZenodoAPI(_) => "Zenodo",
}
}
pub async fn remote_init(
&mut self,
local_metadata: LocalMetadata,
link_only: bool,
) -> Result<()> {
match self {
Remote::FigShareAPI(fgsh_api) => fgsh_api.remote_init(local_metadata, link_only).await,
Remote::ZenodoAPI(znd_api) => znd_api.remote_init(local_metadata, link_only).await,
Remote::DataDryadAPI(_) => service_not_implemented!("DataDryad"),
}
}
pub async fn get_files(&self) -> Result<Vec<RemoteFile>> {
match self {
Remote::FigShareAPI(fgsh_api) => fgsh_api.get_remote_files().await,
Remote::ZenodoAPI(znd_api) => znd_api.get_remote_files().await,
Remote::DataDryadAPI(_) => service_not_implemented!("DataDryad"),
}
}
pub async fn get_files_hashmap(&self) -> Result<HashMap<String, RemoteFile>> {
let remote_files = self.get_files().await?;
let mut file_map: HashMap<String, RemoteFile> = HashMap::new();
for file in remote_files.into_iter() {
file_map.insert(file.name.clone(), file.clone());
}
Ok(file_map)
}
pub async fn upload(
&self,
data_file: &DataFile,
path_context: &Path,
overwrite: bool,
) -> Result<bool> {
match self {
Remote::FigShareAPI(fgsh_api) => {
fgsh_api.upload(data_file, path_context, overwrite).await
}
Remote::ZenodoAPI(znd_api) => znd_api.upload(data_file, path_context, overwrite).await,
Remote::DataDryadAPI(_) => service_not_implemented!("DataDryad"),
}
}
pub fn get_download_info(
&self,
merged_file: &MergedFile,
path_context: &Path,
overwrite: bool,
) -> Result<Download> {
let data_file = match &merged_file.local {
None => return Err(anyhow!("Cannot download() without local DataFile.")),
Some(file) => file,
};
if data_file.is_alive(path_context) && !overwrite {
return Err(anyhow!(
"Data file '{}' exists locally, and would be \
overwritten by download. Use --overwrite to download.",
data_file.path
));
}
let remote = merged_file
.remote
.as_ref()
.ok_or(anyhow!("Remote is None"))?;
let url = remote
.url
.as_ref()
.ok_or(anyhow!("Cannot download; download URL not set."))?;
let authenticated_url = match self {
Remote::FigShareAPI(fgsh_api) => fgsh_api.authenticate_url(url),
Remote::ZenodoAPI(znd_api) => znd_api.authenticate_url(url),
Remote::DataDryadAPI(_) => service_not_implemented!("DataDryad"),
}?;
let save_path = &data_file.full_path(path_context)?;
let url = Url::parse(&authenticated_url)?;
let filename = save_path.to_string_lossy().to_string();
Ok(Download { url, filename })
}
}
pub fn authenticate_remote(remote: &mut Remote) -> Result<()> {
let auth_keys = AuthKeys::new();
let error_message = |service_name: &str, token_name: &str| {
format!("Expected {} access token not found.\n\n\
If you used 'sdf link', it should have saved this token in ~/.scidataflow_authkeys.yml.\n\
You will need to re-add this key manually, by adding a line to this file like:\n\
{}: <TOKEN>", service_name, token_name)
};
match remote {
Remote::FigShareAPI(ref mut fgsh_api) => {
let token = auth_keys
.keys
.get("figshare")
.cloned()
.ok_or_else(|| anyhow::anyhow!(error_message("FigShare", "figshare")))?;
fgsh_api.set_token(token);
}
Remote::ZenodoAPI(ref mut znd_api) => {
let token = auth_keys
.keys
.get("zenodo")
.cloned()
.ok_or_else(|| anyhow::anyhow!(error_message("Zenodo", "zenodo")))?;
znd_api.set_token(token);
}
_ => Err(anyhow!(
"Could not find correct API in authenticate_remote()"
))?,
}
Ok(())
}
#[derive(Debug)]
pub enum RequestData<T: serde::Serialize> {
Json(T),
Binary(Vec<u8>),
File(tokio::fs::File),
Stream(tokio::fs::File),
Empty,
}