support.rs (3947B)
1 use tracing::error; 2 3 use notedeck::{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 = concat!("version ", env!("CARGO_PKG_VERSION"), "\nCommit hash: ", env!("GIT_COMMIT_HASH"), "\n\nDescribe 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 {lines_displayed} of {num_total_lines} lines in file {file_name}\n===\n\n", 87 ) 88 } 89 90 struct MailtoBuilder { 91 content: Option<String>, 92 address: String, 93 subject: Option<String>, 94 } 95 96 impl MailtoBuilder { 97 fn new(address: String) -> Self { 98 Self { 99 content: None, 100 address, 101 subject: None, 102 } 103 } 104 105 // will be truncated so the whole URL is at most 2000 characters 106 pub fn with_content(mut self, content: String) -> Self { 107 self.content = Some(content); 108 self 109 } 110 111 pub fn with_subject(mut self, subject: String) -> Self { 112 self.subject = Some(subject); 113 self 114 } 115 116 pub fn build(self) -> String { 117 let mut url = String::new(); 118 119 url.push_str("mailto:"); 120 url.push_str(&self.address); 121 122 let has_subject = self.subject.is_some(); 123 124 if has_subject || self.content.is_some() { 125 url.push('?'); 126 } 127 128 if let Some(subject) = self.subject { 129 url.push_str("subject="); 130 url.push_str(&urlencoding::encode(&subject)); 131 } 132 133 if let Some(content) = self.content { 134 if has_subject { 135 url.push('&'); 136 } 137 138 url.push_str("body="); 139 140 let body = urlencoding::encode(&content); 141 142 url.push_str(&body); 143 } 144 145 url 146 } 147 }