// 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;
}
}
}