001package jmri.jmrit.roster; 002 003import java.awt.Component; 004import java.awt.event.ActionEvent; 005import java.io.File; 006import java.io.FileInputStream; 007import java.io.FileNotFoundException; 008import java.io.FileOutputStream; 009import java.io.IOException; 010import java.util.zip.ZipEntry; 011import java.util.zip.ZipOutputStream; 012import javax.swing.Icon; 013import javax.swing.JFileChooser; 014import javax.swing.filechooser.FileNameExtensionFilter; 015 016import jmri.util.ThreadingUtil; 017import jmri.util.swing.CountingBusyDialog; 018import jmri.util.swing.JmriAbstractAction; 019import jmri.util.swing.WindowInterface; 020 021/** 022 * Offer an easy mechanism to save the entire roster contents from one instance 023 * of DecoderPro. The result is a zip format file, containing all of the roster 024 * entries plus the overall roster.xml index file. 025 * 026 * @author david d zuhn 027 * 028 */ 029public class FullBackupExportAction 030 extends JmriAbstractAction { 031 032 // parent component for GUI 033 public FullBackupExportAction(String s, WindowInterface wi) { 034 super(s, wi); 035 _parent = wi.getFrame(); 036 } 037 038 public FullBackupExportAction(String s, Icon i, WindowInterface wi) { 039 super(s, i, wi); 040 _parent = wi.getFrame(); 041 } 042 private Component _parent; 043 private String filename; 044 private CountingBusyDialog dialog; 045 046 /** 047 * @param s Name of this action, e.g. in menus 048 * @param parent Component that action is associated with, used to ensure 049 * proper position in of dialog boxes 050 */ 051 public FullBackupExportAction(String s, Component parent) { 052 super(s); 053 _parent = parent; 054 } 055 056 @Override 057 public void actionPerformed(ActionEvent e) { 058 059 String roster_filename_extension = "roster"; 060 061 JFileChooser chooser = new jmri.util.swing.JmriJFileChooser(); 062 FileNameExtensionFilter filter = new FileNameExtensionFilter( 063 "JMRI full roster files", roster_filename_extension); 064 chooser.setFileFilter(filter); 065 066 int returnVal = chooser.showSaveDialog(_parent); 067 if (returnVal != JFileChooser.APPROVE_OPTION) { 068 return; 069 } 070 071 filename = chooser.getSelectedFile().getAbsolutePath(); 072 073 if (!filename.endsWith("."+roster_filename_extension)) { 074 filename = filename.concat("."+roster_filename_extension); 075 } 076 077 new Thread(() -> {run();}).start(); 078 } 079 080 /** 081 * Actually do the copying 082 */ 083 public void run() { 084 try { 085 086 Roster roster = Roster.getDefault(); 087 088 dialog = new CountingBusyDialog(null, "Exporting Roster", false, roster.getAllEntries().size()); 089 ThreadingUtil.runOnGUIEventually(() -> {dialog.start();}); 090 091 try (ZipOutputStream zipper = new ZipOutputStream(new FileOutputStream(filename))) { 092 093 // create a zip file roster entry for each entry in the main roster 094 int count = 0; 095 for (RosterEntry entry : roster.getAllEntries()) { 096 count++; 097 final int thisCount = count; 098 ThreadingUtil.runOnGUIEventually(() -> {dialog.count(thisCount);}); 099 try { 100 copyFileToStream(entry.getPathName(), "roster", zipper, "roster: "+entry.getId()); 101 102 // process image files if present 103 if (entry.getImagePath() != null && ! entry.getImagePath().isEmpty()) 104 copyFileToStream(entry.getImagePath(), "roster", zipper, "image: "+entry.getId()); 105 if (entry.getIconPath() != null && ! entry.getIconPath().isEmpty()) 106 copyFileToStream(entry.getIconPath(), "roster", zipper, "icon: "+entry.getId()); 107 108 } catch (FileNotFoundException ex) { 109 log.error("Unable to find file in entry {}", entry.getId(), ex); 110 } catch (IOException ex) { 111 log.error("Unable to write during entry {}", entry.getId(), ex); 112 } catch (Exception ex) { 113 log.error("Unexpected exception during entry {}", entry.getId(), ex); 114 } 115 } 116 117 // Now the full roster entry 118 copyFileToStream(Roster.getDefault().getRosterIndexPath(), null, zipper, null); 119 120 zipper.setComment("Roster file saved from DecoderPro " + jmri.Version.name()); 121 122 zipper.close(); 123 124 } catch (FileNotFoundException ex) { 125 log.error("Unable to find file {}", filename, ex); 126 } catch (IOException ex) { 127 log.error("Unable to write to {}", filename, ex); 128 } 129 } finally { 130 ThreadingUtil.runOnGUIEventually(() -> {dialog.finish();}); 131 log.info("Writing backup done"); 132 } 133 } 134 135 /** 136 * Copy a file to an entry in a zip file. 137 * <p> 138 * The basename of the source file will be used in the zip file, placed in 139 * the directory of the zip file specified by dirname. If dirname is null, 140 * the file will be placed in the root level of the zip file. 141 * 142 * @param filename the file to copy 143 * @param dirname the zip file "directory" to place this file in 144 * @param zipper the ZipOutputStream 145 */ 146 private void copyFileToStream(String filename, String dirname, ZipOutputStream zipper, String comment) 147 throws IOException { 148 149 log.debug("write: {}", filename); 150 151 File file = new File(filename); 152 String entryName; 153 154 if (dirname != null) { 155 entryName = dirname + "/" + file.getName(); 156 } else { 157 entryName = file.getName(); 158 } 159 160 ZipEntry zipEntry = new ZipEntry(entryName); 161 162 zipEntry.setTime(file.lastModified()); 163 zipEntry.setSize(file.length()); 164 if (comment != null) { 165 zipEntry.setComment(comment); 166 } 167 168 zipper.putNextEntry(zipEntry); 169 170 FileInputStream fis = new FileInputStream(file); 171 try { 172 int c; 173 while ((c = fis.read()) != -1) { 174 zipper.write(c); 175 } 176 } finally { 177 fis.close(); 178 } 179 180 zipper.closeEntry(); 181 } 182 183 // never invoked, because we overrode actionPerformed above 184 @Override 185 public jmri.util.swing.JmriPanel makePanel() { 186 throw new IllegalArgumentException("Should not be invoked"); 187 } 188 189 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FullBackupExportAction.class); 190}