// use crate::dc_tools::*; use crate::chat::*; use crate::constants::{Viewtype, DC_CONTACT_ID_SELF}; use crate::contact::*; use crate::context::Context; use crate::error::Error; use crate::message::*; use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; use std::path::Path; use zip::write::FileOptions; use serde::Serialize; #[derive(Debug)] pub struct ExportChatResult { chat_json: String, // locations_geo_json: String, referenced_blobs: Vec, } pub fn pack_exported_chat( context: &Context, artifact: ExportChatResult, filename: &str, ) -> zip::result::ZipResult<()> { let path = std::path::Path::new(filename); let file = std::fs::File::create(&path).unwrap(); let mut zip = zip::ZipWriter::new(file); zip.start_file("index.json", Default::default())?; zip.write_all(artifact.chat_json.as_bytes())?; zip.add_directory("blobs/", Default::default())?; let options = FileOptions::default(); for blob_name in artifact.referenced_blobs { let path = context.get_blobdir().join(&blob_name); // println!("adding file {:?} as {:?} ...", path, &blob_name); zip.start_file_from_path(Path::new(&format!("blobs/{}", &blob_name)), options)?; let mut f = File::open(path)?; let mut buffer = Vec::new(); f.read_to_end(&mut buffer)?; zip.write_all(&*buffer)?; buffer.clear(); } zip.finish()?; Ok(()) } #[derive(Serialize)] struct ChatJSON { name: String, color: String, profile_img: Option, contacts: HashMap, messages: Vec, } #[derive(Serialize)] struct ContactJSON { name: String, email: String, color: String, profile_img: Option, } #[derive(Serialize)] struct FileReference { name: String, filesize: String, /* todo human readable file size*/ extension: String, } #[derive(Serialize)] enum MessageJSON { Message { id: u32, author_id: u32, // from_id viewType: Viewtype, timestamp_sort: i64, timestamp_sent: i64, timestamp_rcvd: i64, text: Option, attachment: Option, // location }, // Info Message? } impl MessageJSON { pub fn from_message(message: Message, context: &Context) -> MessageJSON {} } pub async fn export_chat(context: &Context, chat_id: ChatId) -> ExportChatResult { let mut blobs = Vec::new(); let mut chat_author_ids = Vec::new(); // get all messages let messages: Vec> = get_chat_msgs(context, chat_id, 0, None) .await .into_iter() .map(async move |msg_id| Message::load_from_db(context, msg_id).await) .collect(); // push all referenced blobs and populate contactid list for message in &messages { if let Ok(msg) = &message { let filename = msg.get_filename(); if let Some(file) = filename { // push referenced blobs (attachments) blobs.push(file); } chat_author_ids.push(msg.from_id); } } // deduplicate contact list and load the contacts chat_author_ids.dedup(); // load information about the authors let mut chat_authors: HashMap = HashMap::new(); chat_authors.insert( 0, ContactJSON { name: "Err: Contact not found".to_owned(), email: "error@localhost".to_owned(), profile_img: None, color: "grey".to_owned(), }, ); for author_id in chat_author_ids { let contact = Contact::get_by_id(context, author_id).await; if let Ok(c) = contact { let profile_img_path: String; if let Some(path) = c.get_profile_image(context).await { profile_img_path = path .file_name() .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_str() .unwrap() .to_owned(); // push referenced blobs (avatars) blobs.push(profile_img_path.clone()); } else { profile_img_path = "".to_owned(); } chat_authors.insert( author_id, ContactJSON { name: c.get_display_name().to_owned(), email: c.get_addr().to_owned(), profile_img: match profile_img_path != "" { true => Some(profile_img_path), false => None, }, color: format!("{:#}", c.get_color()), // TODO }, ); } } // Load information about the chat let chat: Chat = Chat::load_from_db(context, chat_id).await.unwrap(); let chat_avatar = match chat.get_profile_image(context).await { Some(img) => { let path = img .file_name() .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_str() .unwrap() .to_owned(); blobs.push(path.clone()); Some(format!("blobs/{}", path)) } None => None, }; let chat_json = ChatJSON { name: chat.get_name(), color: format!("{:#}", chat.get_color()), profile_img: chat_avatar, contacts: chat_authors, messages: vec![], //todo }; blobs.dedup(); ExportChatResult { chat_json: serde_json::to_string(&chat_json).unwrap(), referenced_blobs: blobs, } } // fn message_to_html( // author_cache: &HashMap, // message: Message, // context: &Context, // ) -> String { // let author: &ContactInfo = { // if let Some(c) = author_cache.get(&message.get_from_id()) { // c // } else { // author_cache.get(&0).unwrap() // } // }; // let avatar: String = { // if let Some(profile_img) = &author.profile_img { // format!( // "
\ // \"{author_name}\"\\ //
", // author_name = author.name, // author_avatar_src = profile_img // ) // } else { // format!( // "
\ //
\ // {initial}\ //
\ //
", // name = author.name, // initial = author.initial, // color = author.color // ) // } // }; // // save and refernce message source code somehow? // let has_text = message.get_text().is_some() && !message.get_text().unwrap().is_empty(); // let attachment = match message.get_file(context) { // None => "".to_owned(), // Some(file) => { // let modifier_class = if has_text { "content-below" } else { "" }; // let filename = file // .file_name() // .unwrap_or_else(|| std::ffi::OsStr::new("")) // .to_str() // .unwrap() // .to_owned(); // match message.get_viewtype() { // Viewtype::Audio => { // format!("", modifier_class ,filename) // }, // Viewtype::Gif | Viewtype::Image | Viewtype::Sticker => { // format!(" \ // \ // ", modifier_class=modifier_class, filename=filename) // }, // Viewtype::Video => { // format!(" \ // ", modifier_class=modifier_class, filename=filename) // }, // _ => { // format!("
\ //
\ //
\ // {extension} \ //
\ //
\ //
\ // {filename}\ //
{filesize}
\ //
\ //
", // modifier_class=modifier_class, // filename=filename, // filesize=message.get_filebytes(&context) /* todo human readable file size*/, // extension=file.extension().unwrap_or_else(|| std::ffi::OsStr::new("")).to_str().unwrap().to_owned()) // } // } // } // }; // format!( // "
  • \ //
    \ // {avatar}\ //
    \ // {author_name}\ //
    \ // {attachment} //
    \ // {content}\ //
    \ // \ //
    \ //
    \ //
    \ //
  • ", // direction = match message.from_id == DC_CONTACT_ID_SELF { // true => "outgoing", // false => "incoming", // }, // avatar = avatar, // author_name = author.name, // author_color = author.color, // attachment = attachment, // content = message.get_text().unwrap_or_else(|| "".to_owned()), // with_image_no_caption = if !has_text && message.get_viewtype() == Viewtype::Image { // "with-image-no-caption" // } else { // "" // }, // encryption = match message.get_showpadlock() { // true => r#"
    "#, // false => "", // }, // full_time = "Tue, Feb 25, 2020 3:49 PM", // message.get_timestamp() ? // todo // relative_time = "Tue 3:49 PM" // todo // ) // // todo link to raw message data // // todo link to message info // }