public void Push() { var baseRevisions = GetCommonBaseHashesWithRemoteRepo(); if (baseRevisions == null) { const string errorMessage = "Push failed: A common revision could not be found with the server."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } // create a bundle to push var bundleHelper = new PushStorageManager(PathToLocalStorage, GetBundleIdFilenameBase("push", baseRevisions.Select(rev => rev.Number.Hash), _repo.GetTip().Number.Hash)); var bundleFileInfo = new FileInfo(bundleHelper.BundlePath); if (bundleFileInfo.Length == 0) { _progress.WriteStatus("Preparing data to send"); bool bundleCreatedSuccessfully = _repo.MakeBundle(GetHashStringsFromRevisions(baseRevisions), bundleHelper.BundlePath); if (!bundleCreatedSuccessfully) { // try again after clearing revision cache LastKnownCommonBases = new List <Revision>(); baseRevisions = GetCommonBaseHashesWithRemoteRepo(); bundleCreatedSuccessfully = _repo.MakeBundle(GetHashStringsFromRevisions(baseRevisions), bundleHelper.BundlePath); if (!bundleCreatedSuccessfully) { const string errorMessage = "Push failed: Unable to create local bundle."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } } bundleFileInfo.Refresh(); if (bundleFileInfo.Length == 0) { bundleHelper.Cleanup(); _progress.WriteMessage("No changes to send. Push operation completed"); return; } } var req = new HgResumeApiParameters { RepoId = _apiServer.ProjectId, TransId = bundleHelper.TransactionId, StartOfWindow = 0, BundleSize = (int)bundleFileInfo.Length }; req.ChunkSize = (req.BundleSize < InitialChunkSize) ? req.BundleSize : InitialChunkSize; // req.BaseHash = baseRevisions; <-- Unless I'm not reading the php right we don't need to set this on push. JLN Aug-12 _progress.ProgressIndicator.Initialize(); _progress.WriteStatus("Sending data"); int loopCtr = 0; do // loop until finished... or until the user cancels { loopCtr++; if (_progress.CancelRequested) { throw new UserCancelledException(); } int dataRemaining = req.BundleSize - req.StartOfWindow; if (dataRemaining < req.ChunkSize) { req.ChunkSize = dataRemaining; } byte[] bundleChunk = bundleHelper.GetChunk(req.StartOfWindow, req.ChunkSize); /* API parameters * $repoId, $baseHash, $bundleSize, $offset, $data, $transId * */ var response = PushOneChunk(req, bundleChunk); if (response.Status == PushStatus.NotAvailable) { _progress.ProgressIndicator.Initialize(); _progress.ProgressIndicator.Finish(); return; } if (response.Status == PushStatus.Timeout) { _progress.WriteWarning("Push operation timed out. Retrying..."); continue; } if (response.Status == PushStatus.InvalidHash) { // this should not happen...but sometimes it gets into a state where it remembers the wrong basehash of the server (CJH Feb-12) _progress.WriteVerbose("Invalid basehash response received from server... clearing cache and retrying"); // req.BaseHash = GetCommonBaseHashesWithRemoteRepo(false); <-- Unless I'm misreading the php we don't need to set this on a push. JLN Aug-12 continue; } if (response.Status == PushStatus.Fail) { // This 'Fail' intentionally aborts the push attempt. I think we can continue to go around the loop and retry. See Pull also. CP 2012-06 continue; //var errorMessage = "Push operation failed"; //_progress.WriteError(errorMessage); //throw new HgResumeOperationFailed(errorMessage); } if (response.Status == PushStatus.Reset) { FinishPush(req.TransId); bundleHelper.Cleanup(); const string errorMessage = "Push failed: Server reset."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } if (response.Status == PushStatus.Complete || req.StartOfWindow >= req.BundleSize) { if (response.Status == PushStatus.Complete) { _progress.WriteMessage("Finished sending"); } else { _progress.WriteMessage("Finished sending. Server unpacking data"); } _progress.ProgressIndicator.Finish(); // update our local knowledge of what the server has LastKnownCommonBases = new List <Revision>(_repo.BranchingHelper.GetBranches()); // This may be a little optimistic, the server may still be unbundling the data. //FinishPush(req.TransId); // We can't really tell when the server has finished processing our pulled data. The server cleans up after itself. CP 2012-07 bundleHelper.Cleanup(); return; } req.ChunkSize = response.ChunkSize; req.StartOfWindow = response.StartOfWindow; if (loopCtr == 1 && req.StartOfWindow > req.ChunkSize) { string message = String.Format("Resuming push operation at {0} sent", GetHumanReadableByteSize(req.StartOfWindow)); _progress.WriteVerbose(message); } string eta = CalculateEstimatedTimeRemaining(req.BundleSize, req.ChunkSize, req.StartOfWindow); _progress.WriteStatus(string.Format("Sending {0} {1}", GetHumanReadableByteSize(req.BundleSize), eta)); _progress.ProgressIndicator.PercentCompleted = (int)((long)req.StartOfWindow * 100 / req.BundleSize); } while (req.StartOfWindow < req.BundleSize); }
private PushResponse PushOneChunk(HgResumeApiParameters request, byte[] dataToPush) { var pushResponse = new PushResponse(PushStatus.Fail); try { HgResumeApiResponse response = _apiServer.Execute("pushBundleChunk", request, dataToPush, TimeoutInSeconds); if (response == null) { _progress.WriteVerbose("API REQ: {0} Timeout"); pushResponse.Status = PushStatus.Timeout; return(pushResponse); } /* API returns the following HTTP codes: * 200 OK (SUCCESS) * 202 Accepted (RECEIVED) * 412 Precondition Failed (RESEND) * 400 Bad Request (FAIL, UNKNOWNID, and RESET) */ _progress.WriteVerbose("API REQ: {0} RSP: {1} in {2}ms", _apiServer.Url, response.HttpStatus, response.ResponseTimeInMilliseconds); if (response.HttpStatus == HttpStatusCode.ServiceUnavailable && response.Content.Length > 0) { var msg = String.Format("Server temporarily unavailable: {0}", Encoding.UTF8.GetString(response.Content)); _progress.WriteError(msg); pushResponse.Status = PushStatus.NotAvailable; return(pushResponse); } if (response.ResumableResponse.HasNote) { _progress.WriteWarning(String.Format("Server replied: {0}", response.ResumableResponse.Note)); } // the chunk was received successfully if (response.HttpStatus == HttpStatusCode.Accepted) { pushResponse.StartOfWindow = response.ResumableResponse.StartOfWindow; pushResponse.Status = PushStatus.Received; pushResponse.ChunkSize = CalculateChunkSize(request.ChunkSize, response.ResponseTimeInMilliseconds); return(pushResponse); } // the final chunk was received successfully if (response.HttpStatus == HttpStatusCode.OK) { pushResponse.Status = PushStatus.Complete; return(pushResponse); } if (response.HttpStatus == HttpStatusCode.BadRequest) { if (response.ResumableResponse.Status == "UNKNOWNID") { _progress.WriteError("The server {0} does not have the project '{1}'", _targetLabel, _apiServer.ProjectId); return(pushResponse); } if (response.ResumableResponse.Status == "RESET") { _progress.WriteError("Push failed: All chunks were pushed to the server, but the unbundle operation failed. Try again later."); _progress.WriteVerbose(string.Format("Additional error info: {0}", response.ResumableResponse.Error)); pushResponse.Status = PushStatus.Reset; return(pushResponse); } if (response.ResumableResponse.HasError) { if (response.ResumableResponse.Error == "invalid baseHash") { pushResponse.Status = PushStatus.InvalidHash; } else { _progress.WriteError("Server Error: {0}", response.ResumableResponse.Error); } return(pushResponse); } } _progress.WriteWarning("Invalid Server Response '{0}'", response.HttpStatus); return(pushResponse); } catch (WebException e) { _progress.WriteWarning(String.Format("Push data chunk failed: {0}", e.Message)); return(pushResponse); } }
public HgResumeApiResponse Execute(string method, HgResumeApiParameters request, int secondsBeforeTimeout) { return Execute(method, request, new byte[0], secondsBeforeTimeout); }
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 HgResumeApiResponse Execute(string method, HgResumeApiParameters request, byte[] contentToSend, int secondsBeforeTimeout) { ValidateParameters(method, request, contentToSend, secondsBeforeTimeout); HgResumeApiResponse response; if (_responseQueue.Count > 0) { response = _responseQueue.Dequeue(); if (response.HttpStatus == HttpStatusCode.RequestTimeout) { return null; } } else { response = new HgResumeApiResponse {HttpStatus = HttpStatusCode.InternalServerError}; } response.ResponseTimeInMilliseconds = 200; return response; }
public HgResumeApiResponse Execute(string method, HgResumeApiParameters request, byte[] bytesToWrite, int secondsBeforeTimeout) { ValidateParameters(method, request, bytesToWrite, secondsBeforeTimeout); _executeCount++; if (method == "finishPullBundle") { if (CurrentTip != OriginalTip) { PrepareBundle(HgResumeTransport.GetHashStringsFromRevisions(_repo.BranchingHelper.GetBranches())); return ApiResponses.Reset(); // repo changed in between pulls } return ApiResponses.Custom(HttpStatusCode.OK); } if (method == "getRevisions") { IEnumerable<string> revisions = _repo.GetAllRevisions().Select(rev => rev.Number.Hash + ':' + rev.Branch); return ApiResponses.Revisions(string.Join("|", revisions.ToArray())); } if (method == "pullBundleChunk") { 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 ); } if (Array.BinarySearch(request.BaseHashes, 0, request.BaseHashes.Length, CurrentTip) >= 0) { return ApiResponses.PullNoChange(); } var bundleFileInfo = new FileInfo(_helper.BundlePath); if (bundleFileInfo.Exists && bundleFileInfo.Length == 0 || !bundleFileInfo.Exists) { PrepareBundle(request.BaseHashes); } //int offset = Convert.ToInt32(request["offset"]); //int chunkSize = Convert.ToInt32(request["chunkSize"]); var bundleFile = new FileInfo(_helper.BundlePath); if (request.StartOfWindow >= bundleFile.Length) { return ApiResponses.Failed("offset greater than bundleSize"); } var chunk = _helper.GetChunk(request.StartOfWindow, request.ChunkSize); return ApiResponses.PullOk(Convert.ToInt32(bundleFile.Length), chunk); } return ApiResponses.Custom(HttpStatusCode.InternalServerError); }
public HgResumeApiResponse Execute(string method, HgResumeApiParameters parameters, byte[] contentToSend, int secondsBeforeTimeout) { string queryString = parameters.BuildQueryString(); _urlExecuted = String.Format("{0}://{1}/api/v{2}/{3}?{4}", _url.Scheme, _url.Host, APIVERSION, method, queryString); var req = WebRequest.Create(_urlExecuted) as HttpWebRequest; req.UserAgent = String.Format("HgResume v{0}", APIVERSION); req.PreAuthenticate = true; if (!_url.UserInfo.Contains(":")) { throw new HgResumeException("Username or password were not supplied in custom location"); } req.Credentials = new NetworkCredential(UserName, Password); req.Timeout = secondsBeforeTimeout * 1000; // timeout is in milliseconds if (contentToSend.Length == 0) { req.Method = WebRequestMethods.Http.Get; } else { req.Method = WebRequestMethods.Http.Post; req.ContentLength = contentToSend.Length; req.ContentType = "text/plain"; // i'm not sure this is really what we want. The other possibility is "application/x-www-form-urlencoded" using (var reqStream = req.GetRequestStream()) { reqStream.Write(contentToSend, 0, contentToSend.Length); } } HttpWebResponse res; HgResumeApiResponse apiResponse; var stopwatch = new Stopwatch(); stopwatch.Start(); try { using (res = (HttpWebResponse)req.GetResponse()) { apiResponse = HandleResponse(res); } } catch (WebException e) { if (e.Status == WebExceptionStatus.ProtocolError) { using (res = (HttpWebResponse)e.Response) { apiResponse = HandleResponse(res); } } else if (e.Status == WebExceptionStatus.Timeout) { apiResponse = null; } else { throw; // throw for other types of network errors (see WebExceptionStatus for the full list of errors) } } finally { stopwatch.Stop(); } if (apiResponse != null) { apiResponse.ResponseTimeInMilliseconds = stopwatch.ElapsedMilliseconds; } return(apiResponse); }
public HgResumeApiResponse Execute(string method, HgResumeApiParameters parameters, byte[] contentToSend, int secondsBeforeTimeout) { string queryString = parameters.BuildQueryString(); Url = string.Format("{0}://{1}/api/v{2}/{3}?{4}", _url.Scheme, _url.Host, ApiVersion, method, queryString); var req = (HttpWebRequest)WebRequest.Create(Url); req.UserAgent = $"HgResume v{ApiVersion}"; req.PreAuthenticate = true; if (string.IsNullOrEmpty(Properties.Settings.Default.LanguageForgeUser) || string.IsNullOrEmpty(ServerSettingsModel.PasswordForSession)) { throw new HgResumeException("Missing username or password"); } req.Credentials = new NetworkCredential(Properties.Settings.Default.LanguageForgeUser, ServerSettingsModel.PasswordForSession); req.Timeout = secondsBeforeTimeout * 1000; // timeout is in milliseconds if (contentToSend.Length == 0) { req.Method = WebRequestMethods.Http.Get; } else { req.Method = WebRequestMethods.Http.Post; req.ContentLength = contentToSend.Length; req.ContentType = "text/plain"; // i'm not sure this is really what we want. The other possibility is "application/x-www-form-urlencoded" using (var reqStream = req.GetRequestStream()) { reqStream.Write(contentToSend, 0, contentToSend.Length); } } HttpWebResponse res; HgResumeApiResponse apiResponse; var stopwatch = new Stopwatch(); stopwatch.Start(); try { using (res = (HttpWebResponse)req.GetResponse()) { apiResponse = HandleResponse(res); } } catch (WebException e) { if (e.Status == WebExceptionStatus.ProtocolError) { using (res = (HttpWebResponse)e.Response) { apiResponse = HandleResponse(res); } } else if (e.Status == WebExceptionStatus.Timeout) { apiResponse = null; } else { throw; // throw for other types of network errors (see WebExceptionStatus for the full list of errors) } } finally { stopwatch.Stop(); } if (apiResponse != null) { apiResponse.ResponseTimeInMilliseconds = stopwatch.ElapsedMilliseconds; } return(apiResponse); }
private PushResponse PushOneChunk(HgResumeApiParameters request, byte[] dataToPush) { var pushResponse = new PushResponse(PushStatus.Fail); try { HgResumeApiResponse response = _apiServer.Execute("pushBundleChunk", request, dataToPush, TimeoutInSeconds); if (response == null) { _progress.WriteVerbose("API REQ: {0} Timeout"); pushResponse.Status = PushStatus.Timeout; return pushResponse; } /* API returns the following HTTP codes: * 200 OK (SUCCESS) * 202 Accepted (RECEIVED) * 412 Precondition Failed (RESEND) * 400 Bad Request (FAIL, UNKNOWNID, and RESET) */ _progress.WriteVerbose("API REQ: {0} RSP: {1} in {2}ms", _apiServer.Url, response.HttpStatus, response.ResponseTimeInMilliseconds); if (response.HttpStatus == HttpStatusCode.ServiceUnavailable && response.Content.Length > 0) { var msg = String.Format("Server temporarily unavailable: {0}", Encoding.UTF8.GetString(response.Content)); _progress.WriteError(msg); pushResponse.Status = PushStatus.NotAvailable; return pushResponse; } if (response.ResumableResponse.HasNote) { _progress.WriteWarning(String.Format("Server replied: {0}", response.ResumableResponse.Note)); } // the chunk was received successfully if (response.HttpStatus == HttpStatusCode.Accepted) { pushResponse.StartOfWindow = response.ResumableResponse.StartOfWindow; pushResponse.Status = PushStatus.Received; pushResponse.ChunkSize = CalculateChunkSize(request.ChunkSize, response.ResponseTimeInMilliseconds); return pushResponse; } // the final chunk was received successfully if (response.HttpStatus == HttpStatusCode.OK) { pushResponse.Status = PushStatus.Complete; return pushResponse; } if (response.HttpStatus == HttpStatusCode.BadRequest) { if (response.ResumableResponse.Status == "UNKNOWNID") { _progress.WriteError("The server {0} does not have the project '{1}'", _targetLabel, _apiServer.ProjectId); return pushResponse; } if (response.ResumableResponse.Status == "RESET") { _progress.WriteError("Push failed: All chunks were pushed to the server, but the unbundle operation failed. Try again later."); pushResponse.Status = PushStatus.Reset; return pushResponse; } if (response.ResumableResponse.HasError) { if (response.ResumableResponse.Error == "invalid baseHash") { pushResponse.Status = PushStatus.InvalidHash; } else { _progress.WriteError("Server Error: {0}", response.ResumableResponse.Error); } return pushResponse; } } _progress.WriteWarning("Invalid Server Response '{0}'", response.HttpStatus); return pushResponse; } catch (WebException e) { _progress.WriteWarning(String.Format("Push data chunk failed: {0}", e.Message)); return pushResponse; } }
public HgResumeApiResponse Execute(string method, HgResumeApiParameters request, int secondsBeforeTimeout) { return(Execute(method, request, new byte[0], secondsBeforeTimeout)); }
private PullResponse PullOneChunk(HgResumeApiParameters request) { var pullResponse = new PullResponse(PullStatus.Fail); try { HgResumeApiResponse response = _apiServer.Execute("pullBundleChunk", request, TimeoutInSeconds); if (response == null) { _progress.WriteVerbose("API REQ: {0} Timeout", _apiServer.Url); pullResponse.Status = PullStatus.Timeout; return pullResponse; } /* API returns the following HTTP codes: * 200 OK (SUCCESS) * 304 Not Modified (NOCHANGE) * 400 Bad Request (FAIL, UNKNOWNID) */ _progress.WriteVerbose("API REQ: {0} RSP: {1} in {2}ms", _apiServer.Url, response.HttpStatus, response.ResponseTimeInMilliseconds); if (response.ResumableResponse.HasNote) { _progress.WriteMessage(String.Format("Server replied: {0}", response.ResumableResponse.Note)); } if (response.HttpStatus == HttpStatusCode.ServiceUnavailable && response.Content.Length > 0) { var msg = String.Format("Server temporarily unavailable: {0}", Encoding.UTF8.GetString(response.Content)); _progress.WriteError(msg); pullResponse.Status = PullStatus.NotAvailable; return pullResponse; } if (response.HttpStatus == HttpStatusCode.NotModified) { pullResponse.Status = PullStatus.NoChange; return pullResponse; } if (response.HttpStatus == HttpStatusCode.Accepted) { pullResponse.Status = PullStatus.InProgress; return pullResponse; } // chunk pulled OK if (response.HttpStatus == HttpStatusCode.OK) { pullResponse.BundleSize = response.ResumableResponse.BundleSize; pullResponse.Status = PullStatus.OK; pullResponse.ChunkSize = CalculateChunkSize(request.ChunkSize, response.ResponseTimeInMilliseconds); pullResponse.Chunk = response.Content; return pullResponse; } if (response.HttpStatus == HttpStatusCode.BadRequest && response.ResumableResponse.Status == "UNKNOWNID") { // this is not implemented currently (feb 2012 cjh) _progress.WriteError("The server {0} does not have the project '{1}'", _targetLabel, request.RepoId); return pullResponse; } if (response.HttpStatus == HttpStatusCode.BadRequest && response.ResumableResponse.Status == "RESET") { pullResponse.Status = PullStatus.Reset; return pullResponse; } if (response.HttpStatus == HttpStatusCode.BadRequest) { if (response.ResumableResponse.HasError) { if (response.ResumableResponse.Error == "invalid baseHash") { pullResponse.Status = PullStatus.InvalidHash; } else { _progress.WriteWarning("Server Error: {0}", response.ResumableResponse.Error); } } return pullResponse; } if (response.HttpStatus == HttpStatusCode.Unauthorized) { _progress.WriteWarning("There is an authorization problem accessing this project. Check the project ID as well as your username and password. Alternatively, you may not be authorized to access this project."); pullResponse.Status = PullStatus.Unauthorized; } _progress.WriteWarning("Invalid Server Response '{0}'", response.HttpStatus); return pullResponse; } catch (WebException e) { _progress.WriteWarning(String.Format("Pull data chunk failed: {0}", e.Message)); return pullResponse; } }
public void Push() { var baseRevisions = GetCommonBaseHashesWithRemoteRepo(); if (baseRevisions == null) { const string errorMessage = "Push failed: A common revision could not be found with the server."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } // create a bundle to push string tip = _repo.GetTip().Number.Hash; string bundleId = ""; foreach (var revision in baseRevisions) { bundleId += String.Format("{0}-{1}", revision.Number.Hash, tip); } var bundleHelper = new PushStorageManager(PathToLocalStorage, bundleId); var bundleFileInfo = new FileInfo(bundleHelper.BundlePath); if (bundleFileInfo.Length == 0) { _progress.WriteStatus("Preparing data to send"); bool bundleCreatedSuccessfully = _repo.MakeBundle(GetHashStringsFromRevisions(baseRevisions), bundleHelper.BundlePath); if (!bundleCreatedSuccessfully) { // try again after clearing revision cache LastKnownCommonBases = new List<Revision>(); baseRevisions = GetCommonBaseHashesWithRemoteRepo(); bundleCreatedSuccessfully = _repo.MakeBundle(GetHashStringsFromRevisions(baseRevisions), bundleHelper.BundlePath); if (!bundleCreatedSuccessfully) { const string errorMessage = "Push failed: Unable to create local bundle."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } } bundleFileInfo.Refresh(); if (bundleFileInfo.Length == 0) { bundleHelper.Cleanup(); _progress.WriteMessage("No changes to send. Push operation completed"); return; } } var req = new HgResumeApiParameters { RepoId = _apiServer.ProjectId, TransId = bundleHelper.TransactionId, StartOfWindow = 0, BundleSize = (int) bundleFileInfo.Length }; req.ChunkSize = (req.BundleSize < InitialChunkSize) ? req.BundleSize : InitialChunkSize; // req.BaseHash = baseRevisions; <-- Unless I'm not reading the php right we don't need to set this on push. JLN Aug-12 _progress.ProgressIndicator.Initialize(); _progress.WriteStatus("Sending data"); int loopCtr = 0; do // loop until finished... or until the user cancels { loopCtr++; if (_progress.CancelRequested) { throw new UserCancelledException(); } int dataRemaining = req.BundleSize - req.StartOfWindow; if (dataRemaining < req.ChunkSize) { req.ChunkSize = dataRemaining; } byte[] bundleChunk = bundleHelper.GetChunk(req.StartOfWindow, req.ChunkSize); /* API parameters * $repoId, $baseHash, $bundleSize, $offset, $data, $transId * */ var response = PushOneChunk(req, bundleChunk); if (response.Status == PushStatus.NotAvailable) { _progress.ProgressIndicator.Initialize(); _progress.ProgressIndicator.Finish(); return; } if (response.Status == PushStatus.Timeout) { _progress.WriteWarning("Push operation timed out. Retrying..."); continue; } if (response.Status == PushStatus.InvalidHash) { // this should not happen...but sometimes it gets into a state where it remembers the wrong basehash of the server (CJH Feb-12) _progress.WriteVerbose("Invalid basehash response received from server... clearing cache and retrying"); // req.BaseHash = GetCommonBaseHashesWithRemoteRepo(false); <-- Unless I'm misreading the php we don't need to set this on a push. JLN Aug-12 continue; } if (response.Status == PushStatus.Fail) { // This 'Fail' intentionally aborts the push attempt. I think we can continue to go around the loop and retry. See Pull also. CP 2012-06 continue; //var errorMessage = "Push operation failed"; //_progress.WriteError(errorMessage); //throw new HgResumeOperationFailed(errorMessage); } if (response.Status == PushStatus.Reset) { FinishPush(req.TransId); bundleHelper.Cleanup(); const string errorMessage = "Push failed: Server reset."; _progress.WriteError(errorMessage); throw new HgResumeOperationFailed(errorMessage); } if (response.Status == PushStatus.Complete || req.StartOfWindow >= req.BundleSize) { if (response.Status == PushStatus.Complete) { _progress.WriteMessage("Finished sending"); } else { _progress.WriteMessage("Finished sending. Server unpacking data"); } _progress.ProgressIndicator.Finish(); // update our local knowledge of what the server has LastKnownCommonBases = new List<Revision>(_repo.BranchingHelper.GetBranches()); // This may be a little optimistic, the server may still be unbundling the data. //FinishPush(req.TransId); // We can't really tell when the server has finished processing our pulled data. The server cleans up after itself. CP 2012-07 bundleHelper.Cleanup(); return; } req.ChunkSize = response.ChunkSize; req.StartOfWindow = response.StartOfWindow; if (loopCtr == 1 && req.StartOfWindow > req.ChunkSize) { string message = String.Format("Resuming push operation at {0} sent", GetHumanReadableByteSize(req.StartOfWindow)); _progress.WriteVerbose(message); } string eta = CalculateEstimatedTimeRemaining(req.BundleSize, req.ChunkSize, req.StartOfWindow); _progress.WriteStatus(string.Format("Sending {0} {1}", GetHumanReadableByteSize(req.BundleSize), eta)); _progress.ProgressIndicator.PercentCompleted = (int)((long)req.StartOfWindow * 100 / req.BundleSize); } while (req.StartOfWindow < req.BundleSize); }
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 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 void ValidateParameters(string method, HgResumeApiParameters request, byte[] contentToSend, int secondsBeforeTimeout) { Assert.That(method, Is.Not.Empty); Assert.That(secondsBeforeTimeout, Is.GreaterThan(0)); if (method == "getRevisions") { Assert.That(request.RepoId, Is.Not.Empty); Assert.That(request.Quantity, Is.GreaterThan(0)); Assert.That(request.StartOfWindow, Is.GreaterThan(-1)); } else if (method == "pullBundleChunk") { Assert.That(request.RepoId, Is.Not.Empty); Assert.That(request.BaseHashes, Is.Not.Empty); Assert.That(request.ChunkSize, Is.GreaterThan(0)); Assert.That(request.StartOfWindow, Is.GreaterThanOrEqualTo(0)); Assert.That(request.TransId, Is.Not.Empty); } else if (method == "pushBundleChunk") { Assert.That(request.RepoId, Is.Not.Empty); Assert.That(request.BundleSize, Is.GreaterThan(0)); Assert.That(request.StartOfWindow, Is.GreaterThanOrEqualTo(0)); Assert.That(request.TransId, Is.Not.Empty); Assert.That(contentToSend.Length, Is.GreaterThan(0)); } else if (method == "finishPushBundle") { Assert.That(request.TransId, Is.Not.Empty); } else if (method == "finishPullBundle") { Assert.That(request.TransId, Is.Not.Empty); } else { throw new HgResumeException(String.Format("unknown method '{0}'", method)); } }
private PullResponse PullOneChunk(HgResumeApiParameters request) { var pullResponse = new PullResponse(PullStatus.Fail); try { HgResumeApiResponse response = _apiServer.Execute("pullBundleChunk", request, TimeoutInSeconds); if (response == null) { _progress.WriteVerbose("API REQ: {0} Timeout", _apiServer.Url); pullResponse.Status = PullStatus.Timeout; return(pullResponse); } /* API returns the following HTTP codes: * 200 OK (SUCCESS) * 304 Not Modified (NOCHANGE) * 400 Bad Request (FAIL, UNKNOWNID) */ _progress.WriteVerbose("API REQ: {0} RSP: {1} in {2}ms", _apiServer.Url, response.HttpStatus, response.ResponseTimeInMilliseconds); if (response.ResumableResponse.HasNote) { _progress.WriteMessage(String.Format("Server replied: {0}", response.ResumableResponse.Note)); } if (response.HttpStatus == HttpStatusCode.ServiceUnavailable && response.Content.Length > 0) { var msg = String.Format("Server temporarily unavailable: {0}", Encoding.UTF8.GetString(response.Content)); _progress.WriteError(msg); pullResponse.Status = PullStatus.NotAvailable; return(pullResponse); } if (response.HttpStatus == HttpStatusCode.NotModified) { pullResponse.Status = PullStatus.NoChange; return(pullResponse); } if (response.HttpStatus == HttpStatusCode.Accepted) { pullResponse.Status = PullStatus.InProgress; return(pullResponse); } // chunk pulled OK if (response.HttpStatus == HttpStatusCode.OK) { pullResponse.BundleSize = response.ResumableResponse.BundleSize; pullResponse.Status = PullStatus.OK; pullResponse.ChunkSize = CalculateChunkSize(request.ChunkSize, response.ResponseTimeInMilliseconds); pullResponse.Chunk = response.Content; return(pullResponse); } if (response.HttpStatus == HttpStatusCode.BadRequest && response.ResumableResponse.Status == "UNKNOWNID") { // this is not implemented currently (feb 2012 cjh) _progress.WriteError("The server {0} does not have the project '{1}'", _targetLabel, request.RepoId); return(pullResponse); } if (response.HttpStatus == HttpStatusCode.BadRequest && response.ResumableResponse.Status == "RESET") { pullResponse.Status = PullStatus.Reset; return(pullResponse); } if (response.HttpStatus == HttpStatusCode.BadRequest) { if (response.ResumableResponse.HasError) { if (response.ResumableResponse.Error == "invalid baseHash") { pullResponse.Status = PullStatus.InvalidHash; } else { _progress.WriteWarning("Server Error: {0}", response.ResumableResponse.Error); } } return(pullResponse); } if (response.HttpStatus == HttpStatusCode.Unauthorized) { _progress.WriteWarning(Resources.ksHgTransptUnauthorized); pullResponse.Status = PullStatus.Unauthorized; } _progress.WriteWarning("Invalid Server Response '{0}'", response.HttpStatus); return(pullResponse); } catch (WebException e) { _progress.WriteWarning(String.Format("Pull data chunk failed: {0}", e.Message)); return(pullResponse); } }
public HgResumeApiResponse Execute(string method, HgResumeApiParameters parameters, byte[] contentToSend, int secondsBeforeTimeout) { string queryString = parameters.BuildQueryString(); _urlExecuted = String.Format("{0}://{1}/api/v{2}/{3}?{4}", _url.Scheme, _url.Host, APIVERSION, method, queryString); var req = WebRequest.Create(_urlExecuted) as HttpWebRequest; req.UserAgent = String.Format("HgResume v{0}", APIVERSION); req.PreAuthenticate = true; if (!_url.UserInfo.Contains(":")) { throw new HgResumeException("Username or password were not supplied in custom location"); } req.Credentials = new NetworkCredential(UserName, Password); req.Timeout = secondsBeforeTimeout * 1000; // timeout is in milliseconds if (contentToSend.Length == 0) { req.Method = WebRequestMethods.Http.Get; } else { req.Method = WebRequestMethods.Http.Post; req.ContentLength = contentToSend.Length; req.ContentType = "text/plain"; // i'm not sure this is really what we want. The other possibility is "application/x-www-form-urlencoded" using (var reqStream = req.GetRequestStream()) { reqStream.Write(contentToSend, 0, contentToSend.Length); } } HttpWebResponse res; HgResumeApiResponse apiResponse; var stopwatch = new Stopwatch(); stopwatch.Start(); try { using (res = (HttpWebResponse)req.GetResponse()) { apiResponse = HandleResponse(res); } } catch(WebException e) { if (e.Status == WebExceptionStatus.ProtocolError) { using (res = (HttpWebResponse)e.Response) { apiResponse = HandleResponse(res); } } else if (e.Status == WebExceptionStatus.Timeout) { apiResponse = null; } else { throw; // throw for other types of network errors (see WebExceptionStatus for the full list of errors) } } finally { stopwatch.Stop(); } if (apiResponse != null) { apiResponse.ResponseTimeInMilliseconds = stopwatch.ElapsedMilliseconds; } return apiResponse; }