#pragma warning disable 184
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using TriLibCore.General;
using TriLibCore.Mappers;
using TriLibCore.Utils;
using UnityEngine;
namespace TriLibCore
{
/// Represents an Asset Loader class which works with Zip files.
//todo: unify async loading.
public static class AssetLoaderZip
{
///
/// Buffer size used to copy the zip file entries content.
///
private const int ZipBufferSize = 4096;
///
/// Called when all model resources have been loaded.
///
/// The asset loading context, containing callbacks and model loading data.
private static void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
{
var zipLoadCustomContextData = CustomDataHelper.GetCustomData(assetLoaderContext.CustomData);
if (zipLoadCustomContextData != null)
{
if (zipLoadCustomContextData.Stream != null)
{
zipLoadCustomContextData.Stream.Close();
}
if (zipLoadCustomContextData.OnMaterialsLoad != null)
{
zipLoadCustomContextData.OnMaterialsLoad(assetLoaderContext);
}
}
}
///
/// Called when any loading error occurs.
///
/// Thrown error containing an attached context.
private static void OnError(IContextualizedError contextualizedError)
{
if (contextualizedError?.GetContext() is AssetLoaderContext assetLoaderContext)
{
var zipLoadCustomContextData = CustomDataHelper.GetCustomData(assetLoaderContext.CustomData);
if (zipLoadCustomContextData != null)
{
if (zipLoadCustomContextData.Stream != null)
{
zipLoadCustomContextData.Stream.Close();
}
if (zipLoadCustomContextData.OnError != null)
{
zipLoadCustomContextData.OnError.Invoke(contextualizedError);
}
}
}
}
/// Loads a model from the given Zip file path asynchronously.
/// The Zip file path.
/// The Method to call on the Main Thread when the Model is loaded but resources may still pending.
/// The Method to call on the Main Thread when the Model and resources are loaded.
/// The Method to call when the Model loading progress changes.
/// The Method to call on the Main Thread when any error occurs.
/// The Game Object that will be the parent of the loaded Game Object. Can be null.
/// The options to use when loading the Model.
/// The Custom Data that will be passed along the Context.
/// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file.
/// Turn on this field to avoid loading the model immediately and chain the Tasks.
/// The method to call on the parallel Thread before the Unity objects are created.
/// The asset loader context, containing model loading information and the output game object.
public static AssetLoaderContext LoadModelFromZipFile(string path,
Action onLoad,
Action onMaterialsLoad,
Action onProgress,
Action onError = null,
GameObject wrapperGameObject = null,
AssetLoaderOptions assetLoaderOptions = null,
object customContextData = null,
string fileExtension = null,
bool haltTask = false,
Action onPreLoad = null)
{
Stream stream = null;
var memoryStream = SetupZipModelLoading(onError, ref stream, path, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
{
ZipFile = zipFile,
ZipEntry = zipEntry,
Stream = stream,
OnError = onError,
OnMaterialsLoad = onMaterialsLoad
});
if (customContextData != null)
{
CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
}
return AssetLoader.LoadModelFromStream(memoryStream,
path,
fileExtension,
onLoad,
OnMaterialsLoad,
onProgress,
OnError,
wrapperGameObject,
assetLoaderOptions,
customDataDic,
haltTask,
onPreLoad,
true);
}
/// Loads a model from the given Zip file Stream asynchronously.
/// The Stream containing the Zip data.
/// The Method to call on the Main Thread when the Model is loaded but resources may still pending.
/// The Method to call on the Main Thread when the Model and resources are loaded.
/// The Method to call when the Model loading progress changes.
/// The Method to call on the Main Thread when any error occurs.
/// The Game Object that will be the parent of the loaded Game Object. Can be null.
/// The options to use when loading the Model.
/// The Custom Data that will be passed along the Context.
/// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file.
/// Turn on this field to avoid loading the model immediately and chain the Tasks. ///
/// The Zip file path.
/// The method to call on the parallel Thread before the Unity objects are created.
/// The asset loader context, containing model loading information and the output game object.
public static AssetLoaderContext LoadModelFromZipStream(Stream stream,
Action onLoad,
Action onMaterialsLoad,
Action onProgress,
Action onError = null,
GameObject wrapperGameObject = null,
AssetLoaderOptions assetLoaderOptions = null,
object customContextData = null,
string fileExtension = null,
bool haltTask = false,
string modelFilename = null,
Action onPreLoad = null)
{
var memoryStream = SetupZipModelLoading(onError, ref stream, null, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
{
ZipFile = zipFile,
ZipEntry = zipEntry,
Stream = stream,
OnError = onError,
OnMaterialsLoad = onMaterialsLoad
});
if (customContextData != null)
{
CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
}
return AssetLoader.LoadModelFromStream(memoryStream,
modelFilename,
fileExtension,
onLoad,
OnMaterialsLoad,
onProgress,
OnError,
wrapperGameObject,
assetLoaderOptions,
customDataDic,
haltTask,
onPreLoad,
true);
}
/// Loads a model from the given Zip file path synchronously.
/// The Zip file path.
/// The Method to call on the Main Thread when any error occurs.
/// The Game Object that will be the parent of the loaded Game Object. Can be null.
/// The options to use when loading the Model.
/// The Custom Data that will be passed along the Context.
/// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file.
/// The asset loader context, containing model loading information and the output game object.
public static AssetLoaderContext LoadModelFromZipFileNoThread(string path,
Action onError = null,
GameObject wrapperGameObject = null,
AssetLoaderOptions assetLoaderOptions = null,
object customContextData = null,
string fileExtension = null)
{
Stream stream = null;
var memoryStream = SetupZipModelLoading(onError, ref stream, path, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
{
ZipFile = zipFile,
ZipEntry = zipEntry,
Stream = stream,
OnError = onError
});
if (customContextData != null)
{
CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
}
var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream,
path,
fileExtension,
OnError,
wrapperGameObject,
assetLoaderOptions,
customDataDic,
true);
stream.Close();
return assetLoaderContext;
}
/// Loads a model from the given Zip file Stream synchronously.
/// The Stream containing the Zip data.
/// The Method to call on the Main Thread when any error occurs.
/// The Game Object that will be the parent of the loaded Game Object. Can be null.
/// The options to use when loading the Model.
/// The Custom Data that will be passed along the Context.
/// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file.
/// The asset loader context, containing model loading information and the output game object.
public static AssetLoaderContext LoadModelFromZipStreamNoThread(Stream stream,
Action onError,
GameObject wrapperGameObject = null,
AssetLoaderOptions assetLoaderOptions = null,
object customContextData = null,
string fileExtension = null)
{
var memoryStream = SetupZipModelLoading(onError, ref stream, null, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
{
ZipFile = zipFile,
ZipEntry = zipEntry,
Stream = stream,
OnError = onError
});
if (customContextData != null)
{
CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
}
var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream,
null,
fileExtension,
OnError,
wrapperGameObject,
assetLoaderOptions,
customDataDic,
true);
stream.Close();
return assetLoaderContext;
}
/// Configures the Zip Model loading, adding the Zip External Data/Texture Mappers.
/// The method to execute when any error occurs.
/// The Stream containing the Zip data.
/// The Zip file path.
/// The options to use when loading the Model.
/// The Model inside the Zip file extension.
/// The Zip file instance.
/// The model Zip entry inside the Zip file.
/// The model file data stream.
private static Stream SetupZipModelLoading(Action onError, ref Stream stream, string path, ref AssetLoaderOptions assetLoaderOptions, ref string fileExtension, out ZipFile zipFile, out ZipEntry modelZipEntry)
{
if (assetLoaderOptions == null)
{
assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
}
if (!ArrayUtils.ContainsType(assetLoaderOptions.TextureMappers))
{
assetLoaderOptions.TextureMappers = new TextureMapper[] { ScriptableObject.CreateInstance() };
}
if (!(assetLoaderOptions.ExternalDataMapper is ZipFileExternalDataMapper))
{
assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance();
}
if (stream == null)
{
stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
var validExtensions = Readers.Extensions;
zipFile = new ZipFile(stream);
Stream memoryStream = null;
modelZipEntry = null;
foreach (ZipEntry zipEntry in zipFile)
{
if (!zipEntry.IsFile)
{
continue;
}
var checkingFileExtension = FileUtils.GetFileExtension(zipEntry.Name, false);
if (fileExtension != null && checkingFileExtension == fileExtension)
{
memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
modelZipEntry = zipEntry;
}
else if (validExtensions.Contains(checkingFileExtension))
{
memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
modelZipEntry = zipEntry;
break;
}
}
if (memoryStream == null)
{
var exception = new Exception("Unable to find a suitable model on the Zip file. Please inform a valid model file extension.");
onError?.Invoke(new ContextualizedError(exception, "Error"));
}
return memoryStream;
}
/// Copies the contents of a Zip file Entry into a Memory Stream.
/// The Model inside the Zip file extension.
/// The Zip file Entry.
/// The Zip file instance.
/// A memory stream with the zip file entry contents.
public static Stream ZipFileEntryToStream(out string fileExtension, ZipEntry zipEntry, ZipFile zipFile)
{
var buffer = new byte[ZipBufferSize];
var zipFileStream = zipFile.GetInputStream(zipEntry);
var memoryStream = new MemoryStream(ZipBufferSize);
ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zipFileStream, memoryStream, buffer);
memoryStream.Seek(0, SeekOrigin.Begin);
zipFileStream.Close();
fileExtension = FileUtils.GetFileExtension(zipEntry.Name, false);
return memoryStream;
}
}
}