use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DownloadCheckpoint {
pub key: String,
pub offset: i64,
pub total: u64,
pub sha256_partial: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UploadCheckpoint {
pub key: String,
pub file_id: i64,
pub last_part: i32,
pub total_parts: i32,
pub part_size: usize,
pub total: u64,
pub big: bool,
pub name: String,
pub mime_type: String,
pub started_ms: u64,
}
pub struct CheckpointStore {
dir: PathBuf,
}
impl CheckpointStore {
pub async fn open(session_path: impl AsRef<Path>) -> std::io::Result<Self> {
let dir = session_path
.as_ref()
.parent()
.unwrap_or(Path::new("."))
.join(".ferogram-transfers");
tokio::fs::create_dir_all(&dir).await?;
Ok(Self { dir })
}
fn path_for(&self, key: &str, prefix: &str) -> PathBuf {
let safe: String = key
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' {
c
} else {
'_'
}
})
.collect();
self.dir.join(format!("{prefix}_{safe}.json"))
}
pub fn partial_path(&self, key: &str) -> std::path::PathBuf {
let safe: String = key
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' {
c
} else {
'_'
}
})
.collect();
self.dir.join(format!("dl_{safe}.partial"))
}
pub async fn load_download(&self, key: &str) -> Option<DownloadCheckpoint> {
let path = self.path_for(key, "dl");
let bytes = tokio::fs::read(&path).await.ok()?;
serde_json::from_slice(&bytes).ok()
}
pub async fn save_download(&self, cp: &DownloadCheckpoint) {
let path = self.path_for(&cp.key, "dl");
if let Ok(json) = serde_json::to_vec_pretty(cp) {
let _ = tokio::fs::write(path, json).await;
}
}
pub async fn delete_download(&self, key: &str) {
let path = self.path_for(key, "dl");
let _ = tokio::fs::remove_file(path).await;
}
pub async fn load_upload(&self, key: &str) -> Option<UploadCheckpoint> {
let path = self.path_for(key, "ul");
let bytes = tokio::fs::read(&path).await.ok()?;
serde_json::from_slice(&bytes).ok()
}
pub async fn save_upload(&self, cp: &UploadCheckpoint) {
let path = self.path_for(&cp.key, "ul");
if let Ok(json) = serde_json::to_vec_pretty(cp) {
let _ = tokio::fs::write(path, json).await;
}
}
pub async fn delete_upload(&self, key: &str) {
let path = self.path_for(key, "ul");
let _ = tokio::fs::remove_file(path).await;
}
}
pub fn download_key(dc_id: i32, location: &tl::enums::InputFileLocation) -> String {
let hash = ferogram_crypto::sha256!(&dc_id.to_le_bytes(), format!("{location:?}").as_bytes());
hash.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn upload_key(data: &[u8], name: &str) -> String {
let hash = ferogram_crypto::sha256!(&data[..data.len().min(65536)], name.as_bytes());
hash.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn sha256_hex(data: &[u8]) -> String {
ferogram_crypto::sha256!(data)
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
pub fn now_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
pub const UPLOAD_SESSION_TTL_MS: u64 = 55 * 60 * 1000;