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.
396 lines
14 KiB
396 lines
14 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 ChunkBaseInfo |
|
{ |
|
/// <summary> |
|
/// 相对文件路径。 |
|
/// </summary> |
|
public string Filename { get; set; } |
|
/// <summary> |
|
/// 文件大小。 |
|
/// </summary> |
|
public long FileSize { get; set; } |
|
/// <summary> |
|
/// 文件分块区间最小值。 |
|
/// </summary> |
|
public long RangeMin { get; set; } |
|
/// <summary> |
|
/// 文件分块区间最大值。 |
|
/// </summary> |
|
public long RangeMax { get; set; } |
|
} |
|
|
|
/// <summary> |
|
/// 表示文件分块数据信息。 |
|
/// </summary> |
|
public struct ChunkDataInfo |
|
{ |
|
/// <summary> |
|
/// 相对文件路径。 |
|
/// </summary> |
|
public string Filename { get; set; } |
|
/// <summary> |
|
/// 文件大小。 |
|
/// </summary> |
|
public long FileSize { get; set; } |
|
/// <summary> |
|
/// 文件分块区间最小值。 |
|
/// </summary> |
|
public long RangeMin { get; set; } |
|
/// <summary> |
|
/// 文件分块区间最大值。 |
|
/// </summary> |
|
public long RangeMax { get; set; } |
|
/// <summary> |
|
/// 文件块数据。 |
|
/// </summary> |
|
public ArraySegment<byte> Data { get; set; } |
|
} |
|
|
|
/// <summary> |
|
/// 表示文件信息。 |
|
/// </summary> |
|
public struct FileInfo |
|
{ |
|
/// <summary> |
|
/// 相对文件路径。 |
|
/// </summary> |
|
public string Filename { get; set; } |
|
/// <summary> |
|
/// 文件大小。 |
|
/// </summary> |
|
public long FileSize { get; set; } |
|
/// <summary> |
|
/// 文件标签,可用作扩展。 |
|
/// </summary> |
|
public string Tag { get; set; } |
|
} |
|
|
|
/// <summary> |
|
/// 表示文件传输过程中的状态。 |
|
/// </summary> |
|
internal class TransferedFileStatus |
|
{ |
|
/// <summary> |
|
/// 表示传输文件会话。 |
|
/// </summary> |
|
public IAppSession<BinaryProtocol, BinaryMessage> AppSession; |
|
/// <summary> |
|
/// 表示要传输的文件路径。相对路径和绝对路径均可。 |
|
/// </summary> |
|
public string FullName; |
|
/// <summary> |
|
/// 表示临时的文件名称。 |
|
/// </summary> |
|
public string TempName; |
|
/// <summary> |
|
/// 表示文件名。只包含文件名和扩展名,不包含路径。 |
|
/// </summary> |
|
public string Filename; |
|
/// <summary> |
|
/// 文件大小。 |
|
/// </summary> |
|
public long FileSize; |
|
/// <summary> |
|
/// 表示文件标签。 |
|
/// </summary> |
|
public string Tag; |
|
/// <summary> |
|
/// 表示文件还需传输多少字节。 |
|
/// </summary> |
|
public long TransferedSize; |
|
/// <summary> |
|
/// 表示文件传输过程中的进度变化。 |
|
/// </summary> |
|
public Action<string, string, float> ProgressChanged; |
|
/// <summary> |
|
/// 表示文件传输完毕事件。 |
|
/// </summary> |
|
public Action<string, string> 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<string, TransferedFileStatus> UploadContainer = new Dictionary<string, TransferedFileStatus>(); |
|
private static Dictionary<string, TransferedFileStatus> DownloadContainer = new Dictionary<string, TransferedFileStatus>(); |
|
|
|
/// <summary> |
|
/// 表示文件传输初始化。 |
|
/// </summary> |
|
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<ChunkBaseInfo>(); |
|
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<FileInfo>(); |
|
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<ChunkDataInfo>(); |
|
|
|
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<FileInfo>(); |
|
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<byte>(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); |
|
} |
|
|
|
/// <summary> |
|
/// 异步上传文件。 |
|
/// </summary> |
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string fullname, string filename, string tag = null, Action<string, string, float> progress = null, Action<string, string> 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); |
|
} |
|
|
|
/// <summary> |
|
/// 异步下载文件。 |
|
/// </summary> |
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string fullname, string filename, string tag = null, Action<string, string, float> progress = null, Action<string, string> 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); |
|
} |
|
/// <summary> |
|
/// 文件移动 |
|
/// </summary> |
|
/// <param name="sourceFile"></param> |
|
/// <param name="targetFile"></param> |
|
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); |
|
} |
|
} |
|
} |
|
|
|
} |