/*******************************************************************************
* Copyright (c) 2004, 2006
* Thomas Hallgren, Kenneth Olwing, Mitch Sonies
* Pontus Rydin, Nils Unden, Peer Torngren
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the individual
* copyright holders listed above, as Initial Contributors under such license.
* The text of such license is available at www.eclipse.org.
*******************************************************************************/
package org.eclipse.buckminster.subclipse.internal;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Date;
import org.eclipse.buckminster.core.CorePlugin;
import org.eclipse.buckminster.core.RMContext;
import org.eclipse.buckminster.core.helpers.FileUtils;
import org.eclipse.buckminster.core.reader.IReaderType;
import org.eclipse.buckminster.core.version.ProviderMatch;
import org.eclipse.buckminster.core.version.VersionSelector;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.buckminster.subclipse.Messages;
import org.eclipse.buckminster.subversion.GenericCache;
import org.eclipse.buckminster.subversion.GenericRemoteReader;
import org.eclipse.buckminster.subversion.ISubversionSession;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;
import org.tigris.subversion.svnclientadapter.ISVNDirEntry;
import org.tigris.subversion.svnclientadapter.SVNRevision;
import org.tigris.subversion.svnclientadapter.SVNUrl;
/**
* The SVN repository reader assumes that any repository contains the three
* recommended directories <code>trunk</code>, <code>tags</code>, and
* <code>branches</code>. A missing <code>tags</code> directory is interpreted
* as no <code>tags</code>. A missing <code>branches</code> directory is
* interpreted as no branches. The URL used as the repository identifier must
* contain the path element trunk. Anything that follows the <code>trunk</code>
* element in the path will be considered a <code>module</code> path. The
* repository URL may also contain a query part that in turn may have four
* different flags:
* <dl>
* <dt>moduleBeforeTag</dt>
* <dd>When resolving a tag, put the module name between the <code>tags</code>
* directory and the actual tag</dd>
* <dt>moduleAfterTag</dt>
* <dd>When resolving a tag, append the module name after the actual tag</dd>
* <dt>moduleBeforeBranch</dt>
* <dd>When resolving a branch, put the module name between the
* <code>branches</code> directory and the actual branch</dd>
* <dt>moduleAfterBranch</dt>
* <dd>When resolving a branch, append the module name after the actual branch</dd>
* </dl>
* A fragment in the repository URL will be treated as a sub-module. It will be
* appended at the end of the resolved URL.
*
* @author Thomas Hallgren
* @author Guillaume Chatelet
*/
public class SvnRemoteFileReader extends GenericRemoteReader<ISVNDirEntry, SVNRevision> {
/**
* @param readerType
* @param rInfo
* @param withResolvedBranch
* @throws CoreException
*/
public SvnRemoteFileReader(IReaderType readerType, ProviderMatch rInfo, IProgressMonitor monitor) throws CoreException {
super(readerType, rInfo, monitor);
}
@Override
public void innerMaterialize(IPath destination, IProgressMonitor monitor) throws CoreException {
boolean success = false;
final File destDir = destination.toFile();
final Object[] resultSlot = new Object[1];
monitor.beginTask(this.toString(), 12);
// We need to run the checkout in a separate thread to be able
// to cancel.
//
Thread worker = new Thread() {
@Override
public void run() {
try {
SVNUrl svnURL = TypeTranslator.from(getSession().getSVNUrl(null));
SVNRevision svnRev = getSession().getRevision();
CorePlugin.getLogger().debug("Checking out %s using revision %s", svnURL, svnRev); //$NON-NLS-1$
getSession().getClientAdapter().checkout(svnURL, destDir, svnRev, true);
resultSlot[0] = Boolean.TRUE;
} catch (Throwable e) {
resultSlot[0] = e;
}
}
};
try {
worker.start();
Object result;
int workAmount = 0;
while ((result = resultSlot[0]) == null) {
if (monitor.isCanceled()) {
worker.interrupt();
throw new OperationCanceledException();
}
Thread.sleep(500);
if (workAmount < 10) {
MonitorUtils.worked(monitor, 1);
++workAmount;
}
}
if (result instanceof Throwable)
throw BuckminsterException.wrap((Throwable) result);
success = true;
} catch (InterruptedException e) {
throw BuckminsterException.fromMessage(Messages.svn_checkout_timed_out);
} finally {
if (!success) {
try {
FileUtils.deleteRecursive(destDir, new NullProgressMonitor());
} catch (Throwable t) {
t.printStackTrace();
}
}
monitor.done();
}
}
@Override
protected void fetchRemoteFile(URI url, SVNRevision revision, OutputStream output, IProgressMonitor monitor) throws Exception {
int ticksLeft = 3;
InputStream input = null;
byte[] buf = new byte[0x1000];
final ISVNClientAdapter clientAdapter = getSession().getClientAdapter();
input = clientAdapter.getContent(TypeTranslator.from(url), revision, revision);
int bytesRead = input.read(buf);
while (bytesRead > 0) {
output.write(buf, 0, bytesRead);
bytesRead = input.read(buf);
if (ticksLeft > 0) {
MonitorUtils.worked(monitor, 1);
--ticksLeft;
} else
MonitorUtils.testCancelStatus(monitor);
}
}
@Override
protected ISubversionSession<ISVNDirEntry, SVNRevision> getSession(String repositoryURI, VersionSelector branchOrTag, long revision,
Date timestamp, RMContext context) throws CoreException {
return new SvnSession(repositoryURI, branchOrTag, revision, timestamp, context);
}
@Override
protected ISVNDirEntry[] getTopEntries(IProgressMonitor monitor) throws CoreException {
final URI url = getSession().getSVNUrl(null);
return getSession().innerListFolder(url, monitor);
}
@Override
protected boolean remoteFileExists(URI url, SVNRevision revision, IProgressMonitor monitor) throws CoreException {
try {
final ISVNClientAdapter clientAdapter = getSession().getClientAdapter();
return clientAdapter.getDirEntry(TypeTranslator.from(url), revision) != null;
} catch (Exception e) {
throw BuckminsterException.wrap(e);
}
}
@Override
protected String storeInCache(String fileName) throws CoreException {
final SvnSession svnSession = getSession();
return GenericCache.cacheKey(svnSession.getSVNUrl(fileName), svnSession.getRevision());
}
private SvnSession getSession() {
return (SvnSession) session;
}
}