You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
8.8 KiB
248 lines
8.8 KiB
using System; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Threading.Tasks; |
|
using System.Web; |
|
using Avalonia.Controls.Notifications; |
|
using Avalonia.Threading; |
|
using MessagePipe; |
|
using Microsoft.Extensions.Logging; |
|
using StabilityMatrix.Avalonia.Helpers; |
|
using StabilityMatrix.Core.Api; |
|
using StabilityMatrix.Core.Attributes; |
|
using StabilityMatrix.Core.Extensions; |
|
using StabilityMatrix.Core.Models; |
|
using StabilityMatrix.Core.Models.Api; |
|
using StabilityMatrix.Core.Models.FileInterfaces; |
|
using StabilityMatrix.Core.Services; |
|
|
|
namespace StabilityMatrix.Avalonia.Services; |
|
|
|
[Singleton(typeof(IModelDownloadLinkHandler)), Singleton(typeof(IAsyncDisposable))] |
|
public class ModelDownloadLinkHandler( |
|
IDistributedSubscriber<string, Uri> uriHandlerSubscriber, |
|
ILogger<ModelDownloadLinkHandler> logger, |
|
ICivitApi civitApi, |
|
INotificationService notificationService, |
|
ISettingsManager settingsManager, |
|
IDownloadService downloadService, |
|
ITrackedDownloadService trackedDownloadService |
|
) : IAsyncDisposable, IModelDownloadLinkHandler |
|
{ |
|
private IAsyncDisposable? uriHandlerSubscription; |
|
private const string DownloadCivitModel = "downloadCivitModel"; |
|
|
|
public async Task StartListening() |
|
{ |
|
uriHandlerSubscription = await uriHandlerSubscriber.SubscribeAsync( |
|
UriHandler.IpcKeySend, |
|
UriReceivedHandler |
|
); |
|
} |
|
|
|
public async ValueTask DisposeAsync() |
|
{ |
|
if (uriHandlerSubscription is not null) |
|
{ |
|
await uriHandlerSubscription.DisposeAsync(); |
|
uriHandlerSubscription = null; |
|
} |
|
} |
|
|
|
private void UriReceivedHandler(Uri receivedUri) |
|
{ |
|
logger.LogDebug("ModelDownloadLinkHandler Received URI: {Uri}", receivedUri.PathAndQuery); |
|
if (!receivedUri.Host.Equals(DownloadCivitModel, StringComparison.OrdinalIgnoreCase)) |
|
return; |
|
|
|
var queryDict = HttpUtility.ParseQueryString(receivedUri.Query); |
|
var modelIdStr = queryDict["modelId"]; |
|
var modelVersionIdStr = queryDict["modelVersionId"]; |
|
var type = queryDict["type"]; |
|
var format = queryDict["format"]; |
|
var size = queryDict["size"]; |
|
var fp = queryDict["fp"]; |
|
|
|
if ( |
|
string.IsNullOrWhiteSpace(modelIdStr) |
|
|| string.IsNullOrWhiteSpace(type) |
|
|| string.IsNullOrWhiteSpace(format) |
|
|| !int.TryParse(modelIdStr, out var modelId) |
|
|| !Enum.TryParse<CivitFileType>(type, out var civitFileType) |
|
|| !Enum.TryParse<CivitModelFormat>(format, out var civitFormat) |
|
) |
|
{ |
|
logger.LogError("ModelDownloadLinkHandler: Invalid query parameters"); |
|
|
|
Dispatcher.UIThread.Post( |
|
() => |
|
notificationService.Show( |
|
new Notification( |
|
"Invalid Download Link", |
|
"The download link is invalid", |
|
NotificationType.Error |
|
) |
|
) |
|
); |
|
return; |
|
} |
|
|
|
Dispatcher.UIThread.Post( |
|
() => |
|
notificationService.Show( |
|
"Link Received", |
|
"Successfully received download link", |
|
NotificationType.Warning |
|
) |
|
); |
|
|
|
var modelTask = civitApi.GetModelById(modelId); |
|
modelTask.Wait(); |
|
var model = modelTask.Result; |
|
|
|
var useModelVersion = !string.IsNullOrWhiteSpace(modelVersionIdStr); |
|
var modelVersionId = useModelVersion ? int.Parse(modelVersionIdStr) : 0; |
|
|
|
var modelVersion = useModelVersion |
|
? model.ModelVersions?.FirstOrDefault(x => x.Id == modelVersionId) |
|
: model.ModelVersions?.FirstOrDefault(); |
|
|
|
if (modelVersion is null) |
|
{ |
|
logger.LogError("ModelDownloadLinkHandler: Model version not found"); |
|
Dispatcher.UIThread.Post( |
|
() => |
|
notificationService.Show( |
|
new Notification( |
|
"Model has no versions available", |
|
"This model has no versions available for download", |
|
NotificationType.Error |
|
) |
|
) |
|
); |
|
return; |
|
} |
|
|
|
var possibleFiles = modelVersion.Files?.Where( |
|
x => x.Type == civitFileType && x.Metadata.Format == civitFormat |
|
); |
|
|
|
if (!string.IsNullOrWhiteSpace(fp) && Enum.TryParse<CivitModelFpType>(fp, out var fpType)) |
|
{ |
|
possibleFiles = possibleFiles?.Where(x => x.Metadata.Fp == fpType); |
|
} |
|
|
|
if (!string.IsNullOrWhiteSpace(size) && Enum.TryParse<CivitModelSize>(size, out var modelSize)) |
|
{ |
|
possibleFiles = possibleFiles?.Where(x => x.Metadata.Size == modelSize); |
|
} |
|
|
|
possibleFiles = possibleFiles?.ToList(); |
|
|
|
if (possibleFiles is null) |
|
{ |
|
Dispatcher.UIThread.Post( |
|
() => |
|
notificationService.Show( |
|
new Notification( |
|
"Model has no files available", |
|
"This model has no files available for download", |
|
NotificationType.Error |
|
) |
|
) |
|
); |
|
logger.LogError("ModelDownloadLinkHandler: Model file not found"); |
|
return; |
|
} |
|
|
|
var selectedFile = possibleFiles.FirstOrDefault() ?? modelVersion.Files?.FirstOrDefault(); |
|
|
|
var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory); |
|
var downloadDirectory = rootModelsDirectory.JoinDir( |
|
selectedFile.Type == CivitFileType.VAE |
|
? SharedFolderType.VAE.GetStringValue() |
|
: model.Type.ConvertTo<SharedFolderType>().GetStringValue() |
|
); |
|
|
|
downloadDirectory.Create(); |
|
var downloadPath = downloadDirectory.JoinFile(selectedFile.Name); |
|
|
|
// Create tracked download |
|
var download = trackedDownloadService.NewDownload(selectedFile.DownloadUrl, downloadPath); |
|
|
|
// Download model info and preview first |
|
var saveCmInfoTask = SaveCmInfo(model, modelVersion, selectedFile, downloadDirectory); |
|
var savePreviewImageTask = SavePreviewImage(modelVersion, downloadPath); |
|
|
|
Task.WaitAll([saveCmInfoTask, savePreviewImageTask]); |
|
|
|
var cmInfoPath = saveCmInfoTask.Result; |
|
var previewImagePath = savePreviewImageTask.Result; |
|
|
|
// Add hash info |
|
download.ExpectedHashSha256 = selectedFile.Hashes.SHA256; |
|
|
|
// Add files to cleanup list |
|
download.ExtraCleanupFileNames.Add(cmInfoPath); |
|
if (previewImagePath is not null) |
|
{ |
|
download.ExtraCleanupFileNames.Add(previewImagePath); |
|
} |
|
|
|
// Add hash context action |
|
download.ContextAction = CivitPostDownloadContextAction.FromCivitFile(selectedFile); |
|
|
|
download.Start(); |
|
|
|
Dispatcher.UIThread.Post( |
|
() => notificationService.Show("Download Started", $"Downloading {selectedFile.Name}") |
|
); |
|
} |
|
|
|
private static async Task<FilePath> SaveCmInfo( |
|
CivitModel model, |
|
CivitModelVersion modelVersion, |
|
CivitFile modelFile, |
|
DirectoryPath downloadDirectory |
|
) |
|
{ |
|
var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Name); |
|
var modelInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTime.UtcNow); |
|
|
|
await modelInfo.SaveJsonToDirectory(downloadDirectory, modelFileName); |
|
|
|
var jsonName = $"{modelFileName}.cm-info.json"; |
|
return downloadDirectory.JoinFile(jsonName); |
|
} |
|
|
|
/// <summary> |
|
/// Saves the preview image to the same directory as the model file |
|
/// </summary> |
|
/// <param name="modelVersion"></param> |
|
/// <param name="modelFilePath"></param> |
|
/// <returns>The file path of the saved preview image</returns> |
|
private async Task<FilePath?> SavePreviewImage(CivitModelVersion modelVersion, FilePath modelFilePath) |
|
{ |
|
// Skip if model has no images |
|
if (modelVersion.Images == null || modelVersion.Images.Count == 0) |
|
{ |
|
return null; |
|
} |
|
|
|
var image = modelVersion.Images[0]; |
|
var imageExtension = Path.GetExtension(image.Url).TrimStart('.'); |
|
if (imageExtension is "jpg" or "jpeg" or "png") |
|
{ |
|
var imageDownloadPath = modelFilePath.Directory!.JoinFile( |
|
$"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}" |
|
); |
|
|
|
var imageTask = downloadService.DownloadToFileAsync(image.Url, imageDownloadPath); |
|
await notificationService.TryAsync(imageTask, "Could not download preview image"); |
|
|
|
return imageDownloadPath; |
|
} |
|
|
|
return null; |
|
} |
|
}
|
|
|