public HgResumeApiResponse Execute(string method, HgResumeApiParameters request, byte[] bytesToWrite, int secondsBeforeTimeout) { ValidateParameters(method, request, bytesToWrite, secondsBeforeTimeout); if (method == "getRevisions") { IEnumerable<Revision> revisions = _repo.GetAllRevisions(); if (revisions.Count() == 0) { return ApiResponses.Revisions("0:default"); } var revisionStrings = _repo.GetAllRevisions().Select(rev => rev.Number.Hash + ':' + rev.Branch); return ApiResponses.Revisions(string.Join("|", revisionStrings.ToArray())); } if (method == "finishPushBundle") { return ApiResponses.PushComplete(); } if (method == "pushBundleChunk") { _executeCount++; if (_cancelCount == _executeCount) { _progress.CancelRequested = true; return ApiResponses.Failed(""); } if (_failCount == _executeCount) { return ApiResponses.Failed(""); } if (_timeoutList.Contains(_executeCount)) { return null; } if (_serverUnavailableList.Any(i => i.ExecuteCount == _executeCount)) { return ApiResponses.NotAvailable( _serverUnavailableList.Where(i => i.ExecuteCount == _executeCount).First().Message ); } _helper = new PullStorageManager(_localStorage.Path, request.TransId); //int bundleSize = Convert.ToInt32(parameters["bundleSize"]); //int offset = Convert.ToInt32(parameters["offset"]); //int chunkSize = bytesToWrite.Length; _helper.WriteChunk(request.StartOfWindow, bytesToWrite); if (request.StartOfWindow + request.ChunkSize == request.BundleSize) { if (_repo.Unbundle(_helper.BundlePath)) { return ApiResponses.PushComplete(); } return ApiResponses.Reset(); } if (request.StartOfWindow + request.ChunkSize < request.BundleSize) { return ApiResponses.PushAccepted(_helper.StartOfWindow); } return ApiResponses.Failed("offset + chunkSize > bundleSize !"); } return ApiResponses.Custom(HttpStatusCode.InternalServerError); }
public bool Pull(string[] baseRevisions) { var tipRevision = _repo.GetTip(); var localTip = "0"; string errorMessage; if (tipRevision != null) { localTip = tipRevision.Number.Hash; } if (baseRevisions.Length == 0) { errorMessage = "Pull failed: No base revision."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } var bundleHelper = new PullStorageManager(PathToLocalStorage, GetBundleIdFilenameBase("pull", baseRevisions, localTip)); var req = new HgResumeApiParameters { RepoId = _apiServer.ProjectId, BaseHashes = baseRevisions, TransId = bundleHelper.TransactionId, StartOfWindow = bundleHelper.StartOfWindow, ChunkSize = InitialChunkSize }; int bundleSize = 0; int loopCtr = 1; bool retryLoop; do { if (_progress.CancelRequested) { throw new UserCancelledException(); } retryLoop = false; var response = PullOneChunk(req); if (response.Status == PullStatus.Unauthorized) { throw new UnauthorizedAccessException(); } if (response.Status == PullStatus.NotAvailable) { _progress.ProgressIndicator.Initialize(); _progress.ProgressIndicator.Finish(); return(false); } if (response.Status == PullStatus.Timeout) { _progress.WriteWarning("Pull operation timed out. Retrying..."); retryLoop = true; continue; } if (response.Status == PullStatus.NoChange) { _progress.WriteMessage("No changes"); bundleHelper.Cleanup(); _progress.ProgressIndicator.Initialize(); _progress.ProgressIndicator.Finish(); return(false); } if (response.Status == PullStatus.InProgress) { _progress.WriteStatus("Preparing data on server"); retryLoop = true; // advance the progress bar 2% to show that something is happening _progress.ProgressIndicator.PercentCompleted = _progress.ProgressIndicator.PercentCompleted + 2; continue; } if (response.Status == PullStatus.InvalidHash) { // this should not happen...but sometimes it gets into a state where it remembers the wrong basehash of the server (CJH Feb-12) retryLoop = true; _progress.WriteVerbose("Invalid basehash response received from server... clearing cache and retrying"); req.BaseHashes = GetHashStringsFromRevisions(GetCommonBaseHashesWithRemoteRepo(false)); continue; } if (response.Status == PullStatus.Fail) { // No need to abort the attempts just because .Net web request says so. See Push also. CP 2012-06 _progress.WriteWarning("Pull data chunk failed"); retryLoop = true; req.StartOfWindow = bundleHelper.StartOfWindow; continue; } if (response.Status == PullStatus.Reset) { retryLoop = true; bundleHelper.Reset(); _progress.WriteVerbose("Server's bundle cache has expired. Restarting pull..."); req.StartOfWindow = bundleHelper.StartOfWindow; continue; } //bundleSizeFromResponse = response.BundleSize; if (loopCtr == 1) { _progress.ProgressIndicator.Initialize(); if (req.StartOfWindow != 0) { string message = String.Format("Resuming pull operation at {0} received", GetHumanReadableByteSize(req.StartOfWindow)); _progress.WriteVerbose(message); } } bundleHelper.WriteChunk(req.StartOfWindow, response.Chunk); req.StartOfWindow = req.StartOfWindow + response.Chunk.Length; req.ChunkSize = response.ChunkSize; if (bundleSize == response.BundleSize && bundleSize != 0) { _progress.ProgressIndicator.PercentCompleted = (int)((long)req.StartOfWindow * 100 / bundleSize); string eta = CalculateEstimatedTimeRemaining(bundleSize, req.ChunkSize, req.StartOfWindow); _progress.WriteStatus(string.Format("Receiving {0} {1}", GetHumanReadableByteSize(bundleSize), eta)); } else { // this is only useful when the bundle size is significantly large (like with a clone operation) such that // the server takes a long time to create the bundle, and the bundleSize continues to rise as the chunks are received bundleSize = response.BundleSize; _progress.WriteStatus(string.Format("Preparing data on server (>{0})", GetHumanReadableByteSize(bundleSize))); } loopCtr++; } while (req.StartOfWindow < bundleSize || retryLoop); if (_repo.Unbundle(bundleHelper.BundlePath)) { _progress.WriteMessage("Pull operation completed successfully"); _progress.ProgressIndicator.Finish(); _progress.WriteMessage("Finished Receiving"); bundleHelper.Cleanup(); var response = FinishPull(req.TransId); if (response == PullStatus.Reset) { /* Calling Pull recursively to finish up another pull will mess up the ProgressIndicator. This case is * // rare enough that it's not worth trying to get the progress indicator working for a recursive Pull() */ _progress.WriteMessage("Remote repo has changed. Initiating additional pull operation"); GetCommonBaseHashesWithRemoteRepo(false); //Rebuild cache with current server information return(Pull()); } // REVIEW: I'm not sure why this was set to the server tip before, if we just pulled then won't our head // be the correct common base? Maybe not if a merge needs to happen, LastKnownCommonBases = new List <Revision>(_repo.BranchingHelper.GetBranches()); return(true); } _progress.WriteError("Received all data but local unbundle operation failed or resulted in multiple heads!"); _progress.ProgressIndicator.Finish(); bundleHelper.Cleanup(); errorMessage = "Pull operation failed"; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); }
public bool Pull(string[] baseRevisions) { var tipRevision = _repo.GetTip(); string localTip = "0"; string errorMessage; if (tipRevision != null) { localTip = tipRevision.Number.Hash; } if (baseRevisions.Length == 0) { errorMessage = "Pull failed: No base revision."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } string bundleId = ""; foreach (var revision in baseRevisions) { bundleId += revision + "_" + localTip + '-'; } bundleId = bundleId.TrimEnd('-'); var bundleHelper = new PullStorageManager(PathToLocalStorage, bundleId); var req = new HgResumeApiParameters { RepoId = _apiServer.ProjectId, BaseHashes = baseRevisions, TransId = bundleHelper.TransactionId, StartOfWindow = bundleHelper.StartOfWindow, ChunkSize = InitialChunkSize }; int bundleSize = 0; int loopCtr = 1; bool retryLoop; do { if (_progress.CancelRequested) { throw new UserCancelledException(); } retryLoop = false; var response = PullOneChunk(req); if (response.Status == PullStatus.Unauthorized) { throw new UnauthorizedAccessException(); } if (response.Status == PullStatus.NotAvailable) { _progress.ProgressIndicator.Initialize(); _progress.ProgressIndicator.Finish(); return false; } if (response.Status == PullStatus.Timeout) { _progress.WriteWarning("Pull operation timed out. Retrying..."); retryLoop = true; continue; } if (response.Status == PullStatus.NoChange) { _progress.WriteMessage("No changes"); bundleHelper.Cleanup(); _progress.ProgressIndicator.Initialize(); _progress.ProgressIndicator.Finish(); return false; } if (response.Status == PullStatus.InProgress) { _progress.WriteStatus("Preparing data on server"); retryLoop = true; // advance the progress bar 2% to show that something is happening _progress.ProgressIndicator.PercentCompleted = _progress.ProgressIndicator.PercentCompleted + 2; continue; } if (response.Status == PullStatus.InvalidHash) { // this should not happen...but sometimes it gets into a state where it remembers the wrong basehash of the server (CJH Feb-12) retryLoop = true; _progress.WriteVerbose("Invalid basehash response received from server... clearing cache and retrying"); req.BaseHashes = GetHashStringsFromRevisions(GetCommonBaseHashesWithRemoteRepo(false)); continue; } if (response.Status == PullStatus.Fail) { // Not sure that we need to abort the attempts just because .Net web request says so. See Push also. CP 2012-06 errorMessage = "Pull data chunk failed"; _progress.WriteError(errorMessage); retryLoop = true; req.StartOfWindow = bundleHelper.StartOfWindow; //_progress.ProgressIndicator.Initialize(); //_progress.ProgressIndicator.Finish(); //throw new HgResumeOperationFailed(errorMessage); continue; } if (response.Status == PullStatus.Reset) { retryLoop = true; bundleHelper.Reset(); _progress.WriteVerbose("Server's bundle cache has expired. Restarting pull..."); req.StartOfWindow = bundleHelper.StartOfWindow; continue; } //bundleSizeFromResponse = response.BundleSize; if (loopCtr == 1) { _progress.ProgressIndicator.Initialize(); if (req.StartOfWindow != 0) { string message = String.Format("Resuming pull operation at {0} received", GetHumanReadableByteSize(req.StartOfWindow)); _progress.WriteVerbose(message); } } bundleHelper.WriteChunk(req.StartOfWindow, response.Chunk); req.StartOfWindow = req.StartOfWindow + response.Chunk.Length; req.ChunkSize = response.ChunkSize; if (bundleSize == response.BundleSize && bundleSize != 0) { _progress.ProgressIndicator.PercentCompleted = (int)((long)req.StartOfWindow * 100 / bundleSize); string eta = CalculateEstimatedTimeRemaining(bundleSize, req.ChunkSize, req.StartOfWindow); _progress.WriteStatus(string.Format("Receiving {0} {1}", GetHumanReadableByteSize(bundleSize), eta)); } else { // this is only useful when the bundle size is significantly large (like with a clone operation) such that // the server takes a long time to create the bundle, and the bundleSize continues to rise as the chunks are received bundleSize = response.BundleSize; _progress.WriteStatus(string.Format("Preparing data on server (>{0})", GetHumanReadableByteSize(bundleSize))); } loopCtr++; } while (req.StartOfWindow < bundleSize || retryLoop); if (_repo.Unbundle(bundleHelper.BundlePath)) { _progress.WriteMessage("Pull operation completed successfully"); _progress.ProgressIndicator.Finish(); _progress.WriteMessage("Finished Receiving"); bundleHelper.Cleanup(); var response = FinishPull(req.TransId); if (response == PullStatus.Reset) { /* Calling Pull recursively to finish up another pull will mess up the ProgressIndicator. This case is // rare enough that it's not worth trying to get the progress indicator working for a recursive Pull() */ _progress.WriteMessage("Remote repo has changed. Initiating additional pull operation"); return Pull(); } // REVIEW: I'm not sure why this was set to the server tip before, if we just pulled then won't our head // be the correct common base? Maybe not if a merge needs to happen, LastKnownCommonBases = new List<Revision>(_repo.BranchingHelper.GetBranches()); return true; } _progress.WriteError("Received all data but local unbundle operation failed or resulted in multiple heads!"); _progress.ProgressIndicator.Finish(); bundleHelper.Cleanup(); errorMessage = "Pull operation failed"; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); }
public void StartOfWindow_NoWrites_Zero() { using (var e = new PullStorageEnvForTest()) { var bundleHelper = new PullStorageManager(e.BaseFolder.Path, "randomHash"); Assert.That(bundleHelper.StartOfWindow, Is.EqualTo(0)); } }
public void WriteChunk_WriteMultipleChunks_AssembledOk() { using (var e = new PullStorageEnvForTest()) { var bundleHelper = new PullStorageManager(e.BaseFolder.Path, "randomHash"); string[] sampleTexts = new[] { "this is a sentence. ", "Another sentence. ", "Yet another word string." }; string combinedText = ""; int startOfWindow = 0; foreach (var sampleText in sampleTexts) { combinedText += sampleText; byte[] bytes = Encoding.UTF8.GetBytes(sampleText); bundleHelper.WriteChunk(startOfWindow, bytes); startOfWindow += bytes.Length; } Assert.That(combinedText, Is.EqualTo(File.ReadAllText(bundleHelper.BundlePath))); } }
public void WriteChunk_Text_WriteOk() { using (var e = new PullStorageEnvForTest()) { var bundleHelper = new PullStorageManager(e.BaseFolder.Path, "randomHash"); string sampleText = "some sample text"; byte[] bytes = Encoding.UTF8.GetBytes(sampleText); bundleHelper.WriteChunk(0, bytes); string fromFile = File.ReadAllText(bundleHelper.BundlePath); Assert.That(sampleText, Is.EqualTo(fromFile)); } }
public void StartOfWindow_WriteBytes_LengthOfBytes() { using (var e = new PullStorageEnvForTest()) { var bundleHelper = new PullStorageManager(e.BaseFolder.Path, "randomHash"); string sampleText = "some sample text"; byte[] bytes = Encoding.UTF8.GetBytes(sampleText); bundleHelper.WriteChunk(0, bytes); Assert.That(bundleHelper.StartOfWindow, Is.EqualTo(bytes.Length)); } }