/* * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.buildServer.xmlReportPlugin; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.xmlReportPlugin.utils.LoggingUtils; import org.jetbrains.annotations.NotNull; import org.springframework.util.AntPathMatcher; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * User: vbedrosova * Date: 16.12.10 * Time: 13:17 */ public class MonitorRulesCommand { public static interface MonitorRulesParameters { @NotNull Rules getRules(); @NotNull String getType(); boolean isParseOutOfDate(); long getStartTime(); @NotNull BuildProgressLogger getThreadLogger(); } public static interface MonitorRulesListener { void modificationDetected(@NotNull File file); } @NotNull private final MonitorRulesParameters myParameters; @NotNull private final FileStates myFileStates; @NotNull private final MonitorRulesListener myListener; private boolean myFirstRun; @NotNull private final Map myMasks = new HashMap(); @NotNull private final Map myKnownStates = new HashMap(); public MonitorRulesCommand(@NotNull MonitorRulesParameters parameters, @NotNull FileStates fileStates, @NotNull MonitorRulesListener listener) { myParameters = parameters; myFileStates = fileStates; myListener = listener; myFirstRun = true; } public void run() { if (myFirstRun) { logWatchingPaths(); checkExistingPaths(); myFirstRun = false; } monitorRules( new MonitorRulesFileProcessor() { public void processFile(@NotNull File file) { if (acceptFile(file)) { //TODO: also grows switch (myFileStates.getFileState(file)) { case ON_PROCESSING: return; case PROCESSED: if (myKnownStates.containsKey(file)) { myKnownStates.remove(file); } return; case UNKNOWN: final long lastModified = file.lastModified(); final long length = file.length(); final FileState fileState = myKnownStates.get(file); if (fileState == null) { myKnownStates.put(file, new FileState(lastModified, length)); modificationDetected(file); } else { if (lastModified > fileState.lastModified || length > fileState.length) { fileState.lastModified = lastModified; fileState.length = length; modificationDetected(file); } } } } } } ); } public void logWatchingPaths() { LoggingUtils.logInTarget(LoggingUtils.getTypeDisplayName(myParameters.getType()) + " report watcher", new Runnable() { public void run() { String message = "Watching paths:"; final List rulesList = myParameters.getRules().getBody(); if (rulesList.isEmpty()) { message += " "; LoggingUtils.warn(message, getThreadLogger()); } else { LoggingUtils.message(message, getThreadLogger()); for (String r : rulesList) { LoggingUtils.message(r, getThreadLogger()); } } } }, getThreadLogger()); } @NotNull private BuildProgressLogger getThreadLogger() { return myParameters.getThreadLogger(); } private void modificationDetected(File file) { myFileStates.addFile(file); myListener.modificationDetected(file); } private void checkExistingPaths() { final List existingPaths = new ArrayList(); monitorRules( new MonitorRulesFileProcessor() { public void processFile(@NotNull File file) { if (file.isFile() && file.canRead() && !isFresh(file) && isIncluded(file)) { existingPaths.add(file); } } }); if (existingPaths.size() > 0) { LoggingUtils.LOG.info("Found " + existingPaths.size() + " files from previous builds or build steps:"); for (File f : existingPaths) { LoggingUtils.LOG.info(f.getPath()); } } } private interface MonitorRulesFileProcessor { void processFile(@NotNull File file); } private void monitorRules(@NotNull MonitorRulesFileProcessor monitorRulesFileProcessor) { for (File rule : myParameters.getRules().getPaths()) { if (rule.isFile()) { monitorRulesFileProcessor.processFile(rule); continue; } if (rule.isDirectory()) { final File[] files = rule.listFiles(); if ((files != null) && (files.length > 0)) { for (File file : files) { monitorRulesFileProcessor.processFile(file); } } continue; } if (isAntMask(rule)) { final MaskData md = getMask(rule); for (File file : collectFiles(md.getPattern(), md.getBaseDir())) { monitorRulesFileProcessor.processFile(file); } } } } private boolean acceptFile(@NotNull File f) { return f.isFile() && f.canRead() && timeConstraintsSatisfied(f) && isIncluded(f); } private boolean timeConstraintsSatisfied(@NotNull File file) { return myParameters.isParseOutOfDate() || isFresh(file); } private boolean isFresh(@NotNull File file) { return file.lastModified() >= myParameters.getStartTime(); } private boolean isIncluded(@NotNull File file) { return myParameters.getRules().shouldInclude(file); } private MaskData getMask(@NotNull File file) { MaskData md; if (!myMasks.containsKey(file)) { final File baseDir = new File(getDirWithoutPattern(file.getPath())); final Pattern pattern = Pattern.compile(FileUtil.convertAntToRegexp(file.getPath().replace(baseDir.getPath(), ""))); md = new MaskData(baseDir, pattern); myMasks.put(file, md); } else { md = myMasks.get(file); } return md; } private static String getDirWithoutPattern(@NotNull String pathWithWildCard) { String t = pathWithWildCard.replace('\\', '/'); final int firstStar = t.indexOf('*'); final int firstQuest = t.indexOf('?'); int mark = firstStar < 0 ? firstQuest : ((firstStar < firstQuest || firstQuest < 0) ? firstStar : firstQuest); final int lastSlash = t.lastIndexOf('/', mark); return lastSlash > 0 ? pathWithWildCard.substring(0, lastSlash) : ""; } private static ArrayList collectFiles(@NotNull Pattern pattern, @NotNull File basePatternDir) { final ArrayList files = new ArrayList(); FileUtil.collectMatchedFiles(basePatternDir, pattern, files); return files; } private static final AntPathMatcher MATCHER = new AntPathMatcher(); private static boolean isAntMask(@NotNull File f) { return isAntMask(f.getPath()); } private static boolean isAntMask(@NotNull String s) { return MATCHER.isPattern(s); } private static class MaskData { @NotNull private final File myBaseDir; @NotNull private final Pattern myPattern; public MaskData(@NotNull File baseDir, @NotNull Pattern pattern) { myBaseDir = baseDir; myPattern = pattern; } @NotNull public File getBaseDir() { return myBaseDir; } @NotNull public Pattern getPattern() { return myPattern; } } private static class FileState { private long lastModified; private long length; private FileState(long lastModified, long length) { this.lastModified = lastModified; this.length = length; } } }