/// <summary> /// Bumps, or signals creation/completion/status update request, based on the stream's current state. /// </summary> /// <param name="streamStatus">Stream status with the source data filled in with info on the stream being signaled</param> /// <param name="cancellationToken">Used to cancel the operation</param> /// <returns>Stream status with target details filled in.</returns> /// <remarks> /// A returned value with equivalent target and sources hashes and a segment length equal to the full data length, /// then a completion signal was sent. In this case, <see cref="OnUploadCompleted( string,string)"/> will have been called. /// Otherwise, data uploading may begin/proceed if the caller has enough info, or the caller may bump again with a more /// targeted segment length. /// </remarks> public async Task <StreamStatus> BumpAsync(StreamStatus streamStatus, CancellationToken cancellationToken = default) { try { using (var client = new snh.HttpClient()) { var response = await client.PatchWithAuthenticationAsync( $"{this.Config.BaseControllerPath}{this.DataSinkControllerPath}", streamStatus.ToJson(), this.AuthenticationHeaderGenerator, this.Logger, cancellationToken).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { throw new DataSinkHttpClientException("Unsuccessful service call response") { ErrorResponse = response }; } using (var streamReader = new StreamReader(await response.Content.ReadAsStreamAsync().ConfigureAwait(false))) using (var jsonTextReader = new JsonTextReader(streamReader)) { cancellationToken.ThrowIfCancellationRequested(); return(new JsonSerializer().Deserialize <StreamStatus>(jsonTextReader)); } } } catch (Exception ex) { this.Logger?.LogError(ex, "Failure to BumpAsync"); throw; } }
public async Task <IActionResult> Bump([FromBody] StreamStatus streamStatus, CancellationToken cancellationToken = default) { this.Logger?.LogDebug("Processing {Verb} request for {StreamStatus} at {Uri}", this.Request.Method, streamStatus?.ToJson() ?? "{}", this.Request.Path); var segmentStart = streamStatus.SegmentHashes[0].Start; var segmentLength = streamStatus.SegmentHashes.Sum(sh => sh.Length); var partition = this.HttpContextAccessor.HttpContext?.User?.Identity?.Name; var targetFilePath = this.GetTargetFilePath(partition, streamStatus.Descriptor.Id); if (!Directory.Exists(Path.GetDirectoryName(targetFilePath))) { Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)); } using (var targetStream = new FileStream(targetFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { switch (targetStream.Length) { case long l when l < streamStatus.Descriptor.Length: // more efficient than SetLength since the file isn't filled with 0s Logger?.LogInformation($"Setting length of file {targetFilePath} with partition {partition} and ID {streamStatus.Descriptor.Id} to {streamStatus.Descriptor.Length}."); targetStream.Position = streamStatus.Descriptor.Length - 1; targetStream.WriteByte(0); targetStream.Position = 0; break; case long l when l > streamStatus.Descriptor.Length: // truncate the file Logger?.LogWarning($"Truncating length of file {targetFilePath} with partition {partition} and ID {streamStatus.Descriptor.Id} to {streamStatus.Descriptor.Length}."); targetStream.SetLength(streamStatus.Descriptor.Length); break; } } var targetFile = new FileInfo(targetFilePath); var hasher = new SegmentHasher(); if (hasher.TryGetSubsegmentHashesOfFirstDiff( streamStatus.SegmentHashes, targetFile, segmentStart, segmentLength, (byte)streamStatus.SegmentHashes.Length, new HashAlgorithmName(streamStatus.Descriptor.HashName), out var diffSubsegmentHashes)) { Logger?.LogDebug($"Difference was found for the data stream with partition {partition}, ID {streamStatus.Descriptor.Id}, segment start {segmentStart}, and segment length {segmentLength}."); streamStatus.SegmentHashes = diffSubsegmentHashes; return(this.Ok(streamStatus)); } // TODO null segment hashes is a poor indicator of a lack of difference // and an even poorer indicator that that the OnUploadCompleted event was raised streamStatus.SegmentHashes = null; // no diff // check if we were comparing the full file, and if so, indicate file upload completeness if (segmentStart == 0 && segmentLength == targetFile.Length) { Logger?.LogInformation($"No difference was found for a full data stream with partition {partition} and ID {streamStatus.Descriptor.Id}. Signaling upload completed."); await this.SignalUploadCompleted(partition, streamStatus.Descriptor.Id, targetFile, cancellationToken).ConfigureAwait(false); } else { Logger?.LogInformation($"No difference was found for a partial data stream with partition {partition}, ID {streamStatus.Descriptor.Id}, segment start {segmentStart}, and segment length {segmentLength}."); } return(this.Ok(streamStatus)); }