package chatty.util.chatlog; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.Path; import java.util.Calendar; import java.util.logging.Logger; /** * Open, lock and write to a single logfile. The name of the logfiles is based * on the given name, but with ".log" added to the end. * * @author tduva */ public class LogFile { /** * Debug logger. */ private static final Logger LOGGER = Logger.getLogger(LogFile.class.getName()); /** * Character set. */ private static final String CHARSET = "UTF-8"; /** * How many different files to try to open in case of error */ private static final int MAX_ATTEMPTS = 3; /** * Write buffer for the LogFile instance. */ private BufferedWriter writer; /** * Rather or not we have a valid file. (eg. is writable) */ private boolean valid; /** * System path to where the file is stored. */ private Path file; /** * The time this LogFile instance was created. */ private Calendar currentTime; private boolean lockFile = true; /** * LogFile constructor. * * @param path The system path of where the LogFile will be stored. * @param name Name of the LogFile to be stored. */ private LogFile(Path path, String name, boolean lockFile) { this.lockFile = lockFile; currentTime = Calendar.getInstance(); // * can't be part of a filename (for Bouncer messages, e.g. *status) name = name.replace("*", "_"); for (int i = 0; i < MAX_ATTEMPTS; i++) { String fileName; if (i == 0) { fileName = name + ".log"; } else { fileName = name + "-" + i + ".log"; } file = path.resolve(fileName); if (tryFile(file.toFile())) { break; } } } /** * Creates a LogFile object for the given path and name. * * @param path The path where the file should be created under. * @param name The name of the log file to be created. * @return The LogFile or null if an error occurred while opening the file. */ public static LogFile get(Path path, String name, boolean lockFile) { LogFile file = new LogFile(path, name, lockFile); if (file.valid) { return file; } return null; } /** * Attempt to write a new line to the LogFile. * * @param line The message to be written to the file. * @return Returns true if the message is successfully logged. */ public boolean write(String line) { if (!valid) { LOGGER.warning("Log: Tried writing to invalid file " + file + ""); return false; } try { writer.write(line); writer.newLine(); writer.flush(); return true; } catch (IOException ex) { LOGGER.warning("Log: Error writing to " + file + " (" + ex.getLocalizedMessage() + ")"); close(); return false; } } /** * Properly close the file, which means it can't be used anymore. */ public void close() { closeResources(); valid = false; LOGGER.info("Log: Closed file " + file.toAbsolutePath()); } /** * Close the writer, if necessary. */ private void closeResources() { try { if (writer != null) { writer.close(); } } catch (IOException ex) { LOGGER.warning("Log: Could not close " + file.toAbsolutePath() + " (" + ex.getLocalizedMessage() + ")"); } } /** * Tries to open the given file and obtain a lock on it. This may fail when * the file is already used by another process, or any other IOException of * course. * * @param file The file to attempt to open. * @return Returns true if the file is successfully opened and locked. */ private boolean tryFile(File file) { try { LOGGER.info("Log: Trying to open " + file.getAbsolutePath()); RandomAccessFile raf = new RandomAccessFile(file, "rw"); raf.seek(raf.length()); FileChannel channel = raf.getChannel(); if (lockFile) { FileLock lock = channel.tryLock(); if (lock != null) { writer = new BufferedWriter(Channels.newWriter(channel, CHARSET)); valid = true; return true; } } else { writer = new BufferedWriter(Channels.newWriter(channel, CHARSET)); valid = true; return true; } } catch (IOException ex) { LOGGER.warning("Log: Lock failed (" + file + " / " + ex + ")"); } LOGGER.warning("Log: Lock failed (" + file + ")"); return false; } /** * Getter for the `valid` property. * * @return The `valid` property. */ public boolean isValid() { return valid; } public boolean isLocked() { return lockFile && valid; } /** * Getter for the path of the LogFile. * * @return The `file` property. */ public Path getPath() { return file; } /** * Getter for the date that this LogFile instance was created. * * @return The `currentTime` property. */ public Calendar getDate() { return currentTime; } }