Пример #1
0
        // 上传文件到到 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);
        }