using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
namespace TcgEngine
{
///
/// 资源下载管理器
///
public class ResourceDownloader : MonoBehaviour
{
[Header("Configuration")]
[Tooltip("资源下载器配置文件")]
public ResourceDownloaderConfig config;
private string currentVersion = "0.0.0";
private string currentMd5 = "";
private string targetVersion = "0.0.1";
private string targetMd5 = "";
private string persistentDataPath;
private string spritesPath;
public static ResourceDownloader instance;
// 事件
public event Action OnDownloadProgress;
public event Action OnDownloadComplete;
public event Action OnDownloadError;
public event Action OnExtractProgress;
public event Action OnExtractComplete;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
InitializePaths();
}
else
{
// 如果已经存在实例,销毁这个重复的组件
Debug.LogWarning("ResourceDownloader instance already exists, destroying duplicate");
Destroy(gameObject);
}
}
private void InitializePaths()
{
persistentDataPath = Application.persistentDataPath;
spritesPath = Path.Combine(persistentDataPath, "Sprites");
// 确保目录存在
if (!Directory.Exists(spritesPath))
{
Directory.CreateDirectory(spritesPath);
}
LoadCurrentVersion();
}
///
/// 检查是否需要更新资源
///
public async Task CheckForUpdates()
{
try
{
Debug.Log($"Starting update check. Current version: {currentVersion}, Current MD5: {currentMd5}");
var serverVersionData = await GetServerVersionData();
if (serverVersionData == null || string.IsNullOrEmpty(serverVersionData.version))
{
return false;
}
targetVersion = serverVersionData.version;
targetMd5 = serverVersionData.md5 ?? "";
Debug.Log($"Server version: {targetVersion}, MD5: {targetMd5}, Local version: {currentVersion}, Local MD5: {currentMd5}");
return await CheckVersionAndMd5();
}
catch (Exception e)
{
Debug.LogError($"Error checking for updates: {e.Message}");
return false;
}
}
///
/// 检查版本和MD5,决定是否需要下载
///
private async Task CheckVersionAndMd5()
{
// 检查版本是否不同
if (currentVersion != targetVersion)
{
Debug.Log($"Version mismatch. Current: {currentVersion}, Target: {targetVersion}");
return true;
}
// 检查本地文件是否存在
if (!Directory.Exists(spritesPath) || Directory.GetFiles(spritesPath, "*", SearchOption.AllDirectories).Length == 0)
{
Debug.Log("Local sprites not found, need to download");
return true;
}
// 版本相同,检查MD5
if (string.IsNullOrEmpty(targetMd5))
{
Debug.Log("No target MD5 provided, assuming files are correct");
return false;
}
string localMd5 = await CalculateDirectoryMd5(spritesPath);
Debug.Log($"版本匹配,开始检查MD5校验... 当前版本: {currentVersion} 目标版本: {targetVersion} 当前MD5: {localMd5} 目标MD5: {targetMd5}");
if (localMd5 != targetMd5)
{
Debug.Log("MD5校验失败,正在删除损坏的文件并重新下载...");
// 删除损坏的文件
if (Directory.Exists(spritesPath))
{
Directory.Delete(spritesPath, true);
}
return true; // 需要重新下载
}
Debug.Log($"✅ MD5校验通过");
return false; // 无需下载
}
///
/// 计算目录下所有文件的MD5和
///
private async Task CalculateDirectoryMd5(string directoryPath)
{
try
{
if (!Directory.Exists(directoryPath))
return "";
var files = Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories);
if (files.Length == 0)
return "";
// 按路径排序确保一致性
Array.Sort(files);
using (var md5 = System.Security.Cryptography.MD5.Create())
{
foreach (string file in files)
{
// 只处理相对路径部分
string relativePath = file.Substring(directoryPath.Length + 1).Replace('\\', '/');
byte[] pathBytes = System.Text.Encoding.UTF8.GetBytes(relativePath);
md5.TransformBlock(pathBytes, 0, pathBytes.Length, null, 0);
// 读取文件内容
byte[] fileBytes = File.ReadAllBytes(file);
md5.TransformBlock(fileBytes, 0, fileBytes.Length, null, 0);
// 每10个文件yield一次,避免阻塞
if (Array.IndexOf(files, file) % 10 == 0)
{
await Task.Yield();
}
}
// 完成哈希计算
md5.TransformFinalBlock(new byte[0], 0, 0);
string result = BitConverter.ToString(md5.Hash).Replace("-", "").ToLowerInvariant();
return result;
}
}
catch (Exception e)
{
Debug.LogError($"Error calculating directory MD5: {e.Message}");
return "";
}
}
///
/// 构建版本化的下载URL
///
private string BuildVersionedDownloadUrl(string version)
{
string targetFileName;
if (string.IsNullOrEmpty(version))
{
Debug.LogError("Version is null");
return "0.0.0";
}
else
{
targetFileName = version;
}
if (config == null)
{
Debug.LogError("ResourceDownloaderConfig is not assigned!");
return "0.0.0";
}
string url = config.GetResourceUrl(targetFileName);
Debug.Log($"Built download URL: {url} (version: {version}, filename: {targetFileName})");
return url;
}
///
/// 验证版本化URL是否存在(可选)
///
private async Task ValidateVersionedUrl(string url)
{
if (config == null || !config.validateVersionedUrl)
return true;
try
{
using (UnityWebRequest request = UnityWebRequest.Head(url))
{
var operation = request.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
if (request.result == UnityWebRequest.Result.Success)
{
Debug.Log($"Validated versioned URL: {url}");
return true;
}
else
{
Debug.LogWarning($"Versioned URL not found: {url} - {request.error}");
return false;
}
}
}
catch (Exception e)
{
Debug.LogError($"Error validating URL {url}: {e.Message}");
return false;
}
}
///
/// 获取服务器版本数据
///
private async Task GetServerVersionData()
{
if (config == null)
{
Debug.LogError("ResourceDownloaderConfig is not assigned!");
return null;
}
string url = config.GetVersionUrl();
Debug.Log($"Version check URL: {url}");
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
var operation = request.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
if (request.result == UnityWebRequest.Result.Success)
{
try
{
var versionData = JsonUtility.FromJson(request.downloadHandler.text);
return versionData;
}
catch (Exception e)
{
Debug.LogError($"Failed to parse version response: {e.Message}");
return null;
}
}
else
{
Debug.LogError($"Failed to get version: {request.error}");
return null;
}
}
}
///
/// 下载并更新资源
///
public async Task DownloadAndUpdateResources()
{
try
{
string zipFilePath = Path.Combine(persistentDataPath, $"{targetVersion}.zip");
// 构建版本化的下载URL
string downloadUrl = BuildVersionedDownloadUrl(targetVersion);
// 验证URL是否存在(可选)
if (config != null && config.validateVersionedUrl)
{
bool urlExists = await ValidateVersionedUrl(downloadUrl);
if (!urlExists)
{
OnDownloadError?.Invoke($"Versioned resource not found for version {targetVersion}");
return false;
}
}
// 下载压缩包
bool downloadSuccess = await DownloadFile(downloadUrl, zipFilePath);
if (!downloadSuccess)
{
OnDownloadError?.Invoke("Failed to download resources");
return false;
}
OnDownloadComplete?.Invoke(zipFilePath);
// 解压文件
bool extractSuccess = await ExtractZipFile(zipFilePath, spritesPath);
if (!extractSuccess)
{
OnDownloadError?.Invoke("Failed to extract resources");
return false;
}
OnExtractComplete?.Invoke();
// 清理下载的压缩包
if (File.Exists(zipFilePath))
{
File.Delete(zipFilePath);
}
// 更新版本信息
SaveCurrentVersion(targetVersion, targetMd5);
currentVersion = targetVersion;
currentMd5 = targetMd5;
Debug.Log($"Resources updated successfully to version {currentVersion}");
return true;
}
catch (Exception e)
{
Debug.LogError($"Error downloading/updating resources: {e.Message}");
OnDownloadError?.Invoke(e.Message);
return false;
}
}
///
/// 下载文件
///
private async Task DownloadFile(string url, string filePath)
{
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
var operation = request.SendWebRequest();
while (!operation.isDone)
{
OnDownloadProgress?.Invoke(request.downloadProgress);
await Task.Yield();
}
if (request.result == UnityWebRequest.Result.Success)
{
File.WriteAllBytes(filePath, request.downloadHandler.data);
return true;
}
else
{
Debug.LogError($"Download failed: {request.error}");
return false;
}
}
}
///
/// 解压ZIP文件
///
private async Task ExtractZipFile(string zipFilePath, string extractPath)
{
try
{
// 清空目标目录
if (Directory.Exists(extractPath))
{
Directory.Delete(extractPath, true);
}
Directory.CreateDirectory(extractPath);
using (var archive = ZipFile.OpenRead(zipFilePath))
{
int totalEntries = archive.Entries.Count;
int processedEntries = 0;
foreach (var entry in archive.Entries)
{
// 跳过目录条目
if (string.IsNullOrEmpty(entry.Name))
continue;
string entryPath = Path.Combine(extractPath, entry.FullName);
string directoryName = Path.GetDirectoryName(entryPath);
if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
using (var entryStream = entry.Open())
using (var outputStream = File.Create(entryPath))
{
await entryStream.CopyToAsync(outputStream);
}
processedEntries++;
OnExtractProgress?.Invoke((float)processedEntries / totalEntries);
if (processedEntries % 10 == 0) // 每10个文件yield一次
{
await Task.Yield();
}
}
}
return true;
}
catch (Exception e)
{
Debug.LogError($"Extract failed: {e.Message}");
return false;
}
}
///
/// 加载当前版本信息
///
private void LoadCurrentVersion()
{
string versionFile = Path.Combine(persistentDataPath, "version.txt");
if (File.Exists(versionFile))
{
string[] lines = File.ReadAllLines(versionFile);
if (lines.Length > 0)
{
currentVersion = lines[0].Trim();
}
if (lines.Length > 1)
{
currentMd5 = lines[1].Trim();
}
Debug.Log($"Loaded current version: {currentVersion}, MD5: {currentMd5}");
}
else
{
currentVersion = "0.0.0";
currentMd5 = "";
Debug.Log($"No version file found, using defaults: {currentVersion}");
}
}
///
/// 保存当前版本信息
///
private void SaveCurrentVersion(string version, string md5 = "")
{
string versionFile = Path.Combine(persistentDataPath, "version.txt");
string content = version;
if (!string.IsNullOrEmpty(md5))
{
content += "\n" + md5;
}
File.WriteAllText(versionFile, content);
Debug.Log($"Saved version: {version}, MD5: {md5}");
}
///
/// 获取本地Sprite资源路径
///
public string GetLocalSpritePath(string relativePath)
{
return Path.Combine(spritesPath, relativePath);
}
///
/// 检查本地资源是否存在
///
public bool HasLocalResources()
{
return Directory.Exists(spritesPath) &&
Directory.GetFiles(spritesPath, "*", SearchOption.AllDirectories).Length > 0;
}
public string GetCurrentVersion()
{
return currentVersion;
}
public static ResourceDownloader Get()
{
if (instance == null)
{
// 如果没有实例,创建一个新的
GameObject go = new GameObject("ResourceDownloader");
instance = go.AddComponent();
DontDestroyOnLoad(go);
// 加载配置文件
instance.LoadConfig();
Debug.Log("Created new ResourceDownloader instance");
}
return instance;
}
///
/// 加载配置文件
///
private void LoadConfig()
{
// 从Resources文件夹加载配置文件
config = Resources.Load("ResourceDownloaderConfig");
if (config == null)
{
Debug.LogError("ResourceDownloaderConfig not found in Resources folder! Please create a ResourceDownloaderConfig asset in the Resources folder.");
}
else
{
Debug.Log("ResourceDownloaderConfig loaded successfully");
}
}
}
///
/// 资源版本响应数据结构
///
[Serializable]
public class ResourceVersionResponse
{
public string version;
public string md5;
public string description;
public long timestamp;
}
}