// 上传文件到到 dp2lbrary 服务器 // 注:已通过 prompt_func 实现通讯出错时候重试 // parameters: // timestamp 时间戳。如果为 null,函数会自动根据文件信息得到一个时间戳 // bRetryOverwiteExisting 是否自动在时间戳不一致的情况下覆盖已经存在的服务器文件。== true,表示当发现时间戳不一致的时候,自动用返回的时间戳重试覆盖 // return: // -1 出错 // 0 上传文件成功 public static int UploadFile( this LibraryChannel channel, Stop stop, string strClientFilePath, string strServerFilePath, string strMetadata, string strStyle, byte[] timestamp, bool bRetryOverwiteExisting, bool bProgressChange, delegate_prompt prompt_func, out byte[] output_timestamp, out string strError) { strError = ""; output_timestamp = null; string strResPath = strServerFilePath; #if NO if (string.IsNullOrEmpty(strMime)) { strMime = PathUtil.MimeTypeFrom(strClientFilePath); } #endif // 只修改 metadata if (string.IsNullOrEmpty(strClientFilePath) == true) { long lRet = channel.SaveResObject( stop, strResPath, null, // strClientFilePath, -1, // 0, // 0 是 bug,会导致清除原有对象内容 2018/8/13 strMetadata, "", timestamp, strStyle, out output_timestamp, out strError); timestamp = output_timestamp; if (lRet == -1) { return(-1); } return(0); } long skip_offset = 0; { REDO_DETECT: // 先检查以前是否有已经上传的局部 long lRet = channel.GetRes(stop, strServerFilePath, 0, 0, "uploadedPartial", out byte[] temp_content, out string temp_metadata, out string temp_outputPath, out byte[] temp_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == LibraryClient.localhost.ErrorCode.NotFound) { // 以前上传的局部不存在,说明只能从头上传 skip_offset = 0; } else { // 探测过程通讯或其他出错 if (prompt_func != null) { if (stop != null && stop.State != 0) { strError = "用户中断"; return(-1); } string action = prompt_func(channel, strError + "\r\n\r\n(重试) 重试操作; (中断) 中断处理", new string[] { "重试", "中断" }, 10); if (action == "重试") { goto REDO_DETECT; } if (action == "中断") { return(-1); } } return(-1); } } else if (lRet > 0) { // *** 发现以前存在 lRet 这么长的已经上传部分 long local_file_length = FileUtil.GetFileLength(strClientFilePath); // 本地文件尺寸居然小于已经上传的临时部分 if (local_file_length < lRet) { // 只能从头上传 skip_offset = 0; } else { // 询问是否断点续传 if (prompt_func != null) { string percent = StringUtil.GetPercentText(lRet, local_file_length); string action = prompt_func(null, $"本地文件 {strClientFilePath} 以前曾经上传过长度为 {lRet} 的部分内容(占整个文件 {percent}),请问现在是否继续上传余下部分? \r\n[是]从断点位置开始继续上传; [否]从头开始上传; [中断]取消这个文件的上传", new string[] { "是", "否", "中断" }, 0); if (action == "是") { skip_offset = lRet; } else if (action == "否") { skip_offset = 0; } else { strError = "取消处理"; return(-1); } } // 如果 prompt_func 为 null 那就不做断点续传,效果是从头开始上传 } } } // 检测文件尺寸 FileInfo fi = new FileInfo(strClientFilePath); if (fi.Exists == false) { strError = "文件 '" + strClientFilePath + "' 不存在..."; return(-1); } string[] ranges = null; if (fi.Length == 0) { // 空文件 ranges = new string[1]; ranges[0] = ""; } else { string strRange = ""; strRange = $"{skip_offset}-{(fi.Length - 1)}"; // 按照100K作为一个chunk // TODO: 实现滑动窗口,根据速率来决定chunk尺寸 ranges = RangeList.ChunkRange(strRange, channel.UploadResChunkSize // 500 * 1024 ); if (bProgressChange && stop != null) { stop.SetProgressRange(0, fi.Length); } } if (timestamp == null) { timestamp = FileUtil.GetFileTimestamp(strClientFilePath); } // byte[] output_timestamp = null; string strWarning = ""; TimeSpan old_timeout = channel.Timeout; try { using (FileStream stream = File.OpenRead(strClientFilePath)) { for (int j = 0; j < ranges.Length; j++) { if (stop != null && stop.State != 0) { strError = "用户中断"; return(-1); } string range = ranges[j]; string strWaiting = ""; if (j == ranges.Length - 1) { strWaiting = " 请耐心等待..."; channel.Timeout = new TimeSpan(0, 40, 0); // 40 分钟 } string strPercent = ""; RangeList rl = new RangeList(range); if (rl.Count != 1) { strError = $"{range} 中只应包含一个连续范围"; return(-1); } { double ratio = (double)((RangeItem)rl[0]).lStart / (double)fi.Length; strPercent = String.Format("{0,3:N}", ratio * (double)100) + "%"; } if (stop != null) { stop.SetMessage( // strMessagePrefix + "正在上载 " + range + "/" + StringUtil.GetLengthText(fi.Length) + " " + strPercent + " " + strClientFilePath + strWarning + strWaiting); if (bProgressChange && rl.Count > 0) { stop.SetProgressValue(rl[0].lStart); } } // 2019/6/23 StreamUtil.FastSeek(stream, rl[0].lStart); int nRedoCount = 0; long save_pos = stream.Position; REDO: // 2019/6/21 // 如果是重做,文件指针要回到合适位置 if (stream.Position != save_pos) { StreamUtil.FastSeek(stream, save_pos); } long lRet = channel.SaveResObject( stop, strResPath, stream, // strClientFilePath, -1, j == ranges.Length - 1 ? strMetadata : null, // 最尾一次操作才写入 metadata range, // j == ranges.Length - 1 ? true : false, // 最尾一次操作,提醒底层注意设置特殊的WebService API超时时间 timestamp, strStyle, out output_timestamp, out strError); if (channel.ErrorCode == DigitalPlatform.LibraryClient.localhost.ErrorCode.TimestampMismatch) { strError = $"{strError}。timestamp={ByteArray.GetHexTimeStampString(timestamp)},output_timestamp={ByteArray.GetHexTimeStampString(output_timestamp)}。parsed:{ParseTimestamp(timestamp)};{ParseTimestamp(output_timestamp)}"; } // Debug.WriteLine($"parsed:{ParseTimestamp(timestamp)};{ParseTimestamp(output_timestamp)}"); timestamp = output_timestamp; strWarning = ""; if (lRet == -1) { // 如果是第一个 chunk,自动用返回的时间戳重试一次覆盖 if (bRetryOverwiteExisting == true && j == 0 && channel.ErrorCode == DigitalPlatform.LibraryClient.localhost.ErrorCode.TimestampMismatch && nRedoCount == 0) { nRedoCount++; goto REDO; } if (prompt_func != null) { if (stop != null && stop.State != 0) { strError = "用户中断"; return(-1); } string action = prompt_func(channel, strError + "\r\n\r\n(重试) 重试操作; (中断) 中断处理", new string[] { "重试", "中断" }, 10); if (action == "重试") { goto REDO; } if (action == "中断") { return(-1); } } return(-1); } } } if (StringUtil.IsInList("_checkMD5", strStyle)) { stop?.SetMessage($"正在校验本地文件 {strClientFilePath} 和刚上传的服务器文件 {strServerFilePath} ..."); // result.Value: // -1 出错 // 0 不匹配 // 1 匹配 // exception: // 可能会抛出异常 var result = CheckMD5( channel, stop, strServerFilePath, strClientFilePath); stop?.SetMessage("MD5 校验完成"); if (result.Value == -1) { strError = result.ErrorInfo; return(-1); } if (result.Value == 0) { strError = $"UploadFile() 出错:本地文件 {strClientFilePath} 和刚上传的服务器文件 {strServerFilePath} MD5 校验不一致。请重新上传"; return(-1); } Debug.Assert(result.Value == 1, ""); } } catch (Exception ex) { strError = "UploadFile() 出现异常: " + ExceptionUtil.GetDebugText(ex); return(-1); } finally { channel.Timeout = old_timeout; } return(0); }