protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("updatePortDevice") .UseAutoSubscription() .UseClientMutationId() .Description("Updates a virtual port device.") .Argument("input", arg => arg.Type <NonNullType <UpdatePortDeviceInputType> >()) .Resolver(ctx => { var input = ctx.Argument <UpdatePortDeviceInput>("input"); var orchestrator = ctx.SnowflakeService <IPluginManager>().GetCollection <IEmulatorOrchestrator>()[input.Orchestrator]; if (orchestrator == null) { return(ErrorBuilder.New() .SetCode("PORT_NOTFOUND_ORCHESTRATOR") .SetMessage("The specified orchestrator was not found.") .Build()); } ctx.SnowflakeService <IEmulatedPortStore>() .SetPort(orchestrator, input.PlatformID, input.PortIndex, input.ControllerID, input.InstanceID, input.Driver, input.ProfileID); return(new UpdatePortPayload() { PortEntry = ctx.SnowflakeService <IEmulatedPortStore>().GetPort(orchestrator, input.PlatformID, input.PortIndex), }); }) .Type <NonNullType <UpdatePortPayloadType> >(); descriptor.Field("clearPortDevice") .UseAutoSubscription() .UseClientMutationId() .Description("Unsets the specified port device.") .Argument("input", arg => arg.Type <NonNullType <ClearPortDeviceInputType> >()) .Resolver(ctx => { var input = ctx.Argument <ClearPortDeviceInput>("input"); var orchestrator = ctx.SnowflakeService <IPluginManager>().GetCollection <IEmulatorOrchestrator>()[input.Orchestrator]; if (orchestrator == null) { return(ErrorBuilder.New() .SetCode("PORT_NOTFOUND_ORCHESTRATOR") .SetMessage("The specified orchestrator was not found.") .Build()); } var oldPort = ctx.SnowflakeService <IEmulatedPortStore>().GetPort(orchestrator, input.PlatformID, input.PortIndex); ctx.SnowflakeService <IEmulatedPortStore>() .ClearPort(orchestrator, input.PlatformID, input.PortIndex); return(new ClearPortPayload() { PortEntry = oldPort, }); }) .Type <NonNullType <ClearPortPayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("updateGameMetadata") .UseClientMutationId() .UseAutoSubscription() .Description("Update or creates the given metadata value for a game.") .Argument("input", arg => arg.Type <UpdateGameMetadataInputType>()) .Resolve(async ctx => { var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); UpdateGameMetadataInput args = ctx.ArgumentValue <UpdateGameMetadataInput>("input"); var game = await gameLibrary.GetGameAsync(args.GameID); game.Record.Metadata[args.MetadataKey] = args.MetadataValue; await gameLibrary.UpdateGameRecordAsync(game.Record); return(new UpdateGameMetadataPayload() { Game = game, Metadata = (game.Record.Metadata as IDictionary <string, IRecordMetadata>)[args.MetadataKey], }); }) .Type <NonNullType <UpdateGameMetadataPayloadType> >(); descriptor.Field("deleteGameMetadata") .UseClientMutationId() .UseAutoSubscription() .Description("Removes a metadata entry for a game.") .Argument("input", arg => arg.Type <DeleteGameMetadataInputType>()) .Resolve(async ctx => { var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); DeleteGameMetadataInput args = ctx.ArgumentValue <DeleteGameMetadataInput>("input"); var game = await gameLibrary.GetGameAsync(args.GameID); (game.Record.Metadata as IDictionary <string, IRecordMetadata>) .TryGetValue(args.MetadataKey, out IRecordMetadata metadata); if (metadata != null) { game.Record.Metadata[metadata.Key] = null; await gameLibrary.UpdateGameRecordAsync(game.Record); } return(new DeleteGameMetadataPayload() { Game = game, Metadata = metadata, }); }) .Type <NonNullType <DeleteGameMetadataPayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("createInputProfile") .Description("Creates a new input profile.") .Argument("input", arg => arg.Type <NonNullType <CreateInputProfileInputType> >()) .UseClientMutationId() .UseAutoSubscription() .Resolver(async ctx => { var input = ctx.Argument <CreateInputProfileInput>("input"); var devices = ctx.SnowflakeService <IDeviceEnumerator>().QueryConnectedDevices(); (int vendorId, string deviceName) = (default, default);
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("createGame") .UseClientMutationId() .UseAutoSubscription() .Description("Creates a new game in the database.") .Argument("input", arg => arg.Type <CreateGameInputType>()) .Resolve(async ctx => { var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); CreateGameInput args = ctx.ArgumentValue <CreateGameInput>("input"); var game = await gameLibrary.CreateGameAsync(args.PlatformID); return(new GamePayload() { Game = game, }); }) .Type <NonNullType <GamePayloadType> >(); descriptor.Field("deleteGame") .UseClientMutationId() .UseAutoSubscription() .Description("Marks a game as deleted. This does not actually purge the game from the database. " + "Instead, a metadata value `game_deleted` is set to true. " + "Permanently deleting a game once created from the database is not permitted.") .Argument("input", arg => arg.Type <DeleteGameInputType>()) .Resolve(async ctx => { var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); DeleteGameInput args = ctx.ArgumentValue <DeleteGameInput>("input"); var game = await gameLibrary.GetGameAsync(args.GameID); game.Record.Metadata["game_deleted"] = "true"; await gameLibrary.UpdateGameRecordAsync(game.Record); return(new GamePayload() { Game = game, }); }) .Type <NonNullType <GamePayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("createEmulationInstance") .Description("Create an emulation instance.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", a => a.Type <CreateEmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <CreateEmulationInstanceInput>("input"); var orchestrator = ctx.SnowflakeService <IPluginManager>() .GetCollection <IEmulatorOrchestrator>()[input.Orchestrator]; if (orchestrator == null) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_ORCHESTRATOR") .SetMessage("The specified orchestrator was not found") .Build()); } var game = await ctx.SnowflakeService <IGameLibrary>() .GetGameAsync(input.GameID); if (game == null) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } var compatibility = orchestrator.CheckCompatibility(game); if (compatibility != EmulatorCompatibility.Ready) { switch (compatibility) { case EmulatorCompatibility.MissingSystemFiles: return(ErrorBuilder.New() .SetCode("ORCH_GAME_MISSING_SYSTEM_FILES") .SetMessage("The specified game can not be run. The emulator requires system files that could not be found.") .Build()); case EmulatorCompatibility.RequiresValidation: return(ErrorBuilder.New() .SetCode("ORCH_GAME_REQUIRES_VALIDATION") .SetMessage("The specified game must be validated before it can run.") .Build()); case EmulatorCompatibility.Unsupported: return(ErrorBuilder.New() .SetCode("ORCH_GAME_UNSUPPORTED") .SetMessage("The specified game is unsupported by this orchestrator.") .Build()); } } var ports = ctx.SnowflakeService <IEmulatedPortsManager>(); if (!ctx.SnowflakeService <IStoneProvider>().Platforms.TryGetValue(game.Record.PlatformID, out var platform)) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_GAME_PLATFORMID") .SetMessage("The specified game has an invalid platform ID.") .Build()); } var controllers = (from i in Enumerable.Range(0, platform.MaximumInputs) select ports.GetControllerAtPort(orchestrator, platform.PlatformID, i)).ToList(); var save = game.WithFiles().WithSaves().GetProfile(input.SaveProfileID); if (save == null) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_SAVE") .SetMessage("The specified save profile does not exist.") .Build()); } var instance = orchestrator.ProvisionEmulationInstance(game, controllers, input.CollectionID, save); var guid = Guid.NewGuid(); if (ctx.GetGameCache().TryAdd(guid, instance)) { return new EmulationInstancePayload() { GameEmulation = instance, InstanceID = guid } } ; return(ErrorBuilder.New() .SetCode("ORCH_ERR_CREATE") .SetMessage("Could not create the emulation instance.") .Build()); }).Type <NonNullType <EmulationInstancePayloadType> >(); descriptor.Field("cleanupEmulation") .Description("Immediately shuts down and cleans up the game emulation. This may or may not persist the " + "save game depending on the emulator, but there may be data loss if the game is not saved properly.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } bool success = false; try { await gameEmulation.DisposeAsync(); success = true; } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_CLEANUPEMULATION") .SetMessage("A fatal error occurred with setting up the environment for this game emulation.") .Build()); } finally { if (success) { ctx.GetGameCache().TryRemove(input.InstanceID, out var _); } } var payload = new CleanupEmulationPayload() { ClientMutationID = input.ClientMutationID, Success = success, InstanceID = input.InstanceID }; await ctx.SendEventMessage(new OnEmulationCleanupMessage(payload)); return(payload); }) .Type <NonNullType <CleanupEmulationPayloadType> >(); descriptor.Field("setupEmulationEnvironment") .Description("Prepares the emulation environment for this game emulation.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } if (gameEmulation.EmulationState != GameEmulationState.RequiresSetupEnvironment) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_STATE") .SetMessage("The specified orchestration mutation is not valid for the current state of the game emulation.") .Build()); } try { await gameEmulation.SetupEnvironment(); } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_SETUPEMULATIONENVIRONMENT") .SetMessage("A fatal error occurred with setting up the environment for this game emulation.") .Build()); } var payload = new EmulationInstancePayload() { ClientMutationID = input.ClientMutationID, GameEmulation = gameEmulation, InstanceID = input.InstanceID, }; await ctx.SendEventMessage(new OnEmulationSetupEnvironmentMessage(payload)); return(payload); }) .Type <NonNullType <EmulationInstancePayloadType> >(); descriptor.Field("compileEmulationConfiguration") .Description("Compiles the configuation file for this emulator.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } if (gameEmulation.EmulationState != GameEmulationState.RequiresCompileConfiguration) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_STATE") .SetMessage("The specified orchestration mutation is not valid for the current state of the game emulation.") .Build()); } try { await gameEmulation.CompileConfiguration(); } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_COMPILEEMULATIONCONFIGURATION") .SetMessage("A fatal error occurred with compiling the configuration for this game emulation.") .Build()); } var payload = new EmulationInstancePayload() { ClientMutationID = input.ClientMutationID, GameEmulation = gameEmulation, InstanceID = input.InstanceID, }; await ctx.SendEventMessage(new OnEmulationCompileConfigurationMessage(payload)); return(payload); }) .Type <NonNullType <EmulationInstancePayloadType> >(); descriptor.Field("restoreEmulationSave") .Description("Restores the save game from the save profile of the game emulation into the emulation working folder.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } if (gameEmulation.EmulationState != GameEmulationState.RequiresRestoreSaveGame) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_STATE") .SetMessage("The specified orchestration mutation is not valid for the current state of the game emulation.") .Build()); } try { await gameEmulation.RestoreSaveGame(); } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_RESTOREEMULATIONSAVE") .SetMessage("An error occurred with restoring the save profile for this game emulation.") .Build()); } var payload = new EmulationInstancePayload() { ClientMutationID = input.ClientMutationID, GameEmulation = gameEmulation, InstanceID = input.InstanceID, }; await ctx.SendEventMessage(new OnEmulationRestoreSaveMessage(payload)); return(payload); }) .Type <NonNullType <EmulationInstancePayloadType> >(); descriptor.Field("startEmulation") .Description("Starts the specified emulation.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } if (gameEmulation.EmulationState != GameEmulationState.CanStartEmulation) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_STATE") .SetMessage("The specified orchestration mutation is not valid for the current state of the game emulation.") .Build()); } var payload = new EmulationInstancePayload() { ClientMutationID = input.ClientMutationID, GameEmulation = gameEmulation, InstanceID = input.InstanceID, }; try { var token = gameEmulation.StartEmulation(); token.Register(async() => { await ctx.SendEventMessage(new OnEmulationStopMessage(payload)); }); } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_STARTEMULATION") .SetMessage("An error occurred with starting this game emulation.") .Build()); } await ctx.SendEventMessage(new OnEmulationStartMessage(payload)); return(payload); }) .Type <NonNullType <EmulationInstancePayloadType> >(); descriptor.Field("stopEmulation") .Description("Stops the specified emulation.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } if (gameEmulation.EmulationState != GameEmulationState.CanStopEmulation) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_STATE") .SetMessage("The specified orchestration mutation is not valid for the current state of the game emulation.") .Build()); } try { await gameEmulation.StopEmulation(); } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_STOPEMULATION") .SetMessage("An error occurred with stopping this game emulation.") .Build()); } return(new EmulationInstancePayload() { GameEmulation = gameEmulation, InstanceID = input.InstanceID, }); }) .Type <NonNullType <EmulationInstancePayloadType> >(); descriptor.Field("persistEmulationSave") .Description("Persists the current state of the emulation save file to the save profile.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <EmulationInstanceInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <EmulationInstanceInput>("input"); if (!ctx.GetGameCache().TryGetValue(input.InstanceID, out var gameEmulation)) { return(ErrorBuilder.New() .SetCode("ORCH_NOTFOUND_INSTANCE") .SetMessage("The specified orchestration instance was not found.") .Build()); } if (gameEmulation.EmulationState != GameEmulationState.CanStartEmulation && gameEmulation.EmulationState != GameEmulationState.RequiresDispose) { return(ErrorBuilder.New() .SetCode("ORCH_INVALID_STATE") .SetMessage("The specified orchestration mutation is not valid for the current state of the game emulation.") .Build()); } try { var save = await gameEmulation.PersistSaveGame(); var payload = new PersistEmulationSavePayload() { GameEmulation = gameEmulation, InstanceID = input.InstanceID, SaveGame = save, }; await ctx.SendEventMessage(new OnEmulationPersistSaveMessage(payload)); return(payload); } catch (Exception e) { return(ErrorBuilder.New() .SetException(e) .SetCode("ORCH_ERR_PERSISTSAVEGAME") .SetMessage("An error occurred with persisting the save profile for this emulation.") .Build()); } }) .Type <NonNullType <PersistEmulationSavePayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("createScrapeContext") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <CreateScrapeContextInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <CreateScrapeContextInput>("input"); var plugins = ctx.SnowflakeService <IPluginManager>(); var cullers = plugins.GetCollection <ICuller>() .Where(c => input.Cullers.Contains(c.Name, StringComparer.InvariantCulture)); var scrapers = plugins.GetCollection <IScraper>() .Where(c => input.Scrapers.Contains(c.Name, StringComparer.InvariantCulture)); var game = await ctx.SnowflakeService <IGameLibrary>().GetGameAsync(input.GameID); if (game == null) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } var jobQueueFactory = ctx.SnowflakeService <IAsyncJobQueueFactory>(); var jobQueue = jobQueueFactory.GetJobQueue <IScrapeContext, IEnumerable <ISeed> >(false); var guid = await jobQueue.QueueJob(new GameScrapeContext(game, scrapers, cullers)); IScrapeContext context = jobQueue.GetSource(guid); ctx.AssignGameGuid(game, guid); return(new CreateScrapeContextPayload() { ClientMutationID = input.ClientMutationID, ScrapeContext = context, Game = game, JobID = guid, }); }) .Type <NonNullType <CreateScrapeContextPayloadType> >(); descriptor.Field("cancelScrapeContext") .Description("Requests cancellation of the specified scrape context. If this succeeds, the next iteration will halt the " + "scrape context regardless of the stage. There is no way to determine whether or not " + "the cancellation succeeded until the scrape context is moved to the next step. Only then will it be eligible for deletion.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <CancelScrapeContextInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <CancelScrapeContextInput>("input"); var jobQueueFactory = ctx.SnowflakeService <IAsyncJobQueueFactory>(); var jobQueue = jobQueueFactory.GetJobQueue <IScrapeContext, IEnumerable <ISeed> >(false); var jobId = input.JobID; var scrapeContext = jobQueue.GetSource(jobId); if (scrapeContext == null) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_SCRAPECONTEXT") .SetMessage("The specified scrape context does not exist.") .Build()); } jobQueue.RequestCancellation(jobId); var payload = new CancelScrapeContextPayload() { ClientMutationID = input.ClientMutationID, ScrapeContext = scrapeContext, JobID = jobId, Game = ctx.GetAssignedGame(jobId) }; await ctx.SendEventMessage(new OnScrapeContextCancelMessage(payload)); return(payload); }) .Type <NonNullType <CancelScrapeContextPayloadType> >(); descriptor.Field("deleteScrapeContext") .Description("Deletes a scrape context, halting its execution.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <DeleteScrapeContextInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <DeleteScrapeContextInput>("input"); var jobQueueFactory = ctx.SnowflakeService <IAsyncJobQueueFactory>(); var jobQueue = jobQueueFactory.GetJobQueue <IScrapeContext, IEnumerable <ISeed> >(false); if (!jobQueue.HasJob(input.JobID)) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_SCRAPECONTEXT") .SetMessage("The specified scrape context does not exist.") .Build()); } jobQueue.RequestCancellation(input.JobID); await jobQueue.GetNext(input.JobID); bool result = jobQueue.TryRemoveSource(input.JobID, out var scrapeContext); var payload = new DeleteScrapeContextPayload() { ScrapeContext = scrapeContext, JobID = input.JobID, Success = result, Game = ctx.GetAssignedGame(input.JobID) .ContinueWith(g => { if (result) { ctx.RemoveAssignment(input.JobID); } return(g); }).Unwrap() }; await ctx.SendEventMessage(new OnScrapeContextDeleteMessage(payload)); return(payload); }) .Type <NonNullType <DeleteScrapeContextPayloadType> >(); descriptor.Field("nextScrapeContextStep") .Description("Proceeds to the next step of the specified scrape context. " + "Returns the output of the next step in the scrape context iterator, until " + "it is exhausted. If the iterator is exhausted, `current` will be null, and `hasNext` will be false . " + "If the specified scrape context does not exist, `context` and `current` will be null, and `hasNext` will be false. ") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <NextScrapeContextStepInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <NextScrapeContextStepInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <IScrapeContext, IEnumerable <ISeed> >(false); var scrapeContext = jobQueue.GetSource(input.JobID); if (scrapeContext == null) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_SCRAPECONTEXT") .SetMessage("The specified scrape context does not exist.") .Build()); } var addSeeds = input.Seeds; if (addSeeds != null) { foreach (var graft in addSeeds) { var seed = scrapeContext.Context[graft.SeedID]; if (seed == null) { continue; } var seedTree = graft.Tree.ToSeedTree(); var seedGraft = seedTree.Collapse(seed, ISeed.ClientSource); scrapeContext.Context.AddRange(seedGraft); } } var(current, movedNext) = await jobQueue.GetNext(input.JobID); if (movedNext) { var payload = new ScrapeContextStepPayload { ScrapeContext = scrapeContext, Current = current, JobID = input.JobID, Game = ctx.GetAssignedGame(input.JobID) }; await ctx.SendEventMessage(new OnScrapeContextStepMessage(payload)); return(payload); } var completePayload = new ScrapeContextCompletePayload { ScrapeContext = scrapeContext, JobID = input.JobID, Game = ctx.GetAssignedGame(input.JobID) }; await ctx.SendEventMessage(new OnScrapeContextCompleteMessage(completePayload)); return(completePayload); }).Type <NonNullType <ScrapeContextPayloadInterface> >(); descriptor.Field("exhaustScrapeContextSteps") .Description("Exhausts the specified scrape context until completion. " + "Returns the output of the last step of the scrape context, when there are no more remaining left to continue with.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <NextScrapeContextStepInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <NextScrapeContextStepInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <IScrapeContext, IEnumerable <ISeed> >(false); var scrapeContext = jobQueue.GetSource(input.JobID); if (scrapeContext == null) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_SCRAPECONTEXT") .SetMessage("The specified scrape context does not exist.") .Build()); } var addSeeds = input.Seeds; if (addSeeds != null) { foreach (var graft in addSeeds) { var seed = scrapeContext.Context[graft.SeedID]; if (seed == null) { continue; } var seedTree = graft.Tree.ToSeedTree(); var seedGraft = seedTree.Collapse(seed, ISeed.ClientSource); scrapeContext.Context.AddRange(seedGraft); } } await foreach (IEnumerable <ISeed> val in jobQueue.AsEnumerable(input.JobID)) { ScrapeContextStepPayload payload = new ScrapeContextStepPayload { ScrapeContext = scrapeContext, Current = val, JobID = input.JobID, Game = ctx.GetAssignedGame(input.JobID) }; await ctx.SendEventMessage(new OnScrapeContextStepMessage(payload)); } var completePayload = new ScrapeContextCompletePayload { ScrapeContext = scrapeContext, JobID = input.JobID, Game = ctx.GetAssignedGame(input.JobID) }; await ctx.SendEventMessage(new OnScrapeContextCompleteMessage(completePayload)); return(completePayload); }).Type <NonNullType <ScrapeContextCompletePayloadType> >(); descriptor.Field("applyScrapeContext") .Description("Applies the specified scrape results to the specified game as-is. Be sure to delete the scrape context afterwards.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <ApplyScrapeContextInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <ApplyScrapeContextInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <IScrapeContext, IEnumerable <ISeed> >(false); var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); var pluginManager = ctx.SnowflakeService <IPluginManager>(); var fileTraversers = pluginManager.GetCollection <IFileInstallationTraverser>() .Where(c => input.FileTraversers.Contains(c.Name, StringComparer.InvariantCulture)); var metaTraversers = pluginManager.GetCollection <IGameMetadataTraverser>() .Where(c => input.MetadataTraversers.Contains(c.Name, StringComparer.InvariantCulture)); var scrapeContext = jobQueue.GetSource(input.JobID); if (scrapeContext == null) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_SCRAPECONTEXT") .SetMessage("The specified scrape context does not exist.") .Build()); } IGame game; if (input.GameID == default) { game = await ctx.GetAssignedGame(input.GameID); } else { game = await gameLibrary.GetGameAsync(input.GameID); } if (game == null) { return(ErrorBuilder.New() .SetCode("SCRP_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } foreach (var traverser in metaTraversers) { await traverser.TraverseAll(game, scrapeContext.Context.Root, scrapeContext.Context); } foreach (var traverser in fileTraversers) { await traverser.TraverseAll(game, scrapeContext.Context.Root, scrapeContext.Context); } var payload = new ApplyScrapeContextPayload { Game = game, ScrapeContext = scrapeContext, }; await ctx.SendEventMessage(new OnScrapeContextApplyMessage(input.JobID, payload)); return(payload); }).Type <NonNullType <ApplyScrapeContextPayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("createSaveProfile") .Description("Create a new save profile.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <CreateSaveProfileInputType>()) .Resolve(async ctx => { var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); var input = ctx.ArgumentValue <CreateSaveProfileInput>("input"); var game = await gameLibrary.GetGameAsync(input.GameID); if (game == null) { return(ErrorBuilder.New() .SetCode("SAVE_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } var saveProfile = game.WithFiles().WithSaves() .CreateProfile(input.ProfileName, input.SaveType, input.ManagementStrategy); return(new CreateSaveProfilePayload() { SaveProfile = saveProfile, Game = game, }); }) .Type <NonNullType <CreateSaveProfilePayloadType> >(); descriptor.Field("deleteSaveProfile") .Description("Delete a save profile.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", arg => arg.Type <DeleteSaveProfileInputType>()) .Resolve(async ctx => { var gameLibrary = ctx.SnowflakeService <IGameLibrary>(); var input = ctx.ArgumentValue <DeleteSaveProfileInput>("input"); var game = await gameLibrary.GetGameAsync(input.GameID); if (game == null) { return(ErrorBuilder.New() .SetCode("SAVE_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } var gameSaves = game.WithFiles().WithSaves(); var saveProfile = gameSaves.GetProfile(input.ProfileID); if (saveProfile == null) { return(ErrorBuilder.New() .SetCode("SAVE_NOTFOUND_PROFILE") .SetMessage("The specified save profile does not exist.") .Build()); } gameSaves.DeleteProfile(input.ProfileID); return(new DeleteSaveProfilePayload() { SaveProfile = saveProfile, Game = game, }); }) .Type <NonNullType <DeleteSaveProfilePayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("updateGameConfigurationValues") .UseAutoSubscription() .UseClientMutationId() .Description("Updates the provided configuration values.") .Argument("input", a => a.Type <UpdateGameConfigurationValueInputType>()) .Resolve(async ctx => { var configStore = ctx.SnowflakeService <IGameLibrary>().GetExtension <IGameConfigurationExtensionProvider>(); var input = ctx.ArgumentValue <UpdateGameConfigurationValueInput>("input"); var newValues = new List <IConfigurationValue>(); var newValueGuid = new List <(Guid valueCollection, Guid value)>(); foreach (var value in input.Values) { var owningGuid = await configStore.GetOwningValueCollectionAsync(value.ValueID); if (owningGuid == default) { continue; // value is an orphan or not found. } await configStore.UpdateValueAsync(value.ValueID, value.Value); newValues.Add(await configStore.GetValueAsync(value.ValueID)); newValueGuid.Add((owningGuid, value.ValueID)); } return(new UpdateGameConfigurationValuePayload() { Values = newValues, Collections = newValueGuid.GroupBy(k => k.valueCollection, v => v.value), }); }).Type <NonNullType <UpdateGameConfigurationValuePayloadType> >(); descriptor.Field("deleteGameConfiguration") .UseAutoSubscription() .UseClientMutationId() .Description("Delete the specified game configuration profile.") .Argument("input", a => a.Type <DeleteGameConfigurationInputType>()) .Resolve(async ctx => { var games = ctx.SnowflakeService <IGameLibrary>(); var configStore = games.GetExtension <IGameConfigurationExtensionProvider>(); var orchestrators = ctx.SnowflakeService <IPluginManager>().GetCollection <IEmulatorOrchestrator>(); var input = ctx.ArgumentValue <DeleteGameConfigurationInput>("input"); IConfigurationCollection config = null; if (input.Retrieval != null) { var orchestrator = orchestrators[input.Retrieval.Orchestrator]; var game = await games.GetGameAsync(input.Retrieval.GameID); if (orchestrator == null) { return(ErrorBuilder.New() .SetCode("CFG_NOTFOUND_ORCHESTRATOR") .SetMessage("The specified orchestrator was not found.") .Build()); } if (game == null) { return(ErrorBuilder.New() .SetCode("CFG_NOTFOUND_GAME") .SetMessage("The specified game was not found.") .Build()); } config = orchestrator.GetGameConfiguration(game, input.CollectionID); if (config == null) { return(ErrorBuilder.New() .SetCode("CFG_NOTFOUND_COLLECTION") .SetMessage("The specified collectionId was not found in the configuration set for the specified orchestrator..") .Build()); } } await configStore.DeleteProfileAsync(input.CollectionID); return(new DeleteGameConfigurationPayload() { CollectionID = input.CollectionID, Configuration = config, }); }).Type <NonNullType <DeleteGameConfigurationPayloadType> >(); descriptor.Field("updatePluginConfigurationValues") .UseAutoSubscription() .UseClientMutationId() .Description("Updates configuration values for a plugin.") .Argument("input", a => a.Type <UpdatePluginConfigurationValueInputType>()) .Resolve(async ctx => { var input = ctx.ArgumentValue <UpdatePluginConfigurationValueInput>("input"); var plugin = ctx.SnowflakeService <IPluginManager>().Get(input.Plugin); if (plugin == null) { return(ErrorBuilder.New() .SetCode("PCFG_NOTFOUND_PLUGIN") .SetMessage("The specified plugin was not found.") .Build()); } var configStore = plugin?.Provision?.ConfigurationStore; if (configStore == null) { return(ErrorBuilder.New() .SetCode("PCFG_NOTPROVISIONED") .SetMessage("The specified plugin was not a provisioned plugin.") .Build()); } var newValues = input.Values.Select(i => i.ValueID); await configStore.SetAsync(input.Values.Select(i => (i.ValueID, i.Value))); var configuration = plugin.GetPluginConfiguration(); return(new UpdatePluginConfigurationValuePayload() { Values = newValues.Select(g => configuration.ValueCollection[g].value).Where(v => v != null).ToList(), Plugin = plugin, }); }).Type <NonNullType <UpdatePluginConfigurationValuePayloadType> >(); }
protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.ExtendMutation(); descriptor.Field("createValidation") .Description("Creates a validation intallation with the specified game and orchestrator.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", a => a.Type <CreateValidationInputType>()) .Resolver(async ctx => { var arg = ctx.Argument <CreateValidationInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <TaskResult <IFile> >(); var orchestrator = ctx.SnowflakeService <IPluginManager>() .GetCollection <IEmulatorOrchestrator>()[arg.Orchestrator]; var game = await ctx.SnowflakeService <IGameLibrary>().GetGameAsync(arg.GameID); if (orchestrator == null) { return(ErrorBuilder.New() .SetCode("INST_NOTFOUND_ORCHESTRATOR") .SetMessage("The specified orchestrator was not found.") .Build()); } if (game == null) { return(ErrorBuilder.New() .SetCode("INST_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } var compatibility = orchestrator.CheckCompatibility(game); if (compatibility != EmulatorCompatibility.RequiresValidation) { return(ErrorBuilder.New() .SetCode("INST_ORCH_CANNOTVALIDATE") .SetMessage("The specified orchestrator can not validate the specified game. Either it is unsupported, " + "or has already been validated.") .SetExtension("compatibility", compatibility.ToString()) .Build()); } var jobId = await jobQueue.QueueJob(orchestrator.ValidateGamePrerequisites(game)); ctx.AssignGameGuid(game, jobId); return(new CreateValidationPayload { JobID = jobId, Game = game, }); }) .Type <NonNullType <CreateValidationPayloadType> >(); descriptor.Field("createInstallation") .Description("Creates a new installation with the specified artifacts and game.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", a => a.Type <CreateInstallationInputType>()) .Resolver(async ctx => { var arg = ctx.Argument <CreateInstallationInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <TaskResult <IFile> >(); var installer = ctx.SnowflakeService <IPluginManager>() .GetCollection <IGameInstaller>()[arg.Installer]; var game = await ctx.SnowflakeService <IGameLibrary>().GetGameAsync(arg.GameID); if (installer == null) { return(ErrorBuilder.New() .SetCode("INST_NOTFOUND_INSTALLER") .SetMessage("The specified installer was not found.") .Build()); } if (game == null) { return(ErrorBuilder.New() .SetCode("INST_NOTFOUND_GAME") .SetMessage("The specified game does not exist.") .Build()); } var jobId = await jobQueue.QueueJob(installer.Install(game, arg.Artifacts)); ctx.AssignGameGuid(game, jobId); return(new CreateInstallationPayload { JobID = jobId, Game = game, }); }) .Type <NonNullType <CreateInstallationPayloadType> >(); descriptor.Field("nextInstallationStep") .Description("Proceeds with the next step in the installation. If an exception occurs, cancellation will automatically be requested.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", a => a.Type <NextInstallationStepInputType>()) .Resolver(async ctx => { var arg = ctx.Argument <NextInstallationStepInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <TaskResult <IFile> >(); var(newFile, moved) = await jobQueue.GetNext(arg.JobID); // Force execution of task in case it wasn't already executed. if (newFile.Error == null) { await newFile; } else { ctx.ReportError(ErrorBuilder.New() .SetException(newFile.Error) .SetCode("INST_ERR_INSTALL") .Build()); jobQueue.RequestCancellation(arg.JobID); await ctx.SendEventMessage(new OnInstallationCancelMessage( new InstallationCancelledPayload() { Game = ctx.GetAssignedGame(arg.JobID), JobID = arg.JobID, ClientMutationID = arg.ClientMutationID, })); } if (moved) { var payload = new InstallationStepPayload() { ClientMutationID = arg.ClientMutationID, JobID = arg.JobID, Current = newFile, Game = ctx.GetAssignedGame(arg.JobID), }; await ctx.SendEventMessage(new OnInstallationStepMessage(payload)); return(payload); } var finishedPayload = new InstallationCompletePayload() { ClientMutationID = arg.ClientMutationID, JobID = arg.JobID, Game = ctx.GetAssignedGame(arg.JobID).ContinueWith(g => { ctx.RemoveAssignment(arg.JobID); return(g); }).Unwrap(), }; await ctx.SendEventMessage(new OnInstallationCompleteMessage(finishedPayload)); return(finishedPayload); }) .Type <NonNullType <InstallationPayloadInterface> >(); descriptor.Field("exhaustInstallationSteps") .Description("Exhaust all steps in the installation. " + "If an error occurs during installation, cancellation will automatically be requested, but the " + "installation must run to completion.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", a => a.Type <NextInstallationStepInputType>()) .Resolver(async ctx => { var arg = ctx.Argument <NextInstallationStepInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <TaskResult <IFile> >(); var gameTask = ctx.GetAssignedGame(arg.JobID); await foreach (TaskResult <IFile> newFile in jobQueue.AsEnumerable(arg.JobID)) { var payload = new InstallationStepPayload() { JobID = arg.JobID, Current = newFile, Game = gameTask, ClientMutationID = arg.ClientMutationID, }; if (newFile.Error == null) { await newFile; await ctx.SendEventMessage(new OnInstallationStepMessage(payload)); } else { jobQueue.RequestCancellation(arg.JobID); await ctx.SendEventMessage(new OnInstallationStepMessage(payload)); await ctx.SendEventMessage(new OnInstallationCancelMessage( new InstallationCancelledPayload() { Game = gameTask, JobID = arg.JobID, ClientMutationID = arg.ClientMutationID, })); } } var finishedPayload = new InstallationCompletePayload() { ClientMutationID = arg.ClientMutationID, JobID = arg.JobID, Game = gameTask .ContinueWith(g => { ctx.RemoveAssignment(arg.JobID); return(g); }).Unwrap(), }; await ctx.SendEventMessage(new OnInstallationCompleteMessage(finishedPayload)); return(finishedPayload); }) .Type <NonNullType <InstallationCompletePayloadType> >(); descriptor.Field("cancelInstallation") .Description("Requests cancellation of an installation. This does not mean the installation step is complete. The installation" + " must be continued to ensure proper cleanup.") .UseClientMutationId() .UseAutoSubscription() .Argument("input", a => a.Type <NextInstallationStepInputType>()) .Resolver(async ctx => { var arg = ctx.Argument <NextInstallationStepInput>("input"); var jobQueue = ctx.SnowflakeService <IAsyncJobQueueFactory>() .GetJobQueue <TaskResult <IFile> >(); jobQueue.RequestCancellation(arg.JobID); var finishedPayload = new InstallationCancelledPayload() { ClientMutationID = arg.ClientMutationID, JobID = arg.JobID, Game = ctx.GetAssignedGame(arg.JobID) }; await ctx.SendEventMessage(new OnInstallationCancelMessage(finishedPayload)); return(finishedPayload); }) .Type <NonNullType <InstallationCancelledPayloadType> >(); }