Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions source/Generic/GameEngineChecker/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="BaseTextBlockStyle" TargetType="TextBlock" />
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Localization/en_US.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
157 changes: 157 additions & 0 deletions source/Generic/GameEngineChecker/GameEngineChecker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using GameEngineChecker.Services;
using GameEngineChecker.ViewModels;
using GameEngineChecker.Views;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace GameEngineChecker
{
public class GameEngineChecker : GenericPlugin
{
private const string ExtensionName = "Game Engine Checker";
private const int PcGamingWikiMaxRequestsPerWindow = 30;
private static readonly TimeSpan PcGamingWikiRateLimitWindow = TimeSpan.FromSeconds(60);
private static readonly ILogger Logger = LogManager.GetLogger();
private readonly Tagger _tagger;
private readonly RateLimiter _rateLimiter;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Both Tagger and RateLimiter are fields and are supposed to work as singletons (-ish). They both have semaphores to ensure only one action is performed at the time.

  • Tagger - it is modifying Playnite database (adding new tags). I had an issue in the past, where letting two Tasks to modify the DB would end up in creating duplicate Tags.
  • RateLimiter - this one protects us from abusing PCGW API. That is why no matter how many processes run, I want to ensure we adhere to PCGW limits, hence this is global.


public override Guid Id { get; } = Guid.Parse("7a21243e-c7cc-4ca7-85bd-f6f96f22e9db");

public GameEngineChecker(IPlayniteAPI api) : base(api)
{
Properties = new GenericPluginProperties
{
HasSettings = false
};
_tagger = new Tagger(PlayniteApi);
_rateLimiter = new RateLimiter(PcGamingWikiRateLimitWindow, PcGamingWikiMaxRequestsPerWindow);
}

public override IEnumerable<MainMenuItem> GetMainMenuItems(GetMainMenuItemsArgs args)
{
yield return new MainMenuItem()
{
MenuSection = $"@{ExtensionName}",
Description = ResourceProvider.GetString("LOCGame_Engine_Checker_MenuItemAddTagSelectedGamesDescription"),
Action = x =>
{
try
{
Task.Run(() => AddTagsToGames(PlayniteApi.MainView.SelectedGames.ToList()));
}
catch (Exception ex)
{
Logger.Error(ex, "Failure running add task. Should not happen.");
}
}
};
}

private async Task AddTagsToGames(IReadOnlyList<Game> games)
{
if (games.Count == 0)
{
return;
}

try
{
var gamesFilter = new GamesFilter(PlayniteApi);
var pcGamingWikiLinkProvider = new PcGamingWikiLinkProvider();
var pcGamingWikiClient = new PcGamingWikiClient(PlayniteApi);
var enginesParser = new EnginesParser();

var gameEngineCheckerService = new GameEngineCheckerService(
PlayniteApi,
gamesFilter,
_rateLimiter,
pcGamingWikiLinkProvider,
pcGamingWikiClient,
enginesParser,
_tagger);

using (var cancellationTokenSource = new CancellationTokenSource())
using (var progressViewModel = ShowProgressDialog(cancellationTokenSource))
{
void ReportProgressAction(float progress)
{
PlayniteApi.MainView.UIDispatcher.Invoke(() => progressViewModel.ProgressValue = progress);
}

var addedCount = await gameEngineCheckerService.AddGameEngineTags(games, ReportProgressAction, cancellationTokenSource.Token);

Logger.Info($"Successfully added game engine to {addedCount} out of {games.Count} games.");
PlayniteApi.Notifications.Add(
"game_engine_checker__added_count",
string.Format(ResourceProvider.GetString("LOCGame_Engine_Checker_ResultsMessage"), addedCount),
NotificationType.Info);
}
}
catch (Exception ex)
{
Logger.Error(ex, "Failure while adding engines to games.");
PlayniteApi.Notifications.Add(
"game_engine_checker__add_error",
string.Format(ResourceProvider.GetString("LOCGame_Engine_Checker_ResultsErrorMessage"), ex.Message, ex.StackTrace),
NotificationType.Error);
}
}

private ProgressViewModel ShowProgressDialog(CancellationTokenSource cts)
{
var progressViewModel = new ProgressViewModel(PlayniteApi, cts);
PlayniteApi.MainView.UIDispatcher.Invoke(() =>
{
var window = ShowDialog(
new ProgressView(progressViewModel),
100,
250,
ResourceProvider.GetString("LOCGame_Engine_Checker_ProgressTitle"),
false,
false);

progressViewModel.SetWindow(window);
}
);

return progressViewModel;
}

private Window ShowDialog(UserControl view, double height, double width, string title, bool showMaximizeButton, bool waitToClose)
{
var window = PlayniteApi.Dialogs.CreateWindow(new WindowCreationOptions()
{
ShowCloseButton = true,
ShowMaximizeButton = showMaximizeButton,
ShowMinimizeButton = false,
});

window.Height = height;
window.Width = width;
window.Title = title;

window.Content = view;
window.Owner = PlayniteApi.Dialogs.GetCurrentAppWindow();
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;

if (waitToClose)
{
window.ShowDialog();
}
else
{
window.Show();
}

return window;
}
}
}
105 changes: 105 additions & 0 deletions source/Generic/GameEngineChecker/GameEngineChecker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CA1D0577-BE3D-422D-808F-262D77A627DF}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GameEngineChecker</RootNamespace>
<AssemblyName>GameEngineChecker</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Playnite.SDK, Version=6.15.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\PlayniteSDK.6.15.0\lib\net462\Playnite.SDK.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="GameEngineChecker.cs" />
<Compile Include="Models\PcGamingWiki\CargoQueryItem.cs" />
<Compile Include="Models\PcGamingWiki\Infobox.cs" />
<Compile Include="Models\PcGamingWiki\PcGamingWikiEngineResponse.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\EnginesParser.cs" />
<Compile Include="Services\GameEngineCheckerService.cs" />
<Compile Include="Services\GamesFilter.cs" />
<Compile Include="Interfaces\IEnginesParser.cs" />
<Compile Include="Interfaces\IGamesFilter.cs" />
<Compile Include="Interfaces\IPcGamingWikiClient.cs" />
<Compile Include="Interfaces\IPcGamingWikiLinkProvider.cs" />
<Compile Include="Interfaces\IRateLimiter.cs" />
<Compile Include="Interfaces\ITagger.cs" />
<Compile Include="Services\PcGamingWikiClient.cs" />
<Compile Include="Services\PcGamingWikiLinkProvider.cs" />
<Compile Include="Services\RateLimiter.cs" />
<Compile Include="Services\Tagger.cs" />
<Compile Include="ViewModels\ProgressViewModel.cs" />
<Compile Include="Views\ProgressView.xaml.cs">
<DependentUpon>ProgressView.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="extension.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="Localization\*.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Page Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\ProgressView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<None Include="icon.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Loading