海淀天下城电子沙盘单机版
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

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);
}
}
}
}