use crate::error::CobbleResult;
use serde::Deserialize;
use std::path::PathBuf;
use time::OffsetDateTime;
use tokio::fs::{remove_dir_all, File};
use tokio::io::AsyncReadExt;
#[cfg_attr(doc_cfg, doc(cfg(feature = "save-games")))]
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct SaveGame {
pub name: String,
pub path: PathBuf,
pub difficulty: Option<Difficulty>,
pub game_type: GameType,
pub game_version: Option<String>,
pub seed: Option<i64>,
#[cfg_attr(feature = "serde", serde(with = "time::serde::rfc3339"))]
pub last_played: OffsetDateTime,
}
impl SaveGame {
#[instrument(
name = "export_save_game",
level = "trace",
skip_all,
fields(
name = self.name,
path = %self.path.to_string_lossy(),
dest,
compression,
)
)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "backup")))]
#[cfg(feature = "backup")]
pub async fn export(
&self,
dest: impl AsRef<std::path::Path>,
compression: u32,
) -> CobbleResult<()> {
crate::utils::archive_entity(self.clone(), dest, compression).await
}
#[instrument(
name = "import_save_game",
level = "trace",
skip_all,
fields(saves_path, src,)
)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "backup")))]
#[cfg(feature = "backup")]
pub async fn import(
saves_path: impl AsRef<std::path::Path>,
src: impl AsRef<std::path::Path>,
) -> CobbleResult<Option<SaveGame>> {
let old_save_game =
crate::utils::unpack_entity_archive::<SaveGame>(src, &saves_path).await?;
let old_dir_name = old_save_game
.path
.file_name()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "Path ends with '..'"))?;
let mut new_dir_path = PathBuf::from(saves_path.as_ref());
new_dir_path.push(old_dir_name);
super::parse_save_game(new_dir_path).await
}
#[instrument(
name = "remove_save_game",
level = "trace",
skip_all,
fields(
name = self.name,
path = %self.path.to_string_lossy(),
)
)]
pub async fn remove(self) -> CobbleResult<()> {
remove_dir_all(&self.path).await?;
Ok(())
}
#[instrument(
name = "save_game_icon",
level = "trace",
skip_all,
fields(
name = self.name,
path = %self.path.to_string_lossy(),
)
)]
pub async fn icon(&self) -> CobbleResult<Option<Vec<u8>>> {
let path = self.icon_path();
if path.is_file() {
let mut file = File::open(path).await?;
let mut buf = vec![];
file.read_to_end(&mut buf).await?;
return Ok(Some(buf));
}
Ok(None)
}
pub fn icon_path(&self) -> PathBuf {
let mut icon_path = self.path.clone();
icon_path.push("icon.png");
icon_path
}
}
#[cfg_attr(doc_cfg, doc(cfg(feature = "backup")))]
#[cfg(feature = "backup")]
impl crate::utils::ArchivableEntity for SaveGame {
const MARKER_NAME: &'static str = "cobble_save_game.json";
fn path(&self) -> PathBuf {
self.path.clone()
}
fn marker_path(&self) -> PathBuf {
let mut path = self.path();
path.push(Self::MARKER_NAME);
path
}
}
#[cfg_attr(doc_cfg, doc(cfg(feature = "save-games")))]
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub enum Difficulty {
Peaceful,
Easy,
Normal,
Hard,
}
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub enum GameType {
Survival,
Creative,
Adventure,
Spectator,
}
#[derive(Clone, Debug, Deserialize)]
pub(super) struct LevelDat {
#[serde(rename = "Data")]
pub data: LevelData,
}
#[derive(Clone, Debug, Deserialize)]
pub(super) struct LevelData {
#[serde(rename = "Difficulty")]
pub difficulty: Option<u8>,
#[serde(rename = "WorldGenSettings")]
pub world_gen_settings: Option<WorldGenSettings>,
#[serde(rename = "GameType", default)]
pub game_type: i32,
#[serde(rename = "LastPlayed")]
pub last_played: i64,
#[serde(rename = "LevelName")]
pub level_name: String,
#[serde(rename = "RandomSeed")]
pub random_seed: Option<i64>,
#[serde(rename = "Version")]
pub version: Option<Version>,
}
#[derive(Clone, Debug, Deserialize)]
pub(super) struct WorldGenSettings {
pub seed: i64,
}
#[derive(Clone, Debug, Deserialize)]
pub(super) struct Version {
#[serde(rename = "Name")]
pub name: String,
}