/*
* The MIT License
*
* Copyright (c) 2004-2010, Andrew Bayer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.plugins.mavenrepocleaner;
import antlr.ANTLRException;
import hudson.FilePath;
import hudson.Util;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.AsyncPeriodicWork;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Logger;
/**
* Clean up Maven repositories
*
* @author Andrew Bayer
*/
@Extension
public class MavenRepoCleanerThread extends AsyncPeriodicWork {
private static MavenRepoCleanerThread theInstance;
private final Calendar cal = new GregorianCalendar();
// so that this can be easily accessed from sub-routine.
private TaskListener listener;
public MavenRepoCleanerThread() {
super("maven-repo-cleanup");
theInstance = this;
}
public long getRecurrencePeriod() {
return HOUR;
}
protected void execute(TaskListener listener) throws InterruptedException, IOException {
try {
if(disabled) {
LOGGER.warning("Disabled. Skipping execution");
return;
}
this.listener = listener;
while (new Date().getTime() - cal.getTimeInMillis() > 1000) {
try {
checkTriggers(cal);
} catch (Throwable e) {
e.printStackTrace();
}
cal.add(Calendar.MINUTE, 1);
}
} finally {
this.listener = null;
}
}
public void checkTriggers(final Calendar cal) throws ANTLRException, IOException, InterruptedException {
Hudson inst = Hudson.getInstance();
MavenRepoCleanerProperty.DescriptorImpl d = (MavenRepoCleanerProperty.DescriptorImpl)inst.getDescriptor(MavenRepoCleanerProperty.class);
if (d!=null) {
if (d.getCronTab() != null) {
if (d.getCronTab().check(cal)) {
for (Node n : inst.getNodes())
if (n instanceof Slave) process((Slave)n, d.getExpirationDays());
process(inst, d.getExpirationDays());
}
}
}
}
public static void invoke() {
theInstance.run();
}
private void process(Hudson h, int expirationDays) throws IOException, InterruptedException {
File jobs = new File(h.getRootDir(), "jobs");
File[] dirs = jobs.listFiles(DIR_FILTER);
if(dirs==null) return;
for (File dir : dirs) {
FilePath repo = new FilePath(new File(new File(dir, "workspace"), ".repository"));
if(shouldBeDeleted(dir.getName(),repo,h,expirationDays)) {
delete(repo);
}
}
}
private boolean shouldBeDeleted(String jobName, FilePath dir, Node n, int expirationDays) throws IOException, InterruptedException {
// TODO: the use of remoting is not optimal.
// One remoting can execute "exists", "lastModified", and "delete" all at once.
TopLevelItem item = Hudson.getInstance().getItem(jobName);
if(item==null) {
// no such project anymore
LOGGER.fine("Repository directory "+dir+" is not owned by any project");
return true;
}
if(!dir.exists())
return false;
long now = new Date().getTime();
// If the marker file doesn't already exist, create it.
FilePath markerFile = new FilePath(dir, ".cleanupMarker");
if (!markerFile.exists()) {
// Giving marker file a modification time of now - 5 minutes, so that a check tomorrow
// will show it greater than 24 hours old.
markerFile.touch(now-(5*MIN));
}
if (item instanceof AbstractProject) {
AbstractProject p = (AbstractProject) item;
MavenRepoCleanerProperty mrcp = (MavenRepoCleanerProperty)p.getProperty(MavenRepoCleanerProperty.class);
if (mrcp!=null) {
if (mrcp.isNotOnThisProject()) {
LOGGER.fine("Repository cleaning disabled for job " + jobName);
return false;
}
}
if (p.isBuilding()) {
LOGGER.fine("Repository directory " + dir + " belongs to a currently running build, so deletion is vetoed.");
return false;
}
MavenRepoCleanerProperty.DescriptorImpl d = (MavenRepoCleanerProperty.DescriptorImpl)mrcp.getDescriptor();
// If expirationStyle is 1, compare against directory's last modified time.
if (d.getExpirationStyle()==1) {
// if younger than the given range, keep it
if(dir.lastModified() + expirationDays * DAY > now) {
LOGGER.fine("Repository directory "+dir+" is only "+ Util.getTimeSpanString(now-dir.lastModified())+" old, so not deleting");
return false;
}
}
// If expirationStyle is 0, compare against marker file's last modified time.
else if (d.getExpirationStyle()==0) {
// if younger than the given range, keep it
if(markerFile.lastModified() + expirationDays * DAY > now) {
LOGGER.fine("Repository directory marker file "+markerFile+" is only "+ Util.getTimeSpanString(now-markerFile.lastModified())+" old, so not deleting");
return false;
}
}
// If expirationStyle is 2, just return true - we're deleting regardless.
else if (d.getExpirationStyle()==2) {
LOGGER.fine("Repository directory "+dir+" should be deleted regardless of age");
return true;
}
}
LOGGER.fine("Going to delete repository directory "+dir);
return true;
}
private void process(Slave s, int expirationDays) throws InterruptedException {
listener.getLogger().println("Scanning "+s.getNodeName());
try {
FilePath path = s.getWorkspaceRoot();
if(path==null) return;
List<FilePath> dirs = path.list(DIR_FILTER);
if(dirs ==null) return;
for (FilePath dir : dirs) {
FilePath repo = new FilePath(dir, ".repository");
if(shouldBeDeleted(dir.getName(),repo,s,expirationDays))
delete(repo);
}
} catch (IOException e) {
e.printStackTrace(listener.error("Failed on "+s.getNodeName()));
}
}
private void delete(FilePath dir) throws InterruptedException {
try {
listener.getLogger().println("Deleting "+dir);
dir.deleteRecursive();
} catch (IOException e) {
e.printStackTrace(listener.error("Failed to delete "+dir));
}
}
private static class DirectoryFilter implements FileFilter, Serializable {
public boolean accept(File f) {
return f.isDirectory();
}
private static final long serialVersionUID = 1L;
}
private static final FileFilter DIR_FILTER = new DirectoryFilter();
private static final Logger LOGGER = Logger.getLogger(MavenRepoCleanerThread.class.getName());
/**
* Can be used to disable workspace clean up.
*/
public static boolean disabled = Boolean.getBoolean(MavenRepoCleanerThread.class.getName()+".disabled");
}