changeset 512:10ca3ede8367

Issue 39: Progress and Cancel support for Clone command
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 14 Dec 2012 20:10:15 +0100
parents 122e0600799f
children a41d955dc360
files src/org/tmatesoft/hg/core/HgCloneCommand.java src/org/tmatesoft/hg/repo/HgBundle.java
diffstat 2 files changed, 84 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgCloneCommand.java	Fri Dec 14 15:39:49 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgCloneCommand.java	Fri Dec 14 20:10:15 2012 +0100
@@ -33,6 +33,7 @@
 import org.tmatesoft.hg.internal.ByteArrayDataAccess;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.DigestHelper;
+import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.RepoInitializer;
 import org.tmatesoft.hg.repo.HgBundle;
 import org.tmatesoft.hg.repo.HgBundle.GroupElement;
@@ -43,8 +44,10 @@
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
+import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.PathRewrite;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
  * WORK IN PROGRESS, DO NOT USE
@@ -101,23 +104,29 @@
 		} else {
 			destination.mkdirs();
 		}
+		ProgressSupport progress = getProgressSupport(null);
+		CancelSupport cancel = getCancelSupport(null, true);
+		cancel.checkCancelled();
 		// if cloning remote repo, which can stream and no revision is specified -
 		// can use 'stream_out' wireproto
 		//
 		// pull all changes from the very beginning
-		// XXX consult getContext() if by any chance has a bundle ready, if not, then read and register 
+		// XXX consult getContext() if by any chance has a bundle ready, if not, then read and register
 		HgBundle completeChanges = srcRepo.getChanges(Collections.singletonList(NULL));
-		WriteDownMate mate = new WriteDownMate(srcRepo.getSessionContext(), destination);
+		cancel.checkCancelled();
+		WriteDownMate mate = new WriteDownMate(srcRepo.getSessionContext(), destination, progress, cancel);
 		try {
 			// instantiate new repo in the destdir
 			mate.initEmptyRepository();
 			// pull changes
 			completeChanges.inspectAll(mate);
+			mate.checkFailure();
 			mate.complete();
 		} catch (IOException ex) {
 			throw new HgInvalidFileException(getClass().getName(), ex);
 		} finally {
 			completeChanges.unlink();
+			progress.done();
 		}
 		return new HgLookup().detect(destination);
 	}
@@ -126,9 +135,11 @@
 	// 1. process changelog, memorize nodeids to index
 	// 2. process manifest, using map from step 3, collect manifest nodeids
 	// 3. process every file, using map from 3, and consult set from step 4 to ensure repo is correct
-	private static class WriteDownMate implements HgBundle.Inspector {
+	private static class WriteDownMate implements HgBundle.Inspector, Lifecycle {
 		private final File hgDir;
 		private final PathRewrite storagePathHelper;
+		private final ProgressSupport progressSupport;
+		private final CancelSupport cancelSupport;
 		private FileOutputStream indexFile;
 		private String filename; // human-readable name of the file being written, for log/exception purposes 
 
@@ -143,12 +154,16 @@
 
 		private final LinkedList<String> fncacheFiles = new LinkedList<String>();
 		private RepoInitializer repoInit;
+		private Lifecycle.Callback lifecycleCallback;
+		private CancelledException cancelException;
 
-		public WriteDownMate(SessionContext ctx, File destDir) {
+		public WriteDownMate(SessionContext ctx, File destDir, ProgressSupport progress, CancelSupport cancel) {
 			hgDir = new File(destDir, ".hg");
 			repoInit = new RepoInitializer();
 			repoInit.setRequires(STORE | FNCACHE | DOTENCODE);
 			storagePathHelper = repoInit.buildDataFilesHelper(ctx);
+			progressSupport = progress;
+			cancelSupport = cancel;
 		}
 
 		public void initEmptyRepository() throws IOException {
@@ -347,6 +362,31 @@
 			}
 			return true;
 		}
+
+		public void start(int count, Callback callback, Object token) {
+			progressSupport.start(count);
+			lifecycleCallback = callback;
+		}
+
+		public void finish(Object token) {
+			progressSupport.done();
+			lifecycleCallback = null;
+		}
+		
+		public void checkFailure() throws CancelledException {
+			if (cancelException != null) {
+				throw cancelException;
+			}
+		}
+		
+		private void stopIfCancelled() {
+			try {
+				cancelSupport.checkCancelled();
+				return;
+			} catch (CancelledException ex) {
+				cancelException = ex;
+				lifecycleCallback.stop();
+			}
+		}
 	}
-
 }
--- a/src/org/tmatesoft/hg/repo/HgBundle.java	Fri Dec 14 15:39:49 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgBundle.java	Fri Dec 14 20:10:15 2012 +0100
@@ -18,18 +18,22 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ConcurrentModificationException;
 
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.ByteArrayDataAccess;
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.DataAccessProvider;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.InflaterDataAccess;
+import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.Patch;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.util.Adaptable;
 import org.tmatesoft.hg.util.CancelledException;
 
 /**
@@ -46,6 +50,7 @@
 	private final File bundleFile;
 	private final DataAccessProvider accessProvider;
 //	private final SessionContext sessionContext;
+	private Lifecycle.BasicCallback flowControl;
 
 	HgBundle(SessionContext ctx, DataAccessProvider dap, File bundle) {
 //		sessionContext = ctx;
@@ -186,6 +191,7 @@
 	}
 
 	// callback to minimize amount of Strings and Nodeids instantiated
+	@Callback
 	public interface Inspector {
 		void changelogStart();
 
@@ -216,6 +222,7 @@
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
+		final Lifecycle lifecycle = lifecycleSetUp(inspector);
 		DataAccess da = null;
 		try {
 			da = getDataStream();
@@ -226,6 +233,7 @@
 			if (da != null) {
 				da.done();
 			}
+			lifecycleTearDown(lifecycle);
 		}
 	}
 
@@ -238,6 +246,7 @@
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
+		final Lifecycle lifecycle = lifecycleSetUp(inspector);
 		DataAccess da = null;
 		try {
 			da = getDataStream();
@@ -252,6 +261,7 @@
 			if (da != null) {
 				da.done();
 			}
+			lifecycleTearDown(lifecycle);
 		}
 	}
 
@@ -264,6 +274,7 @@
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
+		final Lifecycle lifecycle = lifecycleSetUp(inspector);
 		DataAccess da = null;
 		try {
 			da = getDataStream();
@@ -282,6 +293,7 @@
 			if (da != null) {
 				da.done();
 			}
+			lifecycleTearDown(lifecycle);
 		}
 	}
 
@@ -294,6 +306,7 @@
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
+		final Lifecycle lifecycle = lifecycleSetUp(inspector);
 		DataAccess da = null;
 		try {
 			da = getDataStream();
@@ -306,8 +319,34 @@
 			if (da != null) {
 				da.done();
 			}
+			lifecycleTearDown(lifecycle);
 		}
 	}
+	
+	// initialize flowControl, check for concurrent usage, starts lifecyle, if any
+	// return non-null only if inspector is interested in lifecycle events
+	private Lifecycle lifecycleSetUp(Inspector inspector) throws ConcurrentModificationException {
+		// Don't need flowControl in case Inspector doesn't implement Lifecycle,
+		// however is handy not to expect it == null inside internalInspect* 
+		// XXX Once there's need to make this class thread-safe,
+		// shall move flowControl to thread-local state.
+		if (flowControl != null) {
+			throw new ConcurrentModificationException("HgBundle is in use and not thread-safe yet");
+		}
+		flowControl = new Lifecycle.BasicCallback();
+		final Lifecycle lifecycle = Adaptable.Factory.getAdapter(inspector, Lifecycle.class, null);
+		if (lifecycle != null) {
+			lifecycle.start(-1, flowControl, flowControl);
+		}
+		return lifecycle;
+	}
+	
+	private void lifecycleTearDown(Lifecycle lifecycle) {
+		if (lifecycle != null) {
+			lifecycle.finish(flowControl);
+		}
+		flowControl = null;
+	}
 
 	private void internalInspectChangelog(DataAccess da, Inspector inspector) throws IOException {
 		if (da.isEmpty()) {