using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace Content_Update
{
public class CatalogUpdateCheckOperation : AsyncOperationBase<bool>
{
private readonly MonoBehaviour _owner;
private readonly string _host;
private readonly string _catalog;
private UnityWebRequest uwr;
public CatalogUpdateCheckOperation(MonoBehaviour owner, string host, string catalog)
{
_owner = owner;
_host = host;
_catalog = $"{catalog}.hash";
}
protected override float Progress => uwr.downloadProgress;
protected override string DebugName => "Content Catalog Update Check";
protected override void Execute()
{
_owner.StartCoroutine(DownloadData());
}
private IEnumerator DownloadData()
{
var uri = Path.Combine(_host, _catalog);
using (uwr = new UnityWebRequest(uri))
{
uwr.downloadHandler = new DownloadHandlerBuffer();
yield return uwr.SendWebRequest();
if (uwr.result != UnityWebRequest.Result.Success)
Failed(uwr);
else
Succeeded(uwr);
}
}
private void Succeeded(UnityWebRequest www)
{
var available = IsUpdateAvailable(www.downloadHandler.text);
Complete(available, true, www.error);
}
private void Failed(UnityWebRequest www)
{
Debug.LogWarning($"Content catalog update check failed with error: {www.error}");
Complete(false, false, www.error);
}
private bool IsUpdateAvailable(string hash)
{
var localHash = GetLocalHash();
Debug.Log($"Local Hash: {localHash}, S3 hash: {hash}. Same: {localHash.Equals(hash)}");
return !localHash.Equals(hash);
}
private string GetLocalHash()
{
var path = Path.Combine(CacheLocation, _catalog);
Debug.Log($"getting local hash from path: {path}");
var localHash = LoadFile(path);
return localHash;
}
private static string LoadFile(string path)
{
if (File.Exists(path) == false)
return "";
using var file = new StreamReader(path);
var fileText = file.ReadToEnd();
file.Close();
file.Dispose();
return fileText;
}
protected override void Destroy()
{
uwr.Dispose();
uwr = null;
}
}
}
public static AsyncOperationHandle<bool> IsUpdateAvailable(MonoBehaviour owner, string host, string catalog)
{
var op = new CatalogUpdateCheckOperation(owner, host, catalog);
return ResourceManager.StartOperation(op, default);
}
public static void Release<T>(AsyncOperationHandle<T> op)
{
ResourceManager.Release(op);
}
private IEnumerator CatalogUpdateCheck()
{
var updateOperation = IsUpdateAvailable(this, HOST, CATALOG);
yield return updateOperation;
var updateAvailable = updateOperation.Result;
ContentCatalog.Release(updateOperation);
if (updateAvailable)
{
UpdateFound();
}
else
{
NoUpdateFound();
}
}
private IEnumerator UpdateCatalog()
{
var remotePath = Path.Combine(HOST, CATALOG);
var localHashPath = Path.Combine(CacheLocation, $"{CATALOG}.hash");
var localJsonPath = Path.Combine(CacheLocation, $"{CATALOG}.json");
yield return Download($"{remotePath}.json", localJsonPath);
yield return Download($"{remotePath}.hash", localHashPath);
yield return Addressables.LoadContentCatalogAsync(localJsonPath, true, suffix);
Debug.Log("Catalog loaded successfully");
}
public static IEnumerator Download(string uri, string storagePath)
{
using var uwr = new UnityWebRequest(uri, UnityWebRequest.kHttpVerbGET);
uwr.downloadHandler = new DownloadHandlerFile(storagePath);
yield return uwr.SendWebRequest();
if (uwr.result != UnityWebRequest.Result.Success)
Debug.LogError(uwr.error);
else
Debug.Log("File successfully downloaded and saved to " + storagePath);
}
private static ResourceManager ResourceManager =>
_resourceManager ??= new ResourceManager(new DefaultAllocationStrategy());
This is still a more simplified version of it because it would be too complex to show the exact way im using it. But I believe this is enough information for someone to implement it into their project. (for example, these calls are in a few different files and not just 1 and I combined a few functions together for ease of reading)
might change the enumerator to use a Task also so I don’t have to pass the MonoBehaviour into the operation
Probably would want better failure checks also so you dont end up with a corrupt file if their internet goes down or something.
Technically you don’t have to use the AsyncOperations either, just seemed more correct to use them. could really just take the coroutine out of it and run it by itself
Also technically don’t have to download the hash file again because we already did in the catalog check and could just create a file with that returned hash.