/// <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;
            }
        }
Beispiel #2
0
        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));
        }