001package jmri.jmrit.mailreport; 002 003import jmri.util.startup.PerformFileModel; 004import jmri.util.startup.StartupActionsManager; 005 006import java.awt.FlowLayout; 007import java.io.File; 008import java.io.FileInputStream; 009import java.io.FileNotFoundException; 010import java.io.FileOutputStream; 011import java.io.IOException; 012import java.nio.charset.StandardCharsets; 013import java.util.*; 014import java.util.zip.ZipEntry; 015import java.util.zip.ZipOutputStream; 016 017import javax.mail.internet.AddressException; 018import javax.mail.internet.InternetAddress; 019import javax.swing.BoxLayout; 020import javax.swing.JButton; 021import javax.swing.JCheckBox; 022import javax.swing.JLabel; 023import javax.swing.JPanel; 024import javax.swing.JTextArea; 025import javax.swing.JTextField; 026 027import jmri.InstanceManager; 028import jmri.profile.Profile; 029import jmri.profile.ProfileManager; 030import jmri.util.MultipartMessage; 031import jmri.util.swing.JmriJOptionPane; 032import jmri.util.javaworld.GridLayout2; 033import jmri.util.problemreport.LogProblemReportProvider; 034 035/** 036 * User interface for sending a problem report via email. 037 * <p> 038 * The report is sent to a dedicated SourceForge mailing list, from which people 039 * can retrieve it. 040 * 041 * @author Bob Jacobsen Copyright (C) 2009 042 * @author Matthew Harris Copyright (c) 2014 043 */ 044public class ReportPanel extends JPanel { 045 046 JButton sendButton; 047 JTextField emailField = new JTextField(40); 048 JTextField summaryField = new JTextField(40); 049 JTextArea descField = new JTextArea(8, 40); 050 JCheckBox checkContext; 051 JCheckBox checkNetwork; 052 JCheckBox checkLog; 053 JCheckBox checkPanel; 054 JCheckBox checkProfile; 055 JCheckBox checkCopy; 056 057 // Define which profile sub-directories to include 058 // In lowercase as I was too lazy to do a proper case-insensitive check... 059 String[] profDirs = {"networkservices", "profile", "programmers", "throttle"}; 060 061 public ReportPanel() { 062 ResourceBundle rb = java.util.ResourceBundle.getBundle("jmri.jmrit.mailreport.ReportBundle"); 063 064 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 065 066 JPanel p1; 067 068 p1 = new JPanel(); 069 p1.setLayout(new FlowLayout()); 070 p1.add(new JLabel(rb.getString("LabelTop"))); 071 add(p1); 072 073 // grid of options 074 p1 = new JPanel(); 075 p1.setLayout(new GridLayout2(3, 2)); 076 add(p1); 077 078 JLabel l = new JLabel(rb.getString("LabelEmail")); 079 l.setToolTipText(rb.getString("TooltipEmail")); 080 p1.add(l); 081 emailField.setToolTipText(rb.getString("TooltipEmail")); 082 p1.add(emailField); 083 084 l = new JLabel(rb.getString("LabelSummary")); 085 l.setToolTipText(rb.getString("TooltipSummary")); 086 p1.add(l); 087 summaryField.setToolTipText(rb.getString("TooltipSummary")); 088 p1.add(summaryField); 089 090 l = new JLabel(rb.getString("LabelDescription")); 091 p1.add(l); 092 // This ensures that the long-description JTextArea font 093 // is the same as the JTextField fields. 094 // With some L&F, default font for JTextArea differs. 095 descField.setFont(summaryField.getFont()); 096 descField.setBorder(summaryField.getBorder()); 097 descField.setLineWrap(true); 098 descField.setWrapStyleWord(true); 099 p1.add(descField); 100 101 // buttons on bottom 102 p1 = new JPanel(); 103 p1.setLayout(new FlowLayout()); 104 checkContext = new JCheckBox(rb.getString("CheckContext")); 105 checkContext.setSelected(true); 106 checkContext.addActionListener(new java.awt.event.ActionListener() { 107 @Override 108 public void actionPerformed(java.awt.event.ActionEvent e) { 109 checkNetwork.setEnabled(checkContext.isSelected()); 110 } 111 }); 112 p1.add(checkContext); 113 114 checkNetwork = new JCheckBox(rb.getString("CheckNetwork")); 115 checkNetwork.setSelected(true); 116 p1.add(checkNetwork); 117 118 checkLog = new JCheckBox(rb.getString("CheckLog")); 119 checkLog.setSelected(true); 120 p1.add(checkLog); 121 add(p1); 122 123 p1 = new JPanel(); 124 p1.setLayout(new FlowLayout()); 125 checkPanel = new JCheckBox(rb.getString("CheckPanel")); 126 checkPanel.setSelected(true); 127 p1.add(checkPanel); 128 129 checkProfile = new JCheckBox(rb.getString("CheckProfile")); 130 checkProfile.setSelected(true); 131 p1.add(checkProfile); 132 133 checkCopy = new JCheckBox(rb.getString("CheckCopy")); 134 checkCopy.setSelected(true); 135 p1.add(checkCopy); 136 add(p1); 137 138 sendButton = new javax.swing.JButton(rb.getString("ButtonSend")); 139 sendButton.setToolTipText(rb.getString("TooltipSend")); 140 sendButton.addActionListener(new java.awt.event.ActionListener() { 141 @Override 142 public void actionPerformed(java.awt.event.ActionEvent e) { 143 sendButtonActionPerformed(e); 144 } 145 }); 146 add(sendButton); 147 } 148 149 // made static, public, not final so can be changed via script 150 static public String requestURL = "http://jmri.org/problem-report.php"; // NOI18N 151 152 public void sendButtonActionPerformed(java.awt.event.ActionEvent e) { 153 ResourceBundle rb = ResourceBundle.getBundle("jmri.jmrit.mailreport.ReportBundle"); 154 try { 155 sendButton.setEnabled(false); 156 log.debug("initial checks"); 157 InternetAddress email = new InternetAddress(emailField.getText()); 158 email.validate(); 159 160 log.debug("start send"); 161 162 MultipartMessage msg = new MultipartMessage(requestURL, StandardCharsets.UTF_8.name()); 163 164 // add reporter email address 165 log.debug("start creating message"); 166 msg.addFormField("reporter", emailField.getText()); 167 168 // add if to Cc sender 169 msg.addFormField("sendcopy", checkCopy.isSelected() ? "yes" : "no"); 170 171 // add problem summary 172 msg.addFormField("summary", summaryField.getText()); 173 174 // build detailed error report (include context if selected) 175 String report = descField.getText() + "\r\n"; 176 if (checkContext.isSelected()) { 177 report += "=========================================================\r\n"; // NOI18N 178 report += (new ReportContext()).getReport(checkNetwork.isSelected() && checkNetwork.isEnabled()); 179 } 180 msg.addFormField("problem", report); 181 182 log.debug("start adding attachments"); 183 // add panel file if OK 184 if (checkPanel.isSelected()) { 185 log.debug("prepare panel attachment"); 186 // Check that some startup panel files have been loaded 187 for (PerformFileModel m : InstanceManager.getDefault(StartupActionsManager.class).getActions(PerformFileModel.class)) { 188 String fn = m.getFileName(); 189 File f = new File(fn); 190 log.info("Add panel file loaded at startup: {}", f); 191 msg.addFilePart("logfileupload[]", f); 192 } 193 // Check that a manual panel file has been loaded 194 File file = jmri.configurexml.LoadXmlUserAction.getCurrentFile(); 195 if (file != null) { 196 log.info("Adding manually-loaded panel file: {}", file.getPath()); 197 msg.addFilePart("logfileupload[]", jmri.configurexml.LoadXmlUserAction.getCurrentFile()); 198 } else { 199 // No panel file loaded by manual action 200 log.debug("No panel file manually loaded"); 201 } 202 } 203 204 // add profile files if OK 205 if (checkProfile.isSelected()) { 206 log.debug("prepare profile attachment"); 207 // Check that a profile has been loaded 208 Profile profile = ProfileManager.getDefault().getActiveProfile(); 209 if (profile != null) { 210 File file = profile.getPath(); 211 if (file != null) { 212 log.debug("add profile: {}", file.getPath()); 213 // Now zip-up contents of profile 214 // Create temp file that will be deleted when Java quits 215 File temp = File.createTempFile("profile", ".zip"); 216 temp.deleteOnExit(); 217 218 FileOutputStream out = new FileOutputStream(temp); 219 ZipOutputStream zip = new ZipOutputStream(out); 220 221 addDirectory(zip, file); 222 223 zip.close(); 224 out.close(); 225 226 msg.addFilePart("logfileupload[]", temp); 227 } 228 } else { 229 // No profile loaded 230 log.warn("No profile loaded - not sending"); 231 } 232 } 233 234 // add the log if OK 235 if (checkLog.isSelected()) { 236 log.debug("prepare log attachments"); 237 ServiceLoader<LogProblemReportProvider> loggers = ServiceLoader.load(LogProblemReportProvider.class); 238 for(LogProblemReportProvider provider : loggers) { 239 for(File file : provider.getFiles()) { 240 msg.addFilePart("logfileupload[]", file, "application/octet-stream"); 241 } 242 } 243 loggers.reload(); // allow garbage collection of loaders 244 } 245 log.debug("done adding attachments"); 246 247 // finalise and get server response (if any) 248 log.debug("posting report..."); 249 List<String> response = msg.finish(); 250 log.debug("send complete"); 251 log.debug("server response:"); 252 boolean checkResponse = false; 253 for (String line : response) { 254 log.debug(" line: {}", line); 255 if (line.contains("<p>Message successfully sent!</p>")) { 256 checkResponse = true; 257 } 258 } 259 260 if (checkResponse) { 261 JmriJOptionPane.showMessageDialog(this, rb.getString("InfoMessage"), 262 rb.getString("InfoTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 263 // close containing Frame 264 getTopLevelAncestor().setVisible(false); 265 } else { 266 JmriJOptionPane.showMessageDialog(this, rb.getString("ErrMessage"), 267 rb.getString("ErrTitle"), JmriJOptionPane.ERROR_MESSAGE); // TODO add Bundle to folder and use ErrorTitle key in NamedBeanBundle props 268 sendButton.setEnabled(true); 269 } 270 271 } catch (IOException ex) { 272 log.error("Error when attempting to send report", ex); 273 sendButton.setEnabled(true); 274 } catch (AddressException ex) { 275 log.error("Invalid email address", ex); 276 JmriJOptionPane.showMessageDialog(this, rb.getString("ErrAddress"), 277 rb.getString("ErrTitle"), JmriJOptionPane.ERROR_MESSAGE); // TODO add Bundle to folder and use ErrorTitle key in NamedBeanBundle props 278 sendButton.setEnabled(true); 279 } 280 } 281 282 private void addDirectory(ZipOutputStream out, File source) { 283 log.debug("Add profile: {}", source.getName()); 284 addDirectory(out, source, ""); 285 } 286 287 private void addDirectory(ZipOutputStream out, File source, String directory) { 288 // get directory contents 289 File[] files = source.listFiles(); 290 291 log.debug("Add directory: {}", directory); 292 if ( files == null ) { 293 log.warn("No files in directory {}",source); 294 return; 295 } 296 297 for (File file : files) { 298 // if current file is a directory, call recursively 299 if (file.isDirectory()) { 300 // Only include certain sub-directories 301 if (!directory.equals("") || Arrays.asList(profDirs).contains(file.getName().toLowerCase())) { 302 try { 303 out.putNextEntry(new ZipEntry(directory + file.getName() + "/")); 304 } catch (IOException ex) { 305 log.error("Exception when adding directory", ex); 306 } 307 addDirectory(out, file, directory + file.getName() + "/"); 308 } else { 309 log.debug("Skipping: {}{}", directory, file.getName()); 310 } 311 continue; 312 } 313 // Got here - add file 314 try { 315 // Only include certain files 316 if (!directory.equals("") || file.getName().toLowerCase().matches(".*(config\\.xml|\\.properties)")) { 317 log.debug("Add file: {}{}", directory, file.getName()); 318 byte[] buffer = new byte[1024]; 319 try (FileInputStream in = new FileInputStream(file)) { 320 out.putNextEntry(new ZipEntry(directory + file.getName())); 321 322 int length; 323 while ((length = in.read(buffer)) > 0) { 324 out.write(buffer, 0, length); 325 } 326 out.closeEntry(); 327 in.close(); 328 } 329 } else { 330 log.debug("Skip file: {}{}", directory, file.getName()); 331 } 332 } catch (FileNotFoundException ex) { 333 log.error("Exception when adding file", ex); 334 } catch (IOException ex) { 335 log.error("Exception when adding file", ex); 336 } 337 } 338 } 339 340 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ReportPanel.class); 341 342}