001package jmri.jmrit.operations.trains.excel; 002 003import java.io.File; 004import java.io.IOException; 005import java.util.concurrent.TimeUnit; 006 007import org.jdom2.Attribute; 008import org.jdom2.Element; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 013import jmri.InstanceManager; 014import jmri.jmrit.operations.OperationsManager; 015import jmri.jmrit.operations.setup.Control; 016import jmri.jmrit.operations.trains.TrainManagerXml; 017import jmri.util.FileUtil; 018import jmri.util.SystemType; 019 020public abstract class TrainCustomCommon { 021 022 protected final String xmlElement; 023 protected String directoryName; 024 private String mcAppName = "MC4JMRI.xls"; // NOI18N 025 private final String mcAppArg = ""; // NOI18N 026 private String csvNamesFileName = "CSVFilesFile.txt"; // NOI18N 027 private int fileCount = 0; 028 private long waitTimeSeconds = 0; 029 private Process process; 030 private boolean alive = false; // when true files to be processed 031 032 protected TrainCustomCommon(String dirName, String xmlElement) { 033 directoryName = dirName; 034 this.xmlElement = xmlElement; 035 } 036 037 public String getFileName() { 038 return mcAppName; 039 } 040 041 public void setFileName(String name) { 042 if (!getFileName().equals(name)) { 043 mcAppName = name; 044 InstanceManager.getDefault(TrainManagerXml.class).setDirty(true); 045 } 046 } 047 048 public String getCommonFileName() { 049 return csvNamesFileName; 050 } 051 052 public void setCommonFileName(String name) { 053 csvNamesFileName = name; 054 } 055 056 public String getDirectoryName() { 057 return directoryName; 058 } 059 060 public void setDirectoryName(String name) { 061 directoryName = name; 062 } 063 064 public String getDirectoryPathName() { 065 return InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()).getPath(); 066 } 067 068 /** 069 * Adds one CSV file path to the collection of files to be processed. 070 * 071 * @param csvFile The File to add. 072 * @return true if successful 073 * 074 */ 075 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 076 public synchronized boolean addCsvFile(File csvFile) { 077 // Ignore null files... 078 if (csvFile == null || !excelFileExists()) { 079 return false; 080 } 081 082 // once the process starts, we can't add files to the common file 083 while (InstanceManager.getDefault(TrainCustomManifest.class).isProcessAlive() || 084 InstanceManager.getDefault(TrainCustomSwitchList.class).isProcessAlive()) { 085 synchronized (this) { 086 try { 087 wait(1000); // 1 sec 088 } catch (InterruptedException e) { 089 // we don't care 090 } 091 } 092 } 093 094 fileCount++; 095 waitTimeSeconds = fileCount * Control.excelWaitTime; 096 alive = true; 097 098 File csvNamesFile = new File(InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()), 099 getCommonFileName()); 100 101 try { 102 FileUtil.appendTextToFile(csvNamesFile, csvFile.getAbsolutePath()); 103 log.debug("Queuing file {} to list", csvFile.getAbsolutePath()); 104 } catch (IOException e) { 105 log.error("Unable to write to {}, {}", csvNamesFile, e.getLocalizedMessage()); 106 return false; 107 } 108 return true; 109 } 110 111 /** 112 * Processes the CSV files using a Custom external program that reads the 113 * file of file names. 114 * 115 * @return True if successful. 116 */ 117 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 118 public synchronized boolean process() { 119 120 // check to see it the Excel program is available 121 if (!excelFileExists() || getFileName().isBlank()) { 122 return false; 123 } 124 125 // Only continue if we have some files to process. 126 if (fileCount == 0) { 127 return true; // done 128 } 129 130 // only one copy of the excel program is allowed to run. Two copies running in parallel has issues. 131 while (InstanceManager.getDefault(TrainCustomManifest.class).isProcessAlive() || 132 InstanceManager.getDefault(TrainCustomSwitchList.class).isProcessAlive()) { 133 synchronized (this) { 134 try { 135 wait(1000); // 1 sec 136 } catch (InterruptedException e) { 137 // we don't care 138 } 139 } 140 } 141 142 log.debug("Queued {} files to custom Excel program", fileCount); 143 144 // Build our command string out of these bits 145 // We need to use cmd and start to allow launching data files like 146 // Excel spreadsheets 147 // It should work OK with actual programs. 148 if (SystemType.isWindows()) { 149 String[] cmd = {"cmd", "/c", "start", getFileName(), mcAppArg}; // NOI18N 150 try { 151 process = Runtime.getRuntime().exec(cmd, null, 152 InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName())); 153 } catch (IOException e) { 154 log.error("Unable to execute: {}, {}", getFileName(), e.getLocalizedMessage()); 155 } 156 } else { 157 String[] cmd = {"open", getFileName(), mcAppArg}; // NOI18N 158 try { 159 process = Runtime.getRuntime().exec(cmd, null, 160 InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName())); 161 } catch (IOException e) { 162 log.error("Unable to execute {}, {}", getFileName(), e.getLocalizedMessage()); 163 } 164 } 165 fileCount = 0; 166 return true; 167 } 168 169 public boolean excelFileExists() { 170 File file = new File(InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()), 171 getFileName()); 172 return file.exists(); 173 } 174 175 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 176 public boolean checkProcessReady() { 177 if (!isProcessAlive()) { 178 return true; 179 } 180 if (alive) { 181 log.debug("Wait time: {} seconds process ready", waitTimeSeconds); 182 long loopCount = waitTimeSeconds; // number of seconds to wait 183 while (loopCount-- > 0 && alive) { 184 synchronized (this) { 185 try { 186 wait(1000); // 1 sec 187 } catch (InterruptedException e) { 188 // TODO Auto-generated catch block 189 log.error("Thread unexpectedly interrupted", e); 190 } 191 } 192 } 193 } 194 return !alive; 195 } 196 197 public boolean isProcessAlive() { 198 if (process != null) { 199 return process.isAlive(); 200 } else { 201 return false; 202 } 203 } 204 205 /** 206 * 207 * @return true if process completes without a timeout, false if there's a 208 * timeout. 209 * @throws InterruptedException if process thread is interrupted 210 */ 211 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 212 public boolean waitForProcessToComplete() throws InterruptedException { 213 if (process == null) { 214 return true; // process hasn't been initialized 215 } 216 boolean status = false; 217 synchronized (process) { 218 File file = new File(InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()), 219 getCommonFileName()); 220 if (!file.exists()) { 221 log.debug("Common file not found! Normal when processing multiple files"); 222 } 223 log.debug("Waiting up to {} seconds for Excel program to complete", waitTimeSeconds); 224 status = process.waitFor(waitTimeSeconds, TimeUnit.SECONDS); 225 // printing can take a long time, wait to complete 226 if (status && file.exists()) { 227 long loopCount = waitTimeSeconds; // number of seconds to wait 228 while (loopCount-- > 0 && file.exists()) { 229 synchronized (this) { 230 try { 231 wait(1000); // 1 sec 232 } catch (InterruptedException e) { 233 // TODO Auto-generated catch block 234 log.error("Thread unexpectedly interrupted", e); 235 } 236 } 237 } 238 } 239 if (file.exists()) { 240 log.error("Common file ({}) not deleted! Wait time {} seconds", file.getPath(), waitTimeSeconds); 241 return false; 242 } 243 log.debug("Excel program complete!"); 244 } 245 alive = false; // done! 246 return status; 247 } 248 249 /** 250 * Checks to see if the common file exists 251 * 252 * @return true if the common file exists 253 */ 254 public boolean doesCommonFileExist() { 255 File file = new File(InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()), 256 getCommonFileName()); 257 return file.exists(); 258 } 259 260 public void load(Element options) { 261 Element mc = options.getChild(xmlElement); 262 if (mc != null) { 263 Attribute a; 264 Element directory = mc.getChild(Xml.DIRECTORY); 265 if (directory != null && (a = directory.getAttribute(Xml.NAME)) != null) { 266 setDirectoryName(a.getValue()); 267 } 268 Element file = mc.getChild(Xml.RUN_FILE); 269 if (file != null && (a = file.getAttribute(Xml.NAME)) != null) { 270 mcAppName = a.getValue(); 271 } 272 Element common = mc.getChild(Xml.COMMON_FILE); 273 if (common != null && (a = common.getAttribute(Xml.NAME)) != null) { 274 csvNamesFileName = a.getValue(); 275 } 276 } 277 } 278 279 public void store(Element options) { 280 Element mc = new Element(xmlElement); 281 Element file = new Element(Xml.RUN_FILE); 282 file.setAttribute(Xml.NAME, getFileName()); 283 Element directory = new Element(Xml.DIRECTORY); 284 directory.setAttribute(Xml.NAME, getDirectoryName()); 285 Element common = new Element(Xml.COMMON_FILE); 286 common.setAttribute(Xml.NAME, getCommonFileName()); 287 mc.addContent(directory); 288 mc.addContent(file); 289 mc.addContent(common); 290 options.addContent(mc); 291 } 292 293 private final static Logger log = LoggerFactory.getLogger(TrainCustomCommon.class); 294}