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
4 years ago
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|