/* * Copyright 2011 Ronald Kurniawan. * * This file is part of CodeTraq. * * CodeTraq is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CodeTraq is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CodeTraq. If not, see <http://www.gnu.org/licenses/>. */ package net.mobid.codetraq.runnables; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import net.mobid.codetraq.VersionControlType; import net.mobid.codetraq.persistence.ServerDTO; import net.mobid.codetraq.persistence.ServerRevision; import net.mobid.codetraq.utils.DbUtility; import net.mobid.codetraq.utils.LogService; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepository; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.SVNLogEntryPath; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.wc.SVNWCUtil; /** * This is a worker class that tracks and updates the revision history of a * server. As you might have guessed, this class works by polling the respective * repository servers after UPDATE_IN_MINUTES elapsed. You should set an update time * that is convenient for your own circumnstances. * * @author Ronald Kurniawan * @version 0.1 */ public class ServerTracker implements Runnable { private final int UPDATE_IN_MINUTES = 8; private DbUtility _db = null; /** * Creates a new instance of <code>ServerTracker</code>. * @param db - an instance of <code>DbUtility</code> * @param servers - a <code>List</code> of <code>ServerDTO</code> objects */ public ServerTracker(DbUtility db, List<ServerDTO> servers) { _db = db; setupServers(servers); } /** * Compares the latest revision found in each server, and stores the newer revision * data into the database. */ public void run() { Thread currentThread = Thread.currentThread(); try { while (true) { Thread.yield(); if (currentThread.isInterrupted()) { throw new InterruptedException("Time to pack up and go home"); } // now we need to query the server database and check for changes // for all the servers that are marked for update... List<ServerRevision> servers = _db.getAllServerRevisions(); Iterator it = servers.iterator(); while (it.hasNext()) { ServerRevision sr = (ServerRevision) it.next(); if (sr.shoudlUpdate() && sr.getMinutesSinceLastCheck() >= UPDATE_IN_MINUTES) { if (sr.getVersionControlType() == VersionControlType.SVN) { SVNLogEntry latestLogEntry = getSvnLatestRevisionHistory(sr); int srLastRevision = Integer.parseInt(sr.getLastRevisionId()); if (latestLogEntry != null && latestLogEntry.getRevision() > srLastRevision) { LogService.writeMessage("Found latest revision for " + sr.getServerAddress() + " with timestamp " + latestLogEntry.getDate().getTime()); sr.setLastCheckedTimestamp(System.currentTimeMillis()); // revision-related changes sr.setLastMessage(latestLogEntry.getMessage()); sr.setLastRevisionId(String.valueOf(latestLogEntry.getRevision())); sr.setLastAuthor(latestLogEntry.getAuthor()); sr.setLastCommitter(latestLogEntry.getAuthor()); sr.setLastRevisionTimestamp(latestLogEntry.getDate().getTime()); sr.clearFiles(); if (latestLogEntry.getChangedPaths().size() > 0) { Iterator iterator = latestLogEntry.getChangedPaths().keySet().iterator(); while (iterator.hasNext()) { SVNLogEntryPath ep = (SVNLogEntryPath) latestLogEntry.getChangedPaths().get(iterator.next()); StringBuilder sb = new StringBuilder(); sb.append(ep.getType()).append(" ").append(ep.getPath()); sr.addModifiedFile(sb.toString()); } } _db.updateServerLatestRevision(sr); } } else if (sr.getVersionControlType() == VersionControlType.GIT) { RevCommit latestLogEntry = getGitLatestRevisionHistory(sr); long latestCommitTstamp = ((long) latestLogEntry.getCommitTime()) * 1000; if (latestLogEntry != null && (latestCommitTstamp > sr.getLastRevisionTimestamp())) { LogService.writeMessage("Found latest revision for " + sr.getServerAddress() + " with timestamp " + latestCommitTstamp); sr.setLastCheckedTimestamp(System.currentTimeMillis()); // revision-related changes sr.setLastMessage(latestLogEntry.getFullMessage()); sr.setLastRevisionId(latestLogEntry.getId().getName()); sr.setLastAuthor(latestLogEntry.getAuthorIdent().getName() + " (" + (latestLogEntry.getAuthorIdent().getEmailAddress().length() > 0 ? latestLogEntry.getAuthorIdent().getEmailAddress() : "empty email") + ")"); sr.setLastCommitter(latestLogEntry.getCommitterIdent().getName() + (latestLogEntry.getCommitterIdent().getEmailAddress().length() > 0 ? latestLogEntry.getCommitterIdent().getEmailAddress() : "empty email") + ")"); sr.setLastRevisionTimestamp(latestCommitTstamp); List<String> changedFiles = getChangedFiles(sr); sr.clearFiles(); // add new files for (String m : changedFiles) { sr.addModifiedFile(m); } _db.updateServerLatestRevision(sr); } } } } Thread.sleep(UPDATE_IN_MINUTES * 60 * 1000); } } catch (InterruptedException ie) { LogService.writeMessage("ServerTracker interrupted"); return; } } /* * Turns the update flag on only for servers that are listed in the configuration files. * @param servers - a <code>List</code> of <code>ServerDTO</code> objects */ private void setupServers(List<ServerDTO> servers) { _db.turnAllServerUpdateOff(); // now we need to turn ServerRevision.shouldUpdate to true for every server on the list Iterator it = servers.iterator(); while (it.hasNext()) { ServerDTO server = (ServerDTO) it.next(); ServerRevision sr = _db.getServerRevisionByAddress(server.getServerAddress()); if (sr == null) { sr = new ServerRevision(); sr.setServerAddress(server.getServerAddress()); sr.setServerUsername(server.getServerUsername()); sr.setServerPassword(server.getServerPassword()); sr.setServerShortName(server.getShortName()); sr.setVersionControlType(server.getServerType()); sr.setShouldUpdate(true); sr.setVersionControlType(server.getServerType()); _db.addServerRevision(sr); } else { _db.turnServerUpdateOn(sr); } } } /* * Fetches the latest revision data from a Subversion server. * @param server - a <code>ServerRevision</code> object * @return a <code>SVNLogEntry</code> object containing the latest revision data */ private SVNLogEntry getSvnLatestRevisionHistory(ServerRevision server) { // check server protocol if (!server.getServerAddress().startsWith("http://") && !server.getServerAddress().startsWith("https://") && !server.getServerAddress().startsWith("svn://") && !server.getServerAddress().startsWith("svn+ssh://")) { System.out.printf("Server URL should start with protocol. Valid protocols are %s,%s,%s and %s%n", "http", "https", "svn", "svn+ssh"); LogService.writeMessage("Wrong protocol for " + server.getServerAddress()); return null; } try { if (server.getServerAddress().startsWith("http://") || server.getServerAddress().startsWith("https://")) { DAVRepositoryFactory.setup(); } else if (server.getServerAddress().startsWith("svn://") || server.getServerAddress().startsWith("svn+ssh://")) { SVNRepositoryFactoryImpl.setup(); } SVNURL svnUrl = SVNURL.parseURIDecoded(server.getServerAddress()); LogService.writeMessage("Connecting to " + server.getServerAddress() + "..."); SVNRepository svnRepository = SVNRepositoryFactory.create(svnUrl, null); ISVNAuthenticationManager _auth = SVNWCUtil.createDefaultAuthenticationManager(server.getServerUsername(), server.getServerPassword()); svnRepository.setAuthenticationManager(_auth); Collection latestLogEntry = svnRepository.log(new String[]{""}, null, -1, -1, true, true); SVNLogEntry latest = null; Iterator it = latestLogEntry.iterator(); while (it.hasNext()) { SVNLogEntry entry = (SVNLogEntry) it.next(); if (latest == null || latest.getRevision() < entry.getRevision()) { latest = entry; } } return latest; } catch (SVNException ex) { LogService.getLogger(SvnChecker.class.getName()).log(Level.SEVERE, null, ex); LogService.writeLog(Level.SEVERE, ex); } return null; } /* * Fetches the latest revision data from a GIT repository. * @param sr - a <code>ServerRevision</code> object * @return a <code>RevCommit</code> object containing the latest revision data */ private RevCommit getGitLatestRevisionHistory(ServerRevision sr) { try { File gitPath = new File("gitrepos/" + sr.getServerShortName() + "/.git"); Repository repo = new FileRepository(gitPath); RevCommit[] commits = getCommits(1, repo); return commits[0]; } catch (Exception ex) { LogService.getLogger(ServerTracker.class.getName()).log(Level.SEVERE, null, ex); LogService.writeLog(Level.SEVERE, ex); } return null; } /* * Returns a list of modified files along with their modification status, from * a GIT repository. * @param sr - a <code>ServerRevision</code> object * @return a <code>List</code> of modified files */ private List<String> getChangedFiles(ServerRevision sr) { List<String> modifiedFiles = new ArrayList<String>(); try { File gitPath = new File("gitrepos/" + sr.getServerShortName() + "/.git"); Repository repo = new FileRepository(gitPath); String cr = System.getProperty("line.separator"); DiffFormatter df = new DiffFormatter(new ByteArrayOutputStream()); RevCommit[] commits = getCommits(2, repo); if (commits.length == 2) { RevTree aTree = commits[0].getTree(); RevTree bTree = commits[1].getTree(); df.setRepository(repo); List<DiffEntry> changed = df.scan(aTree, bTree); StringBuilder sb = new StringBuilder(); for (DiffEntry entry : changed) { sb.delete(0, sb.length()); switch (entry.getChangeType()) { case ADD: sb.append("A ").append(entry.getNewPath()).append(cr); break; case DELETE: sb.append("D ").append(entry.getOldPath()).append(cr); break; case MODIFY: sb.append("M ").append(entry.getNewPath()); break; case COPY: sb.append("[Copied] from ").append(entry.getOldPath()).append(" to ").append(entry.getNewPath()); break; case RENAME: sb.append("[Renamed] from ").append(entry.getOldPath()).append(" to ").append(entry.getNewPath()); break; } if (sb.toString().length() > 0) { modifiedFiles.add(sb.toString()); } } } } catch (IOException ex) { LogService.getLogger(ServerTracker.class.getName()).log(Level.SEVERE, null, ex); LogService.writeLog(Level.SEVERE, ex); } return modifiedFiles; } /* * Returns the latest n commit(s) from the local GIT repository of a specified * project. This is normally called after we update the local repository first. * @param howManyCommits - the number of commits we wish to pull * @param repo - a <code>Repository</code> object * @return an array of <code>RevCommit</code> objects */ private RevCommit[] getCommits(int howManyCommits, Repository repo) { if (howManyCommits == 0) { return null; } RevWalk rw = new RevWalk(repo); for (Ref ref : repo.getAllRefs().values()) { try { rw.markStart(rw.parseCommit(ref.getObjectId())); } catch (Exception notACommit) { continue; } } Comparator<RevCommit> commitTimeComparator = new Comparator<RevCommit>() { public int compare(RevCommit o1, RevCommit o2) { if (o1.getCommitTime() == o2.getCommitTime()) { return 0; } else if (o1.getCommitTime() > o2.getCommitTime()) { return 1; } return -1; } }; ArrayList<RevCommit> commits = new ArrayList<RevCommit>(); for (RevCommit commit : rw) { commits.add(commit); } Collections.sort(commits, commitTimeComparator); Collections.reverse(commits); RevCommit[] c = null; if (howManyCommits > commits.size()) { c = commits.toArray(new RevCommit[howManyCommits]); } else { c = new RevCommit[howManyCommits]; for (int i = 0; i < howManyCommits; i++) { c[i] = commits.get(i); } } rw.dispose(); return c; } }