Ejemplo n.º 1
0
        public async Task <IActionResult> GetAsync(string path, long length, long bytesPerChunk)
        {
            // 0. Wait
            await Task.Delay(Constants.FILESTITCH_DELAY);

            // 1. Determine which chunk is next up
            var      chunkFolder = $"{path}.{Constants.PARTIAL}";
            FileInfo fileinfo    = null;

            try
            {
                policyFile.Execute(() => fileinfo = new FileInfo(path));
            }
            catch (Exception ex) when(ex is SecurityException || ex is UnauthorizedAccessException)
            {
                return(Forbid());
            }
            catch (Exception ex) when(ex is FileNotFoundException || ex is DirectoryNotFoundException)
            {
                return(NotFound());
            }

            var fileLength = fileinfo.Length;

            if (fileLength == length)
            {
                try
                {
                    policyFile.Execute(() => Directory.Delete(chunkFolder));
                    return(Ok());
                }
                catch (Exception ex) when(ex is SecurityException || ex is UnauthorizedAccessException)
                {
                    return(Forbid());
                }
                catch (Exception ex) when(ex is FileNotFoundException || ex is DirectoryNotFoundException)
                {
                    return(NotFound());
                }
            }
            long nextChunk = fileLength / bytesPerChunk;

            // 2. Determine if file is ready for next chunk
            bool fileReady = fileLength % bytesPerChunk == 0;

            // 3. Determine if next-up chunk is ready to go
            var  chunkPath       = $"{chunkFolder}/{nextChunk.ToString(Constants.ID_FORMAT)}";
            var  chunkinfo       = new FileInfo(chunkPath);
            var  lastChunk       = length % bytesPerChunk != 0 ? length / bytesPerChunk + 1 : length / bytesPerChunk;
            var  lastChunkLength = length - lastChunk * bytesPerChunk;
            bool chunkReady      = chunkinfo.Length == bytesPerChunk || (nextChunk == lastChunk && chunkinfo.Length == lastChunkLength);

            // 4. If chunk is ready to be stitched, stitch
            if (chunkReady)
            {
                try
                {
                    await policy.ExecuteAsync(async() =>
                    {
                        using (var chunkStream = chunkinfo.Open(FileMode.Open, FileAccess.Read, FileShare.None))
                            using (var fileStream = fileinfo.Open(FileMode.Append, FileAccess.Write, FileShare.None))
                            {
                                await chunkStream.CopyToAsync(fileStream);
                            }
                        chunkinfo.Delete();
                    });
                }
                catch (Exception ex) when(ex is SecurityException || ex is UnauthorizedAccessException)
                {
                    return(Forbid());
                }
                catch (Exception ex) when(ex is FileNotFoundException || ex is DirectoryNotFoundException)
                {
                    return(NotFound());
                }
            }

            // 5. Let someone else do the rest of the work
            var builder       = new HttpRequestBuilder();
            var stitchRequest = builder.SetPath("/api/filestitch")
                                .AddQuery("path", path)
                                .AddQuery("length", length.ToString())
                                .AddQuery("bytesPerChunk", bytesPerChunk.ToString())
                                .Build();
            var stitchMessage = httpMessageFactory.CreateMessage(HttpContext.Request.Headers, stitchRequest);
            await policy.ExecuteAsync(() => topic.SendAsync(stitchMessage));

            return(Ok());
        }
        public async Task <IActionResult> GetAsync(string path)
        {
            long bytesPerMessage = config.GetBytesPerMessage();

            // 1. Check the file exists
            var fileName = Uri.UnescapeDataString(path);
            var partial  = $"{fileName}.{Constants.PARTIAL}";

            try
            {
                FileInfo file = null;
                policyFile.Execute(() => file = new FileInfo(fileName));

                if (file.Exists)
                {
                    return(Conflict());
                }

                policyFile.Execute(() => Directory.CreateDirectory(partial));
            }
            catch (Exception ex) when(ex is SecurityException || ex is UnauthorizedAccessException)
            {
                return(Forbid());
            }
            catch (Exception ex) when(ex is FileNotFoundException || ex is DirectoryNotFoundException)
            {
                return(NotFound());
            }

            // 2. Check the blob exists
            var blob = container.GetBlockBlobReference(path);

            if (!await policy.ExecuteAsync(blob.ExistsAsync))
            {
                return(NotFound());
            }

            // 2. calculate number of message queue messages to create
            await policy.ExecuteAsync(blob.FetchAttributesAsync);

            var  blobLength  = blob.Properties.Length;
            long numMessages = blobLength % bytesPerMessage != 0 ? blobLength / bytesPerMessage + 1 : blobLength / bytesPerMessage;

            // 3. setup reusable objects to minimize `new` memory
            int messagesPerSend = config.GetMessagesPerSend();
            var builder         = new HttpRequestBuilder()
                                  .SetHost("/")
                                  .SetMethod("get")
                                  .SetPath("/api/filechunk");

            foreach (var header in HttpContext.Request.Headers)
            {
                builder.AddHeader(header.Key, header.Value);
            }

            var httpRequestArray = Enumerable.Range(0, messagesPerSend).Select(i => builder.Build()).ToArray();
            var messageArray     = Enumerable.Range(0, messagesPerSend).Select(i => messageFactory.CreateMessage(HttpContext.Request.Headers)).ToArray();
            var messageList      = new List <Message>(messagesPerSend);

            // 4. bin messages into bulk groups and send by bulk: outer loop defines bulk bin, inner loop populates bulk bin
            for (long i = 0; i < numMessages; i += messagesPerSend)
            {
                messageList.Clear();
                for (long j = i; j < numMessages && j < messagesPerSend; j++)
                {
                    var request = httpRequestArray[j % messagesPerSend];
                    request.Query = $"path={path}&length={bytesPerMessage}&startindex = {j * bytesPerMessage}";

                    var message = messageArray[j % messagesPerSend];
                    message.Body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(request));

                    messageList.Add(message);
                }

                // 5. send messages in bulk to topic
                await policy.ExecuteAsync(() => topic.SendAsync(messageList));
            }

            // 6. send a message to a controller that can stitch chunks together
            var stitchRequest = builder.SetPath("/api/filestitch")
                                .AddQuery("path", path)
                                .AddQuery("length", blobLength.ToString())
                                .AddQuery("bytesPerChunk", bytesPerMessage.ToString())
                                .Build();
            var stitchMessage = messageFactory.CreateMessage(HttpContext.Request.Headers, stitchRequest);
            await policy.ExecuteAsync(async() => await topic.SendAsync(stitchMessage));

            return(Ok());
        }