Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open collection JSON file in text editor #2381

Merged
merged 5 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a quick note that 'browse' usually indicates there's an expectation of user interaction with the URL? And that sometimes it's miscategorised by collection devs.

Might be worth noting, not everyone may always know/remember.

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
Loading