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

Feature content library #841

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
@@ -0,0 +1,17 @@
using Snowflake.Model.Game;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Snowflake.Filesystem.Library
{
public interface IContentLibrary
{
public Guid LibraryID { get; }
public DirectoryInfo Path { get; }
public IDirectory OpenRecordLibrary(Guid recordId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Snowflake.Model.Records;
using Snowflake.Model.Records.Game;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Snowflake.Filesystem.Library
{
public interface IContentLibraryStore
{
IContentLibrary CreateLibrary(DirectoryInfo dirInfo);
IContentLibrary? GetRecordLibrary(IRecord record);
IEnumerable<IContentLibrary> GetLibraries();
IContentLibrary? GetLibrary(Guid libraryId);
void SetRecordLibrary(IContentLibrary library, IRecord record);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public interface IGameFileExtension : IGameExtension
}

/// <summary>
/// Fluent extensions to provide access to game configuration access.
/// Fluent extensions to provide access to game filesystem access.
/// </summary>
public static class GameFileExtensionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Snowflake.Model.Records.File
/// </para>
/// <para>
/// The only difference between an <see cref="IFile"/> and an <see cref="IFileRecord"/>
/// is that the mimetypf of an <see cref="IFileRecord"/> must be known. If so, then
/// is that the mimetype of an <see cref="IFileRecord"/> must be known. If so, then
/// metadata can be recorded for it within a <see cref="IMetadataCollection"/>, relative to
/// the manifested <see cref="IFile"/> it wraps.
/// </para>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Snowflake.Filesystem.Library;
using Snowflake.Model.Game;
using Snowflake.Model.Records.File;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using HotChocolate.Types;
using Snowflake.Filesystem.Library;
using Snowflake.Installation.Extensibility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Snowflake.Remoting.GraphQL.Model.Filesystem
{
public sealed class ContentLibraryType
: ObjectType<IContentLibrary>
{
protected override void Configure(IObjectTypeDescriptor<IContentLibrary> descriptor)
{
descriptor.Name("ContentLibrary")
.Description("Describes a content library.");

descriptor.Field(p => p.LibraryID)
.Name("libraryId")
.Description("The unique ID of the content library.")
.Type<NonNullType<UuidType>>();

descriptor.Field(p => p.Path)
.Name("path")
.Description("The actual path of the library on disk.")
.Type<NonNullType<OSDirectoryPathType>>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public static class SnowflakeGraphQLExtensions
public static T SnowflakeService<T>(this IResolverContext context)
where T : class
{
if (!context.ContextData.TryGetValue(SnowflakeGraphQLExtensions.ServicesNamespace, out object container))
return null;
var serviceContainer = (IServiceContainer)container;
return serviceContainer.Get<T>();
if (context.ContextData.TryGetValue(SnowflakeGraphQLExtensions.ServicesNamespace, out object container) && (container is IServiceContainer serviceContainer))
return serviceContainer.Get<T>();

return null;
}

/// <summary>
Expand Down
65 changes: 65 additions & 0 deletions src/Snowflake.Framework.Tests/Model/ContentLibraryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Microsoft.EntityFrameworkCore;
using Snowflake.Model.Database;
using Snowflake.Model.Database.Models;
using Snowflake.Model.Game;
using Snowflake.Tests;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Zio.FileSystems;

namespace Snowflake.Model.Records.Tests
{
public class ContentLibraryTest
{
[Fact]
public void CreateLibraryTest()
{
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
optionsBuilder.UseSqlite($"Data Source={Path.GetTempFileName()}");

var store = new ContentLibraryStore(new PhysicalFileSystem(), optionsBuilder);

var tempDir = TestUtilities.GetTemporaryDirectory();
#pragma warning disable CS0618 // Type or member is obsolete
var library = store.CreateLibrary(tempDir.UnsafeGetPath());
#pragma warning restore CS0618 // Type or member is obsolete

var recordGuid = Guid.NewGuid();
library.OpenRecordLibrary(recordGuid);

Assert.True(tempDir.ContainsDirectory(recordGuid.ToString()));
Assert.Equal(library.LibraryID, store.GetLibrary(library.LibraryID).LibraryID);
}


[Fact]
public void CreateLibraryForGameTest()
{
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
optionsBuilder.UseSqlite($"Data Source={Path.GetTempFileName()}");

var store = new ContentLibraryStore(new PhysicalFileSystem(), optionsBuilder);

var tempDir = TestUtilities.GetTemporaryDirectory();
#pragma warning disable CS0618 // Type or member is obsolete
var library = store.CreateLibrary(tempDir.UnsafeGetPath());
#pragma warning restore CS0618 // Type or member is obsolete

var glib = new GameRecordLibrary(optionsBuilder);
var gl = new GameLibrary(glib);
var game = gl.CreateGame("NINTENDO_NES");

store.SetRecordLibrary(library, game.Record);
Assert.Equal(library.LibraryID, store.GetRecordLibrary(game.Record).LibraryID);

var library2 = store.CreateLibrary(TestUtilities.GetTemporaryDirectory().UnsafeGetPath());
store.SetRecordLibrary(library2, game.Record);
Assert.Equal(library2.LibraryID, store.GetRecordLibrary(game.Record).LibraryID);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void GameLibraryIntegrationCreate_Test()
optionsBuilder.UseSqlite($"Data Source={Path.GetTempFileName()}");
var glib = new GameRecordLibrary(optionsBuilder);
var gl = new GameLibrary(glib);
var game = gl.CreateGame("NINTENDO_NES");
gl.CreateGame("NINTENDO_NES");
}

[Fact]
Expand Down Expand Up @@ -212,7 +212,7 @@ public async Task GameLibraryIntegrationCreateAsync_Test()
optionsBuilder.UseSqlite($"Data Source={Path.GetTempFileName()}");
var glib = new GameRecordLibrary(optionsBuilder);
var gl = new GameLibrary(glib);
var game = await gl.CreateGameAsync("NINTENDO_NES");
await gl.CreateGameAsync("NINTENDO_NES");
}

[Fact]
Expand Down Expand Up @@ -324,6 +324,7 @@ public async Task GameLibraryIntegrationUpdateAsync_Test()
var glib = new GameRecordLibrary(optionsBuilder);
var flib = new FileRecordLibrary(optionsBuilder);


var gl = new GameLibrary(glib);
gl.AddExtension<GameFileExtensionProvider, IGameFileExtension
>(new GameFileExtensionProvider(flib, gfs));
Expand Down
26 changes: 26 additions & 0 deletions src/Snowflake.Framework/Filesystem/InMemoryFileGuidProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Snowflake.Filesystem
{
public class InMemoryFileGuidProvider : IFileGuidProvider
{
public static InMemoryFileGuidProvider GuidProvider { get; } = new();
private ConcurrentDictionary<FileInfo, Guid> GuidStore { get; } = new ConcurrentDictionary<FileInfo, Guid>();

public void SetGuid(FileInfo rawInfo, Guid guid)
{
this.GuidStore.TryAdd(rawInfo, guid);
}

public bool TryGetGuid(FileInfo rawInfo, out Guid guid)
{
return this.GuidStore.TryGetValue(rawInfo, out guid);
}
}
}
34 changes: 34 additions & 0 deletions src/Snowflake.Framework/Filesystem/Library/ContentLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Snowflake.Model.Game;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zio;
using Zio.FileSystems;

namespace Snowflake.Filesystem.Library
{
internal class ContentLibrary : IContentLibrary
{
public ContentLibrary(Guid libraryId, IDirectory rootDirectory)
{
this.LibraryID = libraryId;
this.Root = rootDirectory;
}

public Guid LibraryID { get; }

#pragma warning disable CS0618 // Type or member is obsolete
public DirectoryInfo Path => this.Root.UnsafeGetPath();
#pragma warning restore CS0618 // Type or member is obsolete

private IDirectory Root { get; }

public IDirectory OpenRecordLibrary(Guid recordId)
{
return this.Root.OpenDirectory(recordId.ToString()).AsIndelible();
}
}
}
97 changes: 97 additions & 0 deletions src/Snowflake.Framework/Model/Database/ContentLibraryStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyModel;
using Snowflake.Filesystem;
using Snowflake.Filesystem.Library;
using Snowflake.Model.Database.Exceptions;
using Snowflake.Model.Database.Extensions;
using Snowflake.Model.Database.Models;
using Snowflake.Model.Records;
using Snowflake.Model.Records.Game;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zio;

namespace Snowflake.Model.Database
{
internal class ContentLibraryStore : IContentLibraryStore
{
private DbContextOptionsBuilder<DatabaseContext> Options { get; set; }
public IFileSystem FileSystem { get; }

public ContentLibraryStore(IFileSystem baseFileSystem, DbContextOptionsBuilder<DatabaseContext> options)
{
this.Options = options;
using var context = new DatabaseContext(Options.Options);
context.Database.Migrate();

this.FileSystem = baseFileSystem;
}

public IEnumerable<IContentLibrary> GetLibraries()
{
using var context = new DatabaseContext(this.Options.Options);
// model path is Zio-format
return context.ContentLibraries.AsEnumerable()
.Select(model => new ContentLibrary(model.LibraryID,
new Filesystem.Directory(this.FileSystem.GetOrCreateSubFileSystem(model.Path))));
}

public IContentLibrary CreateLibrary(DirectoryInfo dirInfo)
{
if (!dirInfo.Exists)
{
dirInfo.Create();
}

using var context = new DatabaseContext(this.Options.Options);
if (context.ContentLibraries.Where(l => l.Path == dirInfo.FullName) is ContentLibraryModel library)
{
return new ContentLibrary(library.LibraryID, new Filesystem.Directory(this.FileSystem.GetOrCreateSubFileSystem(this.FileSystem.ConvertPathFromInternal(library.Path))));
}

var libraryId = Guid.NewGuid();

context.ContentLibraries.Add(new() { Path = dirInfo.FullName, LibraryID = libraryId });
context.SaveChanges();
return new ContentLibrary(libraryId,
new Filesystem.Directory(this.FileSystem.GetOrCreateSubFileSystem(this.FileSystem.ConvertPathFromInternal(dirInfo.FullName))));
}


public IContentLibrary? GetLibrary(Guid libraryId)
{
using var context = new DatabaseContext(this.Options.Options);
if (context.ContentLibraries.Find(libraryId) is ContentLibraryModel model)
{
return new ContentLibrary(model.LibraryID, new Filesystem.Directory(this.FileSystem.GetOrCreateSubFileSystem(this.FileSystem.ConvertPathFromInternal(model.Path))));
}
return null;
}

public IContentLibrary? GetRecordLibrary(IRecord record)
{
using var context = new DatabaseContext(this.Options.Options);
if (context.ContentLibraries.FirstOrDefault(g => g.Records.Select(g => g.RecordID).Contains(record.RecordID)) is ContentLibraryModel model)
{
return new ContentLibrary(model.LibraryID, new Filesystem.Directory(this.FileSystem.GetOrCreateSubFileSystem(this.FileSystem.ConvertPathFromInternal(model.Path))));
}
return null;
}

public void SetRecordLibrary(IContentLibrary library, IRecord record)
{
using var context = new DatabaseContext(this.Options.Options);
RecordModel? recordModel = context.Records.Find(record.RecordID);
if (recordModel == null)
{
throw new DependentEntityNotExistsException(record.RecordID);
}
recordModel.ContentLibrary = library.AsModel();
context.SaveChanges();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Snowflake.Model.Records;
using Snowflake.Model.Records.File;
using Snowflake.Model.Records.Game;
using Snowflake.Filesystem.Library;

namespace Snowflake.Model.Database.Extensions
{
Expand All @@ -18,7 +19,7 @@ public static GameRecordModel AsModel(this IGameRecord @this)
PlatformID = @this.PlatformID,
RecordID = @this.RecordID,
RecordType = "game",
Metadata = @this.Metadata.AsModel()
Metadata = @this.Metadata.AsModel(),
};
}

Expand All @@ -43,5 +44,14 @@ public static FileRecordModel AsModel(this (IFile file, string mimetype) @this)
Metadata = new MetadataCollection(@this.file.FileGuid).AsModel()
};
}

public static ContentLibraryModel AsModel(this IContentLibrary @this)
{
return new ContentLibraryModel()
{
LibraryID = @this.LibraryID,
Path = @this.Path.FullName,
};
}
}
}
Loading