Skip to content

Commit

Permalink
Merge pull request #2381 from erri120/feat/2254-10
Browse files Browse the repository at this point in the history
Open collection JSON file in text editor
  • Loading branch information
erri120 authored Dec 16, 2024
2 parents 4430631 + a0e69a4 commit e36e0fb
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -1,28 +1,54 @@
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using NexusMods.Abstractions.Collections.Types;
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Paths;

namespace NexusMods.Abstractions.Collections.Json;

/// <summary>
/// Polymorphic source
/// </summary>
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class ModSource
{
/// <summary>
/// Type.
/// </summary>
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public required ModSourceType Type { get; init; }


/// <summary>
/// Update policy.
/// </summary>
[JsonPropertyName("updatePolicy")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public UpdatePolicy UpdatePolicy { get; init; }

/// <summary>
/// For <see cref="ModSourceType.NexusMods"/>: Nexus Mods Mod ID.
/// </summary>
[JsonPropertyName("modId")]
public ModId ModId { get; init; }

/// <summary>
/// MD5 hash a direct download
/// For <see cref="ModSourceType.NexusMods"/>: Nexus Mods File ID.
/// </summary>
[JsonPropertyName("fileId")]
public FileId FileId { get; init; }

/// <summary>
/// MD5 hash, present in <see cref="ModSourceType.NexusMods"/>,
/// <see cref="ModSourceType.Browse"/>, and <see cref="ModSourceType.Direct"/>.
/// </summary>
[JsonPropertyName("md5")]
public Md5HashValue Md5 { get; init; }

/// <summary>
/// If this is a direct download, this is the URL to download the mod from
/// Present in <see cref="ModSourceType.Browse"/> and <see cref="ModSourceType.Direct"/>,
/// url to the download page.
/// </summary>
[JsonPropertyName("url")]
public Uri? Url { get; init; }
Expand All @@ -33,12 +59,12 @@ public class ModSource
[JsonPropertyName("logicalFilename")]
public string? LogicalFilename { get; init; }

[JsonPropertyName("fileId")]
public FileId FileId { get; init; }

[JsonPropertyName("fileSize")]
public Size FileSize { get; init; }

[JsonPropertyName("fileExpression")]
public RelativePath FileExpression { get; init; } = default;

[JsonPropertyName("tag")]
public string? Tag { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
// ReSharper disable InconsistentNaming

using System.Text.Json.Serialization;

namespace NexusMods.Abstractions.Collections.Json;

/// <summary>
/// The possible sources of a mod
/// </summary>
public enum ModSourceType
{
browse,
direct,
nexus,
bundle,
/// <summary>
/// Sourced from Nexus Mods.
/// </summary>
[JsonStringEnumMemberName("nexus")]
NexusMods,

/// <summary>
/// Bundled with the collection archive.
/// </summary>
[JsonStringEnumMemberName("bundle")]
Bundle,

/// <summary>
/// Downloaded externally via an URL.
/// </summary>
[JsonStringEnumMemberName("Browse")]
Browse,

/// <summary>
/// Downloaded externally via an URL.
/// </summary>
[JsonStringEnumMemberName("Direct")]
Direct,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text.Json.Serialization;
using JetBrains.Annotations;

namespace NexusMods.Abstractions.Collections.Json;

/// <summary>
/// Update policy.
/// </summary>
[PublicAPI]
public enum UpdatePolicy
{
/// <summary>
/// Use the exact version.
/// </summary>
[JsonStringEnumMemberName("exact")]
ExactVersionOnly,

/// <summary>
/// Use the current version, if it's still available.
/// If the file has been archived or deleted, the newest version of the file should be used.
/// </summary>
[JsonStringEnumMemberName("prefer")]
PreferExact,

/// <summary>
/// Use the latest version.
/// </summary>
[JsonStringEnumMemberName("latest")]
LatestVersion,
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private static ResolvedEntitiesLookup ResolveModFiles(

foreach (var collectionMod in collectionRoot.Mods)
{
if (collectionMod.Source.Type != ModSourceType.nexus) continue;
if (collectionMod.Source.Type != ModSourceType.NexusMods) continue;
var fileId = new UidForFile(fileId: collectionMod.Source.FileId, gameId: gameIds[collectionMod.DomainName]);
if (res.ContainsKey(fileId)) continue;

Expand Down Expand Up @@ -169,13 +169,13 @@ private static void UpdateFiles(
var source = collectionMod.Source;
switch (source.Type)
{
case ModSourceType.nexus:
case ModSourceType.NexusMods:
HandleNexusModsDownload(db, tx, downloadEntity, collectionMod, gameIds, resolvedEntitiesLookup);
break;
case ModSourceType.direct or ModSourceType.browse:
case ModSourceType.Direct or ModSourceType.Browse:
HandleExternalDownload(tx, downloadEntity, collectionMod);
break;
case ModSourceType.bundle:
case ModSourceType.Bundle:
HandleBundledFiles(tx, downloadEntity, collectionMod);
break;
default:
Expand Down Expand Up @@ -308,15 +308,24 @@ public async ValueTask<CollectionRoot> ParseCollectionJsonFile(
NexusModsCollectionLibraryFile.ReadOnly collectionLibraryFile,
CancellationToken cancellationToken)
{
if (!collectionLibraryFile.AsLibraryFile().TryGetAsLibraryArchive(out var archive))
throw new InvalidOperationException("The source collection is not a library archive");

var jsonFileEntity = archive.Children.FirstOrDefault(f => f.Path == "collection.json");
var jsonFileEntity = GetCollectionJsonFile(collectionLibraryFile);

await using var data = await _fileStore.GetFileStream(jsonFileEntity.AsLibraryFile().Hash, token: cancellationToken);
var root = await JsonSerializer.DeserializeAsync<CollectionRoot>(data, _jsonSerializerOptions, cancellationToken: cancellationToken);

if (root is null) throw new InvalidOperationException("Unable to deserialize collection JSON file");
return root;
}

/// <summary>
/// Gets the collection JSON file.
/// </summary>
public LibraryArchiveFileEntry.ReadOnly GetCollectionJsonFile(NexusModsCollectionLibraryFile.ReadOnly collectionLibraryFile)
{
if (!collectionLibraryFile.AsLibraryFile().TryGetAsLibraryArchive(out var archive))
throw new InvalidOperationException("The source collection is not a library archive");

var jsonFileEntity = archive.Children.FirstOrDefault(f => f.Path == "collection.json");
return jsonFileEntity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,6 @@ private async Task HandleModUrl(CancellationToken cancel, NXMModUrl modUrl)
var downloadJob = await nexusModsLibrary.CreateDownloadJob(destination, modUrl, cache, cancellationToken: cancel);

var libraryJob = await library.AddDownload(downloadJob);
_logger.LogInformation("{Result}", libraryJob);

// var task = await _downloadService.AddTask(modUrl);
// _ = task.StartAsync();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public CollectionDownloadDesignViewModel() : base(new DesignWindowManager()) { }

public ReactiveCommand<Unit> CommandViewOnNexusMods { get; } = new ReactiveCommand();
public ReactiveCommand<Unit> CommandViewInLibrary { get; } = new ReactiveCommand();
public ReactiveCommand<Unit> CommandOpenJsonFile { get; } = new ReactiveCommand();
public ReactiveCommand<Unit> CommandDeleteAllDownloads { get; } = new ReactiveCommand();
public ReactiveCommand<Unit> CommandDeleteCollection { get; } = new ReactiveCommand();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
</panels:FlexPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="MenuItemOpenJsonFile">
<MenuItem.Header>
<panels:FlexPanel>
<TextBlock>Open JSON file</TextBlock>
</panels:FlexPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="MenuItemDeleteAllDownloads">
<MenuItem.Header>
<panels:FlexPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public CollectionDownloadView()
this.BindCommand(ViewModel, vm => vm.CommandViewInLibrary, view => view.MenuItemViewInLibrary)
.DisposeWith(d);

this.BindCommand(ViewModel, vm => vm.CommandOpenJsonFile, view => view.MenuItemOpenJsonFile)
.DisposeWith(d);

this.BindCommand(ViewModel, vm => vm.CommandDeleteAllDownloads, view => view.MenuItemDeleteAllDownloads)
.DisposeWith(d);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using NexusMods.App.UI.Extensions;
using NexusMods.App.UI.Pages.LibraryPage;
using NexusMods.App.UI.Pages.LibraryPage.Collections;
using NexusMods.App.UI.Pages.TextEdit;
using NexusMods.App.UI.Resources;
using NexusMods.App.UI.Windows;
using NexusMods.App.UI.WorkspaceSystem;
Expand Down Expand Up @@ -61,24 +62,17 @@ public CollectionDownloadViewModel(
_revision = revisionMetadata;
_collection = revisionMetadata.Collection;

var libraryFile = collectionDownloader.GetLibraryFile(revisionMetadata);
var collectionJsonFile = nexusModsLibrary.GetCollectionJsonFile(libraryFile);

TabTitle = _collection.Name;
TabIcon = IconValues.Collections;

TreeDataGridAdapter = new CollectionDownloadTreeDataGridAdapter(nexusModsDataProvider, revisionMetadata);
TreeDataGridAdapter.ViewHierarchical.Value = false;

var requiredDownloadCount = 0;
var optionalDownloadCount = 0;
foreach (var file in _revision.Downloads)
{
var isOptional = file.IsOptional;

requiredDownloadCount += isOptional ? 0 : 1;
optionalDownloadCount += isOptional ? 1 : 0;
}

RequiredDownloadsCount = requiredDownloadCount;
OptionalDownloadsCount = optionalDownloadCount;
RequiredDownloadsCount = collectionDownloader.CountItems(_revision, CollectionDownloader.ItemType.Required);
OptionalDownloadsCount = collectionDownloader.CountItems(_revision, CollectionDownloader.ItemType.Optional);

CommandDownloadRequiredItems = _canDownloadRequiredItems.ToReactiveCommand<Unit>(
executeAsync: (_, cancellationToken) => collectionDownloader.DownloadItems(_revision, itemType: CollectionDownloader.ItemType.Required, db: connection.Db, cancellationToken: cancellationToken),
Expand All @@ -99,6 +93,7 @@ public CollectionDownloadViewModel(
executeAsync: async (_, _) => { await InstallCollectionJob.Create(
serviceProvider,
targetLoadout,
source: libraryFile,
revisionMetadata,
items: collectionDownloader.GetItems(revisionMetadata, CollectionDownloader.ItemType.Required),
group: Optional<NexusCollectionLoadoutGroup.ReadOnly>.None
Expand Down Expand Up @@ -149,6 +144,25 @@ public CollectionDownloadViewModel(

CommandViewInLibrary = new ReactiveCommand(canExecuteSource: R3.Observable.Return(false), initialCanExecute: false);

CommandOpenJsonFile = new ReactiveCommand(
execute: _ =>
{
var pageData = new PageData
{
FactoryId = TextEditorPageFactory.StaticId,
Context = new TextEditorPageContext
{
FileId = collectionJsonFile.AsLibraryFile().LibraryFileId,
FilePath = collectionJsonFile.AsLibraryFile().FileName,
},
};

var workspaceController = GetWorkspaceController();
var behavior = new OpenPageBehavior.NewTab(PanelId);
workspaceController.OpenPage(WorkspaceId, pageData, behavior);
}
);

this.WhenActivated(disposables =>
{
TreeDataGridAdapter.Activate();
Expand Down Expand Up @@ -264,6 +278,7 @@ public CollectionDownloadViewModel(

public ReactiveCommand<Unit> CommandViewOnNexusMods { get; }
public ReactiveCommand<Unit> CommandViewInLibrary { get; }
public ReactiveCommand<Unit> CommandOpenJsonFile { get; }
public ReactiveCommand<Unit> CommandDeleteAllDownloads { get; }
public ReactiveCommand<Unit> CommandDeleteCollection { get; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public interface ICollectionDownloadViewModel : IPageViewModelInterface

ReactiveCommand<Unit> CommandViewOnNexusMods { get; }
ReactiveCommand<Unit> CommandViewInLibrary { get; }
ReactiveCommand<Unit> CommandOpenJsonFile { get; }
ReactiveCommand<Unit> CommandDeleteAllDownloads { get; }
ReactiveCommand<Unit> CommandDeleteCollection { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public ItemContentsFileTreeViewModel(
FactoryId = TextEditorPageFactory.StaticId,
Context = new TextEditorPageContext
{
LoadoutFileId = loadoutFile,
FilePath = loadoutFile.AsLoadoutItemWithTargetPath().TargetPath,
FileId = loadoutFile.LoadoutFileId,
FilePath = loadoutFile.AsLoadoutItemWithTargetPath().TargetPath.Item3,
},
};

Expand Down
7 changes: 5 additions & 2 deletions src/NexusMods.App.UI/Pages/TextEdit/TextEditorPage.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Abstractions.Library.Models;
using NexusMods.Abstractions.Loadouts;
using NexusMods.Abstractions.Loadouts.Files;
using NexusMods.Abstractions.Serialization.Attributes;
using NexusMods.App.UI.WorkspaceSystem;
using NexusMods.Paths;
using OneOf;

namespace NexusMods.App.UI.Pages.TextEdit;

[JsonName("TextEditorPageContext")]
public record TextEditorPageContext : IPageFactoryContext
{
public required LoadoutFileId LoadoutFileId { get; init; }
public required GamePath FilePath { get; init; }
public required OneOf<LoadoutFileId, LibraryFileId> FileId { get; init; }
public required RelativePath FilePath { get; init; }
}

[UsedImplicitly]
Expand Down
Loading

0 comments on commit e36e0fb

Please sign in to comment.