001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2017 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029package edu.wisc.ssec.mcidasv.util; 030 031import ch.qos.logback.core.joran.spi.NoAutoStart; 032import ch.qos.logback.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy; 033import ch.qos.logback.core.rolling.RolloverFailure; 034 035import java.io.File; 036import java.io.IOException; 037 038import java.nio.file.Files; 039import java.nio.file.Path; 040import java.nio.file.Paths; 041 042import java.util.Arrays; 043import java.util.Comparator; 044 045/** 046 * This is a Logback {@literal "triggering policy"} that forces a log 047 * {@literal "roll"} upon starting McIDAS-V. This policy will also attempt to 048 * move the old {@literal "log"} directory to {@literal "archived_logs"} as well 049 * as attempting to remove the oldest {@literal "archived log files"}. 050 * 051 * <p>Credit for the initial implementation belongs to 052 * <a href="http://stackoverflow.com/a/12408445">this StackOverflow post</a>.</p> 053 */ 054@NoAutoStart 055public class StartupTriggeringPolicy<E> 056 extends DefaultTimeBasedFileNamingAndTriggeringPolicy<E> { 057 058 /** 059 * Responsible for determining what to do about the {@literal "logs"} and 060 * {@literal "archived_logs"} subdirectory situation. 061 */ 062 private void renameOldLogDirectory() { 063 String userpath = System.getProperty("mcv.userpath"); 064 if (userpath != null) { 065 Path oldLogPath = Paths.get(userpath, "logs"); 066 Path newLogPath = Paths.get(userpath, "archived_logs"); 067 File oldDirectory = oldLogPath.toFile(); 068 File newDirectory = newLogPath.toFile(); 069 070 // T F = rename 071 // F F = attempt to create 072 // T T = remove old dir 073 // F T = noop 074 if (oldDirectory.exists() && !newDirectory.exists()) { 075 oldDirectory.renameTo(newDirectory); 076 } else if (!oldDirectory.exists() && !newDirectory.exists()) { 077 if (!newDirectory.mkdir()) { 078 addWarn("Could not create '"+newLogPath+'\''); 079 } else { 080 addInfo("Created '"+newLogPath+'\''); 081 } 082 } else if (oldDirectory.exists() && newDirectory.exists()) { 083 addWarn("Both log directories exist; moving files from '" + oldLogPath + "' and attempting to delete"); 084 removeOldLogDirectory(oldDirectory, newDirectory); 085 } else if (!oldDirectory.exists() && newDirectory.exists()) { 086 addInfo('\''+oldLogPath.toString()+"' does not exist; no cleanup is required"); 087 } else { 088 addWarn("Unknown state! oldDirectory.exists()='"+oldDirectory.exists()+"' newDirectory.exists()='"+newDirectory.exists()+'\''); 089 } 090 } 091 } 092 093 /** 094 * Fires off a thread that moves all files within {@code oldDirectory} 095 * into {@code newDirectory}, and then attempts to remove 096 * {@code oldDirectory}. 097 * 098 * @param oldDirectory {@literal "Old"} log file directory. Be aware that 099 * any files within this directory will be relocated to 100 * {@code newDirectory} and this directory will then be removed. Cannot be 101 * {@code null}. 102 * @param newDirectory Destination for any files within 103 * {@code oldDirectory}. Cannot be {@code null}. 104 */ 105 private void removeOldLogDirectory(File oldDirectory, File newDirectory) { 106 File[] files = oldDirectory.listFiles(); 107 new Thread(asyncClearFiles(oldDirectory, newDirectory, files)).start(); 108 } 109 110 /** 111 * Moves all files within {@code oldDirectory} into {@code newDirectory}, 112 * and then removes {@code oldDirectory}. 113 * 114 * @param oldDirectory {@literal "Old"} log file directory. Cannot be 115 * {@code null}. 116 * @param newDirectory {@literal "New"} log file directory. Cannot be 117 * {@code null}. 118 * @param files {link File Files} within {@code oldDirectory} that should 119 * be moved to {@code newDirectory}. Cannot be {@code null}. 120 * 121 * @return Thread that will attempt to relocate any files within 122 * {@code oldDirectory} to {@code newDirectory} and then attempt removal 123 * of {@code oldDirectory}. Be aware that this thread has not yet had 124 * {@literal "start"} called. 125 */ 126 private Runnable asyncClearFiles(final File oldDirectory, 127 final File newDirectory, 128 final File[] files) 129 { 130 return new Runnable() { 131 public void run() { 132 boolean success = true; 133 for (File f : files) { 134 File newPath = new File(newDirectory, f.getName()); 135 if (f.renameTo(newPath)) { 136 addInfo("Moved '"+f.getAbsolutePath()+"' to '"+newPath.getAbsolutePath()+'\''); 137 } else { 138 success = false; 139 addWarn("Could not move '"+f.getAbsolutePath()+"' to '"+newPath.getAbsolutePath()+'\''); 140 } 141 } 142 if (success) { 143 if (oldDirectory.delete()) { 144 addInfo("Removed '"+oldDirectory.getAbsolutePath()+'\''); 145 } else { 146 addWarn("Could not remove '"+oldDirectory.getAbsolutePath()+'\''); 147 } 148 } 149 } 150 }; 151 } 152 153 /** 154 * Finds the archived log files and determines whether or not {@link #asyncCleanFiles(int, java.io.File[])} 155 * should be called (and if it should, this method calls it). 156 * 157 * @param keepFiles Number of archived log files to keep around. 158 */ 159 private void cleanupArchivedLogs(int keepFiles) { 160 String userpath = System.getProperty("mcv.userpath"); 161 if (userpath != null) { 162 Path logDirectory = Paths.get(userpath, "archived_logs"); 163 File[] files = logDirectory.toFile().listFiles(); 164 if ((files != null) && (files.length > keepFiles)) { 165 new Thread(asyncCleanFiles(keepFiles, files)).start(); 166 } 167 new Thread(asyncCleanReallyOldFiles()).start(); 168 } 169 } 170 171 /** 172 * Removes log files archived by a very preliminary version of our Logback 173 * configuration. These files reside within the userpath, and are named 174 * {@literal "mcidasv.1.log.zip"}, {@literal "mcidasv.2.log.zip"}, and 175 * {@literal "mcidasv.3.log.zip"}. 176 * 177 * @return Thread that will attempt to remove the three archived log files. 178 */ 179 private Runnable asyncCleanReallyOldFiles() { 180 return new Runnable() { 181 public void run() { 182 String userpath = System.getProperty("mcv.userpath"); 183 if (userpath != null) { 184 Path userDirectory = Paths.get(userpath); 185 removePath(userDirectory.resolve("mcidasv.1.log.zip")); 186 removePath(userDirectory.resolve("mcidasv.2.log.zip")); 187 removePath(userDirectory.resolve("mcidasv.3.log.zip")); 188 } 189 } 190 }; 191 } 192 193 /** 194 * Convenience method that attempts to delete {@code pathToRemove}. 195 * 196 * @param pathToRemove {@code Path} to the file to delete. 197 * Cannot be {@code null}. 198 */ 199 private void removePath(Path pathToRemove) { 200 try { 201 if (Files.deleteIfExists(pathToRemove)) { 202 addInfo("removing '"+pathToRemove+'\''); 203 } 204 } catch (IOException e) { 205 addError("An exception occurred while trying to remove '"+pathToRemove+'\'', e); 206 } 207 } 208 209 /** 210 * Creates a thread that attempts to remove all but the {@code keep} oldest 211 * files in {@code files} (by using the last modified times). 212 * 213 * @param keep Number of archived log files to keep around. 214 * @param files Archived log files. Cannot be {@code null}. 215 * 216 * @return Thread that will attempt to remove everything except the 217 * specified number of archived log files. Be aware that this thread has 218 * not yet had {@literal "start"} called. 219 */ 220 private Runnable asyncCleanFiles(final int keep, final File[] files) { 221 return new Runnable() { 222 public void run() { 223 Arrays.sort(files, new Comparator<File>() { 224 public int compare(File f1, File f2) { 225 return Long.valueOf(f2.lastModified()).compareTo(f1.lastModified()); 226 } 227 }); 228 for (int i = keep-1; i < files.length; i++) { 229 addInfo("removing '"+files[i]+'\''); 230 try { 231 Files.deleteIfExists(files[i].toPath()); 232 } catch (IOException e) { 233 addWarn("An exception occurred while trying to remove '"+files[i]+'\''); 234 } 235 } 236 } 237 }; 238 } 239 240 /** 241 * Triggers a {@literal "logback rollover"} and calls 242 * {@link #cleanupArchivedLogs(int)}. 243 */ 244 @Override public void start() { 245 renameOldLogDirectory(); 246 super.start(); 247 nextCheck = 0L; 248 isTriggeringEvent(null, null); 249 try { 250 tbrp.rollover(); 251 int maxHistory = tbrp.getMaxHistory(); 252 if (maxHistory > 0) { 253 addInfo("keep "+maxHistory+" most recent archived logs"); 254 cleanupArchivedLogs(maxHistory); 255 } else { 256 addInfo("maxHistory not set; not cleaning archiving logs"); 257 } 258 } catch (RolloverFailure e) { 259 addError("could not perform rollover of log file", e); 260 } 261 } 262}