/// <summary> /// 上传数据流 /// </summary> /// <param name="stream">(确定长度的)数据流</param> /// <param name="key">要保存的key</param> /// <param name="token">上传凭证</param> /// <param name="putExtra">上传可选设置</param> /// <returns>上传数据流后的返回结果</returns> public async Task <HttpResult> UploadStream(Stream stream, string key, string token, PutExtra putExtra) { if (putExtra == null) { putExtra = new PutExtra(); } if (string.IsNullOrEmpty(putExtra.MimeType)) { putExtra.MimeType = ContentType.APPLICATION_OCTET_STREAM; } if (putExtra.ProgressHandler == null) { putExtra.ProgressHandler = DefaultUploadProgressHandler; } if (putExtra.UploadController == null) { putExtra.UploadController = DefaultUploadController; } var fileName = key; if (string.IsNullOrEmpty(key)) { fileName = "fname_temp"; } var result = new HttpResult(); try { var boundary = HttpManager.CreateFormDataBoundary(); var content = new MultipartFormDataContent(boundary); var length = stream.Length; putExtra.ProgressHandler(0, length); // Key if (!string.IsNullOrEmpty(key)) { content.Add(new StringContent(key), "key"); } // Token content.Add(new StringContent(token), "token"); // Other params if (putExtra.Params != null) { foreach (var param in putExtra.Params) { content.Add(new StringContent(param.Value), param.Key); } } // Reuse stream if (!stream.CanSeek) { var ms = new MemoryStream((int)stream.Length); stream.CopyTo(ms); stream.Dispose(); stream = ms; } // CRC32 var crc32 = Crc32.CheckSumStream(stream); stream.Seek(0, SeekOrigin.Begin); content.Add(new StringContent(crc32.ToString()), "crc32"); // Primary content var part = new StreamContent(stream); part.Headers.ContentType = MediaTypeHeaderValue.Parse(putExtra.MimeType); content.Add(part, "file", fileName); // Get upload host var ak = UpToken.GetAccessKeyFromUpToken(token); var bucket = UpToken.GetBucketFromUpToken(token); if (ak == null || bucket == null) { return(HttpResult.InvalidToken); } var uploadHost = await _config.UpHost(ak, bucket); // TODO: Real progress putExtra.ProgressHandler(length / 5, length); result = await _httpManager.PostAsync(uploadHost, content, boundary); putExtra.ProgressHandler(length, length); if (result.Code == (int)HttpCode.OK) { result.RefText += $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff}] [FormUpload] Uploaded: #STREAM# ==> \"{key}\"\n"; } else { result.RefText += $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff}] [FormUpload] Failed: code = {result.Code}, text = {result.Text}\n"; } } catch (Exception ex) { var sb = new StringBuilder(); sb.Append($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff}] [FormUpload] Error: "); var e = ex; while (e != null) { sb.Append(e.Message + " "); e = e.InnerException; } sb.AppendLine(); if (ex is QiniuException qex) { result.Code = qex.HttpResult.Code; result.RefCode = qex.HttpResult.Code; result.Text = qex.HttpResult.Text; result.RefText += sb.ToString(); } else { result.RefCode = (int)HttpCode.USER_UNDEF; result.RefText += sb.ToString(); } } return(result); }
/// <summary> /// 创建块(携带首片数据),同时检查CRC32 /// </summary> /// <param name="resumeBlockerObj">创建分片上次的块请求</param> private async Task MakeBlock(object resumeBlockerObj) { var resumeBlocker = (ResumeBlocker)resumeBlockerObj; var doneEvent = resumeBlocker.DoneEvent; var blockMakeResults = resumeBlocker.BlockMakeResults; var putExtra = resumeBlocker.PutExtra; var blockIndex = resumeBlocker.BlockIndex; var result = new HttpResult(); //check whether to cancel while (true) { var upCtl = resumeBlocker.PutExtra.UploadController(); if (upCtl == UploadControllerAction.Suspended) { doneEvent.WaitOne(1000); } else if (upCtl == UploadControllerAction.Aborted) { doneEvent.Set(); result.Code = (int)HttpCode.USER_CANCELED; result.RefCode = (int)HttpCode.USER_CANCELED; result.RefText += $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff}] [ResumableUpload] Info: upload task is aborted, mkblk {blockIndex}\n"; blockMakeResults.Add(blockIndex, result); return; } else { break; } } var blockBuffer = resumeBlocker.BlockBuffer; var blockSize = blockBuffer.Length; var upToken = resumeBlocker.UploadToken; var uploadedBytesDict = resumeBlocker.UploadedBytesDict; var fileSize = resumeBlocker.FileSize; var progressLock = resumeBlocker.ProgressLock; var resumeInfo = resumeBlocker.ResumeInfo; try { //get upload host var ak = UpToken.GetAccessKeyFromUpToken(upToken); var bucket = UpToken.GetBucketFromUpToken(upToken); if (ak == null || bucket == null) { result = HttpResult.InvalidToken; doneEvent.Set(); return; } var uploadHost = await _config.UpHost(ak, bucket); var url = $"{uploadHost}/mkblk/{blockSize}"; var upTokenStr = $"UpToken {upToken}"; using (var ms = new MemoryStream(blockBuffer, 0, blockSize)) { var data = ms.ToArray(); result = await _httpManager.PostDataAsync(url, data, token : upTokenStr); if (result.Code == (int)HttpCode.OK) { var rc = JsonConvert.DeserializeObject <ResumeContext>(result.Text); if (rc.Crc32 > 0) { var crc1 = rc.Crc32; var crc2 = Crc32.CheckSumSlice(blockBuffer, 0, blockSize); if (crc1 != crc2) { result.RefCode = (int)HttpCode.USER_NEED_RETRY; result.RefText += $" CRC32: remote={crc1}, local={crc2}\n"; } else { //write the mkblk context resumeInfo.Contexts[blockIndex] = rc.Ctx; resumeInfo.ExpiredAt = rc.ExpiredAt; lock (progressLock) { uploadedBytesDict["UploadProgress"] += blockSize; } putExtra.ProgressHandler(uploadedBytesDict["UploadProgress"], fileSize); } } else { result.RefText += $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff}] JSON Decode Error: text = {result.Text}"; result.RefCode = (int)HttpCode.USER_NEED_RETRY; } } else { result.RefCode = (int)HttpCode.USER_NEED_RETRY; } } } catch (Exception ex) { var sb = new StringBuilder(); sb.Append($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff}] mkblk Error: "); var e = ex; while (e != null) { sb.Append(e.Message + " "); e = e.InnerException; } sb.AppendLine(); if (ex is QiniuException qex) { result.Code = qex.HttpResult.Code; result.RefCode = qex.HttpResult.Code; result.Text = qex.HttpResult.Text; result.RefText += sb.ToString(); } else { result.RefCode = (int)HttpCode.USER_UNDEF; result.RefText += sb.ToString(); } } //return the http result blockMakeResults.Add(blockIndex, result); doneEvent.Set(); }