/* * FrontlineSMS <http://www.frontlinesms.com> * Copyright 2007, 2008 kiwanja * * This file is part of FrontlineSMS. * * FrontlineSMS is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * FrontlineSMS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FrontlineSMS. If not, see <http://www.gnu.org/licenses/>. */ package net.frontlinesms; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.border.TitledBorder; import javax.swing.filechooser.FileFilter; import org.apache.log4j.LogManager; import serial.SerialClassFactory; import net.frontlinesms.email.EmailException; import net.frontlinesms.messaging.CommProperties; import net.frontlinesms.plugins.PluginProperties; import net.frontlinesms.resources.ResourceUtils; import net.frontlinesms.ui.SimpleConstraints; import net.frontlinesms.ui.SimpleLayout; import net.frontlinesms.ui.i18n.InternationalisationUtils; /** * Utilities for handling errors. * @author Carlos Eduardo Genz <kadu@masabi.com> * @author Alex Anderson <alex@frontlinesms.com> */ public class ErrorUtils { //> CONSTANTS /** Left ndentation in pixels used for swing components in {@link #showErrorDialog(String, String, Throwable, boolean)} */ private static final int EM__LEFT_INDENT = 10; /** Top indentation in pixels used for swing components in {@link #showErrorDialog(String, String, Throwable, boolean)} */ private static final int EM__TOP_INDENT = 10; /** Linespacing in pixels used for swing components in {@link #showErrorDialog(String, String, Throwable, boolean)} */ private static final int EM__LINESPACING = 10; //> I18N KEYS private static final I18nString I18N_LOGS_COMPRESSED_SAVE_LOCATION = new I18nString("error.logs.compressed.save.location", "E-mail report failure. Where do you want to save your compressed logs?"); private static final I18nString I18N_LOGS_SENT_SUCCESSFULLY = new I18nString("error.logs.sent", "Log files were successfully sent to frontline support team."); private static final I18nString I18N_LOGS_SAVED_SUCCESSFULLY = new I18nString("error.logs.saved", "Logs were saved successfully. Please e-mail them to %0."); private static final I18nString I18N_UNABLE_TO_SEND_LOGS = new I18nString("error.logs.send.failed", "Unable to send logs at this time."); private static final I18nString I18N_COPY_FAILED = new I18nString("error.logs.copy.failed", "Failed to copy [%0]. Your logs are located in [%1]."); private static final I18nString I18N_GO_TO_FORUM = new I18nString("error.logs.navigate.forum", "Go to forum"); private static final I18nString I18N_CLOSE = new I18nString("action.close", "Close"); private static final I18nString I18N_COMMUNITY_DIALOG_TITLE = new I18nString("error.logs.community.dialog.title", "Visit FrontlineSMS Community website?"); private static final I18nString I18N_DETAILS_PROMPT = new I18nString("error.logs.details.prompt", "Please enter your details below."); private static final I18nString I18N_EMAIL_PROMPT = new I18nString("error.logs.email.prompt", "You can e-mail FrontlineSMS support team for troubleshooting."); private static final I18nString I18N_SEND_LOGS = new I18nString("error.logs.action.logs.send", "Send Logs"); private static final I18nString I18N_YOUR_EMAIL = new I18nString("error.logs.field.email", "Your email:"); private static final I18nString I18N_YOUR_NAME = new I18nString("error.logs.field.name", "Your name:"); private static final I18nString I18N_DESCRIPTION = new I18nString("error.logs.field.description", "Description:");; private static final I18nString I18N_VISIT_COMMUNITY_BODY = new I18nString("error.logs.community.dialog.body", "Please also report this error on the FrontlineSMS community forum at %0" + "\n\nWould you like to go there now?"); //> STATIC METHODS /** * Attempts to email error logs to the FrontlineSMS support team at the specified email * address. Returns true if logs were succesfully sent, or succesfully saved at a location * of the user's choosing, or succesfully saved at a different location which the user is * informed of. * @param userName * @param userEmail * @return <code>true</code> if the error report was sent successfully; <code>false</code> otherwise */ public static boolean reportError(String userName, String userEmail, String description) { try { sendLogs(userName, userEmail, description, false); showMessageDialog(I18N_LOGS_SENT_SUCCESSFULLY.toString()); return true; } catch (EmailException e) { // Show user the option to save the logs.zip to a place they know! String dir = showFileChooserForSavingZippedLogs(); if (dir != null) { try { copyLogsZippedToDir(dir); } catch (IOException e1) { showMessageDialog(I18N_COPY_FAILED.toString(FrontlineSMSConstants.ZIPPED_LOGS_FILENAME, ResourceUtils.getConfigDirectoryPath() + "logs")); // FIXME this config path will not be correct when log config is changed from default } showMessageDialog(I18N_LOGS_SAVED_SUCCESSFULLY.toString(FrontlineSMSConstants.FRONTLINE_SUPPORT_EMAIL)); return true; } else { showMessageDialog(I18N_UNABLE_TO_SEND_LOGS.toString()); return false; // Slightly unclear how this would happen } } catch (IOException e) { // Problem writing logs.zip showMessageDialog(I18N_UNABLE_TO_SEND_LOGS.toString()); try { sendLogsToFrontlineSupport(userName, userEmail, description, null); return true; } catch (EmailException e1) { // If it fails, there is nothing we can do. return false; } } finally { // TODO why is this here? Surely this should only be deleted if // copying/emailing was succesful or the whole operation was a // failure? If copying failed, we inform the user the location // the logs, which we then appear to delete(?!) File input = new File(ResourceUtils.getConfigDirectoryPath() + FrontlineSMSConstants.ZIPPED_LOGS_FILENAME); if (input.exists()) { input.deleteOnExit(); } } } private static final void showMessageDialog(String message) { Object[] options = new Object[]{I18N_GO_TO_FORUM, I18N_CLOSE}; String forumMessage = I18N_VISIT_COMMUNITY_BODY.toString(FrontlineSMSConstants.URL_FRONTLINESMS_COMMUNITY); int option = JOptionPane.showOptionDialog(null, message + "\n\n" + forumMessage, I18N_COMMUNITY_DIALOG_TITLE.toString(), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if(option == 0) { // Open community forum link. FrontlineUtils.openExternalBrowser(FrontlineSMSConstants.URL_FRONTLINESMS_COMMUNITY); } } /** * Put the exception stack trace into a StringBuilder, just to show it * friendlier to the user. * @param t Exception * @param sb */ private static final void buildStacktraceAsString(Throwable t, StringBuilder sb) { sb.append(t.getClass()); sb.append(" :: "); sb.append(t.getMessage()); sb.append("\n"); for(StackTraceElement s : t.getStackTrace()) { sb.append("\tat "); sb.append(s.toString()); sb.append("\n"); } if(t.getCause() != null) { sb.append("Caused by: "); buildStacktraceAsString(t.getCause(), sb); } } /** * Get AWT panel showing controls and instructions for emailing an error to the FrontlineSMS team. * @param errorFrame * @return A {@link ComponentDescription} wrapping the email panel */ private static ComponentDescription getEmailPanel(final JFrame errorFrame) { final JPanel emailPanel = new JPanel(new SimpleLayout()); int cumulativeY = EM__LINESPACING; final JLabel emailInstructions = new JLabel(I18N_EMAIL_PROMPT.toString()); ImageIcon emailInstructionsIcon = getImageIcon("/icons/tip.png"); int emailInstructionsIconWidth = 0; if(emailInstructionsIcon != null) { emailInstructions.setIcon(emailInstructionsIcon); emailInstructionsIconWidth = emailInstructionsIcon.getIconWidth(); } emailPanel.add(emailInstructions, new SimpleConstraints(0, cumulativeY)); final int FONT_HEIGHT = emailInstructions.getFontMetrics(emailInstructions.getFont()).getHeight(); cumulativeY += FONT_HEIGHT + EM__LINESPACING; final JLabel detailsInstructions = new JLabel(I18N_DETAILS_PROMPT.toString()); emailPanel.add(detailsInstructions, new SimpleConstraints(emailInstructionsIconWidth, cumulativeY)); cumulativeY += FONT_HEIGHT + EM__LINESPACING; final JLabel nameLabel = new JLabel(I18N_YOUR_NAME.toString()); final JLabel emailLabel = new JLabel(I18N_YOUR_EMAIL.toString()); final JLabel descriptionLabel = new JLabel(I18N_DESCRIPTION.toString()); final int TF_NAME_X = nameLabel.getFontMetrics(nameLabel.getFont()).stringWidth(nameLabel.getText()) + EM__LEFT_INDENT; final int TF_EMAIL_X = emailLabel.getFontMetrics(emailLabel.getFont()).stringWidth(emailLabel.getText()) + EM__LEFT_INDENT; final int TF_DESCRIPTION_X = descriptionLabel.getFontMetrics(descriptionLabel.getFont()).stringWidth(descriptionLabel.getText()) + EM__LEFT_INDENT; final int MAX_X = Math.max(TF_NAME_X, Math.max(TF_EMAIL_X, TF_DESCRIPTION_X)); emailPanel.add(nameLabel, new SimpleConstraints(5, cumulativeY)); final JTextField nameTextfield = new JTextField(35); emailPanel.add(nameTextfield, new SimpleConstraints(MAX_X, cumulativeY)); cumulativeY += FONT_HEIGHT + EM__LINESPACING; emailPanel.add(emailLabel, new SimpleConstraints(5, cumulativeY)); final JTextField emailTextfield = new JTextField(35); emailPanel.add(emailTextfield, new SimpleConstraints(MAX_X, cumulativeY)); cumulativeY += FONT_HEIGHT + EM__LINESPACING; emailPanel.add(descriptionLabel, new SimpleConstraints(5, cumulativeY)); final JTextArea descriptionTextArea = new JTextArea(3, 35); descriptionTextArea.setLineWrap(true); final JScrollPane descriptionScrollPane = new JScrollPane(descriptionTextArea); emailPanel.add(descriptionScrollPane, new SimpleConstraints(MAX_X, cumulativeY)); cumulativeY += 65; final JButton btSend = new JButton(I18N_SEND_LOGS.toString()); ImageIcon sendIcon = getImageIcon("/icons/email_send.png"); if(sendIcon != null) { btSend.setIcon(sendIcon); } btSend.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(reportError(nameTextfield.getText(), emailTextfield.getText(), descriptionTextArea.getText())) { errorFrame.dispose(); } } }); final int BT_SEND_X = 260; emailPanel.add(btSend, new SimpleConstraints(TF_NAME_X + BT_SEND_X + EM__LEFT_INDENT, cumulativeY - 3)); final int PN_EMAIL_HEIGHT = cumulativeY + FONT_HEIGHT*2 + EM__LINESPACING; return new ComponentDescription(emailPanel, PN_EMAIL_HEIGHT); } /** * Converts the stack trace of a {@link Throwable} into a {@link String}. * @param t * @return A string representation of the stacktrace of the throwable */ public static String getStackTraceAsString(Throwable t) { StringBuilder bob = new StringBuilder(); buildStacktraceAsString(t, bob); return bob.toString(); } /** * Show a non-fatal error dialog shpwing stack trace of a {@link Throwable} and * an OK button. Addition actions can be attached to the OK button via the * {@link ActionListener}. * @param title Title of the dialog * @param message Message displayed in the dialog * @param t {@link Throwable} which caused this dialog to be shows * @param actionListener Action listener to attach to the OK button */ public static void showErrorDialog(String title, String message, Throwable t, ActionListener actionListener) { showErrorDialog(title, message, t, false, false, actionListener); } /** * Show the error dialog, either with the option of emailing the error logs to the FronlineSMS * team, or showing an OK button which removes the dialog. You may attach an additional * {@link ActionListener} to the OK button if you are going with this option. * @param title Title of the dialog * @param message Message displayed in the dialog * @param t {@link Throwable} which caused this dialog to be shows * @param fatal <code>true</code> if the application should exit after this dialog is closed, <code>false</code> otherwise * @param showLogging <code>true</code> if the option to send logs to the server should be shown, <code>false</code> if an OK button should be shown instead * @param actionListener Action listener to attach to the OK button if showLogging is <code>false</code> */ private static void showErrorDialog(String title, String message, Throwable t, final boolean fatal, final boolean showLogging, ActionListener actionListener) { final JFrame errorFrame = new JFrame(); errorFrame.setTitle(title); final Container contentPane = errorFrame.getContentPane(); contentPane.setLayout(new SimpleLayout()); errorFrame.setIconImage(FrontlineUtils.getImage("/icons/fatal_error.png", DesktopLauncher.class)); JLabel errorMessage = new JLabel(message); Font messageFont = new Font(errorMessage.getFont().getFontName(), Font.BOLD, 15); errorMessage.setFont(messageFont); final int LB_MESSAGE_Y = EM__TOP_INDENT; contentPane.add(errorMessage, new SimpleConstraints(EM__LEFT_INDENT, LB_MESSAGE_Y)); final int LB_MESSAGE_HEIGHT = errorMessage.getFontMetrics(messageFont).getHeight(); final int FONT_HEIGHT = LB_MESSAGE_HEIGHT; final int BT_DETAILS_Y = LB_MESSAGE_Y + LB_MESSAGE_HEIGHT + EM__LINESPACING; final int BT_DETAILS_HEIGHT = FONT_HEIGHT * 2; final int LAST_COMPONENT_Y = BT_DETAILS_HEIGHT + BT_DETAILS_Y; final ComponentDescription lastComponent; final int LAST_COMPONENT_X; if(showLogging) { lastComponent = getEmailPanel(errorFrame); LAST_COMPONENT_X = 0; } else { // Make a button here which removes this window, and possibly does something in addition to that JButton btOk = new JButton("OK"); // TODO okButton.addActionListener(l) btOk.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { errorFrame.dispose(); } }); btOk.addActionListener(actionListener); lastComponent = new ComponentDescription(btOk, FONT_HEIGHT*2); LAST_COMPONENT_X = 380; } contentPane.add(lastComponent.getComponent(), new SimpleConstraints(EM__LEFT_INDENT+LAST_COMPONENT_X, LAST_COMPONENT_Y)); final JScrollPane details = new JScrollPane(new JTextArea(getStackTraceAsString(t))); final int detailsHeight = 260; details.setPreferredSize(new Dimension(440, detailsHeight)); details.setBorder(new TitledBorder("Stack trace")); JToggleButton btDetails = new JToggleButton("Details >>"); contentPane.add(btDetails, new SimpleConstraints(EM__LEFT_INDENT, BT_DETAILS_Y)); final int finalHeight_noDetails = LAST_COMPONENT_Y + lastComponent.getHeight() + EM__LINESPACING + EM__LINESPACING; btDetails.setIcon(getImageIcon("/icons/about.png")); btDetails.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JToggleButton bt = (JToggleButton) e.getSource(); contentPane.remove(details); contentPane.remove(lastComponent.getComponent()); if (bt.isSelected()) { bt.setText("Details <<"); int detailsY = LAST_COMPONENT_Y; contentPane.add(details, new SimpleConstraints(EM__LEFT_INDENT, detailsY)); contentPane.add(lastComponent.getComponent(), new SimpleConstraints(EM__LEFT_INDENT + LAST_COMPONENT_X, detailsY + detailsHeight + EM__LINESPACING)); errorFrame.setSize(468, finalHeight_noDetails + detailsHeight + EM__LINESPACING); } else { bt.setText("Details >>"); contentPane.add(lastComponent.getComponent(), new SimpleConstraints(EM__LEFT_INDENT + LAST_COMPONENT_X, LAST_COMPONENT_Y)); errorFrame.setSize(468, finalHeight_noDetails); } errorFrame.validate(); errorFrame.repaint(); } }); errorFrame.setSize(468, finalHeight_noDetails); Dimension screen_size = Toolkit.getDefaultToolkit().getScreenSize(); errorFrame.setLocation((screen_size.width - errorFrame.getWidth()) >> 1, (screen_size.height - errorFrame.getHeight()) >> 1); errorFrame.setResizable(false); errorFrame.setVisible(true); errorFrame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { errorFrame.dispose(); if(fatal) System.exit(0); } }); } /** * Generate and display the emergency error Dialog using Swing components. We do this * when something has gone so badly wrong that we can't trust Thinlet to display the * error, and we can't trust our i18n code to give us messages in foreign languages! * @param t the Throwable that caused the error * @param title the title of the dialog that is displayed * @param message the error message to display to the user * @param fatal set TRUE if the application should exit after this dialog is removed; FALSE otherwise */ public static void showErrorDialog(String title, String message, Throwable t, final boolean fatal) { showErrorDialog(title, message, t, fatal, true, null); } /** * Zip the log files and send them via email. * @param name The name to show the email as coming from * @param emailAddress The email address to show the logs as being from * @param resetConfiguration if <code>true</code>, the log configuration will be reloaded after the logs were sent * @throws IOException * @throws MessagingException */ public static void sendLogs(String name, String emailAddress, String description, boolean resetConfiguration) throws IOException, EmailException { LogManager.shutdown(); try { // FIXME this will not actually work if the log directory has been configured to be different to the default ResourceUtils.zip(ResourceUtils.getConfigDirectoryPath() + "logs", new File(ResourceUtils.getConfigDirectoryPath() + FrontlineSMSConstants.ZIPPED_LOGS_FILENAME)); sendLogsToFrontlineSupport(name, emailAddress, description, ResourceUtils.getConfigDirectoryPath() + FrontlineSMSConstants.ZIPPED_LOGS_FILENAME); } finally { if (resetConfiguration) { FrontlineUtils.loadLogConfiguration(); } } } /** * Submit log files to the FrontlineSMS Support email account. * TODO smtp sending should be refactored into email.smtp.SmtpMessageSender * @param fromName * @param fromEmailAddress * @param attachment * @throws MessagingException */ public static void sendLogsToFrontlineSupport(String fromName, String fromEmailAddress, String description, String attachment) throws EmailException { StringBuilder sb = new StringBuilder(); appendDescription(sb, description); appendFrontlineProperties(sb); appendSystemProperties(sb); appendCommProperties(sb); appendPluginProperties(sb); String textContent = sb.toString(); String subject = "FrontlineSMS log files"; FrontlineUtils.sendToFrontlineSupport(fromName, fromEmailAddress, subject, textContent, attachment); } /** * Appends the {@link CommProperties} to the error report body. * @param bob {@link StringBuilder} used for compiling the body of the error report. */ private static void appendCommProperties(StringBuilder bob) { beginSection(bob, "Comm Properties"); bob.append("Serial package name: '" + SerialClassFactory.getInstance().getSerialPackageName() + "'\n"); CommProperties properties = CommProperties.getInstance(); String[] ignoredPortList = properties.getIgnoreList(); bob.append("Ignored ports: " + ignoredPortList.length + "\n"); int lastPortIndex = 0; for(String ignoredPort : ignoredPortList) { ++lastPortIndex; bob.append("Ignored Port " + lastPortIndex + ": '" + ignoredPort + "'\n"); } endSection(bob, "Comm Properties"); } /** * Appends the {@link PluginProperties} to the error report body. * @param bob {@link StringBuilder} used for compiling the body of the error report. */ private static void appendPluginProperties(StringBuilder bob) { beginSection(bob, "Plugin Properties"); PluginProperties pluginProperties = PluginProperties.getInstance(); Collection<String> pluginClassNameList = pluginProperties.getPluginClassNames(); bob.append("Plugins listed: " + pluginClassNameList.size() + "\n"); int lastPluginIndex = 0; for(String pluginClassName : pluginClassNameList) { ++lastPluginIndex; bob.append("Plugin class " + lastPluginIndex + ": '" + pluginClassName + "'\n"); } endSection(bob, "Plugin Properties"); } private static void appendDescription(StringBuilder bob, String description) { beginSection(bob, "User Description"); bob.append(description); bob.append("\n"); endSection(bob, "User Description"); } private static void appendFrontlineProperties(StringBuilder bob) { beginSection(bob, "FrontlineSMS Properties"); // Including flsms version, so we don't need to open logs to find that out. appendProperty(bob, "FrontlineSMS Version", BuildProperties.getInstance().getVersion()); appendProperty(bob, "User ID", AppProperties.getInstance().getUserId()); appendProperty(bob, "User Email", AppProperties.getInstance().getUserEmail()); endSection(bob, "FrontlineSMS Properties"); } /** * Appends pertinent system properties to a {@link StringBuilder}. * @param bob */ private static void appendSystemProperties(StringBuilder bob) { beginSection(bob, "System Properties"); appendSystemProperty(bob, "OS", "os.name"); appendSystemProperty(bob, "OS Architecture", "os.arch"); appendSystemProperty(bob, "OS Version", "os.version"); try { String hostName = InetAddress.getLocalHost().getHostName(); appendProperty(bob, "Host name", hostName); } catch (UnknownHostException ex) { /* ignore me */ } appendSystemProperty(bob, "User Country", "user.country"); appendSystemProperty(bob, "User home", ResourceUtils.SYSPROPERTY_USER_HOME); appendSystemProperty(bob, "User name", "user.name"); appendSystemProperty(bob, "User language", "user.language"); appendSystemProperty(bob, "Java Runtime name", "java.runtime.name"); appendSystemProperty(bob, "Java Vendor", "java.specification.vendor"); appendSystemProperty(bob, "Java Runtime Version", "java.runtime.version"); appendSystemProperty(bob, "Java home", "java.home"); appendSystemProperty(bob, "Java Version", "java.version"); appendSystemProperty(bob, "Java VM Version", "java.vm.version"); endSection(bob, "System Properties"); } /** * Starts a section of the error report body. * Sections started with this method should be ended with {@link #endSection(StringBuilder, String)} * @param bob The {@link StringBuilder} used for building the error report body. * @param sectionName The name of the section of the report that is being started. */ private static void beginSection(StringBuilder bob, String sectionName) { bob.append("\n### Begin Section '" + sectionName + "' ###\n"); } /** * Ends a section of the error report body. * Sections ended with this should have been started with {@link #beginSection(StringBuilder, String)} * @param bob The {@link StringBuilder} used for building the error report body. * @param sectionName The name of the section of the report that is being started. */ private static void endSection(StringBuilder bob, String sectionName) { bob.append("### End Section '" + sectionName + "' ###\n"); } /** * Appends a property to the supplied {@link StringBuilder}. This method should only be called * by {@link #appendSystemProperties(StringBuilder)} and {@link #appendSystemProperty(StringBuilder, String, String)}. * @param bob * @param label * @param value */ private static void appendProperty(StringBuilder bob, String label, String value) { bob.append(label); bob.append(" = "); bob.append(value); bob.append('\n'); } /** * Appends a system property to the supplied {@link StringBuilder}. This method should only be called * by {@link #appendSystemProperties(StringBuilder)}. * @param bob * @param label * @param propertyKey */ private static void appendSystemProperty(StringBuilder bob, String label, String propertyKey) { appendProperty(bob, label, System.getProperty(propertyKey)); } /** * Copies the zipped log files to a specific directory. * @param dir * @throws IOException */ // FIXME should probably replace this with a generic *move* method, and if not // that, then at least a generic *copy* method // FIXME does not close "out" stream safely public static void copyLogsZippedToDir(String dir) throws IOException { File destination = new File(dir, FrontlineSMSConstants.ZIPPED_LOGS_FILENAME); File input = new File(ResourceUtils.getConfigDirectoryPath() + FrontlineSMSConstants.ZIPPED_LOGS_FILENAME); if (input.exists()) { byte[] buffer = new byte[2048]; BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(destination), 2048); ResourceUtils.stream2stream(new FileInputStream(input), out, buffer); out.close(); } } /** * Shows a swing file chooser for choosing the location of the zipped log files. * @return File chooser UI for saving zipped log files */ public static String showFileChooserForSavingZippedLogs() { JFileChooser chooser = new JFileChooser(new File(".")); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); chooser.setDialogTitle(I18N_LOGS_COMPRESSED_SAVE_LOCATION.toString()); chooser.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory(); } @Override public String getDescription() { return "Directories"; } }); int resp = chooser.showSaveDialog(null); if (resp == JFileChooser.APPROVE_OPTION) { return chooser.getSelectedFile().getAbsolutePath(); } return null; } /** * Attempts to load an {@link ImageIcon} from the specified location on the classpath. * @param path The path to the image * @return the {@link ImageIcon} found at the specified location, or <code>null</code> if none could be found. */ private static ImageIcon getImageIcon(String path) { Image image = FrontlineUtils.getImage(path, ErrorUtils.class); if(image == null) { return null; } else { return new ImageIcon(image); } } } class I18nString { final String key; final String defaultValue; I18nString(String key, String defaultValue) { this.key = key; this.defaultValue = defaultValue; } public String getKey() { return key; } public String getDefaultValue() { return defaultValue; } @Override public String toString() { String text = null; try { text = InternationalisationUtils.getI18nString(getKey()); } catch(Throwable t) { /* Something went wrong. We'll return the default value later. */ } if(text != null) { return text; } else { return getDefaultValue(); } } public String toString(String... argValues) { return InternationalisationUtils.formatString(this.toString(), argValues); } } /** * Wrapper for an AWT component that we have built, including the expected * height of the component. * @author Alex */ class ComponentDescription { /** AWT Component we are wrapping */ private final Component component; /** Expected height of the component. */ private final int height; /** * Wrap a component and additional details about it. * @param component * @param height */ ComponentDescription(Component component, int height) { super(); this.component = component; this.height = height; } /** * get {@link #component} * @return {@link #component} */ public Component getComponent() { return component; } /** * get {@link #height} * @return {@link #height} */ public int getHeight() { return height; } }