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 ChunkBaseInfo { /// /// 相对文件路径。 /// public string Filename { get; set; } /// /// 文件大小。 /// public long FileSize { get; set; } /// /// 文件分块区间最小值。 /// public long RangeMin { get; set; } /// /// 文件分块区间最大值。 /// public long RangeMax { get; set; } } /// /// 表示文件分块数据信息。 /// public struct ChunkDataInfo { /// /// 相对文件路径。 /// public string Filename { get; set; } /// /// 文件大小。 /// public long FileSize { get; set; } /// /// 文件分块区间最小值。 /// public long RangeMin { get; set; } /// /// 文件分块区间最大值。 /// public long RangeMax { get; set; } /// /// 文件块数据。 /// public ArraySegment Data { get; set; } } /// /// 表示文件信息。 /// public struct FileInfo { /// /// 相对文件路径。 /// public string Filename { get; set; } /// /// 文件大小。 /// public long FileSize { get; set; } /// /// 文件标签,可用作扩展。 /// public string Tag { get; set; } } /// /// 表示文件传输过程中的状态。 /// internal class TransferedFileStatus { /// /// 表示传输文件会话。 /// public IAppSession AppSession; /// /// 表示要传输的文件路径。相对路径和绝对路径均可。 /// public string FullName; /// /// 表示临时的文件名称。 /// public string TempName; /// /// 表示文件名。只包含文件名和扩展名,不包含路径。 /// public string Filename; /// /// 文件大小。 /// public long FileSize; /// /// 表示文件标签。 /// public string Tag; /// /// 表示文件还需传输多少字节。 /// public long TransferedSize; /// /// 表示文件传输过程中的进度变化。 /// public Action ProgressChanged; /// /// 表示文件传输完毕事件。 /// public Action Completed; } public static class FileTransfer { 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 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 int ChunkSize = 65536 - 8192; private static readonly BufferPool BufferPool = new BufferPool(1, ChunkSize); private static Dictionary UploadContainer = new Dictionary(); private static Dictionary DownloadContainer = new Dictionary(); /// /// 表示文件传输初始化。 /// public static void Initialize() { NetworkMessageDispatcher.AddListener(DOWNLOAD_FILE_INFO_REPLY, OnDownloadFileInfoReply); NetworkMessageDispatcher.AddListener(DOWNLOAD_CHUNK_INFO_REPLY, OnDownloadChunkInfoReply); NetworkMessageDispatcher.AddListener(UPLOAD_FILE_INFO_REPLY, OnUploadFileInfoReply); NetworkMessageDispatcher.AddListener(UPLOAD_CHUNK_INFO_REPLY, OnUploadChunkInfoReply); } public static void Dispose() { NetworkMessageDispatcher.RemoveListener(DOWNLOAD_FILE_INFO_REPLY, OnDownloadFileInfoReply); NetworkMessageDispatcher.RemoveListener(DOWNLOAD_CHUNK_INFO_REPLY, OnDownloadChunkInfoReply); NetworkMessageDispatcher.RemoveListener(UPLOAD_FILE_INFO_REPLY, OnUploadFileInfoReply); NetworkMessageDispatcher.RemoveListener(UPLOAD_CHUNK_INFO_REPLY, OnUploadChunkInfoReply); } private static void OnUploadChunkInfoReply(BinaryMessage message) { var reply = message.Body.Deserialize(); var status = UploadContainer[reply.Filename]; status.TransferedSize -= reply.RangeMax - reply.RangeMin; // 表示文件上传成功 if (status.TransferedSize == 0) { // 通知服务器上传文件成功 var info = new FileInfo() { Filename = status.Filename, FileSize = status.FileSize, Tag = status.Tag, }; status.AppSession.SendRequestAsync(UPLOAD_FILE_INFO_REQUEST, info); } else { var progress = (float)(1.0f - (double)status.TransferedSize / status.FileSize) * 100.0f; if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, status.Filename, progress); //Debug.Log(reply.Filename+":["+reply.RangeMin+","+reply.RangeMax+")"); // 继续上传文件 UploadFileChunkAsync(status, reply.RangeMax); } } private static void OnUploadFileInfoReply(BinaryMessage message) { var reply = message.Body.Deserialize(); var status = UploadContainer[reply.Filename]; if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, status.Filename, 100.0f); if (status.Completed != null) status.Completed(status.FullName, status.Filename); UploadContainer.Remove(status.Filename); } private static void OnDownloadChunkInfoReply(BinaryMessage message) { var info = message.Body.Deserialize(); var status = DownloadContainer[info.Filename]; using (var stream = new FileStream(status.TempName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) { stream.Seek(info.RangeMin, SeekOrigin.Begin); stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count); stream.Flush(); } status.TransferedSize -= info.RangeMax - info.RangeMin; // 表示文件下载成功 if (status.TransferedSize == 0) { // 重命名 //File.Move(status.TempName, status.FullName); FileMove(status.TempName, status.FullName); if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, status.Filename, 100.0f); if (status.Completed != null) status.Completed(status.FullName, status.Filename); DownloadContainer.Remove(status.Filename); } else { var progress = (float)(1.0f - (double)status.TransferedSize / status.FileSize) * 100.0f; if (status.ProgressChanged != null) status.ProgressChanged(status.FullName, status.Filename, progress); // 继续下载文件 DownloadFileChunkAsync(status, info.RangeMax); } } private static void OnDownloadFileInfoReply(BinaryMessage message) { var info = message.Body.Deserialize(); var status = DownloadContainer[info.Filename]; status.FileSize = info.FileSize; status.TransferedSize = info.FileSize; // 预留磁盘空间 // 注意:这里的操作可能会非常耗时,当文件非常大时 using (var stream = new FileStream(status.TempName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { stream.SetLength(info.FileSize); stream.Flush(); } // 开始真正下载文件 DownloadFileChunkAsync(status, 0); } private static void UploadFileChunkAsync(TransferedFileStatus status, long offset) { var buffer = BufferPool.Acquire(); using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { stream.Seek(offset, SeekOrigin.Begin); var readCount = stream.Read(buffer, 0, ChunkSize); var data = new ArraySegment(buffer, 0, readCount); var info = new ChunkDataInfo { Filename = status.Filename, FileSize = status.FileSize, RangeMin = offset, RangeMax = offset + readCount, Data = data }; status.AppSession.SendRequestAsync(UPLOAD_CHUNK_INFO_REQUEST, info); } BufferPool.Release(buffer); } private static void DownloadFileChunkAsync(TransferedFileStatus status, long offset) { var restSize = status.FileSize - offset; var info = new ChunkBaseInfo() { Filename = status.Filename, FileSize = status.FileSize, RangeMin = offset, RangeMax = restSize >= ChunkSize ? offset + ChunkSize : offset + restSize }; status.AppSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, info); } /// /// 异步上传文件。 /// public static void UploadFileAsync(this IAppSession session, string fullname, string filename, string tag = null, Action progress = null, Action completed = null) { var fileinfo = new System.IO.FileInfo(fullname); var status = new TransferedFileStatus() { AppSession = session, FullName = fullname, Filename = filename, FileSize = fileinfo.Length, Tag = tag, TransferedSize = fileinfo.Length, ProgressChanged = progress, Completed = completed }; UploadContainer[status.Filename] = status; UploadFileChunkAsync(status, 0); } /// /// 异步下载文件。 /// public static void DownloadFileAsync(this IAppSession session, string fullname, string filename, string tag = null, Action progress = null, Action completed = null) { var status = new TransferedFileStatus() { AppSession = session, FullName = fullname, TempName = fullname + ".tmp", Filename = filename, Tag = tag, ProgressChanged = progress, Completed = completed, }; DownloadContainer[status.Filename] = status; var info = new FileInfo() { Filename = filename, Tag = tag }; // 发送下载文件请求 session.SendRequestAsync(DOWNLOAD_FILE_INFO_REQUEST, info); } /// /// 文件移动 /// /// /// private static void FileMove(string sourceFile, string targetFile) { if (File.Exists(targetFile)) { File.Delete(targetFile); } else { string directoryName = Path.GetDirectoryName(targetFile); if (!Directory.Exists(directoryName) && directoryName != "") { Directory.CreateDirectory(directoryName); } } try { File.Move(sourceFile, targetFile); } catch (Exception exception) { Debug.LogError(exception.Message); } } } }