示例#1
0
        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> >();
        }
示例#2
0
        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> >();
        }
示例#3
0
 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);
示例#4
0
        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> >();
        }
示例#5
0
        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> >();
        }
示例#6
0
        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> >();
        }
示例#7
0
        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> >();
        }
示例#8
0
        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> >();
        }
示例#9
0
        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> >();
        }