001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 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 java.io.File; 032import java.io.FileOutputStream; 033import java.io.IOException; 034 035import java.util.Date; 036import java.util.concurrent.Future; 037 038import ch.qos.logback.core.rolling.RolloverFailure; 039import ch.qos.logback.core.rolling.TimeBasedFileNamingAndTriggeringPolicy; 040import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; 041import ch.qos.logback.core.rolling.helper.ArchiveRemover; 042import ch.qos.logback.core.rolling.helper.AsynchronousCompressor; 043import ch.qos.logback.core.rolling.helper.CompressionMode; 044import ch.qos.logback.core.rolling.helper.Compressor; 045import ch.qos.logback.core.rolling.helper.FileFilterUtil; 046import ch.qos.logback.core.util.FileUtil; 047 048/** 049 * This Logback {@literal "rolling policy"} copies the contents of a log file 050 * (in this case, mcidasv.log) to the specified destination, and then 051 * {@literal "zeroes out"} the original log file. This approach allows McIDAS-V 052 * users to run a command like {@literal "tail -f mcidasv.log"} without any 053 * issue. Even on Windows. 054 */ 055public class TailFriendlyRollingPolicy<E> extends TimeBasedRollingPolicy<E> { 056 057 private Compressor compressor; 058 059 Future<?> future; 060 061 @Override public void rollover() throws RolloverFailure { 062 063 // when rollover is called the elapsed period's file has 064 // been already closed. This is a working assumption of this method. 065 066 TimeBasedFileNamingAndTriggeringPolicy timeBasedFileNamingAndTriggeringPolicy = getTimeBasedFileNamingAndTriggeringPolicy(); 067 String elapsedPeriodsFileName = 068 timeBasedFileNamingAndTriggeringPolicy.getElapsedPeriodsFileName(); 069 070 String elapsedPeriodStem = FileFilterUtil.afterLastSlash(elapsedPeriodsFileName); 071 072 if (compressionMode == CompressionMode.NONE) { 073 String src = getParentsRawFileProperty(); 074 if (src != null) { 075 if (isFileEmpty(src)) { 076 addInfo("File '"+src+"' exists and is zero-length; avoiding copy"); 077 } else { 078 renameByCopying(src, elapsedPeriodsFileName); 079 } 080 } // else { nothing to do if CompressionMode == NONE and parentsRawFileProperty == null } 081 } else { 082 if (getParentsRawFileProperty() == null) { 083 future = asyncCompress(elapsedPeriodsFileName, elapsedPeriodsFileName, elapsedPeriodStem); 084 } else { 085 future = renamedRawAndAsyncCompress(elapsedPeriodsFileName, elapsedPeriodStem); 086 } 087 } 088 089 ArchiveRemover archiveRemover = getTimeBasedFileNamingAndTriggeringPolicy().getArchiveRemover(); 090 091 if (archiveRemover != null) { 092 archiveRemover.clean(new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime())); 093 } 094 } 095 096 Future<?> asyncCompress(String nameOfFile2Compress, 097 String nameOfCompressedFile, String innerEntryName) 098 throws RolloverFailure 099 { 100 AsynchronousCompressor ac = new AsynchronousCompressor(compressor); 101 return ac.compressAsynchronously(nameOfFile2Compress, nameOfCompressedFile, innerEntryName); 102 } 103 104 Future<?> renamedRawAndAsyncCompress(String nameOfCompressedFile, 105 String innerEntryName) 106 throws RolloverFailure 107 { 108 String parentsRawFile = getParentsRawFileProperty(); 109 String tmpTarget = parentsRawFile + System.nanoTime() + ".tmp"; 110 renameByCopying(parentsRawFile, tmpTarget); 111 return asyncCompress(tmpTarget, nameOfCompressedFile, innerEntryName); 112 } 113 114 /** 115 * Copies the contents of {@code src} into {@code target}, and then 116 * {@literal "zeroes out"} {@code src}. 117 * 118 * @param src Path to the file to be copied. Cannot be {@code null}. 119 * @param target Path to the destination file. Cannot be {@code null}. 120 * 121 * @throws RolloverFailure if copying failed. 122 */ 123 public void renameByCopying(String src, String target) 124 throws RolloverFailure 125 { 126 FileUtil fileUtil = new FileUtil(getContext()); 127 fileUtil.copy(src, target); 128 try (FileOutputStream writer = new FileOutputStream(src)) { 129 addInfo("zeroing out " + src); 130 } catch (IOException e) { 131 addError("Could not reset " + src, e); 132 } 133 } 134 135 /** 136 * Determine if the file at the given path is zero length. 137 * 138 * @param filepath Path to the file to be tested. Cannot be {@code null}. 139 * 140 * @return {@code true} if {@code filepath} exists and is empty. 141 */ 142 private static boolean isFileEmpty(String filepath) { 143 File f = new File(filepath); 144 return f.exists() && (f.length() == 0L); 145 } 146}