196 lines
6.2 KiB
Rust
196 lines
6.2 KiB
Rust
use std::{collections::HashSet, io::{self, Cursor, Read, Write}, path::PathBuf, process::exit};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt, BufWriter}};
|
|
use zip::{write::FileOptions, ZipArchive, ZipWriter};
|
|
|
|
use super::{message::Message, user::User};
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct Project {
|
|
pub users: Vec<User>,
|
|
pub messages: Vec<Message>,
|
|
pub timestamps: HashSet<usize>,
|
|
pub media_index: usize,
|
|
}
|
|
|
|
pub struct ProjectRaw {
|
|
pub project: Project,
|
|
pub zip: ZipArchive<Cursor<Vec<u8>>>
|
|
}
|
|
|
|
impl Project {
|
|
pub async fn load(pathstr: String) -> io::Result<ProjectRaw> {
|
|
let path = PathBuf::from(pathstr);
|
|
|
|
if !path.exists() {
|
|
return Err(io::Error::new(io::ErrorKind::NotFound, "File does not exist!"));
|
|
}
|
|
|
|
let mut file = File::open(path).await?;
|
|
let mut buffer = Vec::new();
|
|
file.read_to_end(&mut buffer).await?;
|
|
|
|
let cursor = Cursor::new(buffer);
|
|
let archive = ZipArchive::new(cursor)?;
|
|
|
|
let mut copy_archive = archive.clone();
|
|
let mut project_file = copy_archive.by_name("project.json").unwrap_or_else(|_| {
|
|
eprintln!("Invalid project format!");
|
|
exit(1);
|
|
});
|
|
|
|
let mut json_content = String::new();
|
|
project_file.read_to_string(&mut json_content)?;
|
|
|
|
let data: Project = serde_json::from_str(&json_content)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
|
|
Ok(ProjectRaw {
|
|
project: data,
|
|
zip: archive
|
|
})
|
|
}
|
|
|
|
pub async fn save_new(&self, file_path: String) -> io::Result<()> {
|
|
let contents = serde_json::to_string(&self)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
|
|
let file = File::create(file_path).await?;
|
|
let mut writer = BufWriter::new(file);
|
|
|
|
let mut buffer = Cursor::new(Vec::new());
|
|
|
|
{
|
|
let mut zip = ZipWriter::new(&mut buffer);
|
|
|
|
let options = FileOptions::default().unix_permissions(0o755);
|
|
zip.add_directory("media/", options)?;
|
|
|
|
zip.start_file("project.json", options)?;
|
|
zip.write_all(contents.as_bytes())?;
|
|
|
|
zip.finish()?;
|
|
}
|
|
|
|
writer.write_all(&buffer.into_inner()).await?;
|
|
writer.flush().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn push_msg(&mut self, message: &Message) -> bool {
|
|
if self.timestamps.contains(&message.timestamp) {
|
|
return false;
|
|
}
|
|
|
|
self.timestamps.insert(message.timestamp);
|
|
self.messages.push(message.clone());
|
|
|
|
return true;
|
|
}
|
|
|
|
pub fn sort_messages(&mut self) {
|
|
self.messages.sort_by_key(|msg| msg.timestamp);
|
|
}
|
|
}
|
|
|
|
|
|
impl ProjectRaw {
|
|
/// Save multiple files to the zip archive.
|
|
///
|
|
/// # Arguments
|
|
/// * `files` - A list of tuples where each tuple contains:
|
|
/// - `target_path`: The path inside the zip archive (e.g., `media/1`).
|
|
/// - `source_path`: The path to the file on the filesystem.
|
|
pub async fn save_media_files(
|
|
&mut self,
|
|
files: Vec<(String, String)>,
|
|
) -> io::Result<()> {
|
|
let mut new_buffer = Cursor::new(Vec::new());
|
|
{
|
|
let mut zip_writer = ZipWriter::new(&mut new_buffer);
|
|
let options = FileOptions::default().unix_permissions(0o644);
|
|
|
|
let replace_files: HashSet<_> = files.iter().map(|(target, _)| target.clone()).collect();
|
|
|
|
for i in 0..self.zip.len() {
|
|
let mut file = self.zip.by_index(i)?;
|
|
let name = file.name().to_string();
|
|
|
|
if replace_files.contains(&name) {
|
|
continue;
|
|
}
|
|
|
|
zip_writer.start_file(name, options)?;
|
|
let mut file_content = Vec::new();
|
|
file.read_to_end(&mut file_content)?;
|
|
zip_writer.write_all(&file_content)?;
|
|
}
|
|
|
|
for (target_path, source_path) in files {
|
|
let mut file = File::open(source_path).await?;
|
|
let mut file_content = Vec::new();
|
|
file.read_to_end(&mut file_content).await?;
|
|
|
|
zip_writer.start_file(target_path, options)?;
|
|
zip_writer.write_all(&file_content)?;
|
|
}
|
|
|
|
zip_writer.finish()?;
|
|
}
|
|
|
|
self.zip = ZipArchive::new(new_buffer)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Save the `Project` to `project.json` and flush the zip archive to the specified file.
|
|
///
|
|
/// # Arguments
|
|
/// * `output_path` - The path to save the zip archive to.
|
|
pub async fn save(&mut self, output_path: &str) -> io::Result<()> {
|
|
let project_json = serde_json::to_string(&self.project)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
|
|
let mut new_buffer = Cursor::new(Vec::new());
|
|
{
|
|
let mut zip_writer = ZipWriter::new(&mut new_buffer);
|
|
let options = FileOptions::default().unix_permissions(0o644);
|
|
|
|
let mut project_json_written = false;
|
|
|
|
for i in 0..self.zip.len() {
|
|
let mut file = self.zip.by_index(i)?;
|
|
let name = file.name();
|
|
|
|
if name == "project.json" {
|
|
// Replace the existing `project.json`.
|
|
zip_writer.start_file("project.json", options)?;
|
|
zip_writer.write_all(project_json.as_bytes())?;
|
|
project_json_written = true;
|
|
} else {
|
|
// Copy other files as-is.
|
|
zip_writer.start_file(name, options)?;
|
|
let mut file_content = Vec::new();
|
|
file.read_to_end(&mut file_content)?;
|
|
zip_writer.write_all(&file_content)?;
|
|
}
|
|
}
|
|
|
|
if !project_json_written {
|
|
zip_writer.start_file("project.json", options)?;
|
|
zip_writer.write_all(project_json.as_bytes())?;
|
|
}
|
|
|
|
zip_writer.finish()?;
|
|
}
|
|
|
|
let mut output_file = File::create(output_path).await?;
|
|
output_file.write_all(&new_buffer.into_inner()).await?;
|
|
output_file.flush().await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|