using System; using System.Collections.Generic; using System.IO; using AX.Network.Common; using AX.Network.Protocols; using IAppSession = AX.Network.Common.IAppSession; namespace AX.NetworkSystem { /// /// 表示传输类型。 /// public enum TransferType { /// /// 下载。 /// Download, /// /// 上传。 /// Upload } /// /// 表示文件块信息。 /// public class ChunkInfo { /// /// 表示文件块对应的文件。 /// public string Filename; /// /// 表示文件的大小。 /// public int FileSize; /// /// 表示文件块的 ID。 /// public int ChunkID; /// /// 表示文件块的总数。 /// public int ChunkCount; /// /// 表示文件块划分的标准大小。 /// public int ChunkSize; public ChunkInfo() { } public ChunkInfo(string filename, int fileSize, int chunkID, int chunkCount, int chunkSize) { this.Filename = filename; this.FileSize = fileSize; this.ChunkID = chunkID; this.ChunkCount = chunkCount; this.ChunkSize = chunkSize; } } /// /// 表示简单的文件块信息。 /// public struct SimpleChunkInfo { /// /// 表示文件块对应的文件。 /// public string Filename; /// /// 表示文件块的 ID。 /// public int ChunkID; public SimpleChunkInfo(string filename, int chunkID) { this.Filename = filename; this.ChunkID = chunkID; } } /// /// 表示有附加数据的文件块信息。 /// public struct AttachedChunkInfo { /// /// 表示文件块对应的文件。 /// public string Filename; /// /// 表示文件块的 ID。 /// public int ChunkID; /// /// 表示文件块的总数。 /// public int ChunkCount; /// /// 表示文件块划分的标准。 /// public int ChunkSize; /// /// 表示文件块的数据。 /// public ArraySegment Data; public AttachedChunkInfo(string filename, int chunkID, int chunkCount, int chunkSize, ArraySegment data) { this.Filename = filename; this.ChunkID = chunkID; this.ChunkCount = chunkCount; this.ChunkSize = chunkSize; this.Data = data; } } /// /// 表示一个文件传输任务。 /// public class TransferTask : IDisposable { private static readonly BufferPool BufferPool = new BufferPool(1, ChunkSize); 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 UPLOAD_FILE_INFO_REQUEST = "UPLOAD_FILE_INFO_REQUEST"; private const string UPLOAD_CHUNK_INFO_REQUEST = "UPLOAD_CHUNK_INFO_REQUEST"; private const int ChunkSize = 64 * 1024; private IAppSession appSession; private FileStream filestream; /// /// 表示该任务的 ID。 /// public int ID { get; internal set; } /// /// 表示本地的存储文件路径。 /// public string[] StoragePath { get; private set; } /// /// 表示该传输任务的传输文件路径。 /// public string[] TransferPath { get; private set; } /// /// 表示当前正在传输的文件所对应的索引位置。 /// public int Position { get; private set; } /// /// 表示当前正在传输的文件块。 /// public ChunkInfo CurrentChunk { get; private set; } /// /// 表示当前传输任务是下载还是上传。 /// public TransferType TransferType { get; private set; } /// /// 表示该任务是否已暂停。 /// public bool IsPaused { get; private set; } private bool isStopped; internal TransferTask(IAppSession session, string[] storagePath, string[] transferPath, TransferType type, int taskID) { if (session == null) throw new ArgumentNullException("session"); this.Reset(storagePath, transferPath, type, taskID); this.appSession = session; } public event Action Started; public event Action Paused; public event Action Resumed; public event Action Stopped; public event Action TransferFileCompleted; public event Action TransferTaskCompleted; public event Action ProgressChanged; protected virtual void OnStarted(string filename) { if (Started != null) Started(filename); } protected virtual void OnPaused(string filename) { if (Paused != null) Paused(filename); } protected virtual void OnResumed(string filename) { if (Resumed != null) Resumed(filename); } protected virtual void OnStopped() { if (Stopped != null) Stopped(); } protected virtual void OnTransferFileCompleted(string filename) { if (TransferFileCompleted != null) TransferFileCompleted(filename); } protected virtual void OnTransferTaskCompleted() { if (TransferTaskCompleted != null) TransferTaskCompleted(); } protected virtual void OnProgressChanged(float curr, float total) { if (ProgressChanged != null) ProgressChanged(curr, total); } public void Start() { isStopped = false; OnStarted(CurrentChunk.Filename); // 下载 if (TransferType == TransferType.Download) { if (CurrentChunk.ChunkID == 0 && CurrentChunk.ChunkCount == 0 && CurrentChunk.FileSize == 0) appSession.SendRequestAsync(DOWNLOAD_FILE_INFO_REQUEST, CurrentChunk.Filename); else { var info = new SimpleChunkInfo(CurrentChunk.Filename, CurrentChunk.ChunkID); appSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, info); } } // 上传 else { var filename = StoragePath[Position]; var fileinfo = new FileInfo(filename); var fileSize = (int)fileinfo.Length; var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1; CurrentChunk.FileSize = fileSize; CurrentChunk.ChunkCount = chunkCount; if (CurrentChunk.ChunkID == 0) { var info = new ChunkInfo(CurrentChunk.Filename, fileSize, CurrentChunk.ChunkID, CurrentChunk.ChunkCount, CurrentChunk.ChunkSize); appSession.SendRequestAsync(UPLOAD_FILE_INFO_REQUEST, info); } else UploadFileChunk(filename); } } public void Pause() { if (!isStopped && !IsPaused) { IsPaused = true; OnPaused(CurrentChunk.Filename); } } public void Resume() { if (!isStopped && IsPaused) { IsPaused = false; OnResumed(CurrentChunk.Filename); if (TransferType == TransferType.Download) { if (CurrentChunk.ChunkID == 0 && CurrentChunk.ChunkCount == 0 && CurrentChunk.FileSize == 0) appSession.SendRequestAsync(DOWNLOAD_FILE_INFO_REQUEST, CurrentChunk.Filename); else { var info = new SimpleChunkInfo(CurrentChunk.Filename, CurrentChunk.ChunkID); appSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, info); } } else { var filename = StoragePath[Position]; var fileinfo = new FileInfo(filename); var fileSize = (int)fileinfo.Length; var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1; CurrentChunk.FileSize = fileSize; CurrentChunk.ChunkCount = chunkCount; if (CurrentChunk.ChunkID == 0) { var info = new ChunkInfo(CurrentChunk.Filename, fileSize, CurrentChunk.ChunkID, CurrentChunk.ChunkCount, CurrentChunk.ChunkSize); appSession.SendRequestAsync(UPLOAD_FILE_INFO_REQUEST, info); } else UploadFileChunk(filename); } } } public void Stop() { isStopped = true; IsPaused = false; OnStopped(); } public void Dispose() { this.Reset(); if (filestream != null) filestream.Dispose(); if (appSession != null) appSession = null; } public void Reset() { this.Position = 0; this.IsPaused = false; this.isStopped = false; this.CurrentChunk = new ChunkInfo(); ; this.CurrentChunk.Filename = ID + "://" + TransferPath[0]; this.CurrentChunk.FileSize = 0; this.CurrentChunk.ChunkID = 0; this.CurrentChunk.ChunkCount = 0; this.CurrentChunk.ChunkSize = ChunkSize; } public void Reset(string[] storagePath, string[] transferPath, TransferType type, int taskID) { if (storagePath == null) throw new ArgumentNullException("storagePath"); if (transferPath == null) throw new ArgumentNullException("transferPath"); if (storagePath.Length != transferPath.Length) throw new ArgumentOutOfRangeException("storagePath or transferPath"); for (var i = 0; i < storagePath.Length; ++i) if (string.IsNullOrEmpty(storagePath[i])) throw new ArgumentException("storagePath or transferPath"); this.StoragePath = storagePath; this.TransferPath = transferPath; this.TransferType = type; this.ID = taskID; this.Reset(); } internal void ProcessDownloadFileReplyInfo(ref ChunkInfo info) { CurrentChunk.FileSize = info.FileSize; CurrentChunk.ChunkID = 0; CurrentChunk.ChunkCount = info.ChunkCount; // 先创建文件夹 var filename = StoragePath[Position]; var folder = Path.GetDirectoryName(filename); if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder)) Directory.CreateDirectory(folder); if (isStopped || IsPaused) return; var chunkInfo = new SimpleChunkInfo(CurrentChunk.Filename, CurrentChunk.ChunkID); appSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, chunkInfo); } internal void ProcessDownloadChunkReplyInfo(ref AttachedChunkInfo info) { var filename = StoragePath[Position]; if (filestream == null) filestream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write, info.Data.Count, false); // 写入文件 filestream.Seek(info.ChunkID * info.ChunkSize, SeekOrigin.Begin); filestream.Write(info.Data.Array, info.Data.Offset, info.Data.Count); filestream.Flush(); CurrentChunk.ChunkID += 1; // 还未下载完该文件的所有文件块 if (CurrentChunk.ChunkID != CurrentChunk.ChunkCount) { var currProgress = ((float)info.ChunkID / (float)info.ChunkCount) * 100.0f; var totalProgress = ((float)Position / (float)StoragePath.Length) * 100.0f; // 报告进度 OnProgressChanged(currProgress, totalProgress); if (isStopped || IsPaused) return; // 继续下载剩余文件块 var chunkInfo = new SimpleChunkInfo(CurrentChunk.Filename, CurrentChunk.ChunkID); appSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, chunkInfo); } // 已下载完一个文件的所有文件块 else { if (filestream != null) { filestream.Close(); filestream = null; } ++Position; // 全部下载完成 if (Position == StoragePath.Length) { var currProgress = 100.0f; var totalProgress = 100.0f; // 报告进度 OnProgressChanged(currProgress, totalProgress); OnTransferTaskCompleted(); } else { var currProgress = 100.0f; var totalProgress = ((float)Position / (float)StoragePath.Length) * 100.0f; // 报告进度 OnProgressChanged(currProgress, totalProgress); OnTransferFileCompleted(info.Filename); CurrentChunk.Filename = ID + "://" + TransferPath[Position]; CurrentChunk.ChunkID = 0; CurrentChunk.ChunkCount = 0; CurrentChunk.FileSize = 0; if (isStopped || IsPaused) return; // 继续下载剩余的文件 appSession.SendRequestAsync(DOWNLOAD_FILE_INFO_REQUEST, CurrentChunk.Filename); } } } internal void ProcessUploadFileReplyInfo(ref SimpleChunkInfo info) { CurrentChunk.ChunkID = 0; if (isStopped || IsPaused) return; // 开始上传文件块 UploadFileChunk(StoragePath[Position]); } internal void ProcessUploadChunkReplyInfo(ref SimpleChunkInfo info) { CurrentChunk.ChunkID += 1; // 表示还没上传完一个文件 if (CurrentChunk.ChunkID != CurrentChunk.ChunkCount) { var currProgress = ((float)CurrentChunk.ChunkID / (float)CurrentChunk.ChunkCount) * 100.0f; var totalProgress = ((float)Position / (float)StoragePath.Length) * 100.0f; // 报告进度 OnProgressChanged(currProgress, totalProgress); if (isStopped || IsPaused) return; // 继续上传剩余的文件块 UploadFileChunk(StoragePath[Position]); } // 已上传完一个文件的所有文件块 else { if (filestream != null) { filestream.Close(); filestream = null; } ++Position; // 全部上传完成 if (Position == StoragePath.Length) { var currProgress = 100.0f; var totalProgress = 100.0f; // 报告进度 OnProgressChanged(currProgress, totalProgress); OnTransferTaskCompleted(); } else { var currProgress = 100.0f; var totalProgress = ((float)Position / (float)StoragePath.Length) * 100.0f; // 报告进度 OnProgressChanged(currProgress, totalProgress); OnTransferFileCompleted(info.Filename); CurrentChunk.Filename = ID + "://" + TransferPath[Position]; CurrentChunk.ChunkID = 0; var fileinfo = new FileInfo(StoragePath[Position]); var fileSize = (int)fileinfo.Length; var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1; CurrentChunk.FileSize = fileSize; CurrentChunk.ChunkCount = chunkCount; if (isStopped || IsPaused) return; // 继续上传剩余的文件 var upInfo = new ChunkInfo(CurrentChunk.Filename, CurrentChunk.FileSize, CurrentChunk.ChunkID, CurrentChunk.ChunkCount, CurrentChunk.ChunkSize); appSession.SendRequestAsync(UPLOAD_FILE_INFO_REQUEST, upInfo); } } } private void UploadFileChunk(string filename) { if (filestream == null) filestream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read, ChunkSize, false); var buffer = BufferPool.Acquire(); var count = filestream.Read(buffer, 0, ChunkSize); var data = new ArraySegment(buffer, 0, count); var info = new AttachedChunkInfo(CurrentChunk.Filename, CurrentChunk.ChunkID, CurrentChunk.ChunkCount, CurrentChunk.ChunkSize, data); appSession.SendRequestAsync(UPLOAD_CHUNK_INFO_REQUEST, info); BufferPool.Release(buffer); } } /// /// 该类用于文件传输,可下载,也可上传。 /// public static class FileTransfer { 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_REPLY = "UPLOAD_FILE_INFO_REPLY"; private const string UPLOAD_CHUNK_INFO_REPLY = "UPLOAD_CHUNK_INFO_REPLY"; private static readonly string[] Delimiters = new string[] { "://" }; private static List taskList = new List(); /// /// 初始化。 /// public static void Initialize() { NetworkMessageDispatcher.AddListener(DOWNLOAD_FILE_INFO_REPLY, ProcessDownloadFileReplyInfo); NetworkMessageDispatcher.AddListener(DOWNLOAD_CHUNK_INFO_REPLY, ProcessDownloadChunkReplyInfo); NetworkMessageDispatcher.AddListener(UPLOAD_FILE_INFO_REPLY, ProcessUploadFileReplyInfo); NetworkMessageDispatcher.AddListener(UPLOAD_CHUNK_INFO_REPLY, UploadChunkReplyInfo); } /// /// 销毁资源。 /// public static void Dispose() { NetworkMessageDispatcher.RemoveListener(DOWNLOAD_FILE_INFO_REPLY, ProcessDownloadFileReplyInfo); NetworkMessageDispatcher.RemoveListener(DOWNLOAD_CHUNK_INFO_REPLY, ProcessDownloadChunkReplyInfo); NetworkMessageDispatcher.RemoveListener(UPLOAD_FILE_INFO_REPLY, ProcessUploadFileReplyInfo); NetworkMessageDispatcher.RemoveListener(UPLOAD_CHUNK_INFO_REPLY, UploadChunkReplyInfo); } /// /// 开启一个传输任务。 /// /// 传输时使用的会话 /// 本地的存储文件路径 /// 要传输的文件路径 /// 传输类型 public static TransferTask CreateTransferTask(IAppSession session, string[] storagePath, string[] transferPath, TransferType type) { var task = new TransferTask(session, storagePath, transferPath, type, taskList.Count); taskList.Add(task); return task; } public static bool DestroyTransferTask(TransferTask task) { if (task == null) return true; task.Dispose(); return taskList.Remove(task); } public static bool DestroyTransferTask(int taskID) { var index = taskList.FindIndex((task) => task.ID == taskID); if (index < 0) return false; taskList[index].Dispose(); taskList.RemoveAt(index); return true; } public static TransferTask GetTransferTask(int taskID) { var index = taskList.FindIndex((task) => task.ID == taskID); if (index < 0) return null; return taskList[index]; } public static List GetTaskList() { return taskList; } public static void Clear() { for (var i = 0; i < taskList.Count; ++i) taskList[i].Dispose(); taskList.Clear(); } private static void ProcessDownloadFileReplyInfo(BinaryMessage message) { var info = message.Body.Deserialize(); var array = info.Filename.Split(Delimiters, StringSplitOptions.None); var taskID = int.Parse(array[0]); var task = GetTransferTask(taskID); if (task != null) task.ProcessDownloadFileReplyInfo(ref info); } private static void ProcessDownloadChunkReplyInfo(BinaryMessage message) { var info = message.Body.Deserialize(); var array = info.Filename.Split(Delimiters, StringSplitOptions.None); var taskID = int.Parse(array[0]); var task = GetTransferTask(taskID); if (task != null) task.ProcessDownloadChunkReplyInfo(ref info); } private static void ProcessUploadFileReplyInfo(BinaryMessage message) { var info = message.Body.Deserialize(); var array = info.Filename.Split(Delimiters, StringSplitOptions.None); var taskID = int.Parse(array[0]); var task = GetTransferTask(taskID); if (task != null) task.ProcessUploadFileReplyInfo(ref info); } private static void UploadChunkReplyInfo(BinaryMessage message) { var info = message.Body.Deserialize(); var array = info.Filename.Split(Delimiters, StringSplitOptions.None); var taskID = int.Parse(array[0]); var task = GetTransferTask(taskID); if (task != null) task.ProcessUploadChunkReplyInfo(ref info); } } }