using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Assets.Scenes.Ride.Scripts; using Assets.Scripts.Apis.Models; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace Assets.AR { public class ARDownloader { private MapRoute _route; private readonly string _localARDataPath; private readonly string _localARRoutePath; private readonly string _localVideoPath; private readonly string _serverARDataPath; private readonly string _serverARRoutePath; private readonly string _serverVideoPath; private bool IsVideoNeedDownload { get; set; } private bool IsARDataNeedDownload { get; set; } private bool IsARRouteNeedDownload { get; set; } public bool IsDone => !IsVideoNeedDownload && !IsARDataNeedDownload && !IsARRouteNeedDownload; private float _total { get; set; } private float _videoProgress; public float VideoProgress { get => _videoProgress; set { _videoProgress = value; ComputeProgress(); } } private float _dataProgress; public float DataProgress { get => _dataProgress; set { _dataProgress = value; ComputeProgress(); } } private float _routeProgress; public float RouteProgress { get => _routeProgress; set { _routeProgress = value; ComputeProgress(); } } private Action ReportProgress { get; set; } public ARDownloader(MapRoute route) { this._route = route; var dataPath = $"{PFConstants.ARFolder}/{_route.Id}"; var path = PFConstants.VideoFolder; Helper.CreateDirectoryIfNotExsit(dataPath); _localARDataPath = $"{dataPath}/{_route.Id}.json"; _localARRoutePath = $"{dataPath}/route-{_route.Id}.json"; _localVideoPath = $"{path}/{_route.FileName}"; _serverARDataPath = _route.ARConfig; _serverARRoutePath = _route.VideoRoute; _serverVideoPath = _route.Url; } private void ComputeProgress() { var progress = (_videoProgress + _routeProgress + _dataProgress) / _total; ReportProgress?.Invoke(progress); } public async Task DownLoadDefault(Action progress = null,CancellationTokenSource cancellation = default(CancellationTokenSource)) { ReportProgress = progress; if(IsVideoNeedDownload) await DownLoadVideoAsync(Progress.Create(x => VideoProgress = x),cancellation); if(IsARRouteNeedDownload) await DownLoadARRouteAsync(Progress.Create(x=> RouteProgress = x),cancellation); if(IsARDataNeedDownload) await DownLoadARDataAsync(Progress.Create(x=> DataProgress = x),cancellation); } private async UniTask DownLoadVideoAsync(IProgress progress = null,CancellationTokenSource cancellation = default(CancellationTokenSource)) { var result = await DownloadToFileAsync(_serverVideoPath, _localVideoPath, progress, cancellation); IsVideoNeedDownload = !result.isDone; return result; } private async UniTask DownLoadARRouteAsync(IProgress progress = null,CancellationTokenSource cancellation = default(CancellationTokenSource)) { var result = await DownloadToFileAsync(_serverARRoutePath, _localARRoutePath, progress, cancellation); IsARRouteNeedDownload = !result.isDone; return result; } private async UniTask DownLoadARDataAsync(IProgress progress = null, CancellationTokenSource cancellation = default(CancellationTokenSource)) { var result = await DownloadToFileAsync(_serverARDataPath, _localARDataPath, progress, cancellation); IsARDataNeedDownload = !result.isDone; return result; } public async Task CheckAllAsync() { IsARRouteNeedDownload = await CheckARRouteAsync(); IsARDataNeedDownload = await CheckARDataAsync(); IsVideoNeedDownload = await CheckVideoAsync(); if (IsVideoNeedDownload) _total += 1f; if (IsARRouteNeedDownload) _total += 1f; if (IsARDataNeedDownload) _total += 1f; return IsVideoNeedDownload || IsARRouteNeedDownload || IsARDataNeedDownload; } /// /// return true if the video is needed to download /// /// private async Task CheckVideoAsync() { try { if (!File.Exists(_localVideoPath)) return true; return await HeadFileAsync(_serverVideoPath, _localVideoPath); } catch (Exception e) { return false; } } /// /// return true if the video need to update or download /// /// private async Task CheckARRouteAsync() { if (!File.Exists(_localARRoutePath)) return true; return await HeadFileAsync(_serverARRoutePath, _localARRoutePath); } /// /// return true if the video is needed to download /// /// private async Task CheckARDataAsync() { if (!File.Exists(_localARDataPath)) return true; return await HeadFileAsync(_serverARDataPath, _localARDataPath); } /// /// 下载文件到本地(持续存入磁盘减少内存占用) /// /// 文件连接 /// 本地文件全路径 /// 下载进度 /// private static async UniTask DownloadToFileAsync(string downloadUrl, string fullPath, IProgress progress = null, CancellationTokenSource cancellation = default(CancellationTokenSource)) { var dh = new DownloadHandlerFile(fullPath) { removeFileOnAbort = true }; var request = UnityWebRequest.Get(downloadUrl); request.downloadHandler = dh; request.method = UnityWebRequest.kHttpVerbGET; var result = await request.SendWebRequest().ToUniTask(progress, PlayerLoopTiming.Update, (cancellation?.Token ?? default(CancellationToken))); var etag = result.GetResponseHeader("x-oss-hash-crc64ecma"); var path = fullPath + ".etag"; File.WriteAllText(path, etag); return result; } /// /// head request for file meta info such as etag. /// /// download url /// local path for storage /// private static async Task HeadFileAsync(string downloadUrl, string fullPath, IProgress progress = null, CancellationTokenSource cancellation = default(CancellationTokenSource)) { try { var etagFile = fullPath + ".etag"; var etag = string.Empty; if (File.Exists(etagFile)) { etag = File.ReadAllText(etagFile); } var request = UnityWebRequest.Head(downloadUrl); var result = await request.SendWebRequest().ToUniTask(progress, PlayerLoopTiming.Update, (cancellation?.Token ?? default(CancellationToken))); var responseTag = result.GetResponseHeader("x-oss-hash-crc64ecma"); return !responseTag.Equals(etag); } catch (Exception e) { return false; } } } }