using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using AX.Network.Common; using AX.Network.Protocols; using AX.Serialization; namespace AX.NetworkSystem { /// /// 表示传输的文件信息。 /// public struct TransferedFileInfo { /// /// 表示传输的文件名。只包含文件名和扩展名,不包含路径。 /// /// 文件名必须保证唯一。 public string Filename; /// /// 表示传输的文件大小,最大支持 2G。 /// public int FileSize; /// /// 表示文件附加信息。 /// public ArraySegment Tag; } /// /// 表示传输的文件块信息。 /// public struct TransferedChunkInfo { /// /// 表示文件块对应的文件。 /// public string Filename; /// /// 表示文件块总数。 /// public int ChunkCount; /// /// 表示文件块的 ID。 /// public int ChunkId; /// /// 表示文件块划分的标准。 /// public int ChunkSize; /// /// 表示文件块的数据。 /// public ArraySegment Data; } /// /// 表示传输文件块时的回复。 /// public struct TransferedChunkInfoReply { /// /// 表示文件名。 /// public string Filename; /// /// 表示文件块 Id。 /// public int ChunkId; } /// /// 表示传输文件块时的请求。 /// public struct TransferedChunkInfoRequest { /// /// 表示文件名。 /// public string Filename; /// /// 表示文件块 Id。 /// public int ChunkId; /// /// 表示文件块划分的标准。 /// public int ChunkSize; } /// /// 表示错误消息。 /// public struct ErrorInfo { /// /// 表示出错的文件名。 /// public string Filename; /// /// 表示错误码。 /// public int ErrorCode; /// /// 表示错误详细信息。 /// public string ErrorMessage; } /// /// 表示文件传输过程中的状态。 /// internal class TransferedFileStatus { /// /// 表示传输文件会话。 /// public IAppSession AppSession; /// /// 表示要传输的文件路径。相对路径和绝对路径均可。 /// public string FullName; /// /// 表示文件名。只包含文件名和扩展名,不包含路径。 /// public string Filename; /// /// 表示文件总共划分了多少块。 /// public int TotalChunkCount; /// /// 表示当前传输到哪块了。 /// public int CurrentChunkIndex; /// /// 表示当前已经传输了多少块了。 /// public int TransferedChunkCount; /// /// 表示文件传输过程中的进度变化。 /// public Action ProgressChanged; /// /// 表示文件传输完毕事件。 /// public Action Completed; /// /// 表示文件传输时发生错误,当前只针对下载。 /// public Action Error; } public static class FileTransfer { internal class FileSyncStatus { public BitArray BitArray; public ArraySegment Tag; public int TotalChunkCount; public int TransferedChunkCount; } private const string DOWNLOAD_FILE_INFO_REQUEST = "DOWNLOAD_FILE_INFO_REQUEST"; private const string DOWNLOAD_CHUNK_INFO_REQUEST = "DOWNLOAD_CHUNK_INFO_REQUEST"; private const string DOWNLOAD_FILE_INFO_REPLY = "DOWNLOAD_FILE_INFO_REPLY"; private const string DOWNLOAD_CHUNK_INFO_REPLY = "DOWNLOAD_CHUNK_INFO_REPLY"; private const string DOWNLOAD_ERROR = "DOWNLOAD_ERROR"; private const string UPLOAD_FILE_INFO_REQUEST = "UPLOAD_FILE_INFO_REQUEST"; private const string UPLOAD_CHUNK_INFO_REQUEST = "UPLOAD_CHUNK_INFO_REQUEST"; private const string UPLOAD_FILE_INFO_REPLY = "UPLOAD_FILE_INFO_REPLY"; private const string UPLOAD_CHUNK_INFO_REPLY = "UPLOAD_CHUNK_INFO_REPLY"; private const string FORWARD_FILE_INFO_REQUEST = "FORWARD_FILE_INFO_REQUEST"; private const string FORWARD_CHUNK_INFO_REQUEST = "FORWARD_CHUNK_INFO_REQUEST"; private const string FORWARD_FILE_INFO_REPLY = "FORWARD_FILE_INFO_REPLY"; private const string FORWARD_CHUNK_INFO_REPLY = "FORWARD_CHUNK_INFO_REPLY"; private const string FORWARD_FILE_INFO_SYNC = "FORWARD_FILE_INFO_SYNC"; private const string FORWARD_CHUNK_INFO_SYNC = "FORWARD_CHUNK_INFO_SYNC"; private const string PULL_CHUNK_INFO_REQUEST = "PULL_CHUNK_INFO_REQUEST"; private const string PULL_CHUNK_INFO_REPLY = "PULL_CHUNK_INFO_REQUEST"; private const string PULL_FILE_INFO_REQUEST = "PULL_FILE_INFO_REQUEST"; private const string PULL_FILE_INFO_REPLY = "PULL_FILE_INFO_REPLY"; private const string SEND_FILE_SYNC = "SEND_FILE_SYNC"; private static string BaseTempFolder = Application.dataPath + Path.DirectorySeparatorChar + "Temp" + Path.DirectorySeparatorChar; private static string BaseUploadFolder = Application.dataPath + Path.DirectorySeparatorChar + "Upload" + Path.DirectorySeparatorChar; private static string TempFolder = BaseTempFolder; private static string UploadFolder = BaseUploadFolder; private static string DirectorySeparator = new string(new char[] { Path.DirectorySeparatorChar }); private const int ChunkSize = 65536 - 4096; private const int TransferedChunkCount = 1024 * 1024 / ChunkSize; private static readonly BufferPool BufferPool = new BufferPool(1, ChunkSize); private static Dictionary UploadContainer = new Dictionary(); private static Dictionary DownloadContainer = new Dictionary(); private static Dictionary ForwardContainer = new Dictionary(); private static Dictionary FileSyncContainer = new Dictionary(); /// /// 表示文件传输初始化。 /// public static void Initialize() { NetworkMessageDispatcher.AddListener(DOWNLOAD_FILE_INFO_REPLY, OnDownloadFileInfoReply); NetworkMessageDispatcher.AddListener(DOWNLOAD_CHUNK_INFO_REPLY, OnDownloadChunkInfoReply); NetworkMessageDispatcher.AddListener(DOWNLOAD_ERROR, OnDownloadError); NetworkMessageDispatcher.AddListener(UPLOAD_FILE_INFO_REPLY, OnUploadFileInfoReply); NetworkMessageDispatcher.AddListener(UPLOAD_CHUNK_INFO_REPLY, OnUploadChunkInfoReply); NetworkMessageDispatcher.AddListener(FORWARD_FILE_INFO_REPLY, OnForwardFileInfoReply); NetworkMessageDispatcher.AddListener(FORWARD_CHUNK_INFO_REPLY, OnForwardChunkInfoReply); NetworkMessageDispatcher.AddListener(FORWARD_FILE_INFO_SYNC, OnForwardFileInfoSync); NetworkMessageDispatcher.AddListener(FORWARD_CHUNK_INFO_SYNC, OnForwardChunkInfoSync); NetworkMessageDispatcher.AddListener(PULL_FILE_INFO_REPLY, OnPullFileInfoReply); NetworkMessageDispatcher.AddListener(PULL_CHUNK_INFO_REPLY, OnPullChunkInfoReply); } public static void Dispose() { NetworkMessageDispatcher.RemoveListener(DOWNLOAD_FILE_INFO_REPLY, OnDownloadFileInfoReply); NetworkMessageDispatcher.RemoveListener(DOWNLOAD_CHUNK_INFO_REPLY, OnDownloadChunkInfoReply); NetworkMessageDispatcher.RemoveListener(DOWNLOAD_ERROR, OnDownloadError); NetworkMessageDispatcher.RemoveListener(UPLOAD_FILE_INFO_REPLY, OnUploadFileInfoReply); NetworkMessageDispatcher.RemoveListener(UPLOAD_CHUNK_INFO_REPLY, OnUploadChunkInfoReply); NetworkMessageDispatcher.RemoveListener(FORWARD_FILE_INFO_REPLY, OnForwardFileInfoReply); NetworkMessageDispatcher.RemoveListener(FORWARD_CHUNK_INFO_REPLY, OnForwardChunkInfoReply); NetworkMessageDispatcher.RemoveListener(FORWARD_FILE_INFO_SYNC, OnForwardFileInfoSync); NetworkMessageDispatcher.RemoveListener(FORWARD_CHUNK_INFO_SYNC, OnForwardChunkInfoSync); NetworkMessageDispatcher.RemoveListener(PULL_FILE_INFO_REPLY, OnPullFileInfoReply); NetworkMessageDispatcher.RemoveListener(PULL_CHUNK_INFO_REPLY, OnPullChunkInfoReply); } /// /// 设置文件下载时的临时目录。 /// /// 用户指定的临时目录 /// 注意:最好在登录成功之后进行设置。用户指定的临时完整路径是 Application.dataPath/Temp/temp/。 public static void SetTempPath(string temp) { if (temp.EndsWith(DirectorySeparator)) TempFolder = BaseTempFolder + temp; else TempFolder = BaseTempFolder + temp + DirectorySeparator; } /// /// 获得文件下载时的临时目录。 /// public static string GetTempPath() { return TempFolder; } /// /// 设置文件上传目录。 /// /// 用户指定的文件上传目录 /// 注意:最好在登录成功之后进行设置。 public static void SetUploadPath(string upload) { if (upload.EndsWith(DirectorySeparator)) UploadFolder = upload; else UploadFolder = upload + DirectorySeparator; } /// /// 获得文件上传目录。 /// public static string GetUploadPath() { return UploadFolder; } private static void OnPullFileInfoReply(BinaryMessage message) { var info = message.Body.Deserialize(); var status = default(FileSyncStatus); if (!FileSyncContainer.TryGetValue(info.Filename, out status)) { status = new FileSyncStatus(); status.Tag = info.Tag; FileSyncContainer.Add(info.Filename, status); } else status.Tag = info.Tag; } private static void OnPullChunkInfoReply(BinaryMessage message) { var status = default(FileSyncStatus); var info = message.Body.Deserialize(); using (var stream = new FileStream(TempFolder + info.Filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { var position = info.ChunkId * info.ChunkSize; stream.Seek(position, SeekOrigin.Begin); stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count); stream.Flush(); } status.BitArray[info.ChunkId] = true; ++status.TransferedChunkCount; // 确实同步完成 if (status.TransferedChunkCount == info.ChunkCount) { FileSyncContainer.Remove(info.Filename); ForwardContainer.Remove(info.Filename); var tag = status.Tag.Deserialize>>(); NetworkMessageDispatcher.SendMessage(tag.Key, tag.Value, TempFolder + info.Filename); } else { // 拉取未完成的文件块 var chunkId = GetChunkId(status.BitArray); if (chunkId > 0) { var request = new TransferedChunkInfoRequest(); request.Filename = info.Filename; request.ChunkId = chunkId; request.ChunkSize = ChunkSize; var session = ForwardContainer[info.Filename].AppSession; session.SendRequestAsync(PULL_CHUNK_INFO_REQUEST, request); } } } private static void OnForwardChunkInfoSync(BinaryMessage message) { var info = message.Body.Deserialize(); var status = default(FileSyncStatus); if (!FileSyncContainer.TryGetValue(info.Filename, out status)) { status = new FileSyncStatus(); FileSyncContainer.Add(info.Filename, status); // 拉取文件信息 var session = ForwardContainer[info.Filename].AppSession; session.SendRequestAsync(PULL_FILE_INFO_REQUEST, info.Filename); } if (status.BitArray == null) { status.BitArray = new BitArray(info.ChunkCount); status.TotalChunkCount = info.ChunkCount; } using (var stream = new FileStream(TempFolder + info.Filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { var position = info.ChunkId * info.ChunkSize; stream.Seek(position, SeekOrigin.Begin); stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count); stream.Flush(); } status.BitArray[info.ChunkId] = true; status.TransferedChunkCount += 1; // 同步到了最后一个文件块 if (info.ChunkId == info.ChunkCount - 1) { // 确实同步完成 if (status.TransferedChunkCount == info.ChunkCount) { FileSyncContainer.Remove(info.Filename); ForwardContainer.Remove(info.Filename); var tag = status.Tag.Deserialize>>(); NetworkMessageDispatcher.SendMessage(tag.Key, tag.Value, TempFolder + info.Filename); } else { // 拉取未完成的文件块 var chunkId = GetChunkId(status.BitArray); if (chunkId > 0) { var request = new TransferedChunkInfoRequest(); request.Filename = info.Filename; request.ChunkId = chunkId; request.ChunkSize = ChunkSize; var session = ForwardContainer[info.Filename].AppSession; session.SendRequestAsync(PULL_CHUNK_INFO_REQUEST, request); } } } } private static void OnForwardFileInfoSync(BinaryMessage message) { var info = message.Body.Deserialize(); var fullname = TempFolder + info.Filename; if (!Directory.Exists(TempFolder)) Directory.CreateDirectory(TempFolder); if (!File.Exists(fullname)) { using (var stream = new FileStream(fullname, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { stream.SetLength(info.FileSize); stream.Flush(); } } var status = default(FileSyncStatus); if (!FileSyncContainer.TryGetValue(info.Filename, out status)) { status = new FileSyncStatus(); status.Tag = info.Tag; FileSyncContainer.Add(info.Filename, status); } } private static void OnForwardChunkInfoReply(BinaryMessage message) { // 传输文件块 var reply = message.Body.Deserialize(); var status = default(TransferedFileStatus); if (!ForwardContainer.TryGetValue(reply.Filename, out status)) return; ++status.TransferedChunkCount; // 表示文件上传成功 if (status.TransferedChunkCount == status.TotalChunkCount) { if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, 100.0f); if (status.Completed != null) status.Completed(status.FullName); //ForwardContainer.Remove(status.Filename); } else { var progress = ((float)status.TransferedChunkCount / status.TotalChunkCount) * 100.0f; if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, progress); ++status.CurrentChunkIndex; // 继续上传文件 if (status.CurrentChunkIndex < status.TotalChunkCount) ForwardFileChunkAsync(status, status.CurrentChunkIndex); } } private static void OnForwardFileInfoReply(BinaryMessage message) { var pair = message.Body.Deserialize>>(); var filename = pair.Key; var body = pair.Value.Deserialize>>(); // 通知客户端收到回复了 NetworkMessageDispatcher.SendMessage(body.Key, body.Value, filename); // 开始真正上传文件 var status = ForwardContainer[filename]; var count = Math.Min(status.TotalChunkCount, TransferedChunkCount); status.CurrentChunkIndex = count - 1; for (var i = 0; i < count; ++i) ForwardFileChunkAsync(status, i); } private static void OnUploadChunkInfoReply(BinaryMessage message) { // 传输文件块 var reply = message.Body.Deserialize(); var status = default(TransferedFileStatus); if (!UploadContainer.TryGetValue(reply.Filename, out status)) return; ++status.TransferedChunkCount; // 表示文件上传成功 if (status.TransferedChunkCount == status.TotalChunkCount) { if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, 100.0f); if (status.Completed != null) status.Completed(status.FullName); UploadContainer.Remove(status.Filename); } else { var progress = ((float)status.TransferedChunkCount / status.TotalChunkCount) * 100.0f; if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, progress); ++status.CurrentChunkIndex; // 继续上传文件 if (status.CurrentChunkIndex < status.TotalChunkCount) UploadFileChunkAsync(status, status.CurrentChunkIndex); } } private static void OnUploadFileInfoReply(BinaryMessage message) { // 开始真正上传文件 var filename = message.Body.Deserialize(); var status = UploadContainer[filename]; var count = Math.Min(status.TotalChunkCount, TransferedChunkCount); status.CurrentChunkIndex = count - 1; for (var i = 0; i < count; ++i) UploadFileChunkAsync(status, i); } private static void OnDownloadError(BinaryMessage message) { var error = message.Body.Deserialize(); var status = DownloadContainer[error.Filename]; if (status.Error != null) status.Error(error); DownloadContainer.Remove(error.Filename); } private static void OnDownloadChunkInfoReply(BinaryMessage message) { var info = message.Body.Deserialize(); var status = DownloadContainer[info.Filename]; using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) { stream.Seek(info.ChunkId * info.ChunkSize, SeekOrigin.Begin); stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count); stream.Flush(); } status.TotalChunkCount = info.ChunkCount; ++status.TransferedChunkCount; // 表示文件下载成功 if (status.TransferedChunkCount == status.TotalChunkCount) { if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, 100.0f); if (status.Completed != null) status.Completed(status.FullName); DownloadContainer.Remove(status.Filename); } else { var progress = ((float)status.TransferedChunkCount / status.TotalChunkCount) * 100.0f; if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, progress); // 继续下载文件 if (status.CurrentChunkIndex == 0) { var count = Math.Min(info.ChunkCount, TransferedChunkCount); for (var i = 1; i < count; ++i) DownloadFileChunkAsync(status, i); status.CurrentChunkIndex += count; } else { if (status.CurrentChunkIndex < status.TotalChunkCount) { DownloadFileChunkAsync(status, status.CurrentChunkIndex); ++status.CurrentChunkIndex; } } } } private static void OnDownloadFileInfoReply(BinaryMessage message) { // 开始真正下载文件 var info = message.Body.Deserialize(); var status = DownloadContainer[info.Filename]; using (var stream = new FileStream(status.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { stream.SetLength(info.FileSize); stream.Flush(); } status.CurrentChunkIndex = 0; // 下载第一块 DownloadFileChunkAsync(status, 0); } private static void UploadFileChunkAsync(TransferedFileStatus status, int chunkId) { var buffer = BufferPool.Acquire(); using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { stream.Seek(chunkId * ChunkSize, SeekOrigin.Begin); var readCount = stream.Read(buffer, 0, ChunkSize); var data = new ArraySegment(buffer, 0, readCount); var info = new TransferedChunkInfo(); info.Filename = status.Filename; info.ChunkCount = status.TotalChunkCount; info.ChunkId = chunkId; info.ChunkSize = ChunkSize; info.Data = data; status.AppSession.SendRequestAsync(UPLOAD_CHUNK_INFO_REQUEST, info); } BufferPool.Release(buffer); } private static void ForwardFileChunkAsync(TransferedFileStatus status, int chunkId) { var buffer = BufferPool.Acquire(); using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { stream.Seek(chunkId * ChunkSize, SeekOrigin.Begin); var readCount = stream.Read(buffer, 0, ChunkSize); var data = new ArraySegment(buffer, 0, readCount); var info = new TransferedChunkInfo(); info.Filename = status.Filename; info.ChunkCount = status.TotalChunkCount; info.ChunkId = chunkId; info.ChunkSize = ChunkSize; info.Data = data; status.AppSession.SendRequestAsync(FORWARD_CHUNK_INFO_REQUEST, info); } BufferPool.Release(buffer); } private static void DownloadFileChunkAsync(TransferedFileStatus status, int chunkId) { var request = new TransferedChunkInfoRequest(); request.Filename = status.Filename; request.ChunkId = chunkId; status.AppSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, request); } // 获得未完成的文件块索引 private static int GetChunkId(BitArray array) { for (var i = 0; i < array.Length; ++i) if (!array[i]) return i; return -1; } public static void UploadFileAsync(this IAppSession session, string filename) { UploadFileAsync(session, filename, null, null); } public static void UploadFileAsync(this IAppSession session, string filename, Action progress) { UploadFileAsync(session, filename, progress, null); } public static void UploadFileAsync(this IAppSession session, string filename, Action completed) { UploadFileAsync(session, filename, null, completed); } /// /// 异步上传文件。 /// public static void UploadFileAsync(this IAppSession session, string filename, Action progress, Action completed) { var fullname = UploadFolder + filename; if (!File.Exists(fullname)) throw new ArgumentException("filename"); var fileinfo = new FileInfo(fullname); var fileSize = (int)fileinfo.Length; var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1; var info = new TransferedFileInfo(); info.Filename = filename; info.FileSize = fileSize; var status = new TransferedFileStatus(); status.AppSession = session; status.FullName = fullname; status.Filename = filename; status.TotalChunkCount = chunkCount; status.ProgressChanged = progress; status.Completed = completed; UploadContainer.Add(status.Filename, status); // 发送上传文件请求 session.SendRequestAsync(UPLOAD_FILE_INFO_REQUEST, info); } public static void SendFileAsync(this IAppSession session, string header, string filename) { SendFileAsync(session, header, filename, (Action)null); } public static void SendFileAsync(this IAppSession session, string header, T body, string filename) { SendFileAsync(session, header, body, filename, null, null); } public static void SendFileAsync(this IAppSession session, string header, string filename, Action progress) { SendFileAsync(session, header, filename, progress, null); } public static void SendFileAsync(this IAppSession session, string header, T body, string filename, Action progress) { SendFileAsync(session, header, body, filename, progress, null); } public static void SendFileAsync(this IAppSession session, string header, string filename, Action progress, Action completed) { var pair = new KeyValuePair>(header, default(ArraySegment)); SendFileAsync(session, filename, pair, progress, completed); } public static void SendFileAsync(this IAppSession session, string header, T body, string filename, Action progress, Action completed) { var buffer = BufferPool.Acquire(); var segment = body.Serialize(); Buffer.BlockCopy(segment.Array, segment.Offset, buffer, 0, segment.Count); var rawBody = new ArraySegment(buffer, 0, segment.Count); var pair = new KeyValuePair>(header, rawBody); SendFileAsync(session, filename, pair, progress, completed); BufferPool.Release(buffer); } /// /// 异步转发文件。 /// /// 注意,调用此方法传输的文件应该都是小文件(不大于 1M 的文件)。传输大文件可能会比较低效。 public static void SendFileAsync(this IAppSession session, string filename, T tag, Action progress, Action completed) { if (!File.Exists(filename)) throw new ArgumentException("filename"); var fileinfo = new FileInfo(filename); var fileSize = (int)fileinfo.Length; var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1; var info = new TransferedFileInfo(); info.Filename = fileinfo.Name; info.FileSize = fileSize; var buffer = BufferPool.Acquire(); var segment = tag.Serialize(); Buffer.BlockCopy(segment.Array, segment.Offset, buffer, 0, segment.Count); info.Tag = new ArraySegment(buffer, 0, segment.Count); var status = new TransferedFileStatus(); status.AppSession = session; status.FullName = filename; status.Filename = fileinfo.Name; status.TotalChunkCount = chunkCount; status.ProgressChanged = progress; status.Completed = completed; ForwardContainer.Add(status.Filename, status); // 发送转发文件请求 session.SendRequestAsync(FORWARD_FILE_INFO_REQUEST, info); BufferPool.Release(buffer); } public static void DownloadFileAsync(this IAppSession session, string filename) { DownloadFileAsync(session, filename, null, null); } public static void DownloadFileAsync(this IAppSession session, string filename, Action completed) { DownloadFileAsync(session, filename, null, completed); } public static void DownloadFileAsync(this IAppSession session, string filename, Action progress) { DownloadFileAsync(session, filename, progress, null); } /// /// 异步下载文件。 /// public static void DownloadFileAsync(this IAppSession session, string fullname, Action progress, Action completed) { DownloadFileAsync(session, fullname, progress, completed, null); } /// /// 异步下载文件。 /// public static void DownloadFileAsync(this IAppSession session, string fullname, Action progress, Action completed, Action error) { if (string.IsNullOrEmpty(fullname)) throw new ArgumentException("fullname 不能为空!"); var actualName = TempFolder + fullname; var directory = Path.GetDirectoryName(TempFolder + fullname); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); var status = new TransferedFileStatus(); status.AppSession = session; status.FullName = actualName; status.Filename = fullname; status.ProgressChanged = progress; status.Completed = completed; status.Error = error; DownloadContainer.Add(status.Filename, status); var info = new TransferedFileInfo(); info.Filename = fullname; info.FileSize = 0; // 发送下载文件请求 session.SendRequestAsync(DOWNLOAD_FILE_INFO_REQUEST, info); } } }