mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-14 08:41:21 +10:00
217 lines
7.5 KiB
Rust
217 lines
7.5 KiB
Rust
use std::{
|
|
fs::{self, File, create_dir_all},
|
|
io::{self, Read, Write},
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use crate::error::BackupError;
|
|
|
|
use super::{backup_manager::BackupHandler, placeholder::*};
|
|
use database::GameVersion;
|
|
use log::{debug, warn};
|
|
use rustix::path::Arg;
|
|
use tempfile::tempfile;
|
|
|
|
use super::{backup_manager::BackupManager, metadata::CloudSaveMetadata, normalise::normalize};
|
|
|
|
pub fn resolve(meta: &mut CloudSaveMetadata) -> File {
|
|
let f = File::create_new("save").unwrap();
|
|
let compressor = zstd::Encoder::new(f, 22).unwrap();
|
|
let mut tarball = tar::Builder::new(compressor);
|
|
let manager = BackupManager::new();
|
|
for file in meta.files.iter_mut() {
|
|
let id = uuid::Uuid::new_v4().to_string();
|
|
let os = match file
|
|
.conditions
|
|
.iter()
|
|
.find_map(|p| match p {
|
|
super::conditions::Condition::Os(os) => Some(os),
|
|
_ => None
|
|
})
|
|
.cloned()
|
|
{
|
|
Some(os) => os,
|
|
None => {
|
|
warn!(
|
|
"File {:?} could not be backed up because it did not provide an OS",
|
|
&file
|
|
);
|
|
continue;
|
|
}
|
|
};
|
|
let handler = match manager.sources.get(&(manager.current_platform, os)) {
|
|
Some(h) => *h,
|
|
None => continue,
|
|
};
|
|
let t_path = PathBuf::from(normalize(&file.path, os));
|
|
println!("{:?}", &t_path);
|
|
let path = parse_path(t_path, handler, &meta.game_version).unwrap();
|
|
let f = std::fs::metadata(&path).unwrap(); // TODO: Fix unwrap here
|
|
if f.is_dir() {
|
|
tarball.append_dir_all(&id, path).unwrap();
|
|
} else if f.is_file() {
|
|
tarball
|
|
.append_file(&id, &mut File::open(path).unwrap())
|
|
.unwrap();
|
|
}
|
|
file.id = Some(id);
|
|
}
|
|
let binding = serde_json::to_string(meta).unwrap();
|
|
let serialized = binding.as_bytes();
|
|
let mut file = tempfile().unwrap();
|
|
file.write_all(serialized).unwrap();
|
|
tarball.append_file("metadata", &mut file).unwrap();
|
|
tarball.into_inner().unwrap().finish().unwrap()
|
|
}
|
|
|
|
pub fn extract(file: PathBuf) -> Result<(), BackupError> {
|
|
let tmpdir = tempfile::tempdir().unwrap();
|
|
|
|
// Reopen the file for reading
|
|
let file = File::open(file).unwrap();
|
|
|
|
let decompressor = zstd::Decoder::new(file).unwrap();
|
|
let mut f = tar::Archive::new(decompressor);
|
|
f.unpack(tmpdir.path()).unwrap();
|
|
|
|
let path = tmpdir.path();
|
|
|
|
let mut manifest = File::open(path.join("metadata")).unwrap();
|
|
|
|
let mut manifest_slice = Vec::new();
|
|
manifest.read_to_end(&mut manifest_slice).unwrap();
|
|
|
|
let manifest: CloudSaveMetadata = serde_json::from_slice(&manifest_slice).unwrap();
|
|
|
|
for file in manifest.files {
|
|
let current_path = path.join(file.id.as_ref().unwrap());
|
|
|
|
let manager = BackupManager::new();
|
|
let os = match file
|
|
.conditions
|
|
.iter()
|
|
.find_map(|p| match p {
|
|
super::conditions::Condition::Os(os) => Some(os),
|
|
_ => None
|
|
})
|
|
.cloned()
|
|
{
|
|
Some(os) => os,
|
|
None => {
|
|
warn!(
|
|
"File {:?} could not be replaced up because it did not provide an OS",
|
|
&file
|
|
);
|
|
continue;
|
|
}
|
|
};
|
|
let handler = match manager.sources.get(&(manager.current_platform, os)) {
|
|
Some(h) => *h,
|
|
None => continue,
|
|
};
|
|
|
|
let new_path = parse_path(file.path.into(), handler, &manifest.game_version)?;
|
|
create_dir_all(new_path.parent().unwrap()).unwrap();
|
|
|
|
println!(
|
|
"Current path {:?} copying to {:?}",
|
|
¤t_path, &new_path
|
|
);
|
|
|
|
copy_item(current_path, new_path).unwrap();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn copy_item<P: AsRef<Path>>(src: P, dest: P) -> io::Result<()> {
|
|
let src_path = src.as_ref();
|
|
let dest_path = dest.as_ref();
|
|
|
|
let metadata = fs::metadata(src_path)?;
|
|
|
|
if metadata.is_file() {
|
|
// Ensure the parent directory of the destination exists for a file copy
|
|
if let Some(parent) = dest_path.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
fs::copy(src_path, dest_path)?;
|
|
} else if metadata.is_dir() {
|
|
// For directories, we call the recursive helper function.
|
|
// The destination for the recursive copy is the `dest_path` itself.
|
|
copy_dir_recursive(src_path, dest_path)?;
|
|
} else {
|
|
// Handle other file types like symlinks if necessary,
|
|
// for now, return an error or skip.
|
|
return Err(io::Error::other(
|
|
format!("Source {:?} is neither a file nor a directory", src_path),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
|
fs::create_dir_all(dest)?;
|
|
|
|
for entry in fs::read_dir(src)? {
|
|
let entry = entry?;
|
|
let entry_path = entry.path();
|
|
let entry_file_name = match entry_path.file_name() {
|
|
Some(name) => name,
|
|
None => continue, // Skip if somehow there's no file name
|
|
};
|
|
let dest_entry_path = dest.join(entry_file_name);
|
|
let metadata = entry.metadata()?;
|
|
|
|
if metadata.is_file() {
|
|
debug!(
|
|
"Writing file {} to {}",
|
|
entry_path.display(),
|
|
dest_entry_path.display()
|
|
);
|
|
fs::copy(&entry_path, &dest_entry_path)?;
|
|
} else if metadata.is_dir() {
|
|
copy_dir_recursive(&entry_path, &dest_entry_path)?;
|
|
}
|
|
// Ignore other types like symlinks for this basic implementation
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn parse_path(
|
|
path: PathBuf,
|
|
backup_handler: &dyn BackupHandler,
|
|
game: &GameVersion,
|
|
) -> Result<PathBuf, BackupError> {
|
|
println!("Parsing: {:?}", &path);
|
|
let mut s = PathBuf::new();
|
|
for component in path.components() {
|
|
match component.as_str().unwrap() {
|
|
ROOT => s.push(backup_handler.root_translate(&path, game)?),
|
|
GAME => s.push(backup_handler.game_translate(&path, game)?),
|
|
BASE => s.push(backup_handler.base_translate(&path, game)?),
|
|
HOME => s.push(backup_handler.home_translate(&path, game)?),
|
|
STORE_USER_ID => s.push(backup_handler.store_user_id_translate(&path, game)?),
|
|
OS_USER_NAME => s.push(backup_handler.os_user_name_translate(&path, game)?),
|
|
WIN_APP_DATA => s.push(backup_handler.win_app_data_translate(&path, game)?),
|
|
WIN_LOCAL_APP_DATA => s.push(backup_handler.win_local_app_data_translate(&path, game)?),
|
|
WIN_LOCAL_APP_DATA_LOW => {
|
|
s.push(backup_handler.win_local_app_data_low_translate(&path, game)?)
|
|
}
|
|
WIN_DOCUMENTS => s.push(backup_handler.win_documents_translate(&path, game)?),
|
|
WIN_PUBLIC => s.push(backup_handler.win_public_translate(&path, game)?),
|
|
WIN_PROGRAM_DATA => s.push(backup_handler.win_program_data_translate(&path, game)?),
|
|
WIN_DIR => s.push(backup_handler.win_dir_translate(&path, game)?),
|
|
XDG_DATA => s.push(backup_handler.xdg_data_translate(&path, game)?),
|
|
XDG_CONFIG => s.push(backup_handler.xdg_config_translate(&path, game)?),
|
|
SKIP => s.push(backup_handler.skip_translate(&path, game)?),
|
|
_ => s.push(PathBuf::from(component.as_os_str())),
|
|
}
|
|
}
|
|
|
|
println!("Final line: {:?}", &s);
|
|
Ok(s)
|
|
}
|