Пример #1
0
        public async Task <UploadContextModel> UploadSendChunkAsync(
            UploadContextModel uploadContext,
            int chunkIndex,
            long rangeFrom,
            long rangeTo,
            Stream stream,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var request = await Connection.RequestAsync(new HttpMethod("PATCH"), APIEndpoint.FileUploadChunk.Endpoint, new
            {
                uploadContext,
                chunkIndex,
            }))
            {
                // todo: hmm... will this let us reuse a stream at current offset? no good for retries.
                request.Content = new StreamContent(stream);

                request.Content.Headers.Add("Content-Range",
                                            new ContentRangeHeaderValue(
                                                rangeFrom,
                                                rangeTo,
                                                uploadContext.FileLength
                                                ).ToString()
                                            );
                request.Headers.TransferEncodingChunked = true;

                using (var response = await Connection.SendAsync(request))
                {
                    await Connection.DocumentsEnsureSuccessStatus(response);

                    return(await Connection.ReadAsAsync <UploadContextModel>(response));
                }
            }
        }
Пример #2
0
        public async Task <UploadContextModel> PatchRange(
            UploadContextModel uploadContext,
            int chunkIndex,
            CancellationToken cancellationToken = default(CancellationToken)
            )
        {
            if (Request.Headers["Content-Range"].Count == 0)
            {
                throw new Exception("Missing required Content-Range header");
            }
            var range = ContentRangeHeaderValue.Parse(Request.Headers["Content-Range"].ToString());

            if (range.From == null)
            {
                throw new ArgumentNullException("Range Header From must be specified");
            }
            if (range.To == null)
            {
                throw new ArgumentNullException("Range Header To must be specified");
            }
            if ((long)range.Length != uploadContext.FileLength)
            {
                throw new ArgumentNullException("Range Header Length does not match UploadContext.FileLength");
            }


            return(await FileContentsService.UploadChunkAsync(
                       uploadContext, (long)range.From, (long)range.To, Request.Body, chunkIndex, cancellationToken));
        }
Пример #3
0
        public async Task <UploadContextModel> UploadChunkAsync(
            UploadContextModel uploadContext,
            long from,
            long to,
            Stream inputStream,
            int chunkIndex,
            CancellationToken cancellationToken = default(CancellationToken)
            )
        {
            var uploadStateToken = UploadTokenModel.Parse(uploadContext.UploadToken);

            var fileLocator = await FileStore.FileLocatorGetAsync(uploadStateToken.Identifier);

            var uploadChunk = new UploadChunkModel
            {
                Identifier   = new UploadChunkIdentifier(uploadStateToken.Identifier, fileLocator + '.' + chunkIndex.ToString()),
                ChunkIndex   = chunkIndex,
                PositionFrom = from,
                PositionTo   = to
            };

            uploadContext.SequentialState = await BackendClient.UploadChunkAsync(
                await LoadConfigurationAsync(uploadStateToken.Identifier as OrganizationIdentifier),
                uploadStateToken.Identifier.UploadKey,
                fileLocator,
                uploadChunk.Identifier.UploadChunkKey,
                chunkIndex,
                uploadContext.TotalChunks,
                uploadContext.SequentialState,
                uploadChunk.PositionFrom,
                uploadChunk.PositionTo,
                uploadContext.FileLength,
                inputStream,
                cancellationToken
                );

            uploadChunk.State   = uploadContext.SequentialState;
            uploadChunk.Success = true;

            await UploadChunkStore.InsertAsync(uploadChunk);

            return(uploadContext);
        }
Пример #4
0
        public async Task <FileModel> UploadCompleteAsync(
            UploadContextModel uploadContext,
            CancellationToken cancellationToken = default(CancellationToken)
            )
        {
            var uploadStateToken = UploadTokenModel.Parse(uploadContext.UploadToken);

            var upload = await UploadStore.GetOneAsync(uploadStateToken.Identifier, new[]
            {
                new PopulationDirective
                {
                    Name = nameof(UploadModel.Chunks)
                }
            });

            var chunkStates = upload.Chunks?.Rows
                              .OrderBy(c => c.ChunkIndex)
                              .Select(c => new ChunkedStatusModel
            {
                ChunkIndex     = c.ChunkIndex,
                UploadChunkKey = c.Identifier.UploadChunkKey,
                State          = c.State,
                Success        = true
            })
                              .ToArray();

            var fileLocator = await FileStore.FileLocatorGetAsync(uploadStateToken.Identifier);

            var returnData = await BackendClient.CompleteChunkedUploadAsync(
                await LoadConfigurationAsync(uploadStateToken.Identifier as OrganizationIdentifier),
                uploadStateToken.Identifier.UploadKey,
                fileLocator,
                chunkStates
                );

            var fileModel = await FileStore.GetOneAsync(uploadStateToken.Identifier);

            await FileStore.UpdateAsync(fileModel);

            if (returnData != null)
            {
                await FileStore.HashSetAsync(
                    fileModel.Identifier,
                    GetHash(returnData, "md5"),
                    GetHash(returnData, "sha1"),
                    GetHash(returnData, "sha256")
                    );
            }

            await FileStore.UploadingStatusSetAsync(fileModel.Identifier, false);

            await UploadStore.Cleanup(upload.Identifier);

            var evt = new FileContentsUploadCompleteEvent
            {
                FileIdentifier = fileModel.Identifier
            };

            evt.Populate(fileModel);

            await EventSender.SendAsync(evt);

            return(fileModel);
        }
Пример #5
0
 public Task <FileModel> UploadEndAsync(UploadContextModel uploadContext, CancellationToken cancellationToken = default(CancellationToken))
 => Connection.APICallAsync <FileModel>(HttpMethod.Post, APIEndpoint.FileUploadEnd, bodyContent: uploadContext, cancellationToken: cancellationToken);
Пример #6
0
        public async Task <ActionResult> UploadChunk(
            PathIdentifier pathIdentifier,
            BrowserFileInformation fileInformation,
            string token,
            CancellationToken cancellationToken = default(CancellationToken)
            )
        {
            if (!Request.ContentLength.HasValue)
            {
                throw new Exception("Missing Content-Length header");
            }
            if (Request.Headers["Content-Range"].Count == 0)
            {
                throw new Exception("Missing Content-Range header");
            }
            if (Request.Headers["Content-Disposition"].Count == 0)
            {
                throw new Exception("Missing Content-Disposition header");
            }

            var range = ContentRangeHeaderValue.Parse(Request.Headers["Content-Range"]);

            if (!range.HasLength)
            {
                throw new Exception("Content-Range header does not include total length");
            }

            long from       = (long)range.From;
            long to         = (long)range.To;
            long fileLength = (long)range.Length;

            var fileName = fileInformation?.Name;

            if (fileName == null)
            {
                var contentDisposition = ContentDispositionHeaderValue.Parse(Request.Headers["Content-Disposition"]);
                fileName = contentDisposition?.FileName;
                if (fileName == null)
                {
                    throw new Exception("Filename is not specified in either Content-Disposition header or fileInformation");
                }

                // for some dumb reason, the ContentDispositionHeaderValue parser doesn't finish parsing file names
                // it leaves them quoted
                if (fileName.StartsWith('"') && fileName.EndsWith('"') && fileName.Length > 1)
                {
                    fileName = WebUtility.UrlDecode(fileName.Substring(1, fileName.Length - 2));
                }
            }

            Stream             stream     = Request.Body;
            UploadContextModel tokenState = null;

            // test retries
            //if ((new Random()).NextDouble() < .3)
            //    throw new Exception("Chaos Monkey");

            bool isFirstChunk = from == 0;
            bool isLastChunk  = to == (fileLength - 1);

            FileIdentifier fileIdentifier = null;

            var folderModel = await Connection.Folder.GetOrThrowAsync(pathIdentifier);

            var modules = ModuleConfigurator.GetActiveModules(folderModel);

            // === Step 1: Begin Upload
            if (isFirstChunk)
            {
                var fileModel = new FileModel
                {
                    Identifier = new FileIdentifier(pathIdentifier, null),
                    Name       = fileName,
                    Modified   = fileInformation?.LastModified != null
                        ? epoch.AddMilliseconds(fileInformation.LastModified.Value)
                        : DateTime.UtcNow,
                    Length   = fileLength,
                    MimeType = fileInformation?.Type ?? Request.ContentType ?? "application/octet-stream",
                }.InitializeEmptyMetadata();

                fileModel.Created = new DateTime(Math.Min(DateTime.UtcNow.Ticks, fileModel.Modified.Ticks), DateTimeKind.Utc);

                // some browsers will send us the relative path of a file during a folder upload
                var relativePath = fileInformation?.FullPath;
                if (!string.IsNullOrWhiteSpace(relativePath) && relativePath != "/")
                {
                    if (relativePath.StartsWith("/"))
                    {
                        relativePath = relativePath.Substring(1);
                    }

                    pathIdentifier = pathIdentifier.CreateChild(relativePath);
                }

                fileModel.MetaPathIdentifierWrite(pathIdentifier);

                foreach (var module in modules)
                {
                    await module.PreUploadAsync(folderModel, fileModel);
                }

                tokenState = await Connection.File.UploadBeginAsync(fileModel, cancellationToken : cancellationToken);
            }
            else
            {
                if (token == null)
                {
                    throw new Exception("Uploaded secondary chunk without token");
                }

                tokenState = JsonConvert.DeserializeObject <UploadContextModel>(token);
            }


            // === Step 2: Send this Chunk
            using (stream)
            {
                if (!isLastChunk)
                {
                    if (to - from != tokenState.ChunkSize - 1)
                    {
                        throw new Exception($"Chunk Size Mismatch: received ({to - from}) expected ({tokenState.ChunkSize})");
                    }
                }

                int chunkIndex = (int)(from / tokenState.ChunkSize);

                tokenState = await Connection.File.UploadSendChunkAsync(
                    tokenState,
                    chunkIndex,
                    from,
                    to,
                    stream,
                    cancellationToken : cancellationToken
                    );
            }

            // === Step 3: End the Upload
            if (isLastChunk)  // if file is one chunk, all three steps happen in a single call
            {
                var fileModel = await Connection.File.UploadEndAsync(tokenState, cancellationToken : cancellationToken);

                fileIdentifier = fileModel.Identifier;

                foreach (var module in modules)
                {
                    await module.PostUploadAsync(folderModel, fileModel);
                }
            }

            return(Json(new APIResponse <UploadedFileResponse>
            {
                Response = new UploadedFileResponse
                {
                    FileIdentifier = fileIdentifier,
                    Token = JsonConvert.SerializeObject(tokenState)
                }
            }));
        }
Пример #7
0
 public async Task <FileModel> PostEnd([FromBody] UploadContextModel uploadContext)
 {
     return(await FileContentsService.UploadCompleteAsync(uploadContext));
 }