use path_absolutize;
use curl::easy::{Easy, Form};
use curl::Error;
use path_absolutize::*;
use cpclib_sna as sna;
use cpclib_disc as disc;
use crate::disc::amsdos::AmsdosFileType;
use std::fs;
use std::path::Path;
use custom_error::custom_error;
use crate::sna::{Snapshot, SnapshotVersion};
custom_error! {#[allow(missing_docs)] pub XferError
ConnectionError{source: Error} = "There is a connection error with the Cpc Wifi.",
ConnectionError2{source: reqwest::Error} = "There is a connection error with the Cpc Wifi.",
CdError{from: String, to: String} = @ {
format!(
"Unable to move in {}. Current working directory is {}.",
from, to)
},
InternalError{reason: String} = @ {
format!("Internal error: {}", reason)
}
}
#[derive(Debug)]
pub struct M4File {
fname: String,
unknown: String,
size: String,
}
impl M4File {
pub fn fname(&self) -> &str {
&self.fname
}
}
impl From<&str> for M4File {
fn from(line: &str) -> Self {
let mut splitted = line.split(',');
Self {
fname: splitted.next().unwrap().into(),
unknown: splitted.next().unwrap().into(),
size: splitted.next().unwrap().into(),
}
}
}
#[derive(Debug)]
pub struct M4FilesList {
cwd: String,
files: Vec<M4File>,
}
impl From<&str> for M4FilesList {
fn from(buffer: &str) -> Self {
let mut iter = buffer.lines();
let mut path = iter.next().unwrap();
if path == "//" {
path = "/";
}
let files = iter.map(|s| s.into()).collect::<Vec<M4File>>();
Self {
cwd: path.into(),
files,
}
}
}
#[allow(missing_docs)]
impl M4FilesList {
pub fn cwd(&self) -> &String {
&self.cwd
}
pub fn nb_files(&self) -> usize {
self.files.len()
}
pub fn files(&self) -> &Vec<M4File> {
&self.files
}
}
#[derive(Debug)]
pub struct CpcXfer {
hostname: String,
}
#[allow(missing_docs)]
impl CpcXfer {
pub fn new<S: AsRef<str>>(hostname: S) -> Self {
Self {
hostname: String::from(hostname.as_ref()),
}
}
pub fn hostname(&self) -> &str {
&self.hostname
}
fn uri(&self, path: &str) -> String {
format!("http://{}/{}", self.hostname, path)
}
fn simple_query(&self, query: &[(&str, &str)]) -> reqwest::Result<reqwest::Response> {
reqwest::Client::new()
.get(&self.uri("config.cgi"))
.query(query)
.header("User-Agent", "User-Agent: cpcxfer")
.send()
}
pub fn reset_m4(&self) -> Result<(), XferError> {
self.simple_query(&[("mres", "")])?;
Ok(())
}
pub fn reset_cpc(&self) -> Result<(), XferError> {
self.simple_query(&[("cres", "")])?;
Ok(())
}
pub fn run_rom_current_path(&self, fname: &str) -> Result<(), XferError> {
self.simple_query(&[("run", fname)])?;
Ok(())
}
pub fn run(&self, path: &str) -> Result<(), XferError> {
let absolute = self.absolute_path(path)?;
self.simple_query(&[("run2", &absolute)])?;
Ok(())
}
pub fn rm<S: AsRef<str>>(&self, path: S) -> Result<(), XferError> {
self.simple_query(&[("rm", path.as_ref())])?;
Ok(())
}
pub fn upload<P>(
&self,
path: P,
m4_path: &str,
header: Option<(AmsdosFileType, u16, u16)>,
) -> Result<(), XferError>
where
P: AsRef<Path>,
{
self.upload_impl(path.as_ref(), m4_path, header)
}
#[allow(clippy::similar_names)]
pub fn upload_impl(
&self,
path: &Path,
m4_path: &str,
header: Option<(AmsdosFileType, u16, u16)>,
) -> Result<(), XferError> {
let local_fname = path.to_str().unwrap();
if m4_path.len() > 255 {
panic!(
"{} path is too long (should be limited to 255 chars)",
m4_path
);
}
let _file_contents = fs::read(local_fname).expect("Unable to read PC file");
let local_fname = match header {
Some(_header) => {
unimplemented!();
}
None => {
local_fname
}
};
let destination = Path::new(m4_path).join(
Path::new(local_fname)
.file_name()
.expect("Unable to retreive the filename of the file to upload"),
);
let destination = destination.to_str().unwrap().to_owned();
println!("Destination : {:?}", destination);
let mut form = Form::new();
form.part("upfile")
.file(local_fname)
.filename(&destination)
.add()
.unwrap();
let mut easy = Easy::new();
easy.url(&self.uri("files.shtml"))?;
easy.httppost(form)?;
easy.perform()?;
Ok(())
}
pub fn upload_and_run_sna(&self, sna: &Snapshot) -> Result<(), XferError> {
use tempfile::Builder;
let file = Builder::new()
.prefix("xfer")
.suffix(".sna")
.rand_bytes(4)
.tempfile()
.or_else(|e|{
Err(XferError::InternalError{reason: e.to_string()})
})?;
let temp_path = file.into_temp_path();
sna.save(
&temp_path,
SnapshotVersion::V2)
.or_else(|e|{
Err(XferError::InternalError{reason: format!("Unable to save the snapshot. {}", e)})
})?;
self.upload_and_run(
&temp_path,
None)?;
std::thread::sleep(std::time::Duration::from_secs(5));
temp_path.close();
Ok(())
}
pub fn upload_and_run<P: AsRef<Path>>(
&self,
path: P,
header: Option<(AmsdosFileType, u16, u16)>,
) -> Result<(), XferError> {
self.upload_and_run_impl(path.as_ref(), header)
}
fn upload_and_run_impl(
&self,
path: &Path,
header: Option<(AmsdosFileType, u16, u16)>,
) -> Result<(), XferError> {
self.upload_impl(path, "/tmp", header)?;
self.run(&format!(
"/tmp/{}",
path.file_name().unwrap().to_str().unwrap()
))?;
Ok(())
}
pub fn current_folder_content(&self) -> Result<M4FilesList, XferError> {
self.download_dir()
}
pub fn current_working_directory(&self) -> Result<String, XferError> {
let data = self.download_dir()?;
Ok(data.cwd().clone())
}
fn download_dir(&self) -> Result<M4FilesList, XferError> {
let mut dst = Vec::new();
{
{
let mut easy = Easy::new();
easy.url(&self.uri("sd/m4/dir.txt"))?;
let mut easy = easy.transfer();
easy.write_function(|data| {
dst.extend_from_slice(data);
Ok(data.len())
})?;
easy.perform()?;
}
}
let content =
std::str::from_utf8(&dst).expect("Unable to create an UTF8 string for M4 content");
Ok(M4FilesList::from(content))
}
pub fn cd(&self, directory: &str) -> Result<(), XferError> {
let mut directory = if let Some('/') = directory.chars().next() {
directory.to_owned()
} else {
self.absolute_path(directory)?
};
self.ls_request(&directory)?;
if directory.chars().rev().next().unwrap() != '/' {
directory.push('/');
}
let cwd = self.current_working_directory()?;
if cwd == directory {
Ok(())
} else {
Err(XferError::CdError {
from: directory,
to: cwd,
})
}
}
fn absolute_path(&self, relative: &str) -> Result<String, XferError> {
match relative.chars().next() {
None => Err(XferError::InternalError {
reason: "No path provided".into(),
}),
Some('/') => Ok(relative.to_owned()),
_ => {
let cwd = self.current_working_directory()?;
let absolute = Path::new(&cwd).join(relative);
let absolute = absolute.absolutize().unwrap();
let path: String = absolute.to_str().unwrap().into();
if cfg!(target_os = "windows")
{
return Ok(path.replace("C:\\", "/"));
}
Ok(path)
}
}
}
pub fn ls_request(&self, folder: &str) -> Result<(), XferError> {
let mut easy = Easy::new();
let folder = easy.url_encode(folder.as_bytes());
easy.get(true)?;
let url = format!("{}?ls={}", self.uri("config.cgi"), folder);
easy.url(&url)?;
easy.perform()?;
Ok(())
}
}