diff --git a/Assets/Scripts/AssetsDownloader.cs b/Assets/Scripts/AssetsDownloader.cs index 7cf44bc..ae41256 100644 --- a/Assets/Scripts/AssetsDownloader.cs +++ b/Assets/Scripts/AssetsDownloader.cs @@ -12,550 +12,478 @@ using UnityEngine.SceneManagement; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace QuestAppLauncher -{ - public class InstallAPK - { - private bool installApp(string apkPath) -{ - bool success = true; - //GameObject.Find("TextDebug").GetComponent().text = "Installing App"; +namespace QuestAppLauncher { + public class InstallAPK { + private bool installApp(string apkPath) { + bool success = true; + //GameObject.Find("TextDebug").GetComponent().text = "Installing App"; - try - { + try { //Get Activity then Context AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); - AndroidJavaObject currentActivity = unityPlayer.GetStatic("currentActivity"); - AndroidJavaObject unityContext = currentActivity.Call("getApplicationContext"); + AndroidJavaObject currentActivity = unityPlayer.GetStatic < AndroidJavaObject > ("currentActivity"); + AndroidJavaObject unityContext = currentActivity.Call < AndroidJavaObject > ("getApplicationContext"); //Get the package Name - string packageName = unityContext.Call("getPackageName"); + string packageName = unityContext.Call < string > ("getPackageName"); string authority = packageName + ".fileprovider"; AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent"); - string ACTION_VIEW = intentObj.GetStatic("ACTION_VIEW"); + string ACTION_VIEW = intentObj.GetStatic < string > ("ACTION_VIEW"); AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW); - - int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic("FLAG_ACTIVITY_NEW_TASK"); - int FLAG_GRANT_READ_URI_PERMISSION = intentObj.GetStatic("FLAG_GRANT_READ_URI_PERMISSION"); + int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic < int > ("FLAG_ACTIVITY_NEW_TASK"); + int FLAG_GRANT_READ_URI_PERMISSION = intentObj.GetStatic < int > ("FLAG_GRANT_READ_URI_PERMISSION"); //File fileObj = new File(String pathname); AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath); //FileProvider object that will be used to call it static function AndroidJavaClass fileProvider = new AndroidJavaClass("android.support.v4.content.FileProvider"); //getUriForFile(Context context, String authority, File file) - AndroidJavaObject uri = fileProvider.CallStatic("getUriForFile", unityContext, authority, fileObj); + AndroidJavaObject uri = fileProvider.CallStatic < AndroidJavaObject > ("getUriForFile", unityContext, authority, fileObj); - intent.Call("setDataAndType", uri, "application/vnd.android.package-archive"); - intent.Call("addFlags", FLAG_ACTIVITY_NEW_TASK); - intent.Call("addFlags", FLAG_GRANT_READ_URI_PERMISSION); + intent.Call < AndroidJavaObject > ("setDataAndType", uri, "application/vnd.android.package-archive"); + intent.Call < AndroidJavaObject > ("addFlags", FLAG_ACTIVITY_NEW_TASK); + intent.Call < AndroidJavaObject > ("addFlags", FLAG_GRANT_READ_URI_PERMISSION); currentActivity.Call("startActivity", intent); //GameObject.Find("TextDebug").GetComponent().text = "Success"; - } - catch (System.Exception e) - { + } catch (System.Exception e) { //GameObject.Find("TextDebug").GetComponent().text = "Error: " + e.Message; success = false; - } + } - return success; -} + return success; } + } + /// + /// Downloads assets (app icons packs and names files) from configured repos. + /// + public class AssetsDownloader { + // Download cache folder that contains downloaded files + const string DownloadCacheFolder = "download_cache"; + + // Manifest file to track what we've downloaded + const string DownloadManifestFile = "download_manifest.json"; + + // Temporary filename for download + const string TempDownloadFileExtention = ".tmp_download"; + + // GitHub API url + const string GithubApiUrl = @ "http://api.github.com/"; + + // APK Remote config url + const string APKCUrl = @ "http://vrdevices.hooverhigh.ml/sys/oculus-apk.json"; + + // If Github account is a org set this to true + public bool isorg = true; + + public string GithubUrl = ""; + + // Rate limit in minutes + const int RateLimitInMins = 5; + + // Used for mutual exclusion when loading assets + static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + /// - /// Downloads assets (app icons packs and names files) from configured repos. + /// Class that tracks each file downloaded. + /// Used to determine whether an asset has been updated since last download. /// - public class AssetsDownloader - { - // Download cache folder that contains downloaded files - const string DownloadCacheFolder = "download_cache"; + [Serializable] + public class AssetInfo { + // Repo uri + public string RepoUri; - // Manifest file to track what we've downloaded - const string DownloadManifestFile = "download_manifest.json"; + // Asset's url + public string Url; - // Temporary filename for download - const string TempDownloadFileExtention = ".tmp_download"; + // Asset's updated at timestamp + public string UpdatedAt; - // GitHub API url - const string GithubApiUrl = @"http://api.github.com/"; - - // If Github account is a org set this to true - public bool isorg = true; - - public string GithubUrl = ""; + // Asset's tag name + public string TagName; + } - // Rate limit in minutes - const int RateLimitInMins = 5; + /// + /// Mapping of asset name to asset info above. This is persisted in the download manifest. + /// + [Serializable] + public class AssetsManifest { + // File name -> release metadata + public Dictionary < string, AssetInfo > Metadata = new Dictionary < string, AssetInfo > (); - // Used for mutual exclusion when loading assets - static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + // Last updated timestamp + public DateTime LastUpdated; + } - /// - /// Class that tracks each file downloaded. - /// Used to determine whether an asset has been updated since last download. - /// - [Serializable] - public class AssetInfo - { - // Repo uri - public string RepoUri; + /// + /// Helper method to download assets asynchronously. After completion, scene is automatically reloaded + /// if new assets have been downloaded. + /// + /// Current config + /// Download progress interface - used to indicate download progress + /// + public static async Task DownloadAssetsAsync(Config config, IDownloadProgress downloadProgress = null, bool forceCheck = false) { + // Start background thread + Task.Run(async () => { + // Mutual exclusion while loading assets + await AssetsDownloader.semaphoreSlim.WaitAsync(); - // Asset's url - public string Url; + // Attach / detatch JNI. Required for any calls into JNI from background threads. + AndroidJNI.AttachCurrentThread(); - // Asset's updated at timestamp - public string UpdatedAt; + try { + // Download assets from repos. + AssetsDownloader assetsDownloader = new AssetsDownloader(); + return await assetsDownloader.DownloadFromReposAsync(config, downloadProgress, forceCheck); + } finally { + AssetsDownloader.semaphoreSlim.Release(); + AndroidJNI.DetachCurrentThread(); + } + }).ContinueWith((downloadedAssets) => { + if (downloadedAssets.Result) { + // We downloaded new assets, so re-load the scene + Debug.Log("Downloaded new assets. Re-populating panel"); + SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name); + } + }); + } - // Asset's tag name - public string TagName; + /// + /// Asynchronously download assets from configured repos. + /// + /// Current config + /// Download progress interface + /// + private async Task < bool > DownloadFromReposAsync(Config config, IDownloadProgress downloadProgress = null, bool forceCheck = false) { + if (null == config.downloadRepos) { + // No repos configured, so return + return false; + } + + // Load the download manifest. This is used to compare if we're up-to-date or not. + AssetsManifest manifest = LoadManifest(); + if (null == manifest) { + manifest = new AssetsManifest(); + } + + // Rate limit update checks to one per couple of minutes, to avoid GitHub's rate limit + if (!forceCheck && DateTime.Now.Subtract(manifest.LastUpdated).TotalMinutes < RateLimitInMins) { + Debug.LogFormat("Exceeded rate limit of {0} mins - last checked for update on {1}", RateLimitInMins, manifest.LastUpdated); + return false; + } + + // Mark that we've just checked for updates & update manifest + manifest.LastUpdated = DateTime.Now; + SaveManifest(manifest); + + if (null != downloadProgress) { + downloadProgress.OnCheckingForUpdates(); + } + + // Download the assets metadata - used to determine whether we are up-to-date or not + var assetsInfo = await DownloadAssetsMetadata(config, manifest, downloadProgress, forceCheck); + if (assetsInfo.Count == 0) { + // No updates have been found, so return + Debug.Log("No updates found"); + if (null != downloadProgress) { + downloadProgress.OnNoUpdatesAvailable(); } - /// - /// Mapping of asset name to asset info above. This is persisted in the download manifest. - /// - [Serializable] - public class AssetsManifest - { - // File name -> release metadata - public Dictionary Metadata = new Dictionary(); + return false; + } - // Last updated timestamp - public DateTime LastUpdated; + // Download the assets + var downloadedAssets = await DownloadFromReposInternalAsync(manifest, assetsInfo, downloadProgress); + if (null != downloadProgress) { + downloadProgress.OnUpdateFinish(); + } + + return downloadedAssets; + } + + /// + /// Downloads assets metadata. This is used to determine whether our assets are up-to-date. + /// + /// Current config + /// Download progress interface + /// + private async Task < Dictionary < string, AssetInfo >> DownloadAssetsMetadata( + Config config, AssetsManifest manifest, IDownloadProgress downloadProgress = null, bool forceCheck = false) { + // Get asset info from repos + var assetsInfo = new Dictionary < string, + AssetInfo > (StringComparer.OrdinalIgnoreCase); + + // Get the set of repo URIs (removing any duplicates) + var configRepos = new HashSet < string > (); + foreach(var item in config.downloadRepos) { + if (null == item.type || !string.Equals(item.type, Config.DownloadRepo_Type_GitHub, StringComparison.OrdinalIgnoreCase) || !string.Equals(item.type, Config.DownloadRepo_Type_PrivUrl, StringComparison.OrdinalIgnoreCase)) { + // For now, we only support GitHub repos, or our private update url + continue; } - /// - /// Helper method to download assets asynchronously. After completion, scene is automatically reloaded - /// if new assets have been downloaded. - /// - /// Current config - /// Download progress interface - used to indicate download progress - /// - public static async Task DownloadAssetsAsync(Config config, IDownloadProgress downloadProgress = null, bool forceCheck = false) - { - // Start background thread - Task.Run(async () => - { - // Mutual exclusion while loading assets - await AssetsDownloader.semaphoreSlim.WaitAsync(); + configRepos.Add(item.repoUri); + } - // Attach / detatch JNI. Required for any calls into JNI from background threads. - AndroidJNI.AttachCurrentThread(); - - try - { - // Download assets from repos. - AssetsDownloader assetsDownloader = new AssetsDownloader(); - return await assetsDownloader.DownloadFromReposAsync(config, downloadProgress, forceCheck); - } - finally - { - AssetsDownloader.semaphoreSlim.Release(); - AndroidJNI.DetachCurrentThread(); - } - }).ContinueWith((downloadedAssets) => - { - if (downloadedAssets.Result) - { - // We downloaded new assets, so re-load the scene - Debug.Log("Downloaded new assets. Re-populating panel"); - SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name); - } - }); + var reposLoaded = new HashSet < string > (); + foreach(var repoUri in configRepos) { + // Get assets from the GitHub repo + var repoLoaded = await GetAssetsInfoFromGithubRepoAsync(repoUri, assetsInfo, downloadProgress); + if (repoLoaded) { + reposLoaded.Add(repoUri); } + } - /// - /// Asynchronously download assets from configured repos. - /// - /// Current config - /// Download progress interface - /// - private async Task DownloadFromReposAsync(Config config, IDownloadProgress downloadProgress = null, bool forceCheck = false) - { - if (null == config.downloadRepos) - { - // No repos configured, so return - return false; - } + Debug.LogFormat("Assets info contains {0} entries", assetsInfo.Count); - // Load the download manifest. This is used to compare if we're up-to-date or not. - AssetsManifest manifest = LoadManifest(); - if (null == manifest) - { - manifest = new AssetsManifest(); - } - - // Rate limit update checks to one per couple of minutes, to avoid GitHub's rate limit - if (!forceCheck && DateTime.Now.Subtract(manifest.LastUpdated).TotalMinutes < RateLimitInMins) - { - Debug.LogFormat("Exceeded rate limit of {0} mins - last checked for update on {1}", RateLimitInMins, manifest.LastUpdated); - return false; - } - - // Mark that we've just checked for updates & update manifest - manifest.LastUpdated = DateTime.Now; - SaveManifest(manifest); - - if (null != downloadProgress) - { - downloadProgress.OnCheckingForUpdates(); - } - - // Download the assets metadata - used to determine whether we are up-to-date or not - var assetsInfo = await DownloadAssetsMetadata(config, manifest, downloadProgress, forceCheck); - if (assetsInfo.Count == 0) - { - // No updates have been found, so return - Debug.Log("No updates found"); - if (null != downloadProgress) - { - downloadProgress.OnNoUpdatesAvailable(); - } - - return false; - } - - // Download the assets - var downloadedAssets = await DownloadFromReposInternalAsync(manifest, assetsInfo, downloadProgress); - if (null != downloadProgress) - { - downloadProgress.OnUpdateFinish(); - } - - return downloadedAssets; + // Enumerate asset metadata in our download manifest + var deletedAssets = new HashSet < string > (); + foreach(var entry in manifest.Metadata) { + if (assetsInfo.ContainsKey(entry.Key)) { + if (string.Equals(entry.Value.UpdatedAt, assetsInfo[entry.Key].UpdatedAt, StringComparison.OrdinalIgnoreCase) && + File.Exists(Path.Combine(GetOrCreateDownloadPath(), entry.Key))) { + // Matched on file name and updated-at timestamp, so we're up to date. Skip this one. + Debug.LogFormat("Asset {0} is already up to date ({1})", entry.Key, entry.Value.UpdatedAt); + assetsInfo.Remove(entry.Key); + } + } else if (reposLoaded.Contains(entry.Value.RepoUri)) { + // The repo no longer has the cached file, so nuke it to keep things in sync + var filePath = Path.Combine(GetOrCreateDownloadPath(), entry.Key); + if (File.Exists(filePath)) { + Debug.LogFormat("Deleting cached file that no longer exists on server: {0} @ {1}", filePath, entry.Value.RepoUri); + File.Delete(filePath); + } + deletedAssets.Add(entry.Key); } + } - /// - /// Downloads assets metadata. This is used to determine whether our assets are up-to-date. - /// - /// Current config - /// Download progress interface - /// - private async Task> DownloadAssetsMetadata( - Config config, AssetsManifest manifest, IDownloadProgress downloadProgress = null, bool forceCheck = false) - { - // Get asset info from repos - var assetsInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); + // Remove old cached files + foreach(var entry in deletedAssets) { + manifest.Metadata.Remove(entry); + } - // Get the set of repo URIs (removing any duplicates) - var configRepos = new HashSet(); - foreach (var item in config.downloadRepos) - { - if (null == item.type || !string.Equals(item.type, Config.DownloadRepo_Type_GitHub, StringComparison.OrdinalIgnoreCase) || !string.Equals(item.type, Config.DownloadRepo_Type_PrivUrl, StringComparison.OrdinalIgnoreCase)) - { - // For now, we only support GitHub repos, or our private update url - continue; - } + if (deletedAssets.Count > 0) { + // Persist manifest + SaveManifest(manifest); + } - configRepos.Add(item.repoUri); - } + return assetsInfo; + } - var reposLoaded = new HashSet(); - foreach (var repoUri in configRepos) - { - // Get assets from the GitHub repo - var repoLoaded = await GetAssetsInfoFromGithubRepoAsync(repoUri, assetsInfo, downloadProgress); - if (repoLoaded) - { - reposLoaded.Add(repoUri); - } - } + /// + /// Download assets from repos. The download manifest is also updated. + /// + /// Download manifest + /// Assets to download + /// Download progress interface + /// + private async Task < bool > DownloadFromReposInternalAsync( + AssetsManifest manifest, Dictionary < string, AssetInfo > assetsInfo, IDownloadProgress downloadProgress = null) { + var downloadedAsset = false; - Debug.LogFormat("Assets info contains {0} entries", assetsInfo.Count); - - // Enumerate asset metadata in our download manifest - var deletedAssets = new HashSet(); - foreach (var entry in manifest.Metadata) - { - if (assetsInfo.ContainsKey(entry.Key)) - { - if (string.Equals(entry.Value.UpdatedAt, assetsInfo[entry.Key].UpdatedAt, StringComparison.OrdinalIgnoreCase) && - File.Exists(Path.Combine(GetOrCreateDownloadPath(), entry.Key))) - { - // Matched on file name and updated-at timestamp, so we're up to date. Skip this one. - Debug.LogFormat("Asset {0} is already up to date ({1})", entry.Key, entry.Value.UpdatedAt); - assetsInfo.Remove(entry.Key); - } - } - else if (reposLoaded.Contains(entry.Value.RepoUri)) - { - // The repo no longer has the cached file, so nuke it to keep things in sync - var filePath = Path.Combine(GetOrCreateDownloadPath(), entry.Key); - if (File.Exists(filePath)) - { - Debug.LogFormat("Deleting cached file that no longer exists on server: {0} @ {1}", filePath, entry.Value.RepoUri); - File.Delete(filePath); - } - deletedAssets.Add(entry.Key); - } - } - - // Remove old cached files - foreach (var entry in deletedAssets) - { - manifest.Metadata.Remove(entry); - } - - if (deletedAssets.Count > 0) - { - // Persist manifest - SaveManifest(manifest); - } - - return assetsInfo; + foreach(var entry in assetsInfo) { + // Download asset + var success = await DownloadAssetFromGitHubRepoAsync(entry.Key, entry.Value, downloadProgress); + if (success) { + // Update manifest + manifest.Metadata[entry.Key] = entry.Value; + downloadedAsset = true; } + } - /// - /// Download assets from repos. The download manifest is also updated. - /// - /// Download manifest - /// Assets to download - /// Download progress interface - /// - private async Task DownloadFromReposInternalAsync( - AssetsManifest manifest, Dictionary assetsInfo, IDownloadProgress downloadProgress = null) - { - var downloadedAsset = false; + if (downloadedAsset) { + // Persist manifest + SaveManifest(manifest); + } - foreach (var entry in assetsInfo) - { - // Download asset - var success = await DownloadAssetFromGitHubRepoAsync(entry.Key, entry.Value, downloadProgress); - if (success) - { - // Update manifest - manifest.Metadata[entry.Key] = entry.Value; - downloadedAsset = true; - } - } + return downloadedAsset; + } - if (downloadedAsset) - { - // Persist manifest - SaveManifest(manifest); - } + /// + /// Download asset metadata from given uri. The assets info parameter is populate with the metadata. + /// + /// The repo uri to download + /// Assets info mapping that is populate by this function + /// Download progress interface + /// + private async Task < bool > GetAssetsInfoFromGithubRepoAsync(string repoUri, + Dictionary < string, AssetInfo > assetsInfo, IDownloadProgress downloadProgress = null) { + if (isorg == true) { + GithubUrl = GithubApiUrl + "orgs/"; + } else { + GithubUrl = GithubApiUrl; + } + var requestUrl = GithubUrl + repoUri; + Debug.LogFormat("Reading assets from {0}", requestUrl); - return downloadedAsset; - } + try { + // Request asset url + using(var req = new UnityWebRequest(requestUrl)) { + req.downloadHandler = new DownloadHandlerBuffer(); + await req.SendWebRequest(); + if (req.isNetworkError || req.isHttpError) { + // Error reading asset metadata, so return. + Debug.LogFormat("Error reading asset info: {0}", req.error); - /// - /// Download asset metadata from given uri. The assets info parameter is populate with the metadata. - /// - /// The repo uri to download - /// Assets info mapping that is populate by this function - /// Download progress interface - /// - private async Task GetAssetsInfoFromGithubRepoAsync(string repoUri, - Dictionary assetsInfo, IDownloadProgress downloadProgress = null) - { - if (isorg == true) { - GithubUrl = GithubApiUrl + "orgs/"; + var responseHeaders = req.GetResponseHeaders(); + if (null != responseHeaders && responseHeaders.ContainsKey("X-RateLimit-Remaining") && + responseHeaders["X-RateLimit-Remaining"] == "0") { + // Github request limit reached. Display a friendly error message. + Debug.LogFormat("Request limit reached"); + + if (null != downloadProgress) { + downloadProgress.OnError(string.Format("Error updating: Request Limit Reached - try again later. ({1})", + req.error, requestUrl)); + } } else { - GithubUrl = GithubApiUrl; + if (null != downloadProgress) { + downloadProgress.OnError(string.Format("Error updating: {0} ({1})", + req.error, requestUrl)); + } } - var requestUrl = GithubUrl + repoUri; - Debug.LogFormat("Reading assets from {0}", requestUrl); + return false; + } - try - { - // Request asset url - using (var req = new UnityWebRequest(requestUrl)) - { - req.downloadHandler = new DownloadHandlerBuffer(); - await req.SendWebRequest(); - if (req.isNetworkError || req.isHttpError) - { - // Error reading asset metadata, so return. - Debug.LogFormat("Error reading asset info: {0}", req.error); + // Parse the returned asset metadata + var jObject = JObject.Parse(req.downloadHandler.text); + var tagName = jObject["tag_name"].Value < string > (); - var responseHeaders = req.GetResponseHeaders(); - if (null != responseHeaders && responseHeaders.ContainsKey("X-RateLimit-Remaining") && - responseHeaders["X-RateLimit-Remaining"] == "0") - { - // Github request limit reached. Display a friendly error message. - Debug.LogFormat("Request limit reached"); + foreach(var property in jObject["assets"]) { + var updatedAt = property["updated_at"].Value < string > (); + var url = property["url"].Value < string > (); + var name = property["name"].Value < string > (); - if (null != downloadProgress) - { - downloadProgress.OnError(string.Format("Error updating: Request Limit Reached - try again later. ({1})", - req.error, requestUrl)); - } - } - else - { - if (null != downloadProgress) - { - downloadProgress.OnError(string.Format("Error updating: {0} ({1})", - req.error, requestUrl)); - } - } - return false; - } - - // Parse the returned asset metadata - var jObject = JObject.Parse(req.downloadHandler.text); - var tagName = jObject["tag_name"].Value(); - - foreach (var property in jObject["assets"]) - { - var updatedAt = property["updated_at"].Value(); - var url = property["url"].Value(); - var name = property["name"].Value(); - - // For now, simply accept any iconpack*.zip and appnames*.txt/json. - if ((name.StartsWith("iconpack") && name.EndsWith(".zip")) || - (name.StartsWith("appnames") && name.EndsWith(".txt")) || - (name.StartsWith("appnames") && name.EndsWith(".json"))) - { - assetsInfo[name] = new AssetInfo { RepoUri = repoUri, Url = url, UpdatedAt = updatedAt, TagName = tagName }; - } - } - } + // For now, simply accept any iconpack*.zip and appnames*.txt/json. + if ((name.StartsWith("iconpack") && name.EndsWith(".zip")) || + (name.StartsWith("appnames") && name.EndsWith(".txt")) || + (name.StartsWith("appnames") && name.EndsWith(".json"))) { + assetsInfo[name] = new AssetInfo { + RepoUri = repoUri, Url = url, UpdatedAt = updatedAt, TagName = tagName + }; } - catch (Exception e) - { - Debug.LogFormat("Exception reading asset info: {0} ", e.Message); - if (null != downloadProgress) - { - downloadProgress.OnError(string.Format("Error updating: {0} ({1})", - e.Message, requestUrl)); - } - - return false; - } - - return true; + } + } + } catch (Exception e) { + Debug.LogFormat("Exception reading asset info: {0} ", e.Message); + if (null != downloadProgress) { + downloadProgress.OnError(string.Format("Error updating: {0} ({1})", + e.Message, requestUrl)); } - /// - /// Download a single asset (file) from repo - /// - /// Name of the asset (file) to download - /// Asset info (metadata) - /// Download progress interface - /// - private async Task DownloadAssetFromGitHubRepoAsync(string name, AssetInfo assetInfo, - IDownloadProgress downloadProgress) - { - var filePath = Path.Combine(GetOrCreateDownloadPath(), name); - var tempFilePath = filePath + TempDownloadFileExtention; - Debug.LogFormat("Downloading asset {0} from {1}", filePath, assetInfo.Url); + return false; + } - try - { - // Request asset url - using (var req = new UnityWebRequest(assetInfo.Url)) - { - req.SetRequestHeader("Accept", "application/octet-stream"); - if (null != downloadProgress) - { - downloadProgress.OnDownloadStart(name); - } - var downloadHandler = new DownloadHandlerFileWithProgress(tempFilePath, downloadProgress.OnDownloadProgress); - downloadHandler.removeFileOnAbort = true; - req.downloadHandler = downloadHandler; - await req.SendWebRequest(); + return true; + } - if (req.isNetworkError || req.isHttpError) - { - // Error reading asset metadata, so return. - Debug.LogFormat("Error downloading asset: {0}", req.error); - if (null != downloadProgress) - { - downloadProgress.OnError(string.Format("Error updating: {0} ({1})", - req.error, assetInfo.Url)); - } + /// + /// Download a single asset (file) from repo + /// + /// Name of the asset (file) to download + /// Asset info (metadata) + /// Download progress interface + /// + private async Task < bool > DownloadAssetFromGitHubRepoAsync(string name, AssetInfo assetInfo, + IDownloadProgress downloadProgress) { + var filePath = Path.Combine(GetOrCreateDownloadPath(), name); + var tempFilePath = filePath + TempDownloadFileExtention; + Debug.LogFormat("Downloading asset {0} from {1}", filePath, assetInfo.Url); - if (File.Exists(tempFilePath)) - { - File.Delete(tempFilePath); - } + try { + // Request asset url + using(var req = new UnityWebRequest(assetInfo.Url)) { + req.SetRequestHeader("Accept", "application/octet-stream"); + if (null != downloadProgress) { + downloadProgress.OnDownloadStart(name); + } + var downloadHandler = new DownloadHandlerFileWithProgress(tempFilePath, downloadProgress.OnDownloadProgress); + downloadHandler.removeFileOnAbort = true; + req.downloadHandler = downloadHandler; + await req.SendWebRequest(); - return false; - } - - // Rename temp file to desination file to ensure that we are not downloading - // an error body message into the destination file. - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - - File.Move(tempFilePath, filePath); - - if (null != downloadProgress) - { - downloadProgress.OnDownloadFinish(); - } - - return true; - } + if (req.isNetworkError || req.isHttpError) { + // Error reading asset metadata, so return. + Debug.LogFormat("Error downloading asset: {0}", req.error); + if (null != downloadProgress) { + downloadProgress.OnError(string.Format("Error updating: {0} ({1})", + req.error, assetInfo.Url)); } - catch (Exception e) - { - Debug.LogFormat("Exception downloading asset: {0} ", e.Message); - if (null != downloadProgress) - { - downloadProgress.OnError(string.Format("Error updating: {0} ({1})", - e.Message, assetInfo.Url)); - } + + if (File.Exists(tempFilePath)) { + File.Delete(tempFilePath); } return false; + } + + // Rename temp file to desination file to ensure that we are not downloading + // an error body message into the destination file. + if (File.Exists(filePath)) { + File.Delete(filePath); + } + + File.Move(tempFilePath, filePath); + + if (null != downloadProgress) { + downloadProgress.OnDownloadFinish(); + } + + return true; } - - /// - /// Load download manifest - /// - /// Download manifest - static private AssetsManifest LoadManifest() - { - var manifestPath = Path.Combine(GetOrCreateDownloadPath(), DownloadManifestFile); - if (!File.Exists(manifestPath)) - { - return null; - } - - Debug.Log("Found manifest: " + manifestPath); - var jsonManifest = File.ReadAllText(manifestPath); - - try - { - AssetsManifest manifest = new AssetsManifest(); - return JsonConvert.DeserializeObject(jsonManifest); - } - catch (Exception e) - { - Debug.Log(string.Format("Failed to read manifest: {0}", e.Message)); - } - - return null; + } catch (Exception e) { + Debug.LogFormat("Exception downloading asset: {0} ", e.Message); + if (null != downloadProgress) { + downloadProgress.OnError(string.Format("Error updating: {0} ({1})", + e.Message, assetInfo.Url)); } + } - /// - /// Persist download manifest - /// - /// Download manifest to persist - static private void SaveManifest(AssetsManifest manifest) - { - var manifestPath = Path.Combine(GetOrCreateDownloadPath(), DownloadManifestFile); - Debug.Log("Saving manifest: " + manifestPath); - - try - { - File.WriteAllText(manifestPath, JsonConvert.SerializeObject(manifest, Formatting.Indented)); - } - catch (Exception e) - { - Debug.Log(string.Format("Failed to write manifest: {0}", e.Message)); - } - } - - static public string GetOrCreateDownloadPath() - { - string path = Path.Combine(UnityEngine.Application.persistentDataPath, DownloadCacheFolder); - Directory.CreateDirectory(path); - return path; - } + return false; } + + /// + /// Load download manifest + /// + /// Download manifest + static private AssetsManifest LoadManifest() { + var manifestPath = Path.Combine(GetOrCreateDownloadPath(), DownloadManifestFile); + if (!File.Exists(manifestPath)) { + return null; + } + + Debug.Log("Found manifest: " + manifestPath); + var jsonManifest = File.ReadAllText(manifestPath); + + try { + AssetsManifest manifest = new AssetsManifest(); + return JsonConvert.DeserializeObject < AssetsManifest > (jsonManifest); + } catch (Exception e) { + Debug.Log(string.Format("Failed to read manifest: {0}", e.Message)); + } + + return null; + } + + /// + /// Persist download manifest + /// + /// Download manifest to persist + static private void SaveManifest(AssetsManifest manifest) { + var manifestPath = Path.Combine(GetOrCreateDownloadPath(), DownloadManifestFile); + Debug.Log("Saving manifest: " + manifestPath); + + try { + File.WriteAllText(manifestPath, JsonConvert.SerializeObject(manifest, Formatting.Indented)); + } catch (Exception e) { + Debug.Log(string.Format("Failed to write manifest: {0}", e.Message)); + } + } + + static public string GetOrCreateDownloadPath() { + string path = Path.Combine(UnityEngine.Application.persistentDataPath, DownloadCacheFolder); + Directory.CreateDirectory(path); + return path; + } + } }