/// <summary> /// 创建块(携带首片数据),同时检查CRC32 /// </summary> /// <param name="chunkBuffer">数据片,此操作都会携带第一个数据片</param> /// <param name="blockSize">块大小,除了最后一块可能不足4MB,前面的所有数据块恒定位4MB</param> /// <param name="chunkSize">分片大小,一个块可以被分为若干片依次上传然后拼接或者不分片直接上传整块</param> /// <param name="upToken">上传凭证</param> /// <returns>此操作执行后的返回结果</returns> private HttpResult MakeBlock(byte[] chunkBuffer, long blockSize, long chunkSize, string upToken) { HttpResult result = new HttpResult(); try { //get upload host string ak = UpToken.GetAccessKeyFromUpToken(upToken); string bucket = UpToken.GetBucketFromUpToken(upToken); if (ak == null || bucket == null) { return(HttpResult.InvalidToken); } string uploadHost = this.config.UpHost(ak, bucket); string url = string.Format("{0}/mkblk/{1}", uploadHost, blockSize); string upTokenStr = string.Format("UpToken {0}", upToken); using (MemoryStream ms = new MemoryStream(chunkBuffer, 0, (int)chunkSize)) { byte[] data = ms.ToArray(); result = httpManager.PostData(url, data, upTokenStr); if (result.Code == (int)HttpCode.OK) { ResumeContext rc = JsonConvert.DeserializeObject <ResumeContext>(result.Text); if (rc.Crc32 > 0) { uint crc_1 = rc.Crc32; uint crc_2 = CRC32.CheckSumSlice(chunkBuffer, 0, (int)chunkSize); if (crc_1 != crc_2) { result.RefCode = (int)HttpCode.USER_NEED_RETRY; result.RefText += string.Format(" CRC32: remote={0}, local={1}\n", crc_1, crc_2); } } else { result.RefText += string.Format("[{0}] JSON Decode Error: text = {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Text); result.RefCode = (int)HttpCode.USER_NEED_RETRY; } } else { result.RefCode = (int)HttpCode.USER_NEED_RETRY; } } } catch (Exception ex) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("[{0}] mkblk Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); Exception e = ex; while (e != null) { sb.Append(e.Message + " "); e = e.InnerException; } sb.AppendLine(); if (ex is QiniuException) { QiniuException qex = (QiniuException)ex; 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="stream">待上传文件流</param> /// <param name="key">要保存的文件名称</param> /// <param name="upToken">上传凭证</param> /// <param name="putExtra">可选配置参数</param> /// <returns>上传文件后返回结果</returns> public HttpResult UploadStream(Stream stream, string key, string upToken, PutExtra putExtra) { HttpResult result = new HttpResult(); //check put extra if (putExtra == null) { putExtra = new PutExtra(); } if (putExtra.ProgressHandler == null) { putExtra.ProgressHandler = DefaultUploadProgressHandler; } if (putExtra.UploadController == null) { putExtra.UploadController = DefaultUploadController; } if (putExtra.MaxRetryTimes == 0) { putExtra.MaxRetryTimes = DEFAULT_MAX_RETRY_TIMES; } //start to upload try { long fileSize = stream.Length; long chunkSize = CHUNK_SIZE; long blockSize = BLOCK_SIZE; byte[] chunkBuffer = new byte[chunkSize]; int blockCount = (int)((fileSize + blockSize - 1) / blockSize); int index = 0; // zero block //check resume record file ResumeInfo resumeInfo = null; if (File.Exists(putExtra.ResumeRecordFile)) { bool useLastRecord = false; resumeInfo = ResumeHelper.Load(putExtra.ResumeRecordFile); if (resumeInfo != null && fileSize == resumeInfo.FileSize) { //check whether ctx expired if (!UnixTimestamp.IsContextExpired(resumeInfo.ExpiredAt)) { useLastRecord = true; } } if (useLastRecord) { index = resumeInfo.BlockIndex; } } if (resumeInfo == null) { resumeInfo = new ResumeInfo() { FileSize = fileSize, BlockIndex = 0, BlockCount = blockCount, Contexts = new string[blockCount], ExpiredAt = 0, }; } //read from offset long offset = index * blockSize; string context = null; long expiredAt = 0; long leftBytes = fileSize - offset; long blockLeft = 0; long blockOffset = 0; HttpResult hr = null; ResumeContext rc = null; stream.Seek(offset, SeekOrigin.Begin); var upts = UploadControllerAction.Activated; bool bres = true; var manualResetEvent = new ManualResetEvent(true); int iTry = 0; while (leftBytes > 0) { // 每上传一个BLOCK之前,都要检查一下UPTS upts = putExtra.UploadController(); if (upts == UploadControllerAction.Aborted) { result.Code = (int)HttpCode.USER_CANCELED; result.RefCode = (int)HttpCode.USER_CANCELED; result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is aborted\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); return(result); } else if (upts == UploadControllerAction.Suspended) { if (bres) { bres = false; manualResetEvent.Reset(); result.RefCode = (int)HttpCode.USER_PAUSED; result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is paused\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); } manualResetEvent.WaitOne(1000); } else { if (!bres) { bres = true; manualResetEvent.Set(); result.RefCode = (int)HttpCode.USER_RESUMED; result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is resumed\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); } #region one-block #region mkblk if (leftBytes < BLOCK_SIZE) { blockSize = leftBytes; } else { blockSize = BLOCK_SIZE; } if (leftBytes < CHUNK_SIZE) { chunkSize = leftBytes; } else { chunkSize = CHUNK_SIZE; } //read data buffer stream.Read(chunkBuffer, 0, (int)chunkSize); iTry = 0; while (++iTry <= putExtra.MaxRetryTimes) { result.RefText += string.Format("[{0}] [ResumableUpload] try mkblk#{1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), iTry); hr = MakeBlock(chunkBuffer, blockSize, chunkSize, upToken); if (hr.Code == (int)HttpCode.OK && hr.RefCode != (int)HttpCode.USER_NEED_RETRY) { break; } } if (hr.Code != (int)HttpCode.OK || hr.RefCode == (int)HttpCode.USER_NEED_RETRY) { result.Shadow(hr); result.RefText += string.Format("[{0}] [ResumableUpload] Error: mkblk: code = {1}, text = {2}, offset = {3}, blockSize = {4}, chunkSize = {5}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text, offset, blockSize, chunkSize); return(result); } if ((rc = JsonConvert.DeserializeObject <ResumeContext>(hr.Text)) == null) { result.Shadow(hr); result.RefCode = (int)HttpCode.USER_UNDEF; result.RefText += string.Format("[{0}] [ResumableUpload] mkblk Error: JSON Decode Error: text = {1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Text); return(result); } context = rc.Ctx; offset += chunkSize; leftBytes -= chunkSize; #endregion mkblk putExtra.ProgressHandler(offset, fileSize); if (leftBytes > 0) { blockLeft = blockSize - chunkSize; blockOffset = chunkSize; while (blockLeft > 0) { #region bput-loop if (blockLeft < CHUNK_SIZE) { chunkSize = blockLeft; } else { chunkSize = CHUNK_SIZE; } stream.Read(chunkBuffer, 0, (int)chunkSize); iTry = 0; while (++iTry <= putExtra.MaxRetryTimes) { result.RefText += string.Format("[{0}] [ResumableUpload] try bput#{1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), iTry); hr = BputChunk(chunkBuffer, blockOffset, chunkSize, context, upToken); if (hr.Code == (int)HttpCode.OK && hr.RefCode != (int)HttpCode.USER_NEED_RETRY) { break; } } if (hr.Code != (int)HttpCode.OK || hr.RefCode == (int)HttpCode.USER_NEED_RETRY) { result.Shadow(hr); result.RefText += string.Format("[{0}] [ResumableUpload] Error: bput: code = {1}, text = {2}, offset = {3}, blockOffset = {4}, chunkSize = {5}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text, offset, blockOffset, chunkSize); return(result); } if ((rc = JsonConvert.DeserializeObject <ResumeContext>(hr.Text)) == null) { result.Shadow(hr); result.RefCode = (int)HttpCode.USER_UNDEF; result.RefText += string.Format("[{0}] [ResumableUpload] bput Error: JSON Decode Error: text = {1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Text); return(result); } context = rc.Ctx; if (expiredAt == 0) { expiredAt = rc.Expired_At; } offset += chunkSize; leftBytes -= chunkSize; blockOffset += chunkSize; blockLeft -= chunkSize; #endregion bput-loop putExtra.ProgressHandler(offset, fileSize); } } #endregion one-block resumeInfo.BlockIndex = index; resumeInfo.Contexts[index] = context; resumeInfo.ExpiredAt = expiredAt; if (!string.IsNullOrEmpty(putExtra.ResumeRecordFile)) { ResumeHelper.Save(resumeInfo, putExtra.ResumeRecordFile); } ++index; } } hr = MakeFile(key, fileSize, key, upToken, putExtra, resumeInfo.Contexts); if (hr.Code != (int)HttpCode.OK) { result.Shadow(hr); result.RefText += string.Format("[{0}] [ResumableUpload] Error: mkfile: code = {1}, text = {2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), hr.Code, hr.Text); return(result); } if (File.Exists(putExtra.ResumeRecordFile)) { File.Delete(putExtra.ResumeRecordFile); } result.Shadow(hr); result.RefText += string.Format("[{0}] [ResumableUpload] Uploaded: \"{1}\" ==> \"{2}\"\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), putExtra.ResumeRecordFile, key); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); StringBuilder sb = new StringBuilder(); sb.AppendFormat("[{0}] [ResumableUpload] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); Exception e = ex; while (e != null) { sb.Append(e.Message + " "); e = e.InnerException; } sb.AppendLine(); result.RefCode = (int)HttpCode.USER_UNDEF; result.RefText += sb.ToString(); } finally { if (stream != null) { stream.Close(); stream.Dispose(); } } return(result); }
/// <summary> /// 创建块(携带首片数据),同时检查CRC32 /// </summary> /// <param name="resumeBlockerObj">创建分片上次的块请求</param> private void MakeBlock(object resumeBlockerObj) { ResumeBlocker resumeBlocker = (ResumeBlocker)resumeBlockerObj; ManualResetEvent doneEvent = resumeBlocker.DoneEvent; Dictionary <long, HttpResult> blockMakeResults = resumeBlocker.BlockMakeResults; PutExtra putExtra = resumeBlocker.PutExtra; long blockIndex = resumeBlocker.BlockIndex; HttpResult result = new HttpResult(); //check whether to cancel while (true) { UploadControllerAction upCtl = resumeBlocker.PutExtra.UploadController(); if (upCtl == UploadControllerAction.Suspended) { doneEvent.WaitOne(1000); continue; } else if (upCtl == UploadControllerAction.Aborted) { doneEvent.Set(); result.Code = (int)HttpCode.USER_CANCELED; result.RefCode = (int)HttpCode.USER_CANCELED; result.RefText += string.Format("[{0}] [ResumableUpload] Info: upload task is aborted, mkblk {1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), blockIndex); blockMakeResults.Add(blockIndex, result); return; } else { break; } } byte[] blockBuffer = resumeBlocker.BlockBuffer; int blockSize = blockBuffer.Length; string upToken = resumeBlocker.UploadToken; Dictionary <string, long> uploadedBytesDict = resumeBlocker.UploadedBytesDict; long fileSize = resumeBlocker.FileSize; object progressLock = resumeBlocker.ProgressLock; ResumeInfo resumeInfo = resumeBlocker.ResumeInfo; try { //get upload host string ak = UpToken.GetAccessKeyFromUpToken(upToken); string bucket = UpToken.GetBucketFromUpToken(upToken); if (ak == null || bucket == null) { result = HttpResult.InvalidToken; doneEvent.Set(); return; } string uploadHost = this.config.UpHost(ak, bucket); string url = string.Format("{0}/mkblk/{1}", uploadHost, blockSize); string upTokenStr = string.Format("UpToken {0}", upToken); using (MemoryStream ms = new MemoryStream(blockBuffer, 0, blockSize)) { byte[] data = ms.ToArray(); result = httpManager.PostData(url, data, upTokenStr); if (result.Code == (int)HttpCode.OK) { ResumeContext rc = JsonConvert.DeserializeObject <ResumeContext>(result.Text); if (rc.Crc32 > 0) { uint crc_1 = rc.Crc32; uint crc_2 = CRC32.CheckSumSlice(blockBuffer, 0, blockSize); if (crc_1 != crc_2) { result.RefCode = (int)HttpCode.USER_NEED_RETRY; result.RefText += string.Format(" CRC32: remote={0}, local={1}\n", crc_1, crc_2); } 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 += string.Format("[{0}] JSON Decode Error: text = {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Text); result.RefCode = (int)HttpCode.USER_NEED_RETRY; } } else { result.RefCode = (int)HttpCode.USER_NEED_RETRY; } } } catch (Exception ex) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("[{0}] mkblk Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); Exception e = ex; while (e != null) { sb.Append(e.Message + " "); e = e.InnerException; } sb.AppendLine(); if (ex is QiniuException) { QiniuException qex = (QiniuException)ex; 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(); }