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 public boolean doesExcelFileExist() { 069 File file = getExcelFile(); 070 return file.exists(); 071 } 072 073 public File getExcelFile() { 074 return new File(InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()), 075 getFileName()); 076 } 077 078 /** 079 * Checks to see if the common file exists 080 * 081 * @return true if the common file exists 082 */ 083 public boolean doesCommonFileExist() { 084 File file = getCommonFile(); 085 return file.exists(); 086 } 087 088 /** 089 * The common file contains a list of CSV files that need to be processed. 090 * 091 * @return the common file. 092 */ 093 public File getCommonFile() { 094 return new File(InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName()), 095 getCommonFileName()); 096 } 097 098 /** 099 * Adds one CSV file path to the collection of files to be processed. 100 * 101 * @param csvFile The File to add. 102 * @return true if successful 103 * 104 */ 105 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 106 public synchronized boolean addCsvFile(File csvFile) { 107 // Ignore null files... 108 if (csvFile == null || !doesExcelFileExist()) { 109 return false; 110 } 111 112 // once the process starts, we can't add files to the common file 113 while (InstanceManager.getDefault(TrainCustomManifest.class).isProcessAlive() || 114 InstanceManager.getDefault(TrainCustomSwitchList.class).isProcessAlive()) { 115 synchronized (this) { 116 try { 117 wait(1000); // 1 sec 118 } catch (InterruptedException e) { 119 // we don't care 120 } 121 } 122 } 123 124 if (fileCount == 0 && doesCommonFileExist()) { 125 log.warn("CSV common file exists!"); 126 } 127 128 fileCount++; 129 waitTimeSeconds = fileCount * Control.excelWaitTime; 130 alive = true; 131 132 File csvNamesFile = getCommonFile(); 133 134 try { 135 FileUtil.appendTextToFile(csvNamesFile, csvFile.getAbsolutePath()); 136 log.debug("Queuing file {} to list", csvFile.getAbsolutePath()); 137 } catch (IOException e) { 138 log.error("Unable to write to {}, {}", csvNamesFile, e.getLocalizedMessage()); 139 return false; 140 } 141 return true; 142 } 143 144 /** 145 * Processes the CSV files using a Custom external program that reads the 146 * file of file names. 147 * 148 * @return True if successful. 149 */ 150 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 151 public synchronized boolean process() { 152 153 // check to see it the Excel program is available 154 if (!doesExcelFileExist() || getFileName().isBlank()) { 155 return false; 156 } 157 158 // Only continue if we have some files to process. 159 if (fileCount == 0) { 160 return true; // done 161 } 162 163 // only one copy of the excel program is allowed to run. Two copies running in parallel has issues. 164 while (InstanceManager.getDefault(TrainCustomManifest.class).isProcessAlive() || 165 InstanceManager.getDefault(TrainCustomSwitchList.class).isProcessAlive()) { 166 synchronized (this) { 167 try { 168 wait(1000); // 1 sec 169 } catch (InterruptedException e) { 170 // we don't care 171 } 172 } 173 } 174 175 log.debug("Queued {} files to custom Excel program", fileCount); 176 177 // Build our command string out of these bits 178 // We need to use cmd and start to allow launching data files like 179 // Excel spreadsheets 180 // It should work OK with actual programs. 181 if (SystemType.isWindows()) { 182 String[] cmd = {"cmd", "/c", "start", getFileName(), mcAppArg}; // NOI18N 183 try { 184 process = Runtime.getRuntime().exec(cmd, null, 185 InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName())); 186 } catch (IOException e) { 187 log.error("Unable to execute: {}, {}", getFileName(), e.getLocalizedMessage()); 188 } 189 } else { 190 String[] cmd = {"open", getFileName(), mcAppArg}; // NOI18N 191 try { 192 process = Runtime.getRuntime().exec(cmd, null, 193 InstanceManager.getDefault(OperationsManager.class).getFile(getDirectoryName())); 194 } catch (IOException e) { 195 log.error("Unable to execute {}, {}", getFileName(), e.getLocalizedMessage()); 196 } 197 } 198 fileCount = 0; 199 return true; 200 } 201 202 203 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 204 public boolean checkProcessReady() { 205 if (!isProcessAlive()) { 206 return true; 207 } 208 if (alive) { 209 log.debug("Wait time: {} seconds process ready", waitTimeSeconds); 210 long loopCount = waitTimeSeconds; // number of seconds to wait 211 while (loopCount-- > 0 && alive) { 212 synchronized (this) { 213 try { 214 wait(1000); // 1 sec 215 } catch (InterruptedException e) { 216 // TODO Auto-generated catch block 217 log.error("Thread unexpectedly interrupted", e); 218 } 219 } 220 } 221 } 222 return !alive; 223 } 224 225 public boolean isProcessAlive() { 226 if (process != null) { 227 return process.isAlive(); 228 } else { 229 return false; 230 } 231 } 232 233 /** 234 * 235 * @return true if process completes without a timeout, false if there's a 236 * timeout. 237 * @throws InterruptedException if process thread is interrupted 238 */ 239 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification = "FindBugs incorrectly reports not guarded by conditional control flow") 240 public boolean waitForProcessToComplete() throws InterruptedException { 241 if (process == null) { 242 return true; // process hasn't been initialized 243 } 244 boolean status = false; 245 synchronized (process) { 246 File file = getCommonFile(); 247 if (!file.exists()) { 248 log.debug("Common file not found! Normal when processing multiple files"); 249 } 250 log.debug("Waiting up to {} seconds for Excel program to complete", waitTimeSeconds); 251 status = process.waitFor(waitTimeSeconds, TimeUnit.SECONDS); 252 // printing can take a long time, wait to complete 253 if (status && file.exists()) { 254 wait(file); 255 } 256 if (file.exists()) { 257 log.error("Excel program failed to complete processing!"); 258 log.error("Common file ({}) not deleted! Wait time {} seconds", file.getPath(), waitTimeSeconds); 259 return false; 260 } else { 261 log.info("Success the Excel program finished processing the CSV file"); 262 } 263 } 264 alive = false; // done! 265 return status; 266 } 267 268 /* 269 * Wait for common file to not exist 270 */ 271 private void wait(File file) { 272 long loopCount = waitTimeSeconds; // number of seconds to wait 273 while (loopCount-- > 0 && file.exists()) { 274 // report after 15 seconds that we're waiting 275 if (loopCount == waitTimeSeconds - 15) { 276 log.info("Waiting for CVS file to be processed by Excel program"); 277 } 278 synchronized (this) { 279 try { 280 wait(1000); // 1 sec 281 } catch (InterruptedException e) { 282 // TODO Auto-generated catch block 283 log.error("Thread unexpectedly interrupted", e); 284 } 285 } 286 } 287 } 288 289 public void load(Element options) { 290 Element mc = options.getChild(xmlElement); 291 if (mc != null) { 292 Attribute a; 293 Element directory = mc.getChild(Xml.DIRECTORY); 294 if (directory != null && (a = directory.getAttribute(Xml.NAME)) != null) { 295 setDirectoryName(a.getValue()); 296 } 297 Element file = mc.getChild(Xml.RUN_FILE); 298 if (file != null && (a = file.getAttribute(Xml.NAME)) != null) { 299 mcAppName = a.getValue(); 300 } 301 Element common = mc.getChild(Xml.COMMON_FILE); 302 if (common != null && (a = common.getAttribute(Xml.NAME)) != null) { 303 csvNamesFileName = a.getValue(); 304 } 305 } 306 } 307 308 public void store(Element options) { 309 Element mc = new Element(xmlElement); 310 Element file = new Element(Xml.RUN_FILE); 311 file.setAttribute(Xml.NAME, getFileName()); 312 Element directory = new Element(Xml.DIRECTORY); 313 directory.setAttribute(Xml.NAME, getDirectoryName()); 314 Element common = new Element(Xml.COMMON_FILE); 315 common.setAttribute(Xml.NAME, getCommonFileName()); 316 mc.addContent(directory); 317 mc.addContent(file); 318 mc.addContent(common); 319 options.addContent(mc); 320 } 321 322 private final static Logger log = LoggerFactory.getLogger(TrainCustomCommon.class); 323}