// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Collections.Generic; using System.Linq; using Google.Android.AppBundle.Editor.Internal.Utils; namespace Google.Android.AppBundle.Editor.Internal.AssetPacks { /// /// Internal-only wrapper around an AssetBundle that by virtue of its texture compression format is a /// variant of the other AssetBundles contained in an . /// public class AssetBundleVariant { /// /// Special value for indicating the AssetBundle file does not exist. /// public const long FileSizeIfMissing = -1L; // Visible for testing. public const string FileMissingText = "file missing"; // Visible for testing. public const string NoneText = "none"; private static readonly string[] EmptyStringArray = new string[0]; /// /// Constructor. Prefer for all cases except deserialization and testing. /// public AssetBundleVariant() { DirectDependencies = EmptyStringArray; AllDependencies = EmptyStringArray; } /// /// If non-empty, these errors will prevent this AssetBundle from being packaged in an app bundle. /// public readonly HashSet Errors = new HashSet(); /// /// The names of any AssetBundles that this one directly depends on. /// public string[] DirectDependencies { get; private set; } /// /// The names of any AssetBundles that this one directly or transitively depends on. /// public string[] AllDependencies { get; private set; } /// /// The size of the file in bytes, or -1L if the file doesn't exist. /// public long FileSizeBytes { get; private set; } /// /// The path to the AssetBundle represented by this wrapper. /// public string Path { get; private set; } /// /// A UI friendly way of displaying dependency info. /// public string DependenciesText { // TODO: consider how to handle an extra long string in the UI. get { return DirectDependencies.Length == 0 ? NoneText : string.Join(", ", DirectDependencies); } } /// /// A UI friendly way of displaying file size or that the file doesn't exist on disk. /// public string FileSizeText { get { // Display file size in Kibibytes using "JEDEC" standard. See https://en.wikipedia.org/wiki/Kilobyte return FileSizeBytes == FileSizeIfMissing ? FileMissingText : (FileSizeBytes / 1024) + " KB"; } } /// /// A UI friendly way of displaying the error with this AssetBundle (if there is one) or the number of errors /// (if there is more than one). If there are no errors, this is null. /// public string ErrorSummary { get { switch (Errors.Count) { case 0: return null; case 1: return NameAndDescriptionAttribute.GetAttribute(Errors.First()).Name; default: return Errors.Count + " Errors"; } } } /// /// Delegate to find a variant when doing the dependencies check. /// See . /// /// The asset pack to search. /// If found, will contain the searched asset pack. /// If found, will contain the delivery mode of the searched asset pack. /// true if the asset pack was found, false otherwise. public delegate bool TryGetDependency(string name, out AssetBundleVariant dependencyVariant, out AssetPackDeliveryMode dependencyDeliveryMode); /// /// Evaluates whether to set any based on problems with this variant's dependencies. /// /// The delivery mode of this asset pack. /// A function returning the asset pack for the given dependency name, /// along with the delivery mode of the asset pack. public void CheckDependencyErrors(AssetPackDeliveryMode deliveryMode, TryGetDependency tryGetDependency) { if (deliveryMode == AssetPackDeliveryMode.DoNotPackage) { return; } foreach (var dependencyName in AllDependencies) { AssetBundleVariant dependencyVariant; AssetPackDeliveryMode dependencyDeliveryMode; if (!tryGetDependency(dependencyName, out dependencyVariant, out dependencyDeliveryMode)) { Errors.Add(AssetPackError.DependencyMissing); continue; } if (dependencyVariant.Errors.Count > 0) { Errors.Add(AssetPackError.DependencyError); } // If this asset pack is marked for delivery, all of its dependencies must be packaged for delivery. if (dependencyDeliveryMode == AssetPackDeliveryMode.DoNotPackage) { Errors.Add(AssetPackError.DependencyNotPackaged); continue; } // An asset pack cannot have dependencies on asset packs with later delivery modes, // e.g. A FastFollow asset pack cannot depend on an OnDemand asset pack. if (deliveryMode < dependencyDeliveryMode) { Errors.Add(AssetPackError.DependencyIncompatibleDelivery); } } } /// /// Creates a new . Evaluates some conditions and may set . /// /// Name of the AssetBundle represented by this variant. /// Size of the file in bytes, or . /// AssetBundles that are direct dependencies of this one. /// AssetBundles that are direct or transitive dependencies of this one. /// The path to the AssetBundle represented by this variant. public static AssetBundleVariant CreateVariant( string assetBundleName, long fileSizeBytes, string[] directDependencies, string[] allDependencies, string path) { var variant = new AssetBundleVariant { DirectDependencies = directDependencies ?? EmptyStringArray, AllDependencies = allDependencies ?? EmptyStringArray, FileSizeBytes = fileSizeBytes, Path = path, }; if (fileSizeBytes == FileSizeIfMissing) { variant.Errors.Add(AssetPackError.FileMissing); } if (!AndroidAppBundle.IsValidModuleName(assetBundleName)) { variant.Errors.Add(AssetPackError.InvalidName); } return variant; } } }