support.rs (3871B)
1 use tracing::error; 2 3 use crate::storage::{DataPath, DataPathType, Directory}; 4 5 pub struct Support { 6 directory: Directory, 7 mailto_url: String, 8 most_recent_log: Option<String>, 9 } 10 11 fn new_log_dir(paths: &DataPath) -> Directory { 12 Directory::new(paths.path(DataPathType::Log)) 13 } 14 15 impl Support { 16 pub fn new(path: &DataPath) -> Self { 17 let directory = new_log_dir(path); 18 19 Self { 20 mailto_url: MailtoBuilder::new(SUPPORT_EMAIL.to_string()) 21 .with_subject("Help Needed".to_owned()) 22 .with_content(EMAIL_TEMPLATE.to_owned()) 23 .build(), 24 directory, 25 most_recent_log: None, 26 } 27 } 28 } 29 30 static MAX_LOG_LINES: usize = 500; 31 static SUPPORT_EMAIL: &str = "support@damus.io"; 32 static EMAIL_TEMPLATE: &str = "Describe the bug you have encountered:\n<-- your statement here -->\n\n===== Paste your log below =====\n\n"; 33 34 impl Support { 35 pub fn refresh(&mut self) { 36 self.most_recent_log = get_log_str(&self.directory); 37 } 38 39 pub fn get_mailto_url(&self) -> &str { 40 &self.mailto_url 41 } 42 43 pub fn get_log_dir(&self) -> Option<&str> { 44 self.directory.file_path.to_str() 45 } 46 47 pub fn get_most_recent_log(&self) -> Option<&String> { 48 self.most_recent_log.as_ref() 49 } 50 } 51 52 fn get_log_str(interactor: &Directory) -> Option<String> { 53 match interactor.get_most_recent() { 54 Ok(Some(most_recent_name)) => { 55 match interactor.get_file_last_n_lines(most_recent_name.clone(), MAX_LOG_LINES) { 56 Ok(file_output) => { 57 return Some( 58 get_prefix( 59 &most_recent_name, 60 file_output.output_num_lines, 61 file_output.total_lines_in_file, 62 ) + &file_output.output, 63 ) 64 } 65 Err(e) => { 66 error!( 67 "Error retrieving the last lines from file {}: {:?}", 68 most_recent_name, e 69 ); 70 } 71 } 72 } 73 Ok(None) => { 74 error!("No files were found."); 75 } 76 Err(e) => { 77 error!("Error fetching the most recent file: {:?}", e); 78 } 79 } 80 81 None 82 } 83 84 fn get_prefix(file_name: &str, lines_displayed: usize, num_total_lines: usize) -> String { 85 format!( 86 "===\nDisplaying the last {} of {} lines in file {}\n===\n\n", 87 lines_displayed, num_total_lines, file_name, 88 ) 89 } 90 91 struct MailtoBuilder { 92 content: Option<String>, 93 address: String, 94 subject: Option<String>, 95 } 96 97 impl MailtoBuilder { 98 fn new(address: String) -> Self { 99 Self { 100 content: None, 101 address, 102 subject: None, 103 } 104 } 105 106 // will be truncated so the whole URL is at most 2000 characters 107 pub fn with_content(mut self, content: String) -> Self { 108 self.content = Some(content); 109 self 110 } 111 112 pub fn with_subject(mut self, subject: String) -> Self { 113 self.subject = Some(subject); 114 self 115 } 116 117 pub fn build(self) -> String { 118 let mut url = String::new(); 119 120 url.push_str("mailto:"); 121 url.push_str(&self.address); 122 123 let has_subject = self.subject.is_some(); 124 125 if has_subject || self.content.is_some() { 126 url.push('?'); 127 } 128 129 if let Some(subject) = self.subject { 130 url.push_str("subject="); 131 url.push_str(&urlencoding::encode(&subject)); 132 } 133 134 if let Some(content) = self.content { 135 if has_subject { 136 url.push('&'); 137 } 138 139 url.push_str("body="); 140 141 let body = urlencoding::encode(&content); 142 143 url.push_str(&body); 144 } 145 146 url 147 } 148 }