You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
896 lines
34 KiB
896 lines
34 KiB
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 |
|
{ |
|
/// <summary> |
|
/// 表示传输的文件信息。 |
|
/// </summary> |
|
public struct TransferedFileInfo |
|
{ |
|
/// <summary> |
|
/// 表示传输的文件名。只包含文件名和扩展名,不包含路径。 |
|
/// </summary> |
|
/// <remarks>文件名必须保证唯一。</remarks> |
|
public string Filename; |
|
/// <summary> |
|
/// 表示传输的文件大小,最大支持 2G。 |
|
/// </summary> |
|
public int FileSize; |
|
|
|
/// <summary> |
|
/// 表示文件附加信息。 |
|
/// </summary> |
|
public ArraySegment<byte> Tag; |
|
} |
|
|
|
/// <summary> |
|
/// 表示传输的文件块信息。 |
|
/// </summary> |
|
public struct TransferedChunkInfo |
|
{ |
|
/// <summary> |
|
/// 表示文件块对应的文件。 |
|
/// </summary> |
|
public string Filename; |
|
|
|
/// <summary> |
|
/// 表示文件块总数。 |
|
/// </summary> |
|
public int ChunkCount; |
|
|
|
/// <summary> |
|
/// 表示文件块的 ID。 |
|
/// </summary> |
|
public int ChunkId; |
|
|
|
/// <summary> |
|
/// 表示文件块划分的标准。 |
|
/// </summary> |
|
public int ChunkSize; |
|
|
|
/// <summary> |
|
/// 表示文件块的数据。 |
|
/// </summary> |
|
public ArraySegment<byte> Data; |
|
} |
|
|
|
/// <summary> |
|
/// 表示传输文件块时的回复。 |
|
/// </summary> |
|
public struct TransferedChunkInfoReply |
|
{ |
|
/// <summary> |
|
/// 表示文件名。 |
|
/// </summary> |
|
public string Filename; |
|
/// <summary> |
|
/// 表示文件块 Id。 |
|
/// </summary> |
|
public int ChunkId; |
|
} |
|
|
|
/// <summary> |
|
/// 表示传输文件块时的请求。 |
|
/// </summary> |
|
public struct TransferedChunkInfoRequest |
|
{ |
|
/// <summary> |
|
/// 表示文件名。 |
|
/// </summary> |
|
public string Filename; |
|
/// <summary> |
|
/// 表示文件块 Id。 |
|
/// </summary> |
|
public int ChunkId; |
|
/// <summary> |
|
/// 表示文件块划分的标准。 |
|
/// </summary> |
|
public int ChunkSize; |
|
} |
|
|
|
/// <summary> |
|
/// 表示错误消息。 |
|
/// </summary> |
|
public struct ErrorInfo |
|
{ |
|
/// <summary> |
|
/// 表示出错的文件名。 |
|
/// </summary> |
|
public string Filename; |
|
/// <summary> |
|
/// 表示错误码。 |
|
/// </summary> |
|
public int ErrorCode; |
|
/// <summary> |
|
/// 表示错误详细信息。 |
|
/// </summary> |
|
public string ErrorMessage; |
|
} |
|
|
|
/// <summary> |
|
/// 表示文件传输过程中的状态。 |
|
/// </summary> |
|
internal class TransferedFileStatus |
|
{ |
|
/// <summary> |
|
/// 表示传输文件会话。 |
|
/// </summary> |
|
public IAppSession<BinaryProtocol, BinaryMessage> AppSession; |
|
/// <summary> |
|
/// 表示要传输的文件路径。相对路径和绝对路径均可。 |
|
/// </summary> |
|
public string FullName; |
|
/// <summary> |
|
/// 表示文件名。只包含文件名和扩展名,不包含路径。 |
|
/// </summary> |
|
public string Filename; |
|
/// <summary> |
|
/// 表示文件总共划分了多少块。 |
|
/// </summary> |
|
public int TotalChunkCount; |
|
/// <summary> |
|
/// 表示当前传输到哪块了。 |
|
/// </summary> |
|
public int CurrentChunkIndex; |
|
/// <summary> |
|
/// 表示当前已经传输了多少块了。 |
|
/// </summary> |
|
public int TransferedChunkCount; |
|
/// <summary> |
|
/// 表示文件传输过程中的进度变化。 |
|
/// </summary> |
|
public Action<string, float> ProgressChanged; |
|
/// <summary> |
|
/// 表示文件传输完毕事件。 |
|
/// </summary> |
|
public Action<string> Completed; |
|
/// <summary> |
|
/// 表示文件传输时发生错误,当前只针对下载。 |
|
/// </summary> |
|
public Action<ErrorInfo> Error; |
|
} |
|
|
|
public static class FileTransfer |
|
{ |
|
internal class FileSyncStatus |
|
{ |
|
public BitArray BitArray; |
|
public ArraySegment<byte> 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<string, TransferedFileStatus> UploadContainer = new Dictionary<string, TransferedFileStatus>(); |
|
private static Dictionary<string, TransferedFileStatus> DownloadContainer = new Dictionary<string, TransferedFileStatus>(); |
|
private static Dictionary<string, TransferedFileStatus> ForwardContainer = new Dictionary<string, TransferedFileStatus>(); |
|
private static Dictionary<string, FileSyncStatus> FileSyncContainer = new Dictionary<string, FileSyncStatus>(); |
|
|
|
/// <summary> |
|
/// 表示文件传输初始化。 |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// 设置文件下载时的临时目录。 |
|
/// </summary> |
|
/// <param name="temp">用户指定的临时目录</param> |
|
/// <remarks>注意:最好在登录成功之后进行设置。用户指定的临时完整路径是 Application.dataPath/Temp/temp/。</remarks> |
|
public static void SetTempPath(string temp) |
|
{ |
|
if (temp.EndsWith(DirectorySeparator)) |
|
TempFolder = BaseTempFolder + temp; |
|
else |
|
TempFolder = BaseTempFolder + temp + DirectorySeparator; |
|
} |
|
|
|
/// <summary> |
|
/// 获得文件下载时的临时目录。 |
|
/// </summary> |
|
public static string GetTempPath() |
|
{ |
|
return TempFolder; |
|
} |
|
|
|
/// <summary> |
|
/// 设置文件上传目录。 |
|
/// </summary> |
|
/// <param name="upload">用户指定的文件上传目录</param> |
|
/// <remarks>注意:最好在登录成功之后进行设置。</remarks> |
|
public static void SetUploadPath(string upload) |
|
{ |
|
if (upload.EndsWith(DirectorySeparator)) |
|
UploadFolder = upload; |
|
else |
|
UploadFolder = upload + DirectorySeparator; |
|
} |
|
|
|
/// <summary> |
|
/// 获得文件上传目录。 |
|
/// </summary> |
|
public static string GetUploadPath() |
|
{ |
|
return UploadFolder; |
|
} |
|
|
|
private static void OnPullFileInfoReply(BinaryMessage message) |
|
{ |
|
var info = message.Body.Deserialize<TransferedFileInfo>(); |
|
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<TransferedChunkInfo>(); |
|
|
|
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<KeyValuePair<string, ArraySegment<byte>>>(); |
|
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<TransferedChunkInfo>(); |
|
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<KeyValuePair<string, ArraySegment<byte>>>(); |
|
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<TransferedFileInfo>(); |
|
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<TransferedChunkInfoReply>(); |
|
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<KeyValuePair<string, ArraySegment<byte>>>(); |
|
var filename = pair.Key; |
|
var body = pair.Value.Deserialize<KeyValuePair<string, ArraySegment<byte>>>(); |
|
|
|
// 通知客户端收到回复了 |
|
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<TransferedChunkInfoReply>(); |
|
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<string>(); |
|
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<ErrorInfo>(); |
|
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<TransferedChunkInfo>(); |
|
|
|
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<TransferedFileInfo>(); |
|
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<byte>(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<byte>(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<BinaryProtocol, BinaryMessage> session, string filename) |
|
{ |
|
UploadFileAsync(session, filename, null, null); |
|
} |
|
|
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string, float> progress) |
|
{ |
|
UploadFileAsync(session, filename, progress, null); |
|
} |
|
|
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string> completed) |
|
{ |
|
UploadFileAsync(session, filename, null, completed); |
|
} |
|
|
|
/// <summary> |
|
/// 异步上传文件。 |
|
/// </summary> |
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string, float> progress, Action<string> 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<BinaryProtocol, BinaryMessage> session, string header, string filename) |
|
{ |
|
SendFileAsync(session, header, filename, (Action<string, float>)null); |
|
} |
|
|
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, T body, string filename) |
|
{ |
|
SendFileAsync(session, header, body, filename, null, null); |
|
} |
|
|
|
public static void SendFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, string filename, Action<string, float> progress) |
|
{ |
|
SendFileAsync(session, header, filename, progress, null); |
|
} |
|
|
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, T body, string filename, Action<string, float> progress) |
|
{ |
|
SendFileAsync(session, header, body, filename, progress, null); |
|
} |
|
|
|
public static void SendFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, string filename, Action<string, float> progress, Action<string> completed) |
|
{ |
|
var pair = new KeyValuePair<string, ArraySegment<byte>>(header, default(ArraySegment<byte>)); |
|
|
|
SendFileAsync(session, filename, pair, progress, completed); |
|
} |
|
|
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, T body, string filename, Action<string, float> progress, Action<string> completed) |
|
{ |
|
var buffer = BufferPool.Acquire(); |
|
var segment = body.Serialize(); |
|
|
|
Buffer.BlockCopy(segment.Array, segment.Offset, buffer, 0, segment.Count); |
|
|
|
var rawBody = new ArraySegment<byte>(buffer, 0, segment.Count); |
|
var pair = new KeyValuePair<string, ArraySegment<byte>>(header, rawBody); |
|
|
|
SendFileAsync(session, filename, pair, progress, completed); |
|
|
|
BufferPool.Release(buffer); |
|
} |
|
|
|
/// <summary> |
|
/// 异步转发文件。 |
|
/// </summary> |
|
/// <remarks>注意,调用此方法传输的文件应该都是小文件(不大于 1M 的文件)。传输大文件可能会比较低效。</remarks> |
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, T tag, Action<string, float> progress, Action<string> 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<byte>(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<BinaryProtocol, BinaryMessage> session, string filename) |
|
{ |
|
DownloadFileAsync(session, filename, null, null); |
|
} |
|
|
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string> completed) |
|
{ |
|
DownloadFileAsync(session, filename, null, completed); |
|
} |
|
|
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string, float> progress) |
|
{ |
|
DownloadFileAsync(session, filename, progress, null); |
|
} |
|
|
|
/// <summary> |
|
/// 异步下载文件。 |
|
/// </summary> |
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string fullname, Action<string, float> progress, Action<string> completed) |
|
{ |
|
DownloadFileAsync(session, fullname, progress, completed, null); |
|
} |
|
|
|
/// <summary> |
|
/// 异步下载文件。 |
|
/// </summary> |
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string fullname, Action<string, float> progress, Action<string> completed, Action<ErrorInfo> 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); |
|
} |
|
} |
|
} |