/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration) * and Cosylab 2002, All rights reserved * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.logging.io; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.swing.JOptionPane; import javax.swing.ProgressMonitor; import javax.swing.SwingUtilities; import alma.acs.logging.engine.io.IOHelper; import alma.acs.logging.engine.io.IOPorgressListener; import com.cosylab.logging.LoggingClient; import com.cosylab.logging.client.cache.LogCache; import com.cosylab.logging.engine.ACS.ACSRemoteErrorListener; import com.cosylab.logging.engine.ACS.ACSRemoteLogListener; import com.cosylab.logging.engine.ACS.LCEngine; /** * This class is intended to support the commons IO * operations aiming to keep the <code>LogTableDataModel</code> class shorter * and more readable. * <P> * This class instantiate a new thread for each load/save operation. * To cleanly close, the <code>done()</code> must be called. * <P> * This class allows only one load/save at a time. * If an I/O is requested while another one is in progress, an exception is thrown. * <P> * This class is not thread safe! * * @author acaproni * */ public class IOLogsHelper implements IOPorgressListener { /** * The thread to load logs * * @author acaproni * */ final class LoadLogs implements Callable<Void> { private final BufferedReader br; private final ACSRemoteLogListener logListener; private final ACSRemoteErrorListener errorListener; private final int range; /** * Constructor * * @param br The the file to read * @param logListener The callback for each new log read from the IO * @param errorListener The listener of errors * @param progressRange The range of the progress bar (if <=0 the progress bar is undetermined) */ public LoadLogs(BufferedReader br,ACSRemoteLogListener logListener, ACSRemoteErrorListener errorListener, int progressRange) { this.br=br; this.logListener=logListener; this.errorListener=errorListener; this.range=progressRange; } /** * @see Callable */ public Void call() throws Exception { // Set the progress range if (range<=0) { progressMonitor = new ProgressMonitor(loggingClient.getContentPane(),"Loading logs...",null,0,Integer.MAX_VALUE); } else { progressMonitor= new ProgressMonitor(loggingClient.getContentPane(),"Loading logs...",null,0,range); } progressMonitor.setMillisToPopup(500); // Apply discard level, filters and audience to the IOHelper LCEngine engine = loggingClient.getEngine(); ioHelper.setDiscardLevel(engine.getDiscardLevel()); ioHelper.setAudience(engine.getAudience()); ioHelper.setFilters(engine.getFilters()); // Load the logs logsRead=0; bytesRead=0; try { ioHelper.loadLogs(br, logListener, null, errorListener, IOLogsHelper.this); } catch (Throwable ioe) { System.err.println("Exception loading the logs: "+ioe.getMessage()); ioe.printStackTrace(System.err); JOptionPane.showInternalMessageDialog(loggingClient.getContentPane(), "Exception loading "+ioe.getMessage(),"Error loading",JOptionPane.ERROR_MESSAGE); } finally { progressMonitor.close(); progressMonitor=null; isPerformingIO=false; } return null; } }; /** * A class to download logs from a remote URL. * The loading of logs from URL si done in 2 steps: * <OL> * <LI>Download the remote file in a temporary file * <Li>Read the downloaded file * </OL> * This is to be able to download compressed files (gz) transparently. * * @see UrlDownloader * @author acaproni * @since 2015.8 * */ public class LoadUrl implements Callable<Void> { // The URL to download private final URL url; private final ACSRemoteLogListener logListener; private final ACSRemoteErrorListener errorListener; /** * Constructor * * @param url The URL to download * @param logListener The callback for each new log read from the IO * @param errorListener The listener of errors */ public LoadUrl(URL url, ACSRemoteLogListener logListener, ACSRemoteErrorListener errorListener) { if (url==null) { throw new IllegalArgumentException("Can't download from a NULL URL!"); } this.url=url; this.logListener=logListener; this.errorListener=errorListener; } public Void call() throws Exception { // Set the progress range SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { progressMonitor = new ProgressMonitor(loggingClient.getContentPane(),"Downloading from URL...",null,0,1); progressMonitor.setMillisToPopup(250); } }); // Download the remote URL String fileToLoad; try { UrlDownloader urlDownloader = new UrlDownloader(url); fileToLoad=urlDownloader.download(); } catch (Throwable t) { System.err.println("Exception downloading URL: "+t.getMessage()); t.printStackTrace(System.err); JOptionPane.showInternalMessageDialog(loggingClient.getContentPane(), "Exception downloading URL "+t.getMessage(),"Error downloading",JOptionPane.ERROR_MESSAGE); isPerformingIO=false; return null; } finally { progressMonitor.close(); progressMonitor=null; } System.out.println("File downloaded "+fileToLoad); // Open and read the file BufferedReader br = null; int len=0; try { br=getIoHelper().getBufferedReader(fileToLoad); File f = new File(fileToLoad); f.deleteOnExit(); if (fileToLoad.toLowerCase().endsWith(".gz")) { len=0; } else { len=(int)f.length(); } loadLogs(br,logListener,errorListener,len); } catch (Throwable fnfe) { JOptionPane.showInternalMessageDialog(loggingClient.getContentPane(), fnfe.getMessage(), "Error opening "+fileToLoad, JOptionPane.ERROR_MESSAGE); } return null; } } /** * The thread to save logs * * @author acaproni * */ final class SaveLogs implements Callable<Void> { private final String fileName; private final boolean compress; private final int level; private final LogCache cache; /** * Constructor * * @param fileName The name of the file to save * @param compress <code>true</code> if the file must be compressed (GZIP) * @param level The level of compression (ignored if <code>compress</code> is <code>false</code>) * @param cache The cache that contains the logs */ public SaveLogs( String fileName, boolean compress, int level, LogCache cache) { this.fileName=fileName; this.compress=compress; this.level=level; this.cache=cache; } /** * @see Thread */ public Void call() throws Exception { // Open the output file BufferedWriter outBW=null; try { outBW = ioHelper.getBufferedWriter(fileName, false, compress, level); } catch (IOException e) { System.err.println("Exception while saving logs: "+e.getMessage()); e.printStackTrace(System.err); JOptionPane.showInternalMessageDialog(loggingClient.getContentPane(), "Exception saving "+e.getMessage(),"Error saving",JOptionPane.ERROR_MESSAGE); isPerformingIO=false; return null; } progressMonitor= new ProgressMonitor(loggingClient.getContentPane(),"Saving logs...",null,0,cache.getSize()); logsWritten=0; try { ioHelper.writeHeader(outBW); ioHelper.saveLogs(outBW, cache.iterator(), IOLogsHelper.this); ioHelper.terminateSave(outBW, true); } catch (Throwable e) { System.err.println("Exception while saving logs: "+e.getMessage()); e.printStackTrace(System.err); JOptionPane.showInternalMessageDialog(loggingClient.getContentPane(), "Exception saving "+e.getMessage(),"Error saving",JOptionPane.ERROR_MESSAGE); } finally { progressMonitor.close(); progressMonitor=null; isPerformingIO=false; } return null; } } /** * We want one single thread to execute IO tasks. */ private final ExecutorService executor = Executors.newSingleThreadExecutor(); /** * The dialog */ private ProgressMonitor progressMonitor; /** * The IOHelper performing load and save */ private final IOHelper ioHelper; /** * The logging client */ private LoggingClient loggingClient=null; /** * The bytes read during a load */ private long bytesRead; /** * The number of logs read while loading */ private int logsRead; /** * The number of logs read while saving */ private int logsWritten; /** * This variable is <code>true</code> when a I/O is in progress * and false otherwise. It will be used by the logging window to enable/disable * menu items. */ private volatile boolean isPerformingIO=false; /** * Build an IOCacheHelper object * * @throws Exception IN case of error building the {@link IOHelper} */ public IOLogsHelper(LoggingClient client) throws Exception { super(); if (client==null) { throw new IllegalArgumentException("Invalid null LoggingClient!"); } loggingClient=client; ioHelper = new IOHelper(); } /** * Load the logs from the given file in the Cache appending their * starting positions in the index vector. * <P> * The logs are appended at the end of the cache as well as the new * positions are appended at the end of the index vector. * The load is performed in a thread as it might be very slow * <P> * The filter, discard level and audience of the engine are applied while loading. * This is done by applying to <code>ioHelper</code> the same constraints * defined in the <code>LCEngine</code>. * * @param br The the file to read * @param logListener The callback for each new log read from the IO * @param errorListener The listener of errors * @param progressRange The range of the progress bar (if <=0 the progress bar is undetermined) */ public void loadLogs(BufferedReader br,ACSRemoteLogListener logListener, ACSRemoteErrorListener errorListener, int progressRange) { if (br==null || logListener==null|| errorListener==null) { throw new IllegalArgumentException("Null parameter received!"); } isPerformingIO=true; executor.submit(new LoadLogs( br, logListener, errorListener, progressRange)); } /** * Load logs from the passed url. * * Loading logs from url is done in 2 steps: * <OL> * <LI>Download the file * <LI>Load the file * </OL> * * Downloading the file and the reading it allows to download and read compressed (gz) * files. * * @param logListener The callback for each new log read from the IO * @param errorListener The listener of errors * @param url The url to read */ public void loadLogsFromUrl(URL url, ACSRemoteLogListener logListener, ACSRemoteErrorListener errorListener) { if (url==null || logListener==null|| errorListener==null) { throw new IllegalArgumentException("Null parameter received!"); } isPerformingIO=true; executor.submit(new LoadUrl(url,logListener,errorListener)); } /** * Return a string formatted for JOptionPane making a word wrap * * @param error The error i.e. the exception * @param msg The message to show * @return A formatted string */ private String formatErrorMsg(String msg) { StringBuilder sb = new StringBuilder(); int count = 0; for (int t=0; t<msg.length(); t++) { char c = msg.charAt(t); sb.append(c); if (c=='\n') { count=0; continue; } if (++count >= 80 && c==' ') { count=0; sb.append('\n'); } } return sb.toString(); } /** * Save the logs in a file. * * @param fileName The name of the file to save * @param compress <code>true</code> if the file must be compressed (GZIP) * @param level The level of compression (ignored if <code>compress</code> is <code>false</code>) * @param cache The cache that contains the logs * @throws IOException * * @see saveLogsFromThread */ public void saveLogs( String fileName, boolean compress, int level, LogCache cache) throws IOException { if (fileName==null || fileName.isEmpty()) { throw new IllegalArgumentException("Invalid file name"); } if (cache==null) { throw new IllegalArgumentException("The cache can't be null"); } isPerformingIO=true; executor.submit(new SaveLogs(fileName, compress, level, cache)); } /** * Release the resource acquired by this object * It terminates the thread so the object can be deleted by the JVM * * NOTE: when the thread is terminated it is not possible to * request asynchronous services * * @param sync If it is <code>true</code> wait the termination of the threads before returning */ public void done(boolean sync) { executor.shutdownNow(); } /** * Moves the progress bar of the progress monitor * * @see alma.acs.logging.engine.io.IOPorgressListener#bytesRead(long) */ @Override public void bytesRead(long bytes) { bytesRead=bytes; if (progressMonitor!=null) { progressMonitor.setProgress((int)bytesRead); } } /** * @see alma.acs.logging.engine.io.IOPorgressListener#bytesWritten(long) */ @Override public void bytesWritten(long bytes) {} /** * Change the label of the progress monitor * * @see alma.acs.logging.engine.io.IOPorgressListener#logsRead(int) */ @Override public void logsRead(int numOfLogs) { logsRead=numOfLogs; if (progressMonitor!=null && logsRead%100==0) { progressMonitor.setNote(""+numOfLogs+" logs read"); if (progressMonitor.isCanceled()) { ioHelper.stopIO(); } } } /** * @see alma.acs.logging.engine.io.IOPorgressListener#logsWritten(int) */ @Override public void logsWritten(int numOfLogs) { logsWritten=numOfLogs; if (progressMonitor!=null && logsWritten%100==0) { progressMonitor.setProgress(logsWritten); progressMonitor.setNote(""+logsWritten+" logs saved"); if (progressMonitor.isCanceled()) { ioHelper.stopIO(); } } } /** * Check if a load/save is in progress * * @return <code>true</code> if a load/save is in progress */ public boolean isPerformingIO() { return isPerformingIO; } public IOHelper getIoHelper() { return ioHelper; } }