# HG changeset patch # User Artem Tikhomirov # Date 1355512215 -3600 # Node ID 10ca3ede836781d7a07d11f4883f76cc86f6b168 # Parent 122e0600799fea902f01550a3a2e3995c5ecacb2 Issue 39: Progress and Cancel support for Clone command diff -r 122e0600799f -r 10ca3ede8367 src/org/tmatesoft/hg/core/HgCloneCommand.java --- 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 fncacheFiles = new LinkedList(); 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(); + } + } } - } diff -r 122e0600799f -r 10ca3ede8367 src/org/tmatesoft/hg/repo/HgBundle.java --- 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()) {