using System;
using System.Collections.Generic;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using TriLibCore.General;
using TriLibCore.Mappers;
using TriLibCore.Utils;
using UnityEngine;
using Object = UnityEngine.Object;
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;
/// 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 Meshes and hierarchy are loaded.
/// The Method to call on the Main Thread when the Model (including Textures and Materials) has been fully 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.
/// The Custom Data that will be passed along the AssetLoaderContext.
/// 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 LoadModelFromZipFile(string path, Action onLoad, Action onMaterialsLoad, Action onProgress, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null)
{
Stream stream = null;
var memoryStream = SetupZipModelLoading(ref stream, path, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
return AssetLoader.LoadModelFromStream(memoryStream, path, fileExtension, onLoad, delegate (AssetLoaderContext assetLoaderContext)
{
var zipLoadCustomContextData = assetLoaderContext.CustomData as ZipLoadCustomContextData;
zipLoadCustomContextData?.Stream.Close();
onMaterialsLoad?.Invoke(assetLoaderContext);
}, onProgress, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
{
ZipFile = zipFile,
Stream = stream,
CustomData = customContextData
});
}
/// 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 Meshes and hierarchy are loaded.
/// The Method to call on the Main Thread when the Model (including Textures and Materials) has been fully 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.
/// The Custom Data that will be passed along the AssetLoaderContext.
/// 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 LoadModelFromZipStream(Stream stream, Action onLoad, Action onMaterialsLoad, Action onProgress, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null)
{
var memoryStream = SetupZipModelLoading(ref stream, null, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
return AssetLoader.LoadModelFromStream(memoryStream, null, fileExtension, onLoad, delegate (AssetLoaderContext assetLoaderContext)
{
var zipLoadCustomContextData = assetLoaderContext.CustomData as ZipLoadCustomContextData;
zipLoadCustomContextData?.Stream.Close();
onMaterialsLoad?.Invoke(assetLoaderContext);
}, onProgress, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
{
ZipFile = zipFile,
Stream = stream,
CustomData = customContextData
});
}
/// 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.
/// The Custom Data that will be passed along the AssetLoaderContext.
/// 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(ref stream, path, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, path, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
{
ZipFile = zipFile,
CustomData = customContextData
});
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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.
/// The Custom Data that will be passed along the AssetLoaderContext.
/// 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(ref stream, null, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, null, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
{
ZipFile = zipFile,
CustomData = customContextData
});
stream.Close();
return assetLoaderContext;
}
///
/// Called when any loading error occurs.
///
/// Thrown error containing an attached context.
private static void OnError(IContextualizedError contextualizedError)
{
var assetLoaderContext = contextualizedError?.GetContext() as AssetLoaderContext;
var zipLoadCustomContextData = assetLoaderContext?.CustomData as ZipLoadCustomContextData;
zipLoadCustomContextData?.Stream.Close();
assetLoaderContext?.OnError?.Invoke(contextualizedError);
}
/// Configures the Zip Model loading, adding the Zip External Data/Texture Mappers.
/// The Stream containing the Zip data.
/// The Zip file path.
/// The Method to call on the Main Thread when any error occurs.
/// The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.
/// The Model inside the Zip file extension.
/// The Zip file instance.
/// The model file data stream.
private static Stream SetupZipModelLoading(ref Stream stream, string path, Action onError, AssetLoaderOptions assetLoaderOptions, ref string fileExtension, out ZipFile zipFile)
{
if (assetLoaderOptions == null)
{
assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
}
assetLoaderOptions.TextureMapper = ScriptableObject.CreateInstance();
assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance();
assetLoaderOptions.FixedAllocations.Add(assetLoaderOptions.TextureMapper);
assetLoaderOptions.FixedAllocations.Add(assetLoaderOptions.ExternalDataMapper);
if (stream == null)
{
stream = new FileStream(path, FileMode.Open);
}
var validExtensions = Readers.Extensions;
zipFile = new ZipFile(stream);
Stream memoryStream = 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);
}
else if (validExtensions.Contains(checkingFileExtension))
{
memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
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.");
if (onError != null)
{
var error = exception as IContextualizedError;
onError(error ?? new ContextualizedError(exception, assetLoaderOptions));
}
else
{
throw exception;
}
}
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;
}
}
}