using System;
using System.IO;
using System.Threading.Tasks;
using AsyncImageLoader;
using Avalonia.Media.Imaging;
using Blake3;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Avalonia.Models;
public record ImageSource : IDisposable
{
private Hash? contentHashBlake3;
///
/// Local file path
///
public FilePath? LocalFile { get; init; }
///
/// Remote URL
///
public Uri? RemoteUrl { get; init; }
///
/// Bitmap
///
public Bitmap? Bitmap { get; set; }
///
/// Optional label for the image
///
public string? Label { get; set; }
public ImageSource(FilePath localFile)
{
LocalFile = localFile;
}
public ImageSource(Uri remoteUrl)
{
RemoteUrl = remoteUrl;
}
public ImageSource(Bitmap bitmap)
{
Bitmap = bitmap;
}
public Task BitmapAsync => GetBitmapAsync();
///
/// Get the bitmap
///
public async Task GetBitmapAsync()
{
if (Bitmap is not null)
return Bitmap;
var loader = ImageLoader.AsyncImageLoader;
// Use local file path if available, otherwise remote URL
var path = LocalFile?.FullPath ?? RemoteUrl?.ToString();
if (path is null)
return null;
// Load the image
Bitmap = await loader.ProvideImageAsync(path).ConfigureAwait(false);
return Bitmap;
}
public async Task GetBlake3HashAsync()
{
// Use cached value if available
if (contentHashBlake3 is not null)
{
return contentHashBlake3.Value;
}
// Only available for local files
if (LocalFile is null)
{
throw new InvalidOperationException("ImageSource is not a local file");
}
var data = await LocalFile.ReadAllBytesAsync();
contentHashBlake3 = await FileHash.GetBlake3ParallelAsync(data);
return contentHashBlake3.Value;
}
///
/// Return a file name with Guid from Blake3 hash
///
public async Task GetHashGuidFileNameAsync()
{
if (LocalFile is null)
{
throw new InvalidOperationException("ImageSource is not a local file");
}
var extension = LocalFile.Info.Extension;
var hash = await GetBlake3HashAsync();
var guid = hash.ToGuid();
return guid + extension;
}
///
/// Return a file name with Guid from Blake3 hash
/// This will throw if the Blake3 hash has not been calculated yet
///
public string GetHashGuidFileNameCached()
{
if (LocalFile is null)
{
throw new InvalidOperationException("ImageSource is not a local file");
}
if (contentHashBlake3 is null)
{
throw new InvalidOperationException("Blake3 hash has not been calculated yet");
}
var extension = LocalFile.Info.Extension;
var guid = contentHashBlake3.Value.ToGuid();
return guid + extension;
}
public string GetHashGuidFileNameCached(string pathPrefix)
{
return Path.Combine(pathPrefix, GetHashGuidFileNameCached());
}
///
/// Clears the cached bitmap
///
protected virtual void Dispose(bool disposing)
{
if (!disposing)
return;
Bitmap?.Dispose();
}
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
public override string ToString()
{
return LocalFile?.FullPath ?? RemoteUrl?.ToString() ?? "";
}
///
/// Implicit conversion to string for async image loader.
/// Resolves with the local file path if available, otherwise the remote URL.
/// Otherwise returns null.
///
public static implicit operator string(ImageSource imageSource) => imageSource.ToString();
}