Compare commits

...

154 Commits
v0.2.1 ... main

Author SHA1 Message Date
Pat Hartl 5ef16fc4cc Add missing collection model 2023-12-04 01:42:02 -06:00
Pat Hartl db8d3e4bf6 Add role pane to settings with ability to add/delete roles 2023-12-04 01:41:49 -06:00
Pat Hartl f2462c0d20 Switch change password icon to lock 2023-12-04 01:41:02 -06:00
Pat Hartl b87fe92c63 Order tag inputs by the label 2023-12-04 01:40:43 -06:00
Pat Hartl c735556281 Add collections to navigation 2023-12-04 01:40:28 -06:00
Pat Hartl d4bcde9d28 Merge branch 'main' of https://github.com/pathartl/LANCommander 2023-12-04 01:40:13 -06:00
Pat Hartl 920b6b26f7 Added collections with the ability to add games to a collection 2023-12-04 01:39:59 -06:00
Pat Hartl d31fccc9f3
Update installermanifest.yaml 2023-12-03 21:12:35 -06:00
Pat Hartl c48d5b5d59 Add migration to fix PowerShell scripts that were updated to use Get-PrimaryDisplay 2023-12-03 14:29:51 -06:00
Pat Hartl dfb9f51acd Remove unused dialog code. Prefix action file picker with {InstallDir} 2023-12-03 14:16:56 -06:00
Pat Hartl 03b0d9da93 Remove Windows-only dashboard charts. Add overall playtime chart. 2023-12-03 14:09:48 -06:00
Pat Hartl e2883322e6 Display total number of games as subtitle 2023-12-03 02:35:41 -06:00
Pat Hartl 50eff6784f Load data a little later on game edit screen. Fix TagsInput sometimes losing data. 2023-12-02 23:48:36 -06:00
Pat Hartl 171f82df3e Fix package references 2023-12-02 23:08:32 -06:00
Pat Hartl 8b7be9157e Import YamlDorNet from Playnite 2023-12-02 18:40:43 -06:00
Pat Hartl 96c6d58486 Revert "Update LANCommander.Release.yml"
This reverts commit 6fb77adb63.
2023-12-02 18:38:09 -06:00
Pat Hartl fd6e3e56a4 Revert "Update LANCommander.Release.yml"
This reverts commit ba46970406.
2023-12-02 18:38:05 -06:00
Pat Hartl d4fe8db48d Add regex flag to save paths 2023-12-02 17:53:54 -06:00
Pat Hartl 281353b86c Remove regex save path database migration 2023-12-02 17:52:08 -06:00
Pat Hartl ba46970406
Update LANCommander.Release.yml 2023-12-02 14:32:06 -06:00
Pat Hartl 6fb77adb63
Update LANCommander.Release.yml 2023-12-02 14:23:17 -06:00
Pat Hartl b38f2b6cef Allow padding of strings by setting a MinLength when converting to bytes 2023-12-01 18:32:43 -06:00
Pat Hartl 7568688c97 Add search, page size, and page index to router 2023-12-01 18:21:10 -06:00
Pat Hartl a8c62151f3 Fix snippets 2023-11-30 19:46:04 -06:00
Pat Hartl 32e135de7b Merge branch 'net8.0' 2023-11-30 19:14:48 -06:00
Pat Hartl c37095d4c4 Merge branch 'main' into net8.0 2023-11-30 19:12:56 -06:00
Pat Hartl a716bafc4d Merge branch 'save-path-regex' 2023-11-30 19:12:47 -06:00
Pat Hartl e4531321b1 Display error message if script couldn't save. Stop success from blocking dialog close. 2023-11-30 19:12:25 -06:00
Pat Hartl 78168e94a5 Fix new scripts not being created 2023-11-30 19:08:20 -06:00
Pat Hartl 82779bcc72 Restore files from save archive based on entries in manifest 2023-11-30 18:37:23 -06:00
Pat Hartl 7b625b2f60 Use Path.DirectorySeparatorChar 2023-11-30 18:31:05 -06:00
Pat Hartl 74d8790ee2 Fix files skipped over if not in install directory 2023-11-30 18:30:15 -06:00
Pat Hartl 745cf0cce6 Prefer Path.DirectorySeparatorChar. Remove unused deserializer 2023-11-30 18:29:11 -06:00
Pat Hartl 7c22aaa139 Record save file paths in manifest. Support regex pathing in upload 2023-11-30 18:25:37 -06:00
Pat Hartl eb73885991 Add unknown to save file download if game doesn't exist. Add .zip file extension 2023-11-30 17:31:40 -06:00
Pat Hartl ce80dfa51f Fix pathing for save in download 2023-11-30 17:31:11 -06:00
Pat Hartl 349001d8f6 Merge branch 'main' into save-path-regex 2023-11-30 17:23:40 -06:00
Pat Hartl d705b34f84 Updated test packages 2023-11-30 17:22:54 -06:00
Pat Hartl 0246ee017c Update ZstdSharp 2023-11-30 00:16:25 -06:00
Pat Hartl 818160d658 Update to .NET 8.0 2023-11-30 00:16:07 -06:00
Pat Hartl 035c98cd18 Initialize save controller 2023-11-30 00:10:54 -06:00
Pat Hartl d3e13aee9e Fix variable snippets 2023-11-29 18:19:28 -06:00
Pat Hartl ce402cf5c1 Refactored archive editor to not bind archives and handle service calls through uploader.
Fixes #40
2023-11-29 18:07:10 -06:00
Pat Hartl 3dbee36886 Remove binding for scripts on script editor for redists 2023-11-29 17:10:48 -06:00
Pat Hartl 7793c9a1e8 Fix button spacing on script editor table 2023-11-29 17:10:25 -06:00
Pat Hartl c4c25ad85b Allow redist detection scripts to run as admin 2023-11-29 17:09:52 -06:00
Pat Hartl 1d2f82fdef Switch redistibutable edit button to link 2023-11-29 17:09:04 -06:00
Pat Hartl a533e9ad8c Fix redistributable downloading 2023-11-29 17:08:33 -06:00
Pat Hartl c3a5edbe46 Update SharpCompress to latest version and rework cancellation 2023-11-28 21:20:07 -06:00
Pat Hartl 8cc97f9bdb Spliit the script editor into its own dialog separate from the table. Only pass IDs between game, editor, and dialog for better performance. 2023-11-28 20:48:03 -06:00
Pat Hartl 875b7b7caa Throw error messages when exception occurs while browsing an archive 2023-11-28 20:46:14 -06:00
Pat Hartl 6cc947b47e Fix adding new archive for redist not showing upon completion
Fixes #42
2023-11-28 19:39:41 -06:00
Pat Hartl a450ac4a18 Fix included packages in Playnite release builds 2023-11-28 18:56:09 -06:00
Pat Hartl ffa24dbecc Fix game deletions not working due to relationship with game saves, save paths, and play sessions 2023-11-28 17:56:16 -06:00
Pat Hartl d0d6701380 Push alert when there is an error deleting a game 2023-11-27 20:28:51 -06:00
Pat Hartl ab67092c2f Fix metadata lookup modal styling 2023-11-22 17:23:21 -06:00
Pat Hartl d97e1f48b3 Fix IGDB check for single player mode 2023-11-22 17:19:15 -06:00
Pat Hartl 50badc981b Update Playntie installer manifest 2023-11-20 19:40:40 -06:00
Pat Hartl b863080842 Fix save errors blocking play session recording. Fix route that SDK client hits for play sessions 2023-11-20 19:33:48 -06:00
Pat Hartl 8b3f2c6cde Force admin authorization for server index and edit pages
Fixes #39
2023-11-20 18:23:37 -06:00
Pat Hartl 70674f900e Fix invalid manifests on disk throwing parsing errors when installing game
Fixes #38
2023-11-20 18:20:34 -06:00
Pat Hartl f21bf4801e Fix invalid SQL in migration for scripts 2023-11-20 18:19:45 -06:00
Pat Hartl ead2c9c3f1 Implement ILogger abstraction for Playnite 2023-11-20 18:19:31 -06:00
Pat Hartl fe0bdf31f6 Add additional script migrations 2023-11-20 17:35:49 -06:00
Pat Hartl 282a1f7c36 Fix authentication status label not updating after authentication 2023-11-20 17:35:11 -06:00
Pat Hartl 5e3384b4fd Avoid exceptions in Write-ReplaceContentInFile 2023-11-20 17:34:33 -06:00
Pat Hartl bc30cc911a Find LANCommander PowerShell module manifest using search
Probably pretty hacky. Environment.CurrentDirectory doesn't seem to be reliable in dev vs prod. In dev it correctly matches the LANCommander.PlaynitePlugin.dll's location. In prod it is the main directory of Playnite.
2023-11-17 22:35:17 -06:00
Pat Hartl 737f2bec84 Fix how arguments are passed to scripts 2023-11-17 21:39:28 -06:00
Pat Hartl a47b77cc5c Run scripts as admin if directed via script contents 2023-11-17 21:39:01 -06:00
Pat Hartl f19ef09ff8 Force IgnoreWow64Redirection in scripts 2023-11-17 21:38:37 -06:00
Pat Hartl 8a408f3a18 Actually fix manifest to include LANCommander.SDK.dll 2023-11-17 21:38:11 -06:00
Pat Hartl 96fd12a72f Include RestSharp and YamlDotNet in extension build 2023-11-17 21:37:55 -06:00
Pat Hartl 1f870366a4 Fix required assembly in PowerShell manifest 2023-11-17 21:34:26 -06:00
Pat Hartl 2a80964fe1 Allow updating of SDK client's server address. Fix Playnite plugin logins when no extension data exists on first start up. 2023-11-17 20:47:11 -06:00
Pat Hartl 6a3c55f669 Fix pathing 2023-11-17 18:05:33 -06:00
Pat Hartl 1f03860360 Include snippets in build 2023-11-17 18:05:26 -06:00
Pat Hartl 314a785fee Rename variable to Display to match previous documentation 2023-11-17 18:04:48 -06:00
Pat Hartl 09d23bcb78 Don't dispose database context for server processes 2023-11-17 12:57:23 -06:00
Pat Hartl 6b005eb384 Backup database before migration 2023-11-17 12:32:09 -06:00
Pat Hartl 39dd24e0ba Don't run through download if game already exists on disk 2023-11-17 11:48:45 -06:00
Pat Hartl 5324723cee Start tracking play sessions 2023-11-17 02:28:46 -06:00
Pat Hartl aff2e991ed
Merge pull request #33 from LANCommander/powershell-enhancements
PowerShell Enhancements
2023-11-17 01:18:41 -06:00
Pat Hartl 14600f5d70 Alter migration to try to automatically convert scripts to new variables where arguments may be missing 2023-11-17 01:16:45 -06:00
Pat Hartl 35fbdd008a Add migration to delete old snippets 2023-11-17 00:54:38 -06:00
Pat Hartl aed9935b16 Updated default snippets 2023-11-17 00:46:05 -06:00
Pat Hartl 2cb7013120 Write final install directory as output for Install-Game. Only process scripts that exist 2023-11-16 15:42:34 -06:00
Pat Hartl 7649e63195 Add test for Convert-AspectRatio 2023-11-16 15:36:45 -06:00
Pat Hartl e723a5345b Scaffold tests for cmdlets 2023-11-16 15:22:20 -06:00
Pat Hartl 982227cf1f Change to inherit from Cmdlet 2023-11-16 15:21:50 -06:00
Pat Hartl 8abc2fa15e Add cmdlet for encoding serialized objects for PS 2023-11-16 15:20:50 -06:00
Pat Hartl ed1b4973d3 Fix cmdlet export specified by module manifest 2023-11-16 14:43:11 -06:00
Pat Hartl 196feedded Load PowerShell module on script execution. Add cmdlet to deserialize/decode variables. 2023-11-16 14:34:00 -06:00
Pat Hartl 2282d9b013 Include PowerShell project in Playnite extension 2023-11-16 14:08:35 -06:00
Pat Hartl 1b72d9002a Switch PowerShell library to 4.6.2. Fix missing includes in project. 2023-11-16 14:07:38 -06:00
Pat Hartl a9f3b7a39d Only execute scripts if they exists on the disk 2023-11-16 14:06:40 -06:00
Pat Hartl 986fb87db1 Make install directory mandatory when uninstalling 2023-11-16 12:06:18 -06:00
Pat Hartl f7fa7aa9f3 Add cmdlet for uninstalling a game 2023-11-16 12:04:55 -06:00
Pat Hartl 49c4b10cf9 Don't get key when uninstalling 2023-11-16 12:04:41 -06:00
Pat Hartl 8bd422249e Fix Install-Game cmdlet name 2023-11-16 12:04:26 -06:00
Pat Hartl 26f03f61fc Add public base URL to LC client 2023-11-16 01:44:35 -06:00
Pat Hartl 839e9b4935 Run scripts after cmdlet install 2023-11-16 01:44:21 -06:00
Pat Hartl c71cf9fedd Cmdlet for installing game 2023-11-16 01:01:17 -06:00
Pat Hartl 86211a7500 Remove unused PowerShell code 2023-11-16 00:47:13 -06:00
Pat Hartl 35f6dadf9c Convert game save registry export commands to inline scripts 2023-11-16 00:47:01 -06:00
Pat Hartl 03828bea60 Remove unused code from uninstall 2023-11-16 00:46:42 -06:00
Pat Hartl 8d85aca0a7 Fix plugin menu items to use new script execution 2023-11-16 00:46:20 -06:00
Pat Hartl 16dc60b90a Run key change script in InstallController 2023-11-16 00:40:27 -06:00
Pat Hartl eb05364542 Fix reference to static method in ScriptHelper 2023-11-15 23:59:12 -06:00
Pat Hartl 97f459eaff Move uninstall script execution to Playnite addon 2023-11-15 23:58:55 -06:00
Pat Hartl cb9f31a00a Use new PowerShell in redistributable installs 2023-11-15 23:53:26 -06:00
Pat Hartl bf2c9ea45a Move script path helpers to ScriptHelper. Execute post-install scripts in Playnite extension. 2023-11-15 23:42:54 -06:00
Pat Hartl 29dcebb70f New PowerShell runtime with the ability to use variables 2023-11-15 22:38:20 -06:00
Pat Hartl baa2b9b206 Started adding PowerShell cmdlets useful for LANCommander scripting 2023-11-13 23:09:06 -06:00
Pat Hartl dc2eff4972 Have manifest writer return the file path 2023-11-13 23:07:50 -06:00
Pat Hartl ae23f621c2 Fix link 2023-11-12 02:20:47 -06:00
Pat Hartl ee62bdf2a1 Updated README 2023-11-12 02:20:24 -06:00
Pat Hartl 5fb4fadfb4 Fix default install directory when logger is provided 2023-11-12 02:10:04 -06:00
Pat Hartl 1a0cff3914
Merge pull request #32 from LANCommander/migrate-installation-to-sdk
Migrate LANCommander Client Code to SDK
2023-11-12 01:53:53 -06:00
Pat Hartl 227411a558 Include ByteSize 2023-11-12 01:52:55 -06:00
Pat Hartl 7c97a3db57 Include download speed in progress dialog 2023-11-12 01:50:34 -06:00
Pat Hartl 6f7c17493c Install redistributables after game is installed 2023-11-12 01:27:15 -06:00
Pat Hartl 81e4848407 Include download percentage in dialog 2023-11-12 01:26:51 -06:00
Pat Hartl 1ede37c031 Simplify game install cancellation. Cancels now happen silently and don't generate a dialog. 2023-11-12 01:04:05 -06:00
Pat Hartl bb980cc063 Avoid exception if manifest is malformed 2023-11-10 21:45:09 -06:00
Pat Hartl 47bb054fd1 Restore progress bar 2023-11-10 21:44:48 -06:00
Pat Hartl ea337dfea1 Feed actual YAML contents to deserializer 2023-11-10 21:37:27 -06:00
Pat Hartl 52a5f5866f Downgrade YamlDotNet 2023-11-10 21:37:02 -06:00
Pat Hartl b77e7f6e53 Pass in default install directory to managers 2023-11-10 21:36:35 -06:00
Pat Hartl 20de9d6cae Allow injection of loggers 2023-11-10 20:53:48 -06:00
Pat Hartl 5237e88612 Null handling for logger 2023-11-10 20:53:28 -06:00
Pat Hartl 73b542856a Refactor GameSaveService into GameSaveManager and SaveController. Update Playnite addon authentication dialogs to use new client. 2023-11-10 01:32:30 -06:00
Pat Hartl 39f2d4b212 Move methods that should be static to ManifestHelper and ScriptHelper. Move install logic to GameManager and RedistributableManager. Update InstallController and UninstallController 2023-11-10 00:29:16 -06:00
Pat Hartl e53709334c Make PowerShellRuntime static 2023-11-09 23:45:37 -06:00
Pat Hartl a679fae0cb Relocate crucial installation logic to SDK 2023-11-09 19:40:38 -06:00
Pat Hartl ff6f9997f5 Added game ID to manifest 2023-11-09 19:38:32 -06:00
Pat Hartl 5d5e137e18 Add flag to mark save path as regex 2023-11-08 20:43:18 -06:00
Pat Hartl 81f8d55694 Fix archive default sort order 2023-11-08 20:25:41 -06:00
Pat Hartl c4793daf07 Fix exception in transfer input if no values are supplied 2023-11-08 19:10:31 -06:00
Pat Hartl 5c4f81cf80
Update installermanifest.yaml 2023-11-06 02:17:41 -06:00
Pat Hartl d111be9828 Remove deprecated server HTTP options 2023-11-05 15:08:09 -06:00
Pat Hartl 63ae3a4f2a Filter out consoles from monitor menu if they haven't been created 2023-11-05 15:06:08 -06:00
Pat Hartl 4b40445bac Fix server consoles tab 2023-11-05 14:45:02 -06:00
Pat Hartl 25de79eeed Handle margin for icons in list by CSS 2023-11-05 12:59:44 -06:00
Pat Hartl 16ba48ed6c Add a bit of margin to icon in server list 2023-11-05 12:56:53 -06:00
Pat Hartl 508f5c18fb Add a bit of margin to icon in server game select 2023-11-05 12:56:05 -06:00
Pat Hartl 53276542e8 Fix icons in server game select 2023-11-05 12:55:09 -06:00
Pat Hartl aec4342188 Update packages 2023-11-05 01:21:43 -06:00
Pat Hartl 2d86ff2518 Restructure redistributable panels to fix initially-empty archive table 2023-11-05 01:00:13 -06:00
Pat Hartl 4393c8fdff Remove empty 2023-11-05 01:31:37 -05:00
Pat Hartl bd15ceaa5b Avoid exception being thrown when server status updates from service 2023-11-04 22:22:37 -05:00
Pat Hartl 03626a75d0 Remove foreaching table 2023-11-04 20:12:11 -05:00
Pat Hartl 78fb812a74 Add descriptiive text for HTTP paths and add a button to get the URL for a path 2023-11-04 20:02:09 -05:00
Pat Hartl 4fb11c1dd7 Change server HTTP options to allow multiple specified paths.
This is useful if you only want to share specific paths via HTTP, or have the paths stored in multiple places and want to unite them under one URL structure.
2023-11-04 19:43:38 -05:00
Pat Hartl d6eff92835 Add submenus for settings and profile. Add logout button. 2023-11-03 23:33:27 -05:00
160 changed files with 20206 additions and 1884 deletions

View File

@ -1,17 +1,14 @@
using LANCommander.PlaynitePlugin.Helpers;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Extensions;
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Models;
using LANCommander.SDK.PowerShell;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.PlaynitePlugin
{
@ -20,15 +17,11 @@ namespace LANCommander.PlaynitePlugin
public static readonly ILogger Logger = LogManager.GetLogger();
private LANCommanderLibraryPlugin Plugin;
private PowerShellRuntime PowerShellRuntime;
private Playnite.SDK.Models.Game PlayniteGame;
public LANCommanderInstallController(LANCommanderLibraryPlugin plugin, Playnite.SDK.Models.Game game) : base(game)
{
Name = "Install using LANCommander";
Plugin = plugin;
PlayniteGame = game;
PowerShellRuntime = new PowerShellRuntime();
}
public override void Install(InstallActionArgs args)
@ -43,450 +36,176 @@ namespace LANCommander.PlaynitePlugin
}
var gameId = Guid.Parse(Game.GameId);
var game = Plugin.LANCommander.GetGame(gameId);
Logger.Trace($"Installing game {game.Title} ({game.Id})...");
string installDirectory = null;
var result = RetryHelper.RetryOnException<ExtractionResult>(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
Logger.Trace("Attempting to download and extract game...");
return DownloadAndExtractGame(game);
});
var gameManager = new GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory);
if (!result.Success && !result.Canceled)
throw new Exception("Could not extract the install archive. Retry the install or check your connection.");
else if (result.Canceled)
throw new Exception("Install was canceled");
Stopwatch stopwatch = new Stopwatch();
var installInfo = new GameInstallationData()
stopwatch.Start();
var lastTotalSize = 0d;
var speed = 0d;
gameManager.OnArchiveExtractionProgress += (long pos, long len) =>
{
InstallDirectory = result.Directory
if (stopwatch.ElapsedMilliseconds > 500)
{
var percent = Math.Ceiling((pos / (decimal)len) * 100);
progress.ProgressMaxValue = len;
progress.CurrentProgressValue = pos;
speed = (double)(progress.CurrentProgressValue - lastTotalSize) / (stopwatch.ElapsedMilliseconds / 1000d);
progress.Text = $"Downloading {Game.Name} ({percent}%) | {ByteSizeLib.ByteSize.FromBytes(speed).ToString("#.#")}/s";
lastTotalSize = pos;
stopwatch.Restart();
}
};
PlayniteGame.InstallDirectory = result.Directory;
SDK.GameManifest manifest = null;
var writeManifestSuccess = RetryHelper.RetryOnException(10, TimeSpan.FromSeconds(1), false, () =>
gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) =>
{
Logger.Trace("Attempting to get game manifest...");
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
{
gameManager.CancelInstall();
manifest = Plugin.LANCommander.GetGameManifest(gameId);
progress.IsIndeterminate = true;
}
};
WriteManifest(manifest, result.Directory);
installDirectory = gameManager.Install(gameId);
return true;
stopwatch.Stop();
},
new GlobalProgressOptions($"Preparing to download {Game.Name}")
{
IsIndeterminate = false,
Cancelable = true,
});
if (!writeManifestSuccess)
throw new Exception("Could not get or write the manifest file. Retry the install or check your connection.");
Logger.Trace("Saving scripts...");
SaveScript(game, result.Directory, ScriptType.Install);
SaveScript(game, result.Directory, ScriptType.Uninstall);
SaveScript(game, result.Directory, ScriptType.NameChange);
SaveScript(game, result.Directory, ScriptType.KeyChange);
// Install any redistributables
var game = Plugin.LANCommanderClient.GetGame(gameId);
if (game.Redistributables != null && game.Redistributables.Count() > 0)
{
Logger.Trace("Installing required redistributables...");
InstallRedistributables(game);
}
try
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.NameChange, Plugin.Settings.PlayerName);
var redistributableManager = new RedistributableManager(Plugin.LANCommanderClient);
var key = Plugin.LANCommander.GetAllocatedKey(game.Id);
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.KeyChange, $"\"{key}\"");
redistributableManager.Install(game);
},
new GlobalProgressOptions("Installing redistributables...")
{
IsIndeterminate = true,
Cancelable = false,
});
}
catch { }
Plugin.UpdateGame(manifest, gameId);
if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory))
{
var manifest = ManifestHelper.Read(installDirectory);
Plugin.DownloadCache.Remove(gameId);
Plugin.UpdateGame(manifest);
var installInfo = new GameInstallationData
{
InstallDirectory = installDirectory,
};
RunInstallScript(installDirectory);
RunNameChangeScript(installDirectory);
RunKeyChangeScript(installDirectory);
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
}
private ExtractionResult DownloadAndExtractGame(LANCommander.SDK.Models.Game game)
else if (result.Canceled)
{
if (game == null)
{
Logger.Trace("Game failed to download! No game was specified!");
var dbGame = Plugin.PlayniteApi.Database.Games.Get(Game.Id);
throw new Exception("Game failed to download!");
dbGame.IsInstalling = false;
dbGame.IsInstalled = false;
Plugin.PlayniteApi.Database.Games.Update(dbGame);
}
else if (result.Error != null)
throw result.Error;
}
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
private int RunInstallScript(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install);
Logger.Trace($"Downloading and extracting \"{game.Title}\" to path {destination}");
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
if (File.Exists(path))
{
try
{
Directory.CreateDirectory(destination);
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
var script = new PowerShellScript();
using (var gameStream = Plugin.LANCommander.StreamGame(game.Id))
using (var reader = ReaderFactory.Open(gameStream))
{
progress.ProgressMaxValue = gameStream.Length;
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory);
script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress);
gameStream.OnProgress += (pos, len) =>
{
progress.CurrentProgressValue = pos;
};
script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install));
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
{
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
{
reader.Cancel();
progress.IsIndeterminate = true;
reader.Dispose();
gameStream.Dispose();
}
};
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
catch (Exception ex)
{
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
{
Logger.Trace("User cancelled the download");
if (Directory.Exists(destination))
{
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
Directory.Delete(destination, true);
}
}
else
{
Logger.Error(ex, $"Could not extract to path {destination}");
if (Directory.Exists(destination))
{
Logger.Trace("Cleaning up orphaned install files after bad install...");
Directory.Delete(destination, true);
return script.Execute();
}
throw new Exception("The game archive could not be extracted. Please try again or fix the archive!");
}
}
},
new GlobalProgressOptions($"Downloading {game.Title}...")
{
IsIndeterminate = false,
Cancelable = true,
});
var extractionResult = new ExtractionResult
{
Canceled = result.Canceled
};
if (!result.Canceled)
{
extractionResult.Success = true;
extractionResult.Directory = destination;
Logger.Trace($"Game successfully downloaded and extracted to {destination}");
return 0;
}
return extractionResult;
private int RunNameChangeScript(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange);
if (File.Exists(path))
{
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory);
script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress);
script.AddVariable("OldPlayerAlias", "");
script.AddVariable("NewPlayerAlias", Plugin.Settings.PlayerName);
script.UseFile(path);
return script.Execute();
}
private void InstallRedistributables(LANCommander.SDK.Models.Game game)
{
foreach (var redistributable in game.Redistributables)
{
string installScriptTempFile = null;
string detectionScriptTempFile = null;
string extractTempPath = null;
try
{
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
installScriptTempFile = SaveTempScript(installScript);
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
detectionScriptTempFile = SaveTempScript(detectionScript);
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
// Redistributable is not installed
if (detectionResult == 0)
{
if (redistributable.Archives.Count() > 0)
{
var extractionResult = DownloadAndExtractRedistributable(redistributable);
if (extractionResult.Success)
{
extractTempPath = extractionResult.Directory;
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
}
}
else
{
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
}
}
}
catch (Exception ex)
{
Logger.Error(ex, $"Redistributable {redistributable.Name} failed to install");
}
finally
{
if (File.Exists(installScriptTempFile))
File.Delete(installScriptTempFile);
if (File.Exists(detectionScriptTempFile))
File.Delete(detectionScriptTempFile);
if (Directory.Exists(extractTempPath))
Directory.Delete(extractTempPath);
}
}
return 0;
}
private ExtractionResult DownloadAndExtractRedistributable(LANCommander.SDK.Models.Redistributable redistributable)
private int RunKeyChangeScript(string installDirectory)
{
if (redistributable == null)
{
Logger.Trace("Redistributable failed to download! No redistributable was specified!");
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange);
throw new Exception("Redistributable failed to download!");
if (File.Exists(path))
{
var script = new PowerShellScript();
var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory);
script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress);
script.AddVariable("AllocatedKey", key);
script.UseFile(path);
return script.Execute();
}
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
Logger.Trace($"Downloading and extracting \"{redistributable.Name}\" to path {destination}");
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
try
{
Directory.CreateDirectory(destination);
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
using (var redistributableStream = Plugin.LANCommander.StreamRedistributable(redistributable.Id))
using (var reader = ReaderFactory.Open(redistributableStream))
{
progress.ProgressMaxValue = redistributableStream.Length;
redistributableStream.OnProgress += (pos, len) =>
{
progress.CurrentProgressValue = pos;
};
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
{
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
{
reader.Cancel();
progress.IsIndeterminate = true;
reader.Dispose();
redistributableStream.Dispose();
}
};
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
catch (Exception ex)
{
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
{
Logger.Trace("User cancelled the download");
if (Directory.Exists(destination))
{
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
Directory.Delete(destination, true);
}
}
else
{
Logger.Error(ex, $"Could not extract to path {destination}");
if (Directory.Exists(destination))
{
Logger.Trace("Cleaning up orphaned install files after bad install...");
Directory.Delete(destination, true);
}
throw new Exception("The redistributable archive could not be extracted. Please try again or fix the archive!");
}
}
},
new GlobalProgressOptions($"Downloading {redistributable.Name}...")
{
IsIndeterminate = false,
Cancelable = true,
});
var extractionResult = new ExtractionResult
{
Canceled = result.Canceled
};
if (!result.Canceled)
{
extractionResult.Success = true;
extractionResult.Directory = destination;
Logger.Trace($"Redistributable successfully downloaded and extracted to {destination}");
}
return extractionResult;
}
private string Download(LANCommander.SDK.Models.Game game)
{
string tempFile = String.Empty;
if (game != null)
{
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
var destination = Plugin.LANCommander.DownloadGame(game.Id, (changed) =>
{
progress.CurrentProgressValue = changed.ProgressPercentage;
}, (complete) =>
{
progress.CurrentProgressValue = 100;
});
// Lock the thread until download is done
while (progress.CurrentProgressValue != 100)
{
}
tempFile = destination;
},
new GlobalProgressOptions($"Downloading {game.Title}...")
{
IsIndeterminate = false,
Cancelable = false,
});
return tempFile;
}
else
throw new Exception("Game failed to download!");
}
private string Extract(LANCommander.SDK.Models.Game game, string archivePath)
{
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
Directory.CreateDirectory(destination);
using (var fs = File.OpenRead(archivePath))
using (var ts = new TrackableStream(fs))
using (var reader = ReaderFactory.Open(ts))
{
progress.ProgressMaxValue = ts.Length;
ts.OnProgress += (pos, len) =>
{
progress.CurrentProgressValue = pos;
};
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
},
new GlobalProgressOptions($"Extracting {game.Title}...")
{
IsIndeterminate = false,
Cancelable = false,
});
return destination;
}
private void WriteManifest(SDK.GameManifest manifest, string installDirectory)
{
var destination = Path.Combine(installDirectory, "_manifest.yml");
Logger.Trace($"Attempting to write manifest to path {destination}");
var serializer = new SerializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
Logger.Trace("Serializing manifest...");
var yaml = serializer.Serialize(manifest);
Logger.Trace("Writing manifest file...");
File.WriteAllText(destination, yaml);
}
private string SaveTempScript(LANCommander.SDK.Models.Script script)
{
var tempPath = Path.GetTempFileName();
File.Move(tempPath, tempPath + ".ps1");
tempPath = tempPath + ".ps1";
Logger.Trace($"Writing script {script.Name} to {tempPath}");
File.WriteAllText(tempPath, script.Contents);
return tempPath;
}
private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
{
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
if (script == null)
return;
if (script.RequiresAdmin)
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
var filename = PowerShellRuntime.GetScriptFilePath(PlayniteGame, type);
if (File.Exists(filename))
File.Delete(filename);
Logger.Trace($"Writing {type} script to {filename}");
File.WriteAllText(filename, script.Contents);
return 0;
}
}
}

View File

@ -12,6 +12,7 @@
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -34,19 +35,28 @@
<Reference Include="BeaconLib, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
<Reference Include="ByteSize, Version=2.1.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ByteSize.2.1.1\lib\net45\ByteSize.dll</HintPath>
</Reference>
<Reference Include="Playnite.SDK, Version=6.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\PlayniteSDK.6.9.0\lib\net462\Playnite.SDK.dll</HintPath>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Playnite.SDK, Version=6.10.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\PlayniteSDK.6.10.0\lib\net462\Playnite.SDK.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath>
</Reference>
<Reference Include="SharpCompress, Version=0.33.0.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
<HintPath>..\packages\SharpCompress.0.33.0\lib\net462\SharpCompress.dll</HintPath>
<Reference Include="SharpCompress, Version=0.34.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
<HintPath>..\packages\SharpCompress.0.34.2\lib\net462\SharpCompress.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
@ -67,14 +77,14 @@
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.CodePages, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encoding.CodePages.7.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
<Reference Include="System.Text.Encoding.CodePages, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encoding.CodePages.8.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.7.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
<Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=7.0.0.3, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.7.0.3\lib\net462\System.Text.Json.dll</HintPath>
<Reference Include="System.Text.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.8.0.0\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
@ -93,18 +103,18 @@
<Reference Include="WindowsBase" />
<Reference Include="YamlDotNet, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\YamlDotNet.5.4.0\lib\net45\YamlDotNet.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ZstdSharp, Version=0.7.2.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
<HintPath>..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ExtractionResult.cs" />
<Compile Include="TrackableStream.cs" />
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
<Compile Include="Helpers\RetryHelper.cs" />
<Compile Include="PowerShellRuntime.cs" />
<Compile Include="Services\GameSaveService.cs" />
<Compile Include="PlayniteLogger.cs" />
<Compile Include="SaveController.cs" />
<Compile Include="UninstallController.cs" />
<Compile Include="InstallController.cs" />
<Compile Include="LANCommanderClient.cs" />
<Compile Include="LANCommanderLibraryPlugin.cs" />
<Compile Include="ViewModels\LANCommanderSettingsViewModel.cs" />
<Compile Include="Views\LANCommanderSettingsView.xaml.cs">
@ -145,6 +155,10 @@
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.PowerShell\LANCommander.PowerShell.csproj">
<Project>{807943bf-0c7d-4ed3-8393-cfee64e3138c}</Project>
<Name>LANCommander.PowerShell</Name>
</ProjectReference>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj">
<Project>{4c2a71fd-a30b-4d62-888a-4ef843d8e506}</Project>
<Name>LANCommander.SDK</Name>

View File

@ -1,5 +1,6 @@
using LANCommander.PlaynitePlugin.Extensions;
using LANCommander.PlaynitePlugin.Services;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.PowerShell;
using Playnite.SDK;
using Playnite.SDK.Events;
using Playnite.SDK.Models;
@ -21,9 +22,8 @@ namespace LANCommander.PlaynitePlugin
{
public static readonly ILogger Logger = LogManager.GetLogger();
internal LANCommanderSettingsViewModel Settings { get; set; }
internal LANCommanderClient LANCommander { get; set; }
internal PowerShellRuntime PowerShellRuntime { get; set; }
internal GameSaveService GameSaveService { get; set; }
internal LANCommander.SDK.Client LANCommanderClient { get; set; }
internal LANCommanderSaveController SaveController { get; set; }
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
public override string Name => "LANCommander";
@ -39,16 +39,14 @@ namespace LANCommander.PlaynitePlugin
Settings = new LANCommanderSettingsViewModel(this);
LANCommander = new LANCommanderClient(Settings.ServerAddress);
LANCommander.Token = new SDK.Models.AuthToken()
LANCommanderClient = new SDK.Client(Settings.ServerAddress, new PlayniteLogger(Logger));
LANCommanderClient.UseToken(new SDK.Models.AuthToken()
{
AccessToken = Settings.AccessToken,
RefreshToken = Settings.RefreshToken,
};
});
PowerShellRuntime = new PowerShellRuntime();
GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
// GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
api.UriHandler.RegisterSource("lancommander", args =>
{
@ -91,7 +89,7 @@ namespace LANCommander.PlaynitePlugin
public bool ValidateConnection()
{
return LANCommander.ValidateToken(LANCommander.Token);
return LANCommanderClient.ValidateToken();
}
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
@ -111,7 +109,7 @@ namespace LANCommander.PlaynitePlugin
}
}
var games = LANCommander
var games = LANCommanderClient
.GetGames()
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
@ -121,7 +119,7 @@ namespace LANCommander.PlaynitePlugin
{
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
var manifest = LANCommander.GetGameManifest(game.Id);
var manifest = LANCommanderClient.GetGameManifest(game.Id);
Logger.Trace("Successfully grabbed game manifest");
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
@ -130,7 +128,7 @@ namespace LANCommander.PlaynitePlugin
{
Logger.Trace("Game already exists in library, updating metadata...");
UpdateGame(manifest, game.Id);
UpdateGame(manifest);
continue;
}
@ -183,13 +181,13 @@ namespace LANCommander.PlaynitePlugin
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon))
metadata.Icon = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
metadata.Icon = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover))
metadata.CoverImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
metadata.CoverImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background))
metadata.BackgroundImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
metadata.BackgroundImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
gameMetadata.Add(metadata);
}
@ -224,9 +222,9 @@ namespace LANCommander.PlaynitePlugin
if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory))
{
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
var installScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.Install);
var nameChangeScriptPath = ScriptHelper.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.NameChange);
var keyChangeScriptPath = ScriptHelper.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange);
var installScriptPath = ScriptHelper.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install);
if (File.Exists(nameChangeScriptPath))
{
@ -243,8 +241,11 @@ namespace LANCommander.PlaynitePlugin
if (result.Result == true)
{
PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
LANCommander.ChangeAlias(result.SelectedString);
var game = nameChangeArgs.Games.First();
RunNameChangeScript(game.InstallDirectory, oldName, result.SelectedString);
LANCommanderClient.ChangeAlias(result.SelectedString);
}
}
};
@ -264,12 +265,12 @@ namespace LANCommander.PlaynitePlugin
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
{
// NUKIEEEE
var newKey = LANCommander.GetNewKey(gameId);
var newKey = LANCommanderClient.GetNewKey(gameId);
if (String.IsNullOrEmpty(newKey))
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
else
PowerShellRuntime.RunScript(keyChangeArgs.Games.First(), SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
RunKeyChangeScript(keyChangeArgs.Games.First().InstallDirectory, newKey);
}
else
{
@ -291,14 +292,10 @@ namespace LANCommander.PlaynitePlugin
Guid gameId;
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
{
PowerShellRuntime.RunScript(installArgs.Games.First(), SDK.Enums.ScriptType.Install);
}
RunInstallScript(installArgs.Games.First().InstallDirectory);
else
{
PlayniteApi.Dialogs.ShowErrorMessage("This game could not be found on the server. Your game may be corrupted.");
}
}
};
}
}
@ -334,12 +331,42 @@ namespace LANCommander.PlaynitePlugin
public override void OnGameStarting(OnGameStartingEventArgs args)
{
GameSaveService.DownloadSave(args.Game);
if (args.Game.PluginId == Id)
{
var gameId = Guid.Parse(args.Game.GameId);
LANCommanderClient.StartPlaySession(gameId);
try
{
SaveController = new LANCommanderSaveController(this, args.Game);
SaveController.Download(args.Game);
}
catch (Exception ex)
{
Logger?.Error(ex, "Could not download save");
}
}
}
public override void OnGameStopped(OnGameStoppedEventArgs args)
{
GameSaveService.UploadSave(args.Game);
if (args.Game.PluginId == Id)
{
var gameId = Guid.Parse(args.Game.GameId);
LANCommanderClient.EndPlaySession(gameId);
try
{
SaveController = new LANCommanderSaveController(this, args.Game);
SaveController.Upload(args.Game);
}
catch (Exception ex)
{
Logger?.Error(ex, "Could not upload save");
}
}
}
public override IEnumerable<TopPanelItem> GetTopPanelItems()
@ -393,6 +420,8 @@ namespace LANCommander.PlaynitePlugin
}
else
{
var oldName = Settings.PlayerName;
Settings.PlayerName = result.SelectedString;
Logger.Trace($"New player name of \"{Settings.PlayerName}\" has been set!");
@ -402,18 +431,28 @@ namespace LANCommander.PlaynitePlugin
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
LANCommander.ChangeAlias(result.SelectedString);
LANCommanderClient.ChangeAlias(result.SelectedString);
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");
PowerShellRuntime.RunScripts(games, SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
foreach (var game in games)
{
var script = new PowerShellScript();
script.AddVariable("OldName", oldName);
script.AddVariable("NewName", Settings.PlayerName);
script.UseFile(ScriptHelper.GetScriptFilePath(game.InstallDirectory, SDK.Enums.ScriptType.NameChange));
script.Execute();
}
}
}
else
Logger.Trace("Name change was cancelled");
}
public Window ShowAuthenticationWindow(string serverAddress = null)
public Window ShowAuthenticationWindow(string serverAddress = null, EventHandler onClose = null)
{
Window window = null;
Application.Current.Dispatcher.Invoke((Action)delegate
@ -435,15 +474,19 @@ namespace LANCommander.PlaynitePlugin
window.Owner = PlayniteApi.Dialogs.GetCurrentAppWindow();
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
window.ResizeMode = ResizeMode.NoResize;
if (onClose != null)
window.Closed += onClose;
window.ShowDialog();
});
return window;
}
public void UpdateGame(SDK.GameManifest manifest, Guid gameId)
public void UpdateGame(SDK.GameManifest manifest)
{
var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString());
var game = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == manifest?.Id.ToString());
if (game == null)
return;
@ -525,5 +568,77 @@ namespace LANCommander.PlaynitePlugin
PlayniteApi.Database.Games.Update(game);
}
private int RunInstallScript(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install);
if (File.Exists(path))
{
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory);
script.AddVariable("ServerAddress", Settings.ServerAddress);
script.UseFile(path);
return script.Execute();
}
return 0;
}
private int RunNameChangeScript(string installDirectory, string oldPlayerAlias, string newPlayerAlias)
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange);
if (File.Exists(path))
{
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory);
script.AddVariable("ServerAddress", Settings.ServerAddress);
script.AddVariable("OldPlayerAlias", oldPlayerAlias);
script.AddVariable("NewPlayerAlias", newPlayerAlias);
script.UseFile(path);
return script.Execute();
}
return 0;
}
private int RunKeyChangeScript(string installDirectory, string key = "")
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange);
if (File.Exists(path))
{
var script = new PowerShellScript();
if (String.IsNullOrEmpty(key))
key = LANCommanderClient.GetAllocatedKey(manifest.Id);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory);
script.AddVariable("ServerAddress", Settings.ServerAddress);
script.AddVariable("AllocatedKey", key);
script.UseFile(path);
return script.Execute();
}
return 0;
}
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
{
public sealed class PlayniteLogger : ILogger
{
private readonly Playnite.SDK.ILogger Logger;
public PlayniteLogger(Playnite.SDK.ILogger logger) {
Logger = logger;
}
public IDisposable BeginScope<TState>(TState state)
{
return default;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
switch (logLevel)
{
case LogLevel.Trace:
Logger?.Trace(formatter.Invoke(state, exception));
break;
case LogLevel.Debug:
Logger?.Debug(formatter.Invoke(state, exception));
break;
case LogLevel.Information:
Logger.Info(formatter.Invoke(state, exception));
break;
case LogLevel.Warning:
Logger.Warn(formatter.Invoke(state, exception));
break;
case LogLevel.Error:
case LogLevel.Critical:
Logger.Error(formatter.Invoke(state, exception));
break;
}
}
}
}

View File

@ -1,165 +0,0 @@
using LANCommander.SDK.Enums;
using Playnite.SDK;
using Playnite.SDK.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Security.RightsManagement;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
{
internal class PowerShellRuntime
{
public static readonly ILogger Logger = LogManager.GetLogger();
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr);
public void RunCommand(string command, bool asAdmin = false)
{
Logger.Trace($"Executing command `{command}` | Admin: {asAdmin}");
var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1");
Logger.Trace($"Creating temp script at path {tempScript}");
File.WriteAllText(tempScript, command);
RunScript(tempScript, asAdmin);
File.Delete(tempScript);
}
public int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null)
{
Logger.Trace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}");
var wow64Value = IntPtr.Zero;
// Disable Wow64 redirection so we can hit areas of the registry absolutely
Wow64DisableWow64FsRedirection(ref wow64Value);
var process = new Process();
process.StartInfo.FileName = "powershell.exe";
process.StartInfo.Arguments = $@"-ExecutionPolicy Unrestricted -File ""{path}""";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = false;
if (arguments != null)
process.StartInfo.Arguments += " " + arguments;
if (workingDirectory != null)
process.StartInfo.WorkingDirectory = workingDirectory;
if (asAdmin)
{
process.StartInfo.Verb = "runas";
process.StartInfo.UseShellExecute = true;
}
process.Start();
process.WaitForExit();
Wow64RevertWow64FsRedirection(ref wow64Value);
return process.ExitCode;
}
public void RunScript(Game game, ScriptType type, string arguments = null)
{
var path = GetScriptFilePath(game, type);
if (File.Exists(path))
{
var contents = File.ReadAllText(path);
if (contents.StartsWith("# Requires Admin"))
RunScript(path, true, arguments);
else
RunScript(path, false, arguments);
}
}
public void RunScriptsAsAdmin(IEnumerable<string> paths, string arguments = null)
{
// Concatenate scripts
var sb = new StringBuilder();
Logger.Trace("Concatenating scripts...");
foreach (var path in paths)
{
var contents = File.ReadAllText(path);
sb.AppendLine(contents);
Logger.Trace($"Added {path}!");
}
Logger.Trace("Done concatenating!");
if (sb.Length > 0)
{
var scriptPath = Path.GetTempFileName();
Logger.Trace($"Creating temp script at path {scriptPath}");
File.WriteAllText(scriptPath, sb.ToString());
RunScript(scriptPath, true, arguments);
}
}
public void RunScripts(IEnumerable<Game> games, ScriptType type, string arguments = null)
{
List<string> scripts = new List<string>();
List<string> adminScripts = new List<string>();
foreach (var game in games)
{
var path = GetScriptFilePath(game, type);
if (!File.Exists(path))
continue;
var contents = File.ReadAllText(path);
if (contents.StartsWith("# Requires Admin"))
adminScripts.Add(path);
else
scripts.Add(path);
}
RunScriptsAsAdmin(adminScripts, arguments);
foreach (var script in scripts)
{
RunScript(script, false, arguments);
}
}
public static string GetScriptFilePath(Game game, ScriptType type)
{
Dictionary<ScriptType, string> filenames = new Dictionary<ScriptType, string>() {
{ ScriptType.Install, "_install.ps1" },
{ ScriptType.Uninstall, "_uninstall.ps1" },
{ ScriptType.NameChange, "_changename.ps1" },
{ ScriptType.KeyChange, "_changekey.ps1" }
};
var filename = filenames[type];
return Path.Combine(game.InstallDirectory, filename);
}
}
}

View File

@ -0,0 +1,61 @@
using LANCommander.SDK;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
namespace LANCommander.PlaynitePlugin
{
public class LANCommanderSaveController : ControllerBase
{
private static readonly ILogger Logger;
private LANCommanderLibraryPlugin Plugin;
public LANCommanderSaveController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
{
Name = "Download save using LANCommander";
Plugin = plugin;
}
public void Download(Game game)
{
if (game != null)
{
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
var saveManager = new GameSaveManager(Plugin.LANCommanderClient);
saveManager.OnDownloadProgress += (downloadProgress) =>
{
progress.CurrentProgressValue = downloadProgress.ProgressPercentage;
};
saveManager.OnDownloadComplete += (downloadComplete) =>
{
progress.CurrentProgressValue = 100;
};
saveManager.Download(game.InstallDirectory);
// Lock the thread until the download is done
while (progress.CurrentProgressValue != 100) { }
},
new GlobalProgressOptions("Downloading latest save...")
{
IsIndeterminate = false,
Cancelable = false
});
}
}
public void Upload(Game game)
{
var saveManager = new GameSaveManager(Plugin.LANCommanderClient);
saveManager.Upload(game.InstallDirectory);
}
}
}

View File

@ -1,289 +0,0 @@
using LANCommander.SDK;
using Playnite.SDK;
using Playnite.SDK.Models;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.PlaynitePlugin.Services
{
internal class GameSaveService
{
private readonly LANCommanderClient LANCommander;
private readonly IPlayniteAPI PlayniteApi;
private readonly PowerShellRuntime PowerShellRuntime;
internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime)
{
LANCommander = lanCommander;
PlayniteApi = playniteApi;
PowerShellRuntime = powerShellRuntime;
}
internal void DownloadSave(Game game)
{
string tempFile = String.Empty;
if (game != null)
{
PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
var destination = LANCommander.DownloadLatestSave(Guid.Parse(game.GameId), (changed) =>
{
progress.CurrentProgressValue = changed.ProgressPercentage;
}, (complete) =>
{
progress.CurrentProgressValue = 100;
});
// Lock the thread until download is done
while (progress.CurrentProgressValue != 100)
{
}
tempFile = destination;
},
new GlobalProgressOptions("Downloading latest save...")
{
IsIndeterminate = false,
Cancelable = false
});
// Go into the archive and extract the files to the correct locations
try
{
var tempLocation = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempLocation);
ExtractFilesFromZip(tempFile, tempLocation);
var deserializer = new DeserializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
var manifestContents = File.ReadAllText(Path.Combine(tempLocation, "_manifest.yml"));
var manifest = deserializer.Deserialize<GameManifest>(manifestContents);
#region Move files
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
bool inInstallDir = savePath.Path.StartsWith("{InstallDir}");
string tempSavePath = Path.Combine(tempLocation, savePath.Id.ToString());
var tempSavePathFile = Path.Combine(tempSavePath, savePath.Path.Replace('/', '\\').Replace("{InstallDir}\\", ""));
var destination = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
if (File.Exists(tempSavePathFile))
{
// Is file, move file
if (File.Exists(destination))
File.Delete(destination);
File.Move(tempSavePathFile, destination);
}
else if (Directory.Exists(tempSavePath))
{
var files = Directory.GetFiles(tempSavePath, "*", SearchOption.AllDirectories);
if (inInstallDir)
{
foreach (var file in files)
{
if (inInstallDir)
{
// Files are in the game's install directory. Move them there from the save path.
destination = file.Replace(tempSavePath, savePath.Path.Replace('/', '\\').TrimEnd('\\').Replace("{InstallDir}", game.InstallDirectory));
if (File.Exists(destination))
File.Delete(destination);
File.Move(file, destination);
}
else
{
// Specified path is probably an absolute path, maybe with environment variables.
destination = Environment.ExpandEnvironmentVariables(file.Replace(tempSavePathFile, savePath.Path.Replace('/', '\\')));
if (File.Exists(destination))
File.Delete(destination);
File.Move(file, destination);
}
}
}
else
{
}
}
}
#endregion
#region Handle registry importing
var registryImportFilePath = Path.Combine(tempLocation, "_registry.reg");
if (File.Exists(registryImportFilePath))
{
var registryImportFileContents = File.ReadAllText(registryImportFilePath);
PowerShellRuntime.RunCommand($"regedit.exe /s \"{registryImportFilePath}\"", registryImportFileContents.Contains("HKEY_LOCAL_MACHINE"));
}
#endregion
// Clean up temp files
Directory.Delete(tempLocation, true);
}
catch (Exception ex)
{
}
}
}
internal void UploadSave(Game game)
{
var manifestPath = Path.Combine(game.InstallDirectory, "_manifest.yml");
if (File.Exists(manifestPath))
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath));
var temp = Path.GetTempFileName();
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
{
using (var archive = ZipArchive.Create())
{
archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
if (Directory.Exists(localPath))
{
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
}
else if (File.Exists(localPath))
{
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
}
#endregion
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
if (Directory.Exists(localPath))
{
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
}
else if (File.Exists(localPath))
{
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
}
#endregion
#region Export registry keys
if (manifest.SavePaths.Any(sp => sp.Type == "Registry"))
{
List<string> tempRegFiles = new List<string>();
var exportCommand = new StringBuilder();
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry"))
{
var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg");
exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\"");
tempRegFiles.Add(tempRegFile);
}
PowerShellRuntime.RunCommand(exportCommand.ToString());
var exportFile = new StringBuilder();
foreach (var tempRegFile in tempRegFiles)
{
exportFile.AppendLine(File.ReadAllText(tempRegFile));
File.Delete(tempRegFile);
}
archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
}
#endregion
archive.AddEntry("_manifest.yml", manifestPath);
using (var ms = new MemoryStream())
{
archive.SaveTo(ms);
ms.Seek(0, SeekOrigin.Begin);
var save = LANCommander.UploadSave(game.GameId, ms.ToArray());
}
}
}
}
}
private void AddDirectoryToZip(ZipArchive zipArchive, string path, string workingDirectory, Guid pathId)
{
foreach (var file in Directory.GetFiles(path))
{
// Oh man is this a hack. We should be removing only the working directory from the start,
// but we're making the assumption that the working dir put in actually prefixes the path.
// Also wtf, that Path.Combine is stripping the pathId out?
zipArchive.AddEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(file)), file);
}
foreach (var child in Directory.GetDirectories(path))
{
// See above
//ZipEntry entry = new ZipEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(path)));
//zipStream.PutNextEntry(entry);
//zipStream.CloseEntry();
AddDirectoryToZip(zipArchive, child, workingDirectory, pathId);
}
}
private void ExtractFilesFromZip(string zipPath, string destination)
{
using (var fs = File.OpenRead(zipPath))
using (var ts = new TrackableStream(fs))
using (var reader = ReaderFactory.Open(ts))
{
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
}

View File

@ -1,4 +1,6 @@
using LANCommander.SDK.Enums;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.PowerShell;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
@ -12,29 +14,49 @@ namespace LANCommander.PlaynitePlugin
public static readonly ILogger Logger = LogManager.GetLogger();
private LANCommanderLibraryPlugin Plugin;
private PowerShellRuntime PowerShellRuntime;
public LANCommanderUninstallController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
{
Name = "Uninstall LANCommander Game";
Plugin = plugin;
PowerShellRuntime = new PowerShellRuntime();
}
public override void Uninstall(UninstallActionArgs args)
{
try
{
PowerShellRuntime.RunScript(Game, ScriptType.Uninstall);
var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory);
try
{
var scriptPath = ScriptHelper.GetScriptFilePath(Game.InstallDirectory, SDK.Enums.ScriptType.Uninstall);
if (!String.IsNullOrEmpty(scriptPath) && File.Exists(scriptPath))
{
var manifest = ManifestHelper.Read(Game.InstallDirectory);
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", Game.InstallDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory);
script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress);
script.UseFile(scriptPath);
script.Execute();
}
}
catch (Exception ex)
{
Logger.Error(ex, "There was an error running the uninstall script");
}
catch { }
Logger.Trace("Attempting to delete install directory...");
if (!String.IsNullOrWhiteSpace(Game.InstallDirectory) && Directory.Exists(Game.InstallDirectory))
Directory.Delete(Game.InstallDirectory, true);
Logger.Trace("Deleted!");
gameManager.Uninstall(Game.InstallDirectory);
}
catch (Exception ex)
{
Logger.Error(ex, "There was an error uninstalling the game");
}
InvokeOnUninstalled(new GameUninstalledEventArgs());
}

View File

@ -100,24 +100,18 @@ namespace LANCommander.PlaynitePlugin.Views
LoginButton.Content = "Logging in...";
}));
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
if (Plugin.LANCommanderClient == null)
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
else
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
Plugin.LANCommanderClient.UseServerAddress(Context.ServerAddress);
var response = await Plugin.LANCommander.AuthenticateAsync(Context.UserName, Context.Password);
var response = await Plugin.LANCommanderClient.AuthenticateAsync(Context.UserName, Context.Password);
Plugin.Settings.ServerAddress = Context.ServerAddress;
Plugin.Settings.AccessToken = response.AccessToken;
Plugin.Settings.RefreshToken = response.RefreshToken;
Plugin.LANCommander.Token = new AuthToken()
{
AccessToken = response.AccessToken,
RefreshToken = response.RefreshToken,
};
var profile = Plugin.LANCommander.GetProfile();
var profile = Plugin.LANCommanderClient.GetProfile();
Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias;
@ -130,6 +124,8 @@ namespace LANCommander.PlaynitePlugin.Views
}
catch (Exception ex)
{
Logger.Error(ex, ex.Message);
Plugin.PlayniteApi.Dialogs.ShowErrorMessage(ex.Message);
LoginButton.Dispatcher.Invoke(new System.Action(() =>
@ -148,24 +144,16 @@ namespace LANCommander.PlaynitePlugin.Views
RegisterButton.IsEnabled = false;
RegisterButton.Content = "Working...";
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
else
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
if (Plugin.LANCommanderClient == null)
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
var response = await Plugin.LANCommander.RegisterAsync(Context.UserName, Context.Password);
var response = await Plugin.LANCommanderClient.RegisterAsync(Context.UserName, Context.Password);
Plugin.Settings.ServerAddress = Context.ServerAddress;
Plugin.Settings.AccessToken = response.AccessToken;
Plugin.Settings.RefreshToken = response.RefreshToken;
Plugin.Settings.PlayerName = Context.UserName;
Plugin.LANCommander.Token = new AuthToken()
{
AccessToken = response.AccessToken,
RefreshToken = response.RefreshToken,
};
Context.Password = String.Empty;
Plugin.SavePluginSettings(Plugin.Settings);

View File

@ -47,7 +47,7 @@ namespace LANCommander.PlaynitePlugin
RefreshToken = Settings.RefreshToken,
};
var task = Task.Run(() => Plugin.LANCommander.ValidateToken(token))
var task = Task.Run(() => Plugin.LANCommanderClient.ValidateToken(token))
.ContinueWith(antecedent =>
{
try
@ -81,16 +81,14 @@ namespace LANCommander.PlaynitePlugin
private void AuthenticateButton_Click(object sender, RoutedEventArgs e)
{
var authWindow = Plugin.ShowAuthenticationWindow();
authWindow.Closed += AuthWindow_Closed;
var authWindow = Plugin.ShowAuthenticationWindow(Settings.ServerAddress, AuthWindow_Closed);
}
private void DisconnectButton_Click(object sender, RoutedEventArgs e)
{
Plugin.Settings.AccessToken = String.Empty;
Plugin.Settings.RefreshToken = String.Empty;
Plugin.LANCommander.Token = null;
Plugin.LANCommanderClient.UseToken(null);
Plugin.SavePluginSettings(Plugin.Settings);

View File

@ -23,8 +23,28 @@ Packages:
- Version: 0.2.1
RequiredApiVersion: 6.0.0
ReleaseDate: 2023-11-03
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.0/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_1.pext
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.1/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_1.pext
Changelog:
- LANCommander servers can now provide game art to Playnite clients
- Added server address to addon settings
- Added disconnect button to addon settings
- Version: 0.2.2
RequiredApiVersion: 6.0.0
ReleaseDate: 2023-11-20
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.2/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_2.pext
Changelog:
- _manifest.yml files in the game's install directory now includes the game's ID
- Installation progress dialog now shows the download percentage and transfer speed
- Full game download will be skipped if the game files already exist, see full release notes for more details
- Play sessions are now recorded to the server with user ID, start time, and end time
- Connection status now updates correctly when authenticating through addon settings
- Version: 0.3.0
RequiredApiVersion: 6.0.0
ReleaseDate: 2023-12-03
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.3.0/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_3_0.pext
Changelog:
- Save paths now support regex patterns (experimental)
- Fixed redistributable archive downloading
- Fixed redistributable scripts not being able to run as admin if marked as such
- Fixed game save downloading
- Fixed addon loading of YamlDotNet library

View File

@ -1,20 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="7.0.0" targetFramework="net462" />
<package id="NuGet.CommandLine" version="6.7.0" targetFramework="net462" developmentDependency="true" />
<package id="PlayniteSDK" version="6.9.0" targetFramework="net462" />
<package id="ByteSize" version="2.1.1" targetFramework="net462" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net462" />
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="8.0.0" targetFramework="net462" />
<package id="Microsoft.Extensions.Logging.Abstractions" version="8.0.0" targetFramework="net462" />
<package id="NuGet.CommandLine" version="6.8.0" targetFramework="net462" developmentDependency="true" />
<package id="PlayniteSDK" version="6.10.0" targetFramework="net462" />
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net462" />
<package id="RestSharp" version="106.15.0" targetFramework="net462" />
<package id="rix0rrr.BeaconLib" version="1.0.2" targetFramework="net462" />
<package id="SharpCompress" version="0.33.0" targetFramework="net462" />
<package id="SharpCompress" version="0.34.2" targetFramework="net462" />
<package id="System.Buffers" version="4.5.1" targetFramework="net462" />
<package id="System.Memory" version="4.5.5" targetFramework="net462" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net462" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="7.0.0" targetFramework="net462" />
<package id="System.Text.Encodings.Web" version="7.0.0" targetFramework="net462" />
<package id="System.Text.Json" version="7.0.3" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="8.0.0" targetFramework="net462" />
<package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net462" />
<package id="System.Text.Json" version="8.0.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
<package id="YamlDotNet" version="5.4.0" targetFramework="net462" />
<package id="ZstdSharp.Port" version="0.7.2" targetFramework="net462" />
</packages>

View File

@ -0,0 +1,64 @@
using LANCommander.PowerShell.Cmdlets;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
namespace LANCommander.PowerShell.Tests
{
[TestClass]
public class CmdletTests
{
[TestMethod]
public void ConvertToSerializedBase64ShouldBeDeserializable()
{
var testPhrase = "Hello world! This should be deserializable back to its original form.";
var encodingCmdlet = new ConvertToSerializedBase64Cmdlet()
{
Input = testPhrase
};
var encodingResults = encodingCmdlet.Invoke().OfType<string>().ToList();
Assert.AreEqual(1, encodingResults.Count);
var decodingCmdlet = new ConvertFromSerializedBase64Cmdlet()
{
Input = encodingResults.First()
};
var decodingResults = decodingCmdlet.Invoke().OfType<string>().ToList();
Assert.AreEqual(1, encodingResults.Count);
Assert.AreEqual(testPhrase, decodingResults.First());
}
[TestMethod]
[DataRow(640, 480, 640, 360, 16, 9)]
[DataRow(1024, 768, 1024, 576, 16, 9)]
[DataRow(1600, 1200, 1600, 900, 16, 9)]
[DataRow(1920, 1080, 1440, 1080, 4, 3)]
[DataRow(1366, 1024, 1024, 768, 4, 3)]
[DataRow(854, 480, 640, 480, 4, 3)]
public void ConvertAspectRatioShouldReturnCorrectBounds(int x1, int y1, int x2, int y2, int ratioX, int ratioY)
{
var aspectRatio = (double)ratioX / (double)ratioY;
var cmdlet = new ConvertAspectRatioCmdlet()
{
AspectRatio = aspectRatio,
Width = x1,
Height = y1
};
var output = cmdlet.Invoke().OfType<DisplayResolution>().ToList();
Assert.AreEqual(1, output.Count);
var bounds = output.First();
Assert.AreEqual(x2, bounds.Width);
Assert.AreEqual(y2, bounds.Height);
}
}
}

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props')" />
<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>{D7069A13-F0AA-4CBF-9013-4276F130A6DD}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LANCommander.PowerShell.Tests</RootNamespace>
<AssemblyName>LANCommander.PowerShell.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</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="Microsoft.Bcl.AsyncInterfaces, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath>
</Reference>
<Reference Include="SharpCompress, Version=0.34.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
<HintPath>..\packages\SharpCompress.0.34.2\lib\net462\SharpCompress.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.CodePages, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encoding.CodePages.8.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="YamlDotNet, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\YamlDotNet.5.4.0\lib\net45\YamlDotNet.dll</HintPath>
</Reference>
<Reference Include="ZstdSharp, Version=0.7.4.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
<HintPath>..\packages\ZstdSharp.Port.0.7.4\lib\net462\ZstdSharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Cmdlets.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.PowerShell\LANCommander.PowerShell.csproj">
<Project>{807943bf-0c7d-4ed3-8393-cfee64e3138c}</Project>
<Name>LANCommander.PowerShell</Name>
</ProjectReference>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj">
<Project>{4c2a71fd-a30b-4d62-888a-4ef843d8e506}</Project>
<Name>LANCommander.SDK</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets')" />
</Project>

View File

@ -0,0 +1,20 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("LANCommander.PowerShell.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LANCommander.PowerShell.Tests")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("d7069a13-f0aa-4cbf-9013-4276f130a6dd")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="5.0.0" targetFramework="net462" />
<package id="MSTest.TestAdapter" version="2.2.10" targetFramework="net462" />
<package id="MSTest.TestFramework" version="2.2.10" targetFramework="net462" />
<package id="RestSharp" version="106.15.0" targetFramework="net462" />
<package id="SharpCompress" version="0.34.2" targetFramework="net462" />
<package id="System.Buffers" version="4.5.1" targetFramework="net462" />
<package id="System.Memory" version="4.5.5" targetFramework="net462" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net462" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="8.0.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
<package id="YamlDotNet" version="5.4.0" targetFramework="net462" />
<package id="ZstdSharp.Port" version="0.7.4" targetFramework="net462" />
</packages>

View File

@ -0,0 +1,45 @@
using System;
using System.Management.Automation;
namespace LANCommander.PowerShell.Cmdlets
{
public class DisplayResolution
{
public int Width { get; set; }
public int Height { get; set; }
}
[Cmdlet(VerbsData.Convert, "AspectRatio")]
[OutputType(typeof(string))]
public class ConvertAspectRatioCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public int Width { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public int Height { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public double AspectRatio { get; set; }
protected override void ProcessRecord()
{
var resolution = new DisplayResolution();
// Display is wider, pillar box
if ((Width / Height) < AspectRatio)
{
resolution.Width = (int)Math.Ceiling(Height * AspectRatio);
resolution.Height = Height;
}
// Letterbox
else
{
resolution.Width = Width;
resolution.Height = (int)Math.Ceiling(Width * (1 / AspectRatio));
}
WriteObject(resolution);
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsData.ConvertFrom, "SerializedBase64")]
[OutputType(typeof(object))]
public class ConvertFromSerializedBase64Cmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public string Input { get; set; }
protected override void ProcessRecord()
{
var xml = Encoding.UTF8.GetString(Convert.FromBase64String(Input));
WriteObject(PSSerializer.Deserialize(xml));
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsData.ConvertTo, "SerializedBase64")]
[OutputType(typeof(object))]
public class ConvertToSerializedBase64Cmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public object Input { get; set; }
protected override void ProcessRecord()
{
var output = Convert.ToBase64String(Encoding.UTF8.GetBytes(PSSerializer.Serialize(Input)));
WriteObject(output);
}
}
}

View File

@ -0,0 +1,48 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using System.Management.Automation;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsData.ConvertTo, "StringBytes")]
[OutputType(typeof(byte[]))]
public class ConvertToStringBytesCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public string Input { get; set; }
[Parameter]
public bool Utf16 { get; set; } = false;
[Parameter]
public bool BigEndian { get; set; } = false;
[Parameter]
public int MaxLength { get; set; } = 0;
[Parameter]
public int MinLength { get; set; } = 0;
protected override void ProcessRecord()
{
byte[] output;
if (MaxLength > 0 && Input.Length > MaxLength)
Input = Input.Substring(0, MaxLength);
if (MinLength > 0 && MinLength < MaxLength)
Input = Input.PadRight(MinLength, '\0');
else if (MinLength > 0)
Input = Input.PadRight(MaxLength, '\0');
if (Utf16 && BigEndian)
output = System.Text.Encoding.BigEndianUnicode.GetBytes(Input);
else if (Utf16)
output = System.Text.Encoding.Unicode.GetBytes(Input);
else
output = System.Text.Encoding.ASCII.GetBytes(Input);
WriteObject(output);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Management.Automation;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsData.Edit, "PatchBinary")]
[OutputType(typeof(string))]
public class EditPatchBinaryCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public long Offset { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public byte[] Data { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string FilePath { get; set; }
protected override void ProcessRecord()
{
using (var writer = File.OpenWrite(FilePath))
{
writer.Seek(Offset, SeekOrigin.Begin);
writer.Write(Data, 0, Data.Length);
}
}
}
}

View File

@ -0,0 +1,19 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using System.Management.Automation;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "GameManifest")]
[OutputType(typeof(GameManifest))]
public class GetGameManifestCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public string Path { get; set; }
protected override void ProcessRecord()
{
WriteObject(ManifestHelper.Read(Path));
}
}
}

View File

@ -0,0 +1,18 @@
using System.Linq;
using System.Management.Automation;
using System.Windows.Forms;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "PrimaryDisplay")]
[OutputType(typeof(string))]
public class GetPrimaryDisplayCmdlet : Cmdlet
{
protected override void ProcessRecord()
{
var screens = Screen.AllScreens;
WriteObject(screens.First(s => s.Primary));
}
}
}

View File

@ -0,0 +1,132 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.PowerShell;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Windows.Forms;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsLifecycle.Install, "Game")]
[OutputType(typeof(string))]
public class InstallGameCmdlet : Cmdlet
{
[Parameter(Mandatory = true)]
public Client Client { get; set; }
[Parameter(Mandatory = true)]
public Guid Id { get; set; }
[Parameter(Mandatory = false)]
public string InstallDirectory { get; set; } = "C:\\Games";
protected override void ProcessRecord()
{
var gameManager = new GameManager(Client, InstallDirectory);
var game = Client.GetGame(Id);
var progress = new ProgressRecord(1, $"Installing {game.Title}", "Progress:");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
gameManager.OnArchiveExtractionProgress += (long position, long length) =>
{
// Only update a max of every 500ms
if (stopwatch.ElapsedMilliseconds > 500)
{
progress.PercentComplete = (int)Math.Ceiling((position / (decimal)length) * 100);
WriteProgress(progress);
stopwatch.Restart();
}
};
var installDirectory = gameManager.Install(Id);
stopwatch.Stop();
RunInstallScript(installDirectory);
RunNameChangeScript(installDirectory);
RunKeyChangeScript(installDirectory);
WriteObject(installDirectory);
}
private int RunInstallScript(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install);
if (File.Exists(path))
{
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", InstallDirectory);
script.AddVariable("ServerAddress", Client.BaseUrl);
script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install));
return script.Execute();
}
return 0;
}
private int RunNameChangeScript(string installDirectory)
{
var user = Client.GetProfile();
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange);
if (File.Exists(path))
{
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", InstallDirectory);
script.AddVariable("ServerAddress", Client.BaseUrl);
script.AddVariable("OldPlayerAlias", "");
script.AddVariable("NewPlayerAlias", user.UserName);
script.UseFile(path);
return script.Execute();
}
return 0;
}
private int RunKeyChangeScript(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange);
if (File.Exists(path))
{
var script = new PowerShellScript();
var key = Client.GetAllocatedKey(manifest.Id);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", InstallDirectory);
script.AddVariable("ServerAddress", Client.BaseUrl);
script.AddVariable("AllocatedKey", key);
script.UseFile(path);
return script.Execute();
}
return 0;
}
}
}

View File

@ -0,0 +1,42 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.PowerShell;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Windows.Forms;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsLifecycle.Uninstall, "Game")]
[OutputType(typeof(string))]
public class UninstallGameCmdlet : Cmdlet
{
[Parameter(Mandatory = true)]
public string InstallDirectory { get; set; }
protected override void ProcessRecord()
{
var scriptPath = ScriptHelper.GetScriptFilePath(InstallDirectory, SDK.Enums.ScriptType.Uninstall);
if (!String.IsNullOrEmpty(scriptPath) && File.Exists(scriptPath))
{
var manifest = ManifestHelper.Read(InstallDirectory);
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", InstallDirectory);
script.AddVariable("GameManifest", manifest);
script.UseFile(scriptPath);
script.Execute();
}
var gameManager = new GameManager(null, InstallDirectory);
gameManager.Uninstall(InstallDirectory);
}
}
}

View File

@ -0,0 +1,24 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using System.Management.Automation;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommunications.Write, "GameManifest")]
[OutputType(typeof(string))]
public class WriteGameManifestCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public string Path { get; set; }
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public GameManifest Manifest { get; set; }
protected override void ProcessRecord()
{
var destination = ManifestHelper.Write(Manifest, Path);
WriteObject(destination);
}
}
}

View File

@ -0,0 +1,36 @@
using System.IO;
using System.Management.Automation;
using System.Text.RegularExpressions;
namespace LANCommander.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommunications.Write, "ReplaceContentInFile")]
[OutputType(typeof(string))]
public class ReplaceContentInFileCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public string Pattern { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public string Substitution { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string FilePath { get; set; }
protected override void ProcessRecord()
{
if (File.Exists(FilePath))
{
var contents = File.ReadAllText(FilePath);
var regex = new Regex(Pattern, RegexOptions.Multiline);
contents = regex.Replace(contents, Substitution);
File.WriteAllText(FilePath, contents);
WriteObject(contents);
}
}
}
}

View File

@ -0,0 +1,75 @@
<?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>{807943BF-0C7D-4ED3-8393-CFEE64E3138C}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LANCommander.PowerShell</RootNamespace>
<AssemblyName>LANCommander.PowerShell</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</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="System" />
<Reference Include="System.Core" />
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\PowerShellStandard.Library.5.1.1\lib\net452\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<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" />
</ItemGroup>
<ItemGroup>
<Compile Include="Cmdlets\ConvertTo-SerializedBase64.cs" />
<Compile Include="Cmdlets\ConvertFrom-SerializedBase64.cs" />
<Compile Include="Cmdlets\Edit-PatchBinary.cs" />
<Compile Include="Cmdlets\Uninstall-Game.cs" />
<Compile Include="Cmdlets\Install-Game.cs" />
<Compile Include="Cmdlets\Write-ReplaceContentInFile.cs" />
<Compile Include="Cmdlets\ConvertTo-StringBytes.cs" />
<Compile Include="Cmdlets\Get-PrimaryDisplay.cs" />
<Compile Include="Cmdlets\Convert-AspectRatio.cs" />
<Compile Include="Cmdlets\Get-GameManifest.cs" />
<Compile Include="Cmdlets\Write-GameManifest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="LANCommander.PowerShell.psd1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj">
<Project>{4c2a71fd-a30b-4d62-888a-4ef843d8e506}</Project>
<Name>LANCommander.SDK</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Binary file not shown.

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("LANCommander.PowerShell")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LANCommander.PowerShell")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("807943bf-0c7d-4ed3-8393-cfee64e3138c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net472" />
</packages>

View File

@ -1,6 +1,6 @@
using LANCommander.SDK;
using LANCommander.SDK.Models;
using Playnite.SDK;
using Microsoft.Extensions.Logging;
using RestSharp;
using System;
using System.Collections.Generic;
@ -11,19 +11,33 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
namespace LANCommander.SDK
{
internal class LANCommanderClient
public class Client
{
public static readonly ILogger Logger = LogManager.GetLogger();
private readonly ILogger Logger;
public readonly RestClient Client;
public AuthToken Token;
private RestClient ApiClient;
private AuthToken Token;
public LANCommanderClient(string baseUrl)
public string BaseUrl;
public Client(string baseUrl)
{
if (!String.IsNullOrWhiteSpace(baseUrl))
Client = new RestClient(baseUrl);
BaseUrl = baseUrl;
if (!String.IsNullOrWhiteSpace(BaseUrl))
ApiClient = new RestClient(BaseUrl);
}
public Client(string baseUrl, ILogger logger)
{
BaseUrl = baseUrl;
if (!String.IsNullOrWhiteSpace(BaseUrl))
ApiClient = new RestClient(BaseUrl);
Logger = logger;
}
private T PostRequest<T>(string route, object body)
@ -32,7 +46,17 @@ namespace LANCommander.PlaynitePlugin
.AddJsonBody(body)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
var response = Client.Post<T>(request);
var response = ApiClient.Post<T>(request);
return response.Data;
}
private T PostRequest<T>(string route)
{
var request = new RestRequest(route)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
var response = ApiClient.Post<T>(request);
return response.Data;
}
@ -42,7 +66,7 @@ namespace LANCommander.PlaynitePlugin
var request = new RestRequest(route)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
var response = Client.Get<T>(request);
var response = ApiClient.Get<T>(request);
return response.Data;
}
@ -58,7 +82,7 @@ namespace LANCommander.PlaynitePlugin
client.DownloadProgressChanged += (s, e) => progressHandler(e);
client.DownloadFileCompleted += (s, e) => completeHandler(e);
client.DownloadFileAsync(new Uri($"{Client.BaseUrl}{route}"), tempFile);
client.DownloadFileAsync(new Uri($"{ApiClient.BaseUrl}{route}"), tempFile);
return tempFile;
}
@ -72,14 +96,14 @@ namespace LANCommander.PlaynitePlugin
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
var ws = client.OpenRead(new Uri($"{Client.BaseUrl}{route}"));
var ws = client.OpenRead(new Uri($"{ApiClient.BaseUrl}{route}"));
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
}
public async Task<AuthResponse> AuthenticateAsync(string username, string password)
public async Task<AuthToken> AuthenticateAsync(string username, string password)
{
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
{
UserName = username,
Password = password
@ -88,7 +112,14 @@ namespace LANCommander.PlaynitePlugin
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return response.Data;
Token = new AuthToken
{
AccessToken = response.Data.AccessToken,
RefreshToken = response.Data.RefreshToken,
Expiration = response.Data.Expiration
};
return Token;
case HttpStatusCode.Forbidden:
case HttpStatusCode.BadRequest:
@ -100,9 +131,9 @@ namespace LANCommander.PlaynitePlugin
}
}
public async Task<AuthResponse> RegisterAsync(string username, string password)
public async Task<AuthToken> RegisterAsync(string username, string password)
{
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
{
UserName = username,
Password = password
@ -111,7 +142,14 @@ namespace LANCommander.PlaynitePlugin
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return response.Data;
Token = new AuthToken
{
AccessToken = response.Data.AccessToken,
RefreshToken = response.Data.RefreshToken,
Expiration = response.Data.Expiration
};
return Token;
case HttpStatusCode.BadRequest:
case HttpStatusCode.Forbidden:
@ -125,33 +163,45 @@ namespace LANCommander.PlaynitePlugin
public async Task<bool> PingAsync()
{
var response = await Client.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
return response.StatusCode == HttpStatusCode.OK;
}
public AuthResponse RefreshToken(AuthToken token)
public AuthToken RefreshToken(AuthToken token)
{
Logger.Trace("Refreshing token...");
Logger?.LogTrace("Refreshing token...");
var request = new RestRequest("/api/Auth/Refresh")
.AddJsonBody(token);
var response = Client.Post<AuthResponse>(request);
var response = ApiClient.Post<AuthResponse>(request);
if (response.StatusCode != HttpStatusCode.OK)
throw new WebException(response.ErrorMessage);
return response.Data;
Token = new AuthToken
{
AccessToken = response.Data.AccessToken,
RefreshToken = response.Data.RefreshToken,
Expiration = response.Data.Expiration
};
return Token;
}
public bool ValidateToken()
{
return ValidateToken(Token);
}
public bool ValidateToken(AuthToken token)
{
Logger.Trace("Validating token...");
Logger?.LogTrace("Validating token...");
if (token == null)
{
Logger.Trace("Token is null!");
Logger?.LogTrace("Token is null!");
return false;
}
@ -160,22 +210,33 @@ namespace LANCommander.PlaynitePlugin
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
{
Logger.Trace("Token is empty!");
Logger?.LogTrace("Token is empty!");
return false;
}
var response = Client.Post(request);
var response = ApiClient.Post(request);
var valid = response.StatusCode == HttpStatusCode.OK;
if (valid)
Logger.Trace("Token is valid!");
Logger?.LogTrace("Token is valid!");
else
Logger.Trace("Token is invalid!");
Logger?.LogTrace("Token is invalid!");
return response.StatusCode == HttpStatusCode.OK;
}
public void UseToken(AuthToken token)
{
Token = token;
}
public void UseServerAddress(string address)
{
BaseUrl = address;
ApiClient = new RestClient(BaseUrl);
}
public IEnumerable<Game> GetGames()
{
return GetRequest<IEnumerable<Game>>("/api/Games");
@ -223,26 +284,26 @@ namespace LANCommander.PlaynitePlugin
public GameSave UploadSave(string gameId, byte[] data)
{
Logger.Trace("Uploading save...");
Logger?.LogTrace("Uploading save...");
var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
request.AddFile(gameId, data, gameId);
var response = Client.Post<GameSave>(request);
var response = ApiClient.Post<GameSave>(request);
return response.Data;
}
public string GetMediaUrl(Media media)
{
return (new Uri(Client.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
return (new Uri(ApiClient.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
}
public string GetKey(Guid id)
{
Logger.Trace("Requesting key allocation...");
Logger?.LogTrace("Requesting key allocation...");
var macAddress = GetMacAddress();
@ -261,7 +322,7 @@ namespace LANCommander.PlaynitePlugin
public string GetAllocatedKey(Guid id)
{
Logger.Trace("Requesting allocated key...");
Logger?.LogTrace("Requesting allocated key...");
var macAddress = GetMacAddress();
@ -283,7 +344,7 @@ namespace LANCommander.PlaynitePlugin
public string GetNewKey(Guid id)
{
Logger.Trace("Requesting new key allocation...");
Logger?.LogTrace("Requesting new key allocation...");
var macAddress = GetMacAddress();
@ -305,20 +366,34 @@ namespace LANCommander.PlaynitePlugin
public User GetProfile()
{
Logger.Trace("Requesting player's profile...");
Logger?.LogTrace("Requesting player's profile...");
return GetRequest<User>("/api/Profile");
}
public string ChangeAlias(string alias)
{
Logger.Trace("Requesting to change player alias...");
Logger?.LogTrace("Requesting to change player alias...");
var response = PostRequest<object>("/api/Profile/ChangeAlias", alias);
return alias;
}
public void StartPlaySession(Guid gameId)
{
Logger?.LogTrace("Starting a game session...");
PostRequest<object>($"/api/PlaySessions/Start/{gameId}");
}
public void EndPlaySession(Guid gameId)
{
Logger?.LogTrace("Ending a game session...");
PostRequest<object>($"/api/PlaySessions/End/{gameId}");
}
private string GetMacAddress()
{
return NetworkInterface.GetAllNetworkInterfaces()

View File

@ -0,0 +1,20 @@
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.Text;
namespace LANCommander.SDK
{
public class ArchiveExtractionProgressArgs : EventArgs
{
public long Position { get; set; }
public long Length { get; set; }
}
public class ArchiveEntryExtractionProgressArgs : EventArgs
{
public ReaderProgress Progress { get; set; }
public IEntry Entry { get; set; }
}
}

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
namespace LANCommander.SDK
{
internal class ExtractionResult
{

View File

@ -0,0 +1,227 @@
using LANCommander.SDK.Enums;
using LANCommander.SDK.Extensions;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Models;
using Microsoft.Extensions.Logging;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace LANCommander.SDK
{
public class GameManager
{
private readonly ILogger Logger;
private Client Client { get; set; }
private string DefaultInstallDirectory { get; set; }
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
private TrackableStream Stream;
private IReader Reader;
public GameManager(Client client, string defaultInstallDirectory)
{
Client = client;
DefaultInstallDirectory = defaultInstallDirectory;
}
public GameManager(Client client, string defaultInstallDirectory, ILogger logger)
{
Client = client;
DefaultInstallDirectory = defaultInstallDirectory;
Logger = logger;
}
/// <summary>
/// Downloads, extracts, and runs post-install scripts for the specified game
/// </summary>
/// <param name="game">Game to install</param>
/// <param name="maxAttempts">Maximum attempts in case of transmission error</param>
/// <returns>Final install path</returns>
/// <exception cref="Exception"></exception>
public string Install(Guid gameId, int maxAttempts = 10)
{
GameManifest manifest = null;
var game = Client.GetGame(gameId);
var destination = Path.Combine(DefaultInstallDirectory, game.Title.SanitizeFilename());
try
{
if (ManifestHelper.Exists(destination))
manifest = ManifestHelper.Read(destination);
}
catch (Exception ex)
{
Logger?.LogTrace(ex, "Error reading manifest before install");
}
if (manifest == null || manifest.Id != gameId)
{
Logger?.LogTrace("Installing game {GameTitle} ({GameId})", game.Title, game.Id);
var result = RetryHelper.RetryOnException<ExtractionResult>(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
{
Logger?.LogTrace("Attempting to download and extract game");
return DownloadAndExtract(game, destination);
});
if (!result.Success && !result.Canceled)
throw new Exception("Could not extract the installer. Retry the install or check your connection");
else if (result.Canceled)
return "";
game.InstallDirectory = result.Directory;
}
else
{
Logger?.LogTrace("Game {GameTitle} ({GameId}) is already installed to {InstallDirectory}", game.Title, game.Id, destination);
game.InstallDirectory = destination;
}
var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () =>
{
Logger?.LogTrace("Attempting to get game manifest");
manifest = Client.GetGameManifest(game.Id);
ManifestHelper.Write(manifest, game.InstallDirectory);
return true;
});
if (!writeManifestSuccess)
throw new Exception("Could not grab the manifest file. Retry the install or check your connection");
Logger?.LogTrace("Saving scripts");
ScriptHelper.SaveScript(game, ScriptType.Install);
ScriptHelper.SaveScript(game, ScriptType.Uninstall);
ScriptHelper.SaveScript(game, ScriptType.NameChange);
ScriptHelper.SaveScript(game, ScriptType.KeyChange);
return game.InstallDirectory;
}
public void Uninstall(string installDirectory)
{
Logger?.LogTrace("Attempting to delete the install directory");
if (Directory.Exists(installDirectory))
Directory.Delete(installDirectory, true);
Logger?.LogTrace("Deleted install directory {InstallDirectory}", installDirectory);
}
private ExtractionResult DownloadAndExtract(Game game, string destination)
{
if (game == null)
{
Logger?.LogTrace("Game failed to download, no game was specified");
throw new ArgumentNullException("No game was specified");
}
Logger?.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination);
var extractionResult = new ExtractionResult
{
Canceled = false,
};
try
{
Directory.CreateDirectory(destination);
Stream = Client.StreamGame(game.Id);
Reader = ReaderFactory.Open(Stream);
Stream.OnProgress += (pos, len) =>
{
OnArchiveExtractionProgress?.Invoke(pos, len);
};
Reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
{
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
{
Entry = e.Item,
Progress = e.ReaderProgress,
});
};
while (Reader.MoveToNextEntry())
{
if (Reader.Cancelled)
break;
Reader.WriteEntryToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true,
PreserveFileTime = true,
});
}
Reader.Dispose();
Stream.Dispose();
}
catch (ReaderCancelledException ex)
{
Logger?.LogTrace("User cancelled the download");
extractionResult.Canceled = true;
if (Directory.Exists(destination))
{
Logger?.LogTrace("Cleaning up orphaned files after cancelled install");
Directory.Delete(destination, true);
}
}
catch (Exception ex)
{
Logger?.LogError(ex, "Could not extract to path {Destination}", destination);
if (Directory.Exists(destination))
{
Logger?.LogTrace("Cleaning up orphaned install files after bad install");
Directory.Delete(destination, true);
}
throw new Exception("The game archive could not be extracted, is it corrupted? Please try again");
}
if (!extractionResult.Canceled)
{
extractionResult.Success = true;
extractionResult.Directory = destination;
Logger?.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination);
}
return extractionResult;
}
public void CancelInstall()
{
Reader?.Cancel();
// Reader?.Dispose();
// Stream?.Dispose();
}
}
}

View File

@ -0,0 +1,285 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Models;
using LANCommander.SDK.PowerShell;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.SDK
{
public class GameSaveManager
{
private readonly Client Client;
public delegate void OnDownloadProgressHandler(DownloadProgressChangedEventArgs e);
public event OnDownloadProgressHandler OnDownloadProgress;
public delegate void OnDownloadCompleteHandler(AsyncCompletedEventArgs e);
public event OnDownloadCompleteHandler OnDownloadComplete;
public GameSaveManager(Client client)
{
Client = client;
}
public void Download(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
string tempFile = String.Empty;
if (manifest != null)
{
var destination = Client.DownloadLatestSave(manifest.Id, (changed) =>
{
OnDownloadProgress?.Invoke(changed);
}, (complete) =>
{
OnDownloadComplete?.Invoke(complete);
});
tempFile = destination;
// Go into the archive and extract the files to the correct locations
try
{
var tempLocation = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempLocation);
ExtractFilesFromZip(tempFile, tempLocation);
#region Move files
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
bool inInstallDir = savePath.Path.StartsWith("{InstallDir}");
string tempSavePath = Path.Combine(tempLocation, savePath.Id.ToString());
foreach (var entry in savePath.Entries)
{
var tempSavePathFile = Path.Combine(tempSavePath, entry.ArchivePath);
destination = Environment.ExpandEnvironmentVariables(entry.ActualPath).Replace("{InstallDir}", installDirectory);
if (File.Exists(tempSavePathFile))
{
if (File.Exists(destination))
File.Delete(destination);
File.Move(tempSavePathFile, destination);
}
else if (Directory.Exists(tempSavePath))
{
var files = Directory.GetFiles(tempSavePath, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
if (inInstallDir)
{
// Files are in the game's install directory. Move them there from the save path.
destination = file.Replace(tempSavePath, savePath.Path.Replace('/', Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar).Replace("{InstallDir}", installDirectory));
if (File.Exists(destination))
File.Delete(destination);
File.Move(file, destination);
}
else
{
// Specified path is probably an absolute path, maybe with environment variables.
destination = Environment.ExpandEnvironmentVariables(file.Replace(tempSavePathFile, savePath.Path.Replace('/', Path.DirectorySeparatorChar)));
if (File.Exists(destination))
File.Delete(destination);
File.Move(file, destination);
}
}
}
}
}
#endregion
#region Handle registry importing
var registryImportFilePath = Path.Combine(tempLocation, "_registry.reg");
if (File.Exists(registryImportFilePath))
{
var registryImportFileContents = File.ReadAllText(registryImportFilePath);
var script = new PowerShellScript();
script.UseInline($"regedit.exe /s \"{registryImportFilePath}\"");
if (registryImportFileContents.Contains("HKEY_LOCAL_MACHINE"))
script.RunAsAdmin();
script.Execute();
}
#endregion
// Clean up temp files
Directory.Delete(tempLocation, true);
}
catch (Exception ex)
{
}
}
}
public void Upload(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
var temp = Path.GetTempFileName();
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
{
using (var archive = ZipArchive.Create())
{
archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
IEnumerable<string> localPaths;
if (savePath.IsRegex)
{
var regex = new Regex(Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory)));
localPaths = Directory.GetFiles(installDirectory, "*", SearchOption.AllDirectories)
.Where(p => regex.IsMatch(p))
.ToList();
}
else
localPaths = new string[] { savePath.Path };
var entries = new List<SavePathEntry>();
foreach (var localPath in localPaths)
{
var actualPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', Path.DirectorySeparatorChar).Replace("{InstallDir}", installDirectory));
var relativePath = actualPath.Replace(installDirectory + Path.DirectorySeparatorChar, "");
if (Directory.Exists(actualPath))
{
AddDirectoryToZip(archive, relativePath, actualPath, savePath.Id);
}
else if (File.Exists(actualPath))
{
archive.AddEntry(Path.Combine(savePath.Id.ToString(), relativePath), actualPath);
}
entries.Add(new SavePathEntry
{
ArchivePath = relativePath,
ActualPath = actualPath.Replace(installDirectory, "{InstallDir}")
});
savePath.Entries = entries;
}
}
#endregion
#region Export registry keys
if (manifest.SavePaths.Any(sp => sp.Type == "Registry"))
{
List<string> tempRegFiles = new List<string>();
var exportCommand = new StringBuilder();
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry"))
{
var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg");
exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\"");
tempRegFiles.Add(tempRegFile);
}
var script = new PowerShellScript();
script.UseInline(exportCommand.ToString());
script.Execute();
var exportFile = new StringBuilder();
foreach (var tempRegFile in tempRegFiles)
{
exportFile.AppendLine(File.ReadAllText(tempRegFile));
File.Delete(tempRegFile);
}
archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
}
#endregion
var tempManifest = Path.GetTempFileName();
File.WriteAllText(tempManifest, ManifestHelper.Serialize(manifest));
archive.AddEntry("_manifest.yml", tempManifest);
using (var ms = new MemoryStream())
{
archive.SaveTo(ms);
ms.Seek(0, SeekOrigin.Begin);
var save = Client.UploadSave(manifest.Id.ToString(), ms.ToArray());
}
}
}
}
private void AddDirectoryToZip(ZipArchive zipArchive, string path, string workingDirectory, Guid pathId)
{
foreach (var file in Directory.GetFiles(path))
{
// Oh man is this a hack. We should be removing only the working directory from the start,
// but we're making the assumption that the working dir put in actually prefixes the path.
// Also wtf, that Path.Combine is stripping the pathId out?
zipArchive.AddEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(file)), file);
}
foreach (var child in Directory.GetDirectories(path))
{
// See above
//ZipEntry entry = new ZipEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(path)));
//zipStream.PutNextEntry(entry);
//zipStream.CloseEntry();
AddDirectoryToZip(zipArchive, child, workingDirectory, pathId);
}
}
private void ExtractFilesFromZip(string zipPath, string destination)
{
using (var fs = File.OpenRead(zipPath))
using (var ts = new TrackableStream(fs))
using (var reader = ReaderFactory.Open(ts))
{
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
}

View File

@ -0,0 +1,73 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization;
namespace LANCommander.SDK.Helpers
{
public static class ManifestHelper
{
public static readonly ILogger Logger;
public const string ManifestFilename = "_manifest.yml";
public static bool Exists(string installDirectory)
{
var path = GetPath(installDirectory);
return File.Exists(path);
}
public static GameManifest Read(string installDirectory)
{
var source = GetPath(installDirectory);
var yaml = File.ReadAllText(source);
var deserializer = new DeserializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
Logger?.LogTrace("Deserializing manifest");
var manifest = deserializer.Deserialize<GameManifest>(yaml);
return manifest;
}
public static string Write(GameManifest manifest, string installDirectory)
{
var destination = GetPath(installDirectory);
Logger?.LogTrace("Attempting to write manifest to path {Destination}", destination);
var yaml = Serialize(manifest);
Logger?.LogTrace("Writing manifest file");
File.WriteAllText(destination, yaml);
return destination;
}
public static string Serialize(GameManifest manifest)
{
var serializer = new SerializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
Logger?.LogTrace("Serializing manifest");
var yaml = serializer.Serialize(manifest);
return yaml;
}
public static string GetPath(string installDirectory)
{
return Path.Combine(installDirectory, ManifestFilename);
}
}
}

View File

@ -1,15 +1,12 @@
using Playnite.SDK;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin.Helpers
namespace LANCommander.SDK.Helpers
{
internal static class RetryHelper
{
internal static readonly ILogger Logger = LogManager.GetLogger();
internal static readonly ILogger Logger;
internal static T RetryOnException<T>(int maxAttempts, TimeSpan delay, T @default, Func<T> action)
{
@ -19,14 +16,14 @@ namespace LANCommander.PlaynitePlugin.Helpers
{
try
{
Logger.Trace($"Attempt #{attempts + 1}/{maxAttempts}...");
Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
attempts++;
return action();
}
catch (Exception ex)
{
Logger.Error(ex, $"Attempt failed!");
Logger?.LogError(ex, $"Attempt failed!");
if (attempts >= maxAttempts)
return @default;

View File

@ -0,0 +1,78 @@
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace LANCommander.SDK.Helpers
{
public static class ScriptHelper
{
public static readonly ILogger Logger;
public static string SaveTempScript(Script script)
{
var tempPath = SaveTempScript(script.Contents);
Logger?.LogTrace("Wrote script {Script} to {Destination}", script.Name, tempPath);
return tempPath;
}
public static string SaveTempScript(string contents)
{
var tempPath = Path.GetTempFileName();
// PowerShell will only run scripts with the .ps1 file extension
File.Move(tempPath, tempPath + ".ps1");
tempPath = tempPath + ".ps1";
File.WriteAllText(tempPath, contents);
return tempPath;
}
public static void SaveScript(Game game, ScriptType type)
{
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
if (script == null)
return;
if (script.RequiresAdmin)
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
var filename = GetScriptFilePath(game, type);
if (File.Exists(filename))
File.Delete(filename);
Logger?.LogTrace("Writing {ScriptType} script to {Destination}", type, filename);
File.WriteAllText(filename, script.Contents);
}
public static string GetScriptFilePath(Game game, ScriptType type)
{
return GetScriptFilePath(game.InstallDirectory, type);
}
public static string GetScriptFilePath(string installDirectory, ScriptType type)
{
Dictionary<ScriptType, string> filenames = new Dictionary<ScriptType, string>() {
{ ScriptType.Install, "_install.ps1" },
{ ScriptType.Uninstall, "_uninstall.ps1" },
{ ScriptType.NameChange, "_changename.ps1" },
{ ScriptType.KeyChange, "_changekey.ps1" }
};
var filename = filenames[type];
return Path.Combine(installDirectory, filename);
}
}
}

View File

@ -1,7 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" />
<PackageReference Include="RestSharp" Version="106.15.0" />
<PackageReference Include="SharpCompress" Version="0.34.2" />
<PackageReference Include="YamlDotNet" Version="5.4.0" />
</ItemGroup>
</Project>

View File

@ -8,5 +8,6 @@ namespace LANCommander.SDK.Models
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTime Expiration { get; set; }
}
}

View File

@ -10,6 +10,7 @@ namespace LANCommander.SDK.Models
public string DirectoryName { get; set; }
public string Description { get; set; }
public DateTime ReleasedOn { get; set; }
public string InstallDirectory { get; set; }
public virtual IEnumerable<Action> Actions { get; set; }
public virtual IEnumerable<Tag> Tags { get; set; }
public virtual Company Publisher { get; set; }

View File

@ -6,6 +6,7 @@ namespace LANCommander.SDK
{
public class GameManifest
{
public Guid Id { get; set; }
public string Title { get; set; }
public string SortTitle { get; set; }
public string Description { get; set; }
@ -46,5 +47,13 @@ namespace LANCommander.SDK
public Guid Id { get; set; }
public string Type { get; set; }
public string Path { get; set; }
public bool IsRegex { get; set; }
public IEnumerable<SavePathEntry> Entries { get; set; }
}
public class SavePathEntry
{
public string ArchivePath { get; set; }
public string ActualPath { get; set; }
}
}

View File

@ -9,6 +9,7 @@ namespace LANCommander.SDK.Models
{
public SavePathType Type { get; set; }
public string Path { get; set; }
public bool IsRegex { get; set; }
public virtual Game Game { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LANCommander.SDK.PowerShell
{
public class PowerShellArgument
{
public string Name { get; set; }
public object Value { get; set; }
public Type Type { get; set; }
public PowerShellArgument(string name, object value, Type type)
{
Name = name;
Value = value;
Type = type;
}
}
}

View File

@ -0,0 +1,193 @@
using LANCommander.SDK.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace LANCommander.SDK.PowerShell
{
public class PowerShellScript
{
private string Contents { get; set; } = "";
private string WorkingDirectory { get; set; } = "";
private bool AsAdmin { get; set; } = false;
private bool ShellExecute { get; set; } = false;
private bool IgnoreWow64 { get; set; } = false;
private ICollection<PowerShellVariable> Variables { get; set; }
private Dictionary<string, string> Arguments { get; set; }
private List<string> Modules { get; set; }
private Process Process { get; set; }
public PowerShellScript()
{
Variables = new List<PowerShellVariable>();
Arguments = new Dictionary<string, string>();
Modules = new List<string>();
Process = new Process();
Process.StartInfo.FileName = "powershell.exe";
Process.StartInfo.RedirectStandardOutput = false;
AddArgument("ExecutionPolicy", "Unrestricted");
var moduleManifests = Directory.EnumerateFiles(Environment.CurrentDirectory, "LANCommander.PowerShell.psd1", SearchOption.AllDirectories);
if (moduleManifests.Any())
AddModule(moduleManifests.First());
IgnoreWow64Redirection();
}
public PowerShellScript UseFile(string path)
{
Contents = File.ReadAllText(path);
return this;
}
public PowerShellScript UseInline(string contents)
{
Contents = contents;
return this;
}
public PowerShellScript UseWorkingDirectory(string path)
{
WorkingDirectory = path;
return this;
}
public PowerShellScript UseShellExecute()
{
ShellExecute = true;
return this;
}
public PowerShellScript AddVariable<T>(string name, T value)
{
Variables.Add(new PowerShellVariable(name, value, typeof(T)));
return this;
}
public PowerShellScript AddArgument<T>(string name, T value)
{
Arguments.Add(name, $"\"{value}\"");
return this;
}
public PowerShellScript AddArgument(string name, int value)
{
Arguments[name] = value.ToString();
return this;
}
public PowerShellScript AddArgument(string name, long value)
{
Arguments[name] = value.ToString();
return this;
}
public PowerShellScript AddModule(string path)
{
Modules.Add(path);
return this;
}
public PowerShellScript RunAsAdmin()
{
AsAdmin = true;
Process.StartInfo.Verb = "runas";
Process.StartInfo.UseShellExecute = true;
return this;
}
public PowerShellScript IgnoreWow64Redirection()
{
IgnoreWow64 = true;
return this;
}
public int Execute()
{
var scriptBuilder = new StringBuilder();
var wow64Value = IntPtr.Zero;
if (Contents.StartsWith("# Requires Admin"))
RunAsAdmin();
foreach (var module in Modules)
{
scriptBuilder.AppendLine($"Import-Module \"{module}\"");
}
foreach (var variable in Variables)
{
scriptBuilder.AppendLine($"${variable.Name} = ConvertFrom-SerializedBase64 \"{Serialize(variable.Value)}\"");
}
scriptBuilder.AppendLine(Contents);
var path = ScriptHelper.SaveTempScript(scriptBuilder.ToString());
AddArgument("File", path);
if (IgnoreWow64)
Wow64DisableWow64FsRedirection(ref wow64Value);
foreach (var argument in Arguments)
{
Process.StartInfo.Arguments += $" -{argument.Key} {argument.Value}";
}
if (!String.IsNullOrEmpty(WorkingDirectory))
Process.StartInfo.WorkingDirectory = WorkingDirectory;
if (ShellExecute)
Process.StartInfo.UseShellExecute = true;
if (AsAdmin)
{
Process.StartInfo.Verb = "runas";
Process.StartInfo.UseShellExecute = true;
}
Process.Start();
Process.WaitForExit();
if (IgnoreWow64)
Wow64RevertWow64FsRedirection(ref wow64Value);
if (File.Exists(path))
File.Delete(path);
return Process.ExitCode;
}
public static string Serialize<T>(T input)
{
// Use the PowerShell serializer to generate XML for our input. Then convert to base64 so we can put it on one line.
return Convert.ToBase64String(Encoding.UTF8.GetBytes(System.Management.Automation.PSSerializer.Serialize(input)));
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr);
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LANCommander.SDK.PowerShell
{
public class PowerShellVariable
{
public string Name { get; set; }
public object Value { get; set; }
public Type Type { get; set; }
public PowerShellVariable(string name, object value, Type type)
{
Name = name;
Value = value;
Type = type;
}
}
}

View File

@ -0,0 +1,184 @@
using LANCommander.SDK.Enums;
using LANCommander.SDK.Extensions;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Models;
using LANCommander.SDK.PowerShell;
using Microsoft.Extensions.Logging;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace LANCommander.SDK
{
public class RedistributableManager
{
private readonly ILogger Logger;
private Client Client { get; set; }
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
public RedistributableManager(Client client)
{
Client = client;
}
public RedistributableManager(Client client, ILogger logger)
{
Client = client;
Logger = logger;
}
public void Install(Game game)
{
foreach (var redistributable in game.Redistributables)
{
Install(redistributable);
}
}
public void Install(Redistributable redistributable)
{
string installScriptTempFile = null;
string detectionScriptTempFile = null;
string extractTempPath = null;
try
{
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
installScriptTempFile = ScriptHelper.SaveTempScript(installScript);
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
detectionScriptTempFile = ScriptHelper.SaveTempScript(detectionScript);
var detectionResult = RunScript(detectionScriptTempFile, redistributable, detectionScript.RequiresAdmin);
// Redistributable is not installed
if (detectionResult == 0)
{
if (redistributable.Archives.Count() > 0)
{
var extractionResult = DownloadAndExtract(redistributable);
if (extractionResult.Success)
{
extractTempPath = extractionResult.Directory;
RunScript(installScriptTempFile, redistributable, installScript.RequiresAdmin, extractTempPath);
}
}
else
{
RunScript(installScriptTempFile, redistributable, installScript.RequiresAdmin, extractTempPath);
}
}
}
catch (Exception ex)
{
Logger?.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name);
}
finally
{
if (File.Exists(installScriptTempFile))
File.Delete(installScriptTempFile);
if (File.Exists(detectionScriptTempFile))
File.Delete(detectionScriptTempFile);
if (Directory.Exists(extractTempPath))
Directory.Delete(extractTempPath);
}
}
private ExtractionResult DownloadAndExtract(Redistributable redistributable)
{
if (redistributable == null)
{
Logger?.LogTrace("Redistributable failed to download! No redistributable was specified");
throw new ArgumentNullException("No redistributable was specified");
}
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
Logger?.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination);
try
{
Directory.CreateDirectory(destination);
using (var redistributableStream = Client.StreamRedistributable(redistributable.Id))
using (var reader = ReaderFactory.Open(redistributableStream))
{
redistributableStream.OnProgress += (pos, len) =>
{
OnArchiveExtractionProgress?.Invoke(pos, len);
};
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
{
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
{
Entry = e.Item,
Progress = e.ReaderProgress,
});
};
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
catch (Exception ex)
{
Logger?.LogError(ex, "Could not extract to path {Destination}", destination);
if (Directory.Exists(destination))
{
Logger?.LogTrace("Cleaning up orphaned files after bad install");
Directory.Delete(destination, true);
}
throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again");
}
var extractionResult = new ExtractionResult
{
Canceled = false
};
if (!extractionResult.Canceled)
{
extractionResult.Success = true;
extractionResult.Directory = destination;
Logger?.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination);
}
return extractionResult;
}
private int RunScript(string path, Redistributable redistributable, bool requiresAdmin = false, string workingDirectory = "")
{
var script = new PowerShellScript();
script.AddVariable("Redistributable", redistributable);
script.UseWorkingDirectory(workingDirectory);
script.UseFile(path);
if (requiresAdmin)
script.RunAsAdmin();
return script.Execute();
}
}
}

View File

@ -1,9 +1,9 @@
using System;
using System.IO;
namespace LANCommander.PlaynitePlugin
namespace LANCommander.SDK
{
internal class TrackableStream : MemoryStream, IDisposable
public class TrackableStream : MemoryStream, IDisposable
{
public delegate void OnProgressDelegate(long Position, long Length);
public event OnProgressDelegate OnProgress = delegate { };

View File

@ -11,6 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LANCommander.SDK", "LANComm
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LANCommander.PCGamingWiki", "LANCommander.PCGamingWiki\LANCommander.PCGamingWiki.csproj", "{2436B817-4475-4E70-9BB2-E1E7866DB79F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LANCommander.PowerShell", "LANCommander.PowerShell\LANCommander.PowerShell.csproj", "{807943BF-0C7D-4ED3-8393-CFEE64E3138C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LANCommander.PowerShell.Tests", "LANCommander.PowerShell.Tests\LANCommander.PowerShell.Tests.csproj", "{D7069A13-F0AA-4CBF-9013-4276F130A6DD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -33,6 +37,14 @@ Global
{2436B817-4475-4E70-9BB2-E1E7866DB79F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2436B817-4475-4E70-9BB2-E1E7866DB79F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2436B817-4475-4E70-9BB2-E1E7866DB79F}.Release|Any CPU.Build.0 = Release|Any CPU
{807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Release|Any CPU.Build.0 = Release|Any CPU
{D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -15,49 +15,20 @@ namespace LANCommander.Areas.Identity.Pages.Account
{
public class LogoutModel : PageModel
{
private readonly SignInManager<User> _signInManager;
private readonly ILogger<LogoutModel> _logger;
private readonly SignInManager<User> SignInManager;
private readonly ILogger<LogoutModel> Logger;
public LogoutModel(SignInManager<User> signInManager, ILogger<LogoutModel> logger)
{
_signInManager = signInManager;
_logger = logger;
SignInManager = signInManager;
Logger = logger;
}
public async Task<IActionResult> OnGet(string returnUrl = null, bool force = false)
public async Task<IActionResult> OnGet()
{
if (force)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
await SignInManager.SignOutAsync();
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return LocalRedirect("/Identity/Account/Login");
}
}
return Page();
}
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
// This needs to be a redirect so that the browser performs a new
// request and the identity for the user gets updated.
return RedirectToPage();
}
return LocalRedirect("/");
}
}
}

View File

@ -11,7 +11,7 @@
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
<SpaceItem>
<Table TItem="Archive" DataSource="@Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true" Responsive>
<Table TItem="Archive" DataSource="@Archives" HidePagination="true" Responsive>
<PropertyColumn Property="a => a.Version" />
<PropertyColumn Property="a => a.CompressedSize">
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
@ -19,7 +19,7 @@
<PropertyColumn Property="a => a.CreatedBy">
@context.CreatedBy?.UserName
</PropertyColumn>
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" />
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" DefaultSortOrder="@SortDirection.Descending" />
<ActionColumn Title="">
<Space Style="display: flex; justify-content: end">
<SpaceItem>
@ -46,15 +46,14 @@
</SpaceItem>
</Space>
<ArchiveUploader @ref="Uploader" OnArchiveUploaded="AddArchive" />
<ArchiveUploader @ref="Uploader" GameId="GameId" RedistributableId="RedistributableId" OnArchiveUploaded="LoadData" />
@code {
[Parameter] public Guid GameId { get; set; }
[Parameter] public Guid RedistributableId { get; set; }
[Parameter] public ICollection<Archive> Archives { get; set; }
[Parameter] public EventCallback<ICollection<Archive>> ArchivesChanged { get; set; }
Archive Archive;
ICollection<Archive> Archives { get; set; }
ArchiveUploader Uploader;
protected override async Task OnInitializedAsync()
@ -66,7 +65,10 @@
private async Task LoadData()
{
Archives = await ArchiveService.Get(a => a.GameId == GameId).OrderByDescending(a => a.CreatedOn).ToListAsync();
if (GameId != Guid.Empty)
Archives = await ArchiveService.Get(a => a.GameId == GameId).ToListAsync();
else if (RedistributableId != Guid.Empty)
Archives = await ArchiveService.Get(a => a.RedistributableId == RedistributableId).ToListAsync();
}
private async Task Download(Archive archive)
@ -78,27 +80,7 @@
private async Task UploadArchive()
{
if (GameId != Guid.Empty)
Archive = new Archive() { GameId = GameId, Id = Guid.NewGuid() };
if (RedistributableId != Guid.Empty)
Archive = new Archive() { RedistributableId = RedistributableId, Id = Guid.NewGuid() };
await Uploader.Open(Archive);
}
private async Task AddArchive(Archive archive)
{
var lastArchive = Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
Archive = await ArchiveService.Add(archive);
await LoadData();
var settings = SettingService.GetSettings();
if (lastArchive != null && settings.Archives.EnablePatching)
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(lastArchive.Id, Archive.Id));
await Uploader.Open();
}
private async Task Delete(Archive archive)
@ -107,6 +89,8 @@
{
await ArchiveService.Delete(archive);
await LoadData();
await MessageService.Success("Archive deleted!");
}
catch (Exception ex)

View File

@ -2,6 +2,7 @@
@using System.Diagnostics;
@using Hangfire;
@using LANCommander.Jobs.Background;
@using Microsoft.EntityFrameworkCore;
@inject HttpClient HttpClient
@inject NavigationManager Navigator
@inject ArchiveService ArchiveService
@ -62,7 +63,9 @@
</Modal>
@code {
[Parameter] public EventCallback<Archive> OnArchiveUploaded { get; set; }
[Parameter] public Guid GameId { get; set; }
[Parameter] public Guid RedistributableId { get; set; }
[Parameter] public EventCallback<Guid> OnArchiveUploaded { get; set; }
Archive Archive;
@ -113,9 +116,21 @@
File = args.File;
}
public async Task Open(Archive archive)
public async Task Open(Guid? archiveId = null)
{
Archive = archive;
if (archiveId.HasValue && archiveId != Guid.Empty)
{
Archive = await ArchiveService.Get(archiveId.Value);
}
else
{
Archive = new Archive();
if (GameId != Guid.Empty)
Archive.GameId = GameId;
else if (RedistributableId != Guid.Empty)
Archive.RedistributableId = RedistributableId;
}
Visible = true;
@ -128,8 +143,8 @@
{
if (FileInput != null)
{
if (!String.IsNullOrWhiteSpace(archive.ObjectKey) && archive.ObjectKey != Guid.Empty.ToString())
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", archive.ObjectKey.ToString());
if (!String.IsNullOrWhiteSpace(Archive.ObjectKey) && Archive.ObjectKey != Guid.Empty.ToString())
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", Archive.ObjectKey.ToString());
else
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", "");
@ -163,12 +178,32 @@
Archive.ObjectKey = objectKey.ToString();
Archive.CompressedSize = File.Size;
if (Archive.Id != Guid.Empty)
Archive = await ArchiveService.Update(Archive);
else
Archive = await ArchiveService.Add(Archive);
Visible = false;
await InvokeAsync(StateHasChanged);
Archive? lastArchive = null;
var settings = SettingService.GetSettings();
if (settings.Archives.EnablePatching)
{
if (Archive.GameId != Guid.Empty)
lastArchive = await ArchiveService.Get(a => a.Id != Archive.Id && a.GameId == Archive.GameId).OrderByDescending(a => a.CreatedOn).FirstOrDefaultAsync();
else if (Archive.RedistributableId != Guid.Empty)
lastArchive = await ArchiveService.Get(a => a.Id != Archive.Id && a.RedistributableId == Archive.RedistributableId).OrderByDescending(a => a.CreatedOn).FirstOrDefaultAsync();
if (lastArchive != null && settings.Archives.EnablePatching)
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(lastArchive.Id, Archive.Id));
}
if (OnArchiveUploaded.HasDelegate)
await OnArchiveUploaded.InvokeAsync(Archive);
await OnArchiveUploaded.InvokeAsync(Archive.Id);
await MessageService.Success("Archive uploaded!");
}

View File

@ -238,6 +238,8 @@
}
async Task<HashSet<FileManagerDirectory>> GetArchiveDirectoriesAsync(Guid archiveId)
{
try
{
var entries = await ArchiveService.GetContents(archiveId);
var directories = new HashSet<FileManagerDirectory>();
@ -258,6 +260,17 @@
root
};
}
catch (FileNotFoundException ex)
{
MessageService.Error("Could not open archive! Is it missing?");
}
catch (Exception ex)
{
MessageService.Error("An unknown error occurred trying to open the archive");
}
return new HashSet<FileManagerDirectory>();
}
string GetEntryName(IFileManagerEntry entry)
{

View File

@ -4,75 +4,12 @@
@using LANCommander.Models
@using LANCommander.Services
@using System.IO.Compression;
@using Microsoft.EntityFrameworkCore;
@inject ScriptService ScriptService
@inject ModalService ModalService
@inject IMessageService MessageService
@{
RenderFragment Footer =
@<Template>
<Button OnClick="Save" Disabled="@(String.IsNullOrWhiteSpace(Script.Name))" Type="@ButtonType.Primary">Save</Button>
<Button OnClick="Close">Close</Button>
</Template>;
}
<Modal Visible="ModalVisible" Footer="@Footer" Title="@(Script == null ? "Add Script" : "Edit Script")" OkText="@("Save")" Maximizable="false" DefaultMaximized="true" Closable="true" OnCancel="Close">
<Form Model="@Script" Layout="@FormLayout.Vertical">
<FormItem>
@foreach (var group in Snippets.Select(s => s.Group).Distinct())
{
<Dropdown>
<Overlay>
<Menu>
@foreach (var snippet in Snippets.Where(s => s.Group == group))
{
<MenuItem OnClick="() => InsertSnippet(snippet)">
@snippet.Name
</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Type="@ButtonType.Primary">@group</Button>
</ChildContent>
</Dropdown>
}
@if (ArchiveId != Guid.Empty)
{
<Button Icon="@IconType.Outline.FolderOpen" OnClick="BrowseForPath" Type="@ButtonType.Text">Browse</Button>
}
<Button Icon="@IconType.Outline.Build" OnClick="() => RegToPowerShell.Open()" Type="@ButtonType.Text">Import .reg</Button>
</FormItem>
<FormItem>
<StandaloneCodeEditor @ref="Editor" Id="editor" ConstructionOptions="EditorConstructionOptions" />
</FormItem>
<FormItem Label="Name">
<Input @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="Type">
<Select @bind-Value="context.Type" TItem="ScriptType" TItemValue="ScriptType" DataSource="Enum.GetValues<ScriptType>().Where(st => AllowedTypes == null || AllowedTypes.Contains(st))">
<LabelTemplate Context="Value">@Value.GetDisplayName()</LabelTemplate>
<ItemTemplate Context="Value">@Value.GetDisplayName()</ItemTemplate>
</Select>
</FormItem>
<FormItem>
<Checkbox @bind-Checked="context.RequiresAdmin">Requires Admin</Checkbox>
</FormItem>
<FormItem Label="Description">
<TextArea @bind-Value="context.Description" MaxLength=500 ShowCount />
</FormItem>
</Form>
</Modal>
<RegToPowerShell @ref="RegToPowerShell" OnParsed="(text) => InsertText(text)" />
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%">
<SpaceItem>
@ -85,8 +22,9 @@
<ActionColumn Title="">
<Space Style="display: flex; justify-content: end">
<SpaceItem>
<Button OnClick="() => Edit(context)" Icon="@IconType.Outline.Edit" Type="@ButtonType.Text" />
<Button OnClick="() => Edit(context.Id)" Icon="@IconType.Outline.Edit" Type="@ButtonType.Text" />
</SpaceItem>
<SpaceItem>
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this script?">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
@ -115,61 +53,51 @@
[Parameter] public Guid GameId { get; set; }
[Parameter] public Guid RedistributableId { get; set; }
[Parameter] public Guid ArchiveId { get; set; }
[Parameter] public ICollection<Script> Scripts { get; set; }
[Parameter] public EventCallback<ICollection<Script>> ScriptsChanged { get; set; }
[Parameter] public IEnumerable<ScriptType> AllowedTypes { get; set; }
Script Script;
ICollection<Script> Scripts { get; set; } = new List<Script>();
bool ModalVisible = false;
IEnumerable<Snippet> Snippets { get; set; }
StandaloneCodeEditor? Editor;
RegToPowerShell RegToPowerShell;
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
protected override async Task OnParametersSetAsync()
{
return new StandaloneEditorConstructionOptions
{
AutomaticLayout = true,
Language = "powershell",
Value = Script.Contents,
Theme = "vs-dark",
};
await LoadData();
}
protected override async Task OnInitializedAsync()
private async Task LoadData()
{
if (Scripts == null)
Scripts = new List<Script>();
Snippets = ScriptService.GetSnippets();
if (Script == null)
Script = new Script();
}
private async void Edit(Script script = null)
{
if (script == null) {
if (GameId != Guid.Empty)
Script = new Script() { GameId = GameId };
Scripts = await ScriptService.Get(s => s.GameId == GameId).ToListAsync();
else if (RedistributableId != Guid.Empty)
Scripts = await ScriptService.Get(s => s.RedistributableId == RedistributableId).ToListAsync();
if (RedistributableId != Guid.Empty)
Script = new Script() { RedistributableId = RedistributableId };
if (Editor != null)
await Editor.SetValue("");
await InvokeAsync(StateHasChanged);
}
else
Script = script;
if (Editor != null && Script != null)
await Editor.SetValue(Script.Contents);
private async void Edit(Guid? scriptId = null)
{
var modalOptions = new ModalOptions()
{
Title = scriptId == null ? "Add Script" : "Edit Script",
Maximizable = false,
DefaultMaximized = true,
Closable = true,
OkText = "Save"
};
ModalVisible = true;
var options = new ScriptEditorOptions()
{
ScriptId = scriptId ?? default,
AllowedTypes = AllowedTypes,
ArchiveId = ArchiveId,
GameId = GameId,
RedistributableId = RedistributableId
};
StateHasChanged();
var modalRef = await ModalService.CreateModalAsync<ScriptEditorDialog, ScriptEditorOptions, Script>(modalOptions, options);
modalRef.OnOk = async (script) =>
{
await LoadData();
};
}
private async void Delete(Script script = null)
@ -179,81 +107,4 @@
await MessageService.Success("Script deleted!");
}
private void Close()
{
ModalVisible = false;
}
private async Task Save()
{
var value = await Editor.GetValue();
Script.Contents = value;
if (Script.Id == Guid.Empty)
Script = await ScriptService.Add(Script);
else
Script = await ScriptService.Update(Script);
Close();
StateHasChanged();
await MessageService.Success("Script saved!");
}
private async Task InsertText(string text)
{
var line = await Editor.GetPosition();
var range = new BlazorMonaco.Range(line.LineNumber, 1, line.LineNumber, 1);
var currentSelections = await Editor.GetSelections();
await Editor.ExecuteEdits("ScriptEditor", new List<IdentifiedSingleEditOperation>()
{
new IdentifiedSingleEditOperation
{
Range = range,
Text = text,
ForceMoveMarkers = true
}
}, currentSelections);
}
private async Task InsertSnippet(Snippet snippet)
{
await InsertText(snippet.Content);
}
private async void BrowseForPath()
{
var modalOptions = new ModalOptions()
{
Title = "Choose Reference",
Maximizable = false,
DefaultMaximized = true,
Closable = true,
OkText = "Insert File Path"
};
var browserOptions = new FilePickerOptions()
{
ArchiveId = ArchiveId,
Select = true,
Multiple = false
};
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
modalRef.OnOk = (results) =>
{
var path = results.FirstOrDefault().Path;
InsertText($"$InstallDir\\{path.Replace('/', '\\')}");
StateHasChanged();
return Task.CompletedTask;
};
}
}

View File

@ -0,0 +1,195 @@
@using LANCommander.Components.FileManagerComponents;
@using LANCommander.Extensions;
@using LANCommander.Data.Enums;
@using LANCommander.Models;
@inherits FeedbackComponent<ScriptEditorOptions, Script>
@inject ScriptService ScriptService
@inject ModalService ModalService
@inject IMessageService MessageService
<Form Model="@Script" Layout="@FormLayout.Vertical">
<FormItem>
@foreach (var group in Snippets.Select(s => s.Group).Distinct())
{
<Dropdown>
<Overlay>
<Menu>
@foreach (var snippet in Snippets.Where(s => s.Group == group))
{
<MenuItem OnClick="() => InsertSnippet(snippet)">
@snippet.Name
</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Type="@ButtonType.Primary">@group</Button>
</ChildContent>
</Dropdown>
}
@if (Options.ArchiveId != Guid.Empty)
{
<Button Icon="@IconType.Outline.FolderOpen" OnClick="BrowseForPath" Type="@ButtonType.Text">Browse</Button>
}
<Button Icon="@IconType.Outline.Build" OnClick="() => RegToPowerShell.Open()" Type="@ButtonType.Text">Import .reg</Button>
</FormItem>
<FormItem>
<StandaloneCodeEditor @ref="Editor" Id="@("editor-" + Id.ToString())" ConstructionOptions="EditorConstructionOptions" />
</FormItem>
<FormItem Label="Name">
<Input @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="Type">
<Select @bind-Value="context.Type" TItem="ScriptType" TItemValue="ScriptType" DataSource="Enum.GetValues<ScriptType>().Where(st => Options.AllowedTypes == null || Options.AllowedTypes.Contains(st))">
<LabelTemplate Context="Value">@Value.GetDisplayName()</LabelTemplate>
<ItemTemplate Context="Value">@Value.GetDisplayName()</ItemTemplate>
</Select>
</FormItem>
<FormItem>
<Checkbox @bind-Checked="context.RequiresAdmin">Requires Admin</Checkbox>
</FormItem>
<FormItem Label="Description">
<TextArea @bind-Value="context.Description" MaxLength=500 ShowCount />
</FormItem>
</Form>
<RegToPowerShell @ref="RegToPowerShell" OnParsed="(text) => InsertText(text)" />
@code {
Guid Id = Guid.NewGuid();
Script Script;
StandaloneCodeEditor? Editor;
RegToPowerShell RegToPowerShell;
IEnumerable<Snippet> Snippets { get; set; }
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
{
return new StandaloneEditorConstructionOptions
{
AutomaticLayout = true,
Language = "powershell",
Value = Script.Contents,
Theme = "vs-dark",
};
}
protected override async Task OnInitializedAsync()
{
if (Options.ScriptId != Guid.Empty)
Script = await ScriptService.Get(Options.ScriptId);
else if (Options.GameId != Guid.Empty)
Script = new Script()
{
GameId = Options.GameId
};
else if (Options.RedistributableId != Guid.Empty)
Script = new Script()
{
RedistributableId = Options.RedistributableId
};
Snippets = ScriptService.GetSnippets();
}
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
{
await Save();
Editor.Dispose();
await base.OkCancelRefWithResult!.OnOk(Script);
}
public override async Task CancelAsync(ModalClosingEventArgs args)
{
Editor.Dispose();
await base.CancelAsync(args);
}
private async void BrowseForPath()
{
var modalOptions = new ModalOptions()
{
Title = "Choose Reference",
Maximizable = false,
DefaultMaximized = true,
Closable = true,
OkText = "Insert File Path"
};
var browserOptions = new FilePickerOptions()
{
ArchiveId = Options.ArchiveId,
Select = true,
Multiple = false
};
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
modalRef.OnOk = (results) =>
{
var path = results.FirstOrDefault().Path;
InsertText($"$InstallDir\\{path.Replace('/', '\\')}");
StateHasChanged();
return Task.CompletedTask;
};
}
private async Task InsertText(string text)
{
var line = await Editor.GetPosition();
var range = new BlazorMonaco.Range(line.LineNumber, 1, line.LineNumber, 1);
var currentSelections = await Editor.GetSelections();
await Editor.ExecuteEdits("ScriptEditor", new List<IdentifiedSingleEditOperation>()
{
new IdentifiedSingleEditOperation
{
Range = range,
Text = text,
ForceMoveMarkers = true
}
}, currentSelections);
}
private async Task InsertSnippet(Snippet snippet)
{
await InsertText(snippet.Content);
}
private async Task Save()
{
try
{
var value = await Editor.GetValue();
Script.Contents = value;
if (Script.Id == Guid.Empty)
Script = await ScriptService.Add(Script);
else
Script = await ScriptService.Update(Script);
MessageService.Success("Script saved!");
}
catch (Exception ex)
{
MessageService.Error("Script could not be saved!");
throw ex;
}
}
}

View File

@ -2,7 +2,7 @@
<Select Mode="tags" TItem="Guid" TItemValue="Guid" @bind-Values="@SelectedValues" OnSelectedItemsChanged="OnSelectedItemsChanged" EnableSearch>
<SelectOptions>
@foreach (var entity in Entities)
@foreach (var entity in Entities.OrderBy(OptionLabelSelector))
{
<SelectOption TItemValue="Guid" TItem="Guid" Value="@entity.Id" Label="@OptionLabelSelector.Invoke(entity)" />
}
@ -45,7 +45,7 @@
}
if (ValuesChanged.HasDelegate)
await ValuesChanged.InvokeAsync();
await ValuesChanged.InvokeAsync(Values);
StateHasChanged();
}

View File

@ -23,6 +23,7 @@
Title = TitleSelector.Invoke(i)
});
if (Values != null)
TargetKeys = Values.Select(i => i.Id.ToString()).ToList();
}
}

View File

@ -0,0 +1,53 @@
using LANCommander.Data.Models;
using LANCommander.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace LANCommander.Controllers.Api
{
[Authorize(AuthenticationSchemes = "Bearer")]
[Route("api/[controller]")]
[ApiController]
public class PlaySessionsController : ControllerBase
{
private readonly PlaySessionService PlaySessionService;
private readonly GameService GameService;
private readonly UserManager<User> UserManager;
public PlaySessionsController(PlaySessionService playSessionService, GameService gameService, UserManager<User> userManager)
{
PlaySessionService = playSessionService;
GameService = gameService;
UserManager = userManager;
}
[HttpPost("Start/{id}")]
public async Task<IActionResult> Start(Guid id)
{
var user = await UserManager.FindByNameAsync(User.Identity.Name);
var game = await GameService.Get(id);
if (game == null || user == null)
return BadRequest();
await PlaySessionService.StartSession(game.Id, user.Id);
return Ok();
}
[HttpPost("End/{id}")]
public async Task<IActionResult> End(Guid id)
{
var user = await UserManager.FindByNameAsync(User.Identity.Name);
var game = await GameService.Get(id);
if (game == null || user == null)
return BadRequest();
await PlaySessionService.EndSession(game.Id, user.Id);
return Ok();
}
}
}

View File

@ -31,7 +31,14 @@ namespace LANCommander.Controllers
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{archive.Game.Title.SanitizeFilename()}.zip");
string name = "";
if (archive.GameId != null && archive.GameId != Guid.Empty)
name = $"{archive.Game.Title.SanitizeFilename()}.zip";
else if (archive.RedistributableId != null && archive.RedistributableId != Guid.Empty)
name = $"{archive.Redistributable.Name.SanitizeFilename()}.zip";
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", name);
}
}
}

View File

@ -30,7 +30,7 @@ namespace LANCommander.Controllers
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/zip", $"{save.User?.UserName} - {save.Game?.Title} - {save.CreatedOn.ToString("MM-dd-yyyy.hh-mm")}");
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/zip", $"{save.User?.UserName} - {(save.Game == null ? "Unknown" : save.Game?.Title)} - {save.CreatedOn.ToString("MM-dd-yyyy.hh-mm")}.zip");
}
}
}

View File

@ -20,14 +20,44 @@ namespace LANCommander.Controllers
if (server == null)
return NotFound();
path = path.Replace('/', Path.DirectorySeparatorChar);
var filename = Path.Combine(server.HTTPRootPath, path);
if (!System.IO.File.Exists(filename))
if (server.HttpPaths == null || server.HttpPaths.Count == 0)
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", Path.GetFileName(filename));
// Sanitize
if (path == null)
path = "/";
path = path.Trim('/');
path = path + "/";
var httpPath = server.HttpPaths.FirstOrDefault(hp => path.StartsWith(hp.Path.TrimStart('/')));
// Check to see if there's a root path defined if nothing else matches
if (httpPath == null)
httpPath = server.HttpPaths.FirstOrDefault(hp => hp.Path == "/");
if (httpPath == null)
return Forbid();
var relativePath = path.Substring(httpPath.Path.TrimStart('/').Length).Replace('/', Path.DirectorySeparatorChar).TrimStart('\\');
var localPath = Path.Combine(httpPath.LocalPath, relativePath).TrimEnd('\\');
var attrs = System.IO.File.GetAttributes(localPath);
if ((attrs & FileAttributes.Directory) == FileAttributes.Directory)
{
if (!System.IO.Directory.Exists(localPath))
return NotFound();
return Json(Directory.GetFileSystemEntries(localPath).Select(fse => fse.Substring(localPath.Length)));
}
else
{
if (!System.IO.File.Exists(localPath))
return NotFound();
return File(new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", Path.GetFileName(localPath));
}
}
}
}

View File

@ -17,6 +17,26 @@ namespace LANCommander.Data
{
base.OnModelCreating(builder);
builder.ConfigureBaseRelationships<Data.Models.Action>();
builder.ConfigureBaseRelationships<Archive>();
builder.ConfigureBaseRelationships<Category>();
builder.ConfigureBaseRelationships<Collection>();
builder.ConfigureBaseRelationships<Company>();
builder.ConfigureBaseRelationships<Game>();
builder.ConfigureBaseRelationships<GameSave>();
builder.ConfigureBaseRelationships<Genre>();
builder.ConfigureBaseRelationships<Key>();
builder.ConfigureBaseRelationships<Media>();
builder.ConfigureBaseRelationships<MultiplayerMode>();
builder.ConfigureBaseRelationships<PlaySession>();
builder.ConfigureBaseRelationships<Redistributable>();
builder.ConfigureBaseRelationships<SavePath>();
builder.ConfigureBaseRelationships<Script>();
builder.ConfigureBaseRelationships<Server>();
builder.ConfigureBaseRelationships<ServerConsole>();
builder.ConfigureBaseRelationships<ServerHttpPath>();
builder.ConfigureBaseRelationships<Tag>();
builder.Entity<Genre>()
.HasMany(g => g.Games)
.WithMany(g => g.Genres);
@ -34,6 +54,7 @@ namespace LANCommander.Data
.HasMany(t => t.Games)
.WithMany(g => g.Tags);
#region Game Relationships
builder.Entity<Game>()
.HasMany(g => g.Archives)
.WithOne(g => g.Game)
@ -63,6 +84,28 @@ namespace LANCommander.Data
.IsRequired(true)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Game>()
.HasMany(g => g.Media)
.WithOne(m => m.Game)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Game>()
.HasMany(g => g.SavePaths)
.WithOne(p => p.Game)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Game>()
.HasMany(g => g.PlaySessions)
.WithOne(ps => ps.Game)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Game>()
.HasMany(g => g.GameSaves)
.WithOne(gs => gs.Game)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Game>()
.HasMany(g => g.Developers)
.WithMany(c => c.DevelopedGames)
@ -89,29 +132,28 @@ namespace LANCommander.Data
gr => gr.HasOne<Redistributable>().WithMany().HasForeignKey("RedistributableId"),
gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId")
);
#endregion
builder.Entity<Game>()
.HasMany(g => g.Media)
.WithOne(m => m.Game)
.OnDelete(DeleteBehavior.Cascade);
#region User Relationships
builder.Entity<User>()
.HasMany(u => u.GameSaves)
.WithOne(gs => gs.User)
.IsRequired(true)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Game>()
.HasMany(g => g.GameSaves)
.WithOne(gs => gs.Game)
builder.Entity<User>()
.HasMany(u => u.PlaySessions)
.WithOne(ps => ps.User)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
.OnDelete(DeleteBehavior.Cascade);
#endregion
#region Server Relationships
builder.Entity<Server>()
.HasOne(s => s.Game)
.WithMany(g => g.Servers)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Server>()
.HasMany<ServerConsole>()
@ -119,6 +161,14 @@ namespace LANCommander.Data
.IsRequired(true)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Server>()
.HasMany<ServerHttpPath>()
.WithOne(s => s.Server)
.IsRequired(true)
.OnDelete(DeleteBehavior.Cascade);
#endregion
#region Redistributable Relationships
builder.Entity<Redistributable>()
.HasMany(r => r.Archives)
.WithOne(a => a.Redistributable)
@ -130,6 +180,29 @@ namespace LANCommander.Data
.WithOne(s => s.Redistributable)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
#endregion
#region Collection Relationships
builder.Entity<Collection>()
.HasMany(c => c.Games)
.WithMany(g => g.Collections)
.UsingEntity<Dictionary<string, object>>(
"CollectionGame",
cg => cg.HasOne<Game>().WithMany().HasForeignKey("GameId"),
cg => cg.HasOne<Collection>().WithMany().HasForeignKey("CollectionId")
);
#endregion
#region Role Relationships
builder.Entity<Role>()
.HasMany(r => r.Collections)
.WithMany(c => c.Roles)
.UsingEntity<Dictionary<string, object>>(
"RoleCollection",
rc => rc.HasOne<Collection>().WithMany().HasForeignKey("CollectionId"),
rc => rc.HasOne<Role>().WithMany().HasForeignKey("RoleId")
);
#endregion
}
public DbSet<Game>? Games { get; set; }
@ -146,6 +219,8 @@ namespace LANCommander.Data
public DbSet<GameSave>? GameSaves { get; set; }
public DbSet<PlaySession>? PlaySessions { get; set; }
public DbSet<Server>? Servers { get; set; }
public DbSet<ServerConsole>? ServerConsoles { get; set; }

View File

@ -0,0 +1,23 @@
using LANCommander.Data.Models;
using Microsoft.EntityFrameworkCore;
namespace LANCommander.Data
{
public static class ModelBuilderExtensions
{
public static void ConfigureBaseRelationships<T>(this ModelBuilder modelBuilder) where T : BaseModel
{
modelBuilder.Entity<T>()
.HasOne(x => x.CreatedBy)
.WithMany()
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<T>()
.HasOne(x => x.UpdatedBy)
.WithMany()
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
}
}
}

View File

@ -10,10 +10,13 @@ namespace LANCommander.Data.Models
[Display(Name = "Created On")]
public DateTime CreatedOn { get; set; }
[Display(Name = "Created By")]
public virtual User? CreatedBy { get; set; }
[Display(Name = "Updated On")]
public DateTime UpdatedOn { get; set; }
[Display(Name = "Updated By")]
public virtual User? UpdatedBy { get; set; }
}

View File

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace LANCommander.Data.Models
{
[Table("Collections")]
public class Collection : BaseModel
{
public string Name { get; set; }
[JsonIgnore]
public virtual ICollection<Game> Games { get; set; }
[JsonIgnore]
public virtual ICollection<Role> Roles { get; set; }
}
}

View File

@ -31,6 +31,7 @@ namespace LANCommander.Data.Models
public virtual ICollection<Archive>? Archives { get; set; }
public virtual ICollection<Script>? Scripts { get; set; }
public virtual ICollection<GameSave>? GameSaves { get; set; }
public virtual ICollection<PlaySession>? PlaySessions { get; set; }
public virtual ICollection<SavePath>? SavePaths { get; set; }
public virtual ICollection<Server>? Servers { get; set; }
public virtual ICollection<Redistributable>? Redistributables { get; set; }
@ -38,5 +39,6 @@ namespace LANCommander.Data.Models
public string? ValidKeyRegex { get; set; }
public virtual ICollection<Key>? Keys { get; set; }
public virtual ICollection<Collection> Collections { get; set; }
}
}

View File

@ -7,7 +7,7 @@ namespace LANCommander.Data.Models
[Table("GameSaves")]
public class GameSave : BaseModel
{
public Guid GameId { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(GameId))]
[InverseProperty("GameSaves")]

View File

@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace LANCommander.Data.Models
{
[Table("PlaySessions")]
public class PlaySession : BaseModel
{
public Guid? GameId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(GameId))]
[InverseProperty("PlaySessions")]
public virtual Game? Game { get; set; }
public Guid UserId { get; set; }
[ForeignKey(nameof(UserId))]
[InverseProperty("PlaySessions")]
public virtual User? User { get; set; }
[Display(Name = "Start")]
public DateTime? Start { get; set; }
[Display(Name = "End")]
public DateTime? End { get; set; }
}
}

View File

@ -6,5 +6,6 @@ namespace LANCommander.Data.Models
[Table("Roles")]
public class Role : IdentityRole<Guid>
{
public virtual ICollection<Collection> Collections { get; set; }
}
}

View File

@ -10,6 +10,7 @@ namespace LANCommander.Data.Models
{
public SavePathType Type { get; set; }
public string Path { get; set; }
public bool IsRegex { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]

View File

@ -17,9 +17,6 @@ namespace LANCommander.Data.Models
public bool Autostart { get; set; }
public int AutostartDelay { get; set; }
public bool EnableHTTP { get; set; }
public string HTTPRootPath { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(GameId))]
@ -27,5 +24,6 @@ namespace LANCommander.Data.Models
public virtual Game? Game { get; set; }
public virtual ICollection<ServerConsole>? ServerConsoles { get; set; }
public virtual ICollection<ServerHttpPath>? HttpPaths { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace LANCommander.Data.Models
{
public class ServerHttpPath : BaseModel
{
public string LocalPath { get; set; }
public string Path { get; set; }
public Guid ServerId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(ServerId))]
[InverseProperty("HttpPaths")]
public virtual Server Server { get; set; }
}
}

View File

@ -46,6 +46,9 @@ namespace LANCommander.Data.Models
[JsonIgnore]
public virtual ICollection<GameSave>? GameSaves { get; set; }
[JsonIgnore]
public virtual ICollection<PlaySession>? PlaySessions { get; set; }
[JsonIgnore]
public bool Approved { get; set; }

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-LANCommander-C1F79CFA-9767-4AD7-BD5A-2549F8328A2D</UserSecretsId>
@ -22,26 +22,26 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AntDesign" Version="0.15.5" />
<PackageReference Include="AntDesign.Charts" Version="0.3.1" />
<PackageReference Include="AntDesign" Version="0.16.1" />
<PackageReference Include="AntDesign.Charts" Version="0.3.2" />
<PackageReference Include="Blazor-ApexCharts" Version="1.0.1" />
<PackageReference Include="BlazorMonaco" Version="3.1.0" />
<PackageReference Include="ByteSize" Version="2.1.1" />
<PackageReference Include="CoreRCON" Version="5.0.5" />
<PackageReference Include="craftersmine.SteamGridDB.Net" Version="1.1.5" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" />
<PackageReference Include="Hangfire.Core" Version="1.8.5" />
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Hangfire.Core" Version="1.8.6" />
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
<PackageReference Include="IGDB" Version="2.3.2" />
<PackageReference Include="IPXRelayDotNet" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.11">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -50,15 +50,15 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.10" />
<PackageReference Include="NLog" Version="5.2.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.4" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.5" />
<PackageReference Include="RegParserDotNet" Version="1.0.4" />
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
<PackageReference Include="swashbuckle" Version="5.6.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
<PackageReference Include="XtermBlazor" Version="1.9.0" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
<PackageReference Include="XtermBlazor" Version="1.10.0" />
<PackageReference Include="YamlDotNet" Version="13.3.1" />
</ItemGroup>
@ -66,7 +66,6 @@
<Folder Include="bin\Debug\net6.0\" />
<Folder Include="Data\Migrations\" />
<Folder Include="Migrations\" />
<Folder Include="Pages\Games\Archives\" />
<Folder Include="Scripts\" />
</ItemGroup>
@ -88,6 +87,81 @@
<None Update="favicon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Copy Directory.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Create Directory.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Create Registry Path.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Get User Registry Virtual Store.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Patch Binary.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Remove Directory.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Rename File.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Sanitize Filename.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Set Compatibility Mode.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Set Registry Key.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Trim String.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Examples\Write to File.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Functions\Convert-AspectRatio.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Functions\ConvertTo-StringBytes.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Functions\Edit-PatchBinary.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Functions\Get-GameManifest.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Functions\Get-PrimaryDisplay.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Functions\Write-ReplaceContentInFile.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\Allocated Key.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\Default Install Directory.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\Game Install Directory.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\Game Manifest.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\New Player Alias.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\Old Player Alias.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Snippets\Variables\Server Address.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddServerHttpPaths : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ServerHttpPath",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
LocalPath = table.Column<string>(type: "TEXT", nullable: false),
Path = table.Column<string>(type: "TEXT", nullable: false),
ServerId = table.Column<Guid>(type: "TEXT", nullable: false),
ServerId1 = table.Column<Guid>(type: "TEXT", nullable: true),
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ServerHttpPath", x => x.Id);
table.ForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_CreatedById",
column: x => x.CreatedById,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_UpdatedById",
column: x => x.UpdatedById,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_ServerHttpPath_Servers_ServerId",
column: x => x.ServerId,
principalTable: "Servers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ServerHttpPath_Servers_ServerId1",
column: x => x.ServerId1,
principalTable: "Servers",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_ServerHttpPath_CreatedById",
table: "ServerHttpPath",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_ServerHttpPath_ServerId",
table: "ServerHttpPath",
column: "ServerId");
migrationBuilder.CreateIndex(
name: "IX_ServerHttpPath_ServerId1",
table: "ServerHttpPath",
column: "ServerId1");
migrationBuilder.CreateIndex(
name: "IX_ServerHttpPath_UpdatedById",
table: "ServerHttpPath",
column: "UpdatedById");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ServerHttpPath");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class RemoveDeprecatedServerHttpOptions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "EnableHTTP",
table: "Servers");
migrationBuilder.DropColumn(
name: "HTTPRootPath",
table: "Servers");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "EnableHTTP",
table: "Servers",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "HTTPRootPath",
table: "Servers",
type: "TEXT",
nullable: false,
defaultValue: "");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
using LANCommander.Data.Enums;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class DeleteDeprecatedSnippets : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
string[] snippetsToRemove = new string[]
{
"Examples\\Replace Content In File.ps1",
"Examples\\Separate ASCII Bytes.ps1",
"Examples\\String to ASCII Bytes.ps1",
"Functions\\Get-43Resolution.ps1",
"Functions\\Get-AsciiBytes.ps1",
"Functions\\Patch-Binary.ps1",
"Functions\\Separate-AsciiBytes.ps1",
"Variables\\Display.ps1",
"Variables\\InstallDir.ps1",
"Variables\\NewName.ps1",
"Variables\\OldName.ps1",
};
foreach (var snippet in snippetsToRemove)
{
var path = Path.Combine("Snippets", snippet);
if (File.Exists(path))
File.Delete(path);
}
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$NewName = $args[0]' || char(13) || char(10), '')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$OldName = \"\"' || char(13) || char(10) || 'if ($args[1]) {' || char(13) || char(10) || char(9) || '$OldName = $args[1]' || char(13) || char(10) || '}' || char(13) || char(10), '')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$InstallDir = $PSScriptRoot' || char(13) || char(10), '')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$InstallDir', '$InstallDirectory')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$NewName', '$NewPlayerAlias')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$OldName', '$OldPlayerAlias')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, 'function Write-ReplaceContentInFile', 'function Write-ReplaceContentInFile-Old')");
migrationBuilder.Sql($"UPDATE Scripts SET Contents = REPLACE(Contents, '$args[0]', '$AllocatedKey') WHERE Type = {(int)ScriptType.Install}");
migrationBuilder.Sql($"UPDATE Scripts SET Contents = REPLACE(Contents, '$args[0]', '$AllocatedKey') WHERE Type = {(int)ScriptType.KeyChange}");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, 'Add-Type -AssemblyName System.Windows.Forms' || char(13) || char(10), '')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$Display = [System.Windows.Forms.Screen]::AllScreens | Where-Object Primary | Select Bounds', '$Display = Get-PrimaryDisplay')");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddPlaySessions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PlaySessions",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
GameId = table.Column<Guid>(type: "TEXT", nullable: false),
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
Start = table.Column<DateTime>(type: "TEXT", nullable: true),
End = table.Column<DateTime>(type: "TEXT", nullable: true),
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PlaySessions", x => x.Id);
table.ForeignKey(
name: "FK_PlaySessions_AspNetUsers_CreatedById",
column: x => x.CreatedById,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_PlaySessions_AspNetUsers_UpdatedById",
column: x => x.UpdatedById,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_PlaySessions_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_PlaySessions_Games_GameId",
column: x => x.GameId,
principalTable: "Games",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_PlaySessions_CreatedById",
table: "PlaySessions",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_PlaySessions_GameId",
table: "PlaySessions",
column: "GameId");
migrationBuilder.CreateIndex(
name: "IX_PlaySessions_UpdatedById",
table: "PlaySessions",
column: "UpdatedById");
migrationBuilder.CreateIndex(
name: "IX_PlaySessions_UserId",
table: "PlaySessions",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PlaySessions");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,975 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class FixDeletionBehaviors : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Actions_AspNetUsers_CreatedById",
table: "Actions");
migrationBuilder.DropForeignKey(
name: "FK_Actions_AspNetUsers_UpdatedById",
table: "Actions");
migrationBuilder.DropForeignKey(
name: "FK_Archive_AspNetUsers_CreatedById",
table: "Archive");
migrationBuilder.DropForeignKey(
name: "FK_Archive_AspNetUsers_UpdatedById",
table: "Archive");
migrationBuilder.DropForeignKey(
name: "FK_Categories_AspNetUsers_CreatedById",
table: "Categories");
migrationBuilder.DropForeignKey(
name: "FK_Categories_AspNetUsers_UpdatedById",
table: "Categories");
migrationBuilder.DropForeignKey(
name: "FK_Companies_AspNetUsers_CreatedById",
table: "Companies");
migrationBuilder.DropForeignKey(
name: "FK_Companies_AspNetUsers_UpdatedById",
table: "Companies");
migrationBuilder.DropForeignKey(
name: "FK_Games_AspNetUsers_CreatedById",
table: "Games");
migrationBuilder.DropForeignKey(
name: "FK_Games_AspNetUsers_UpdatedById",
table: "Games");
migrationBuilder.DropForeignKey(
name: "FK_GameSaves_AspNetUsers_CreatedById",
table: "GameSaves");
migrationBuilder.DropForeignKey(
name: "FK_GameSaves_AspNetUsers_UpdatedById",
table: "GameSaves");
migrationBuilder.DropForeignKey(
name: "FK_GameSaves_Games_GameId",
table: "GameSaves");
migrationBuilder.DropForeignKey(
name: "FK_Genres_AspNetUsers_CreatedById",
table: "Genres");
migrationBuilder.DropForeignKey(
name: "FK_Genres_AspNetUsers_UpdatedById",
table: "Genres");
migrationBuilder.DropForeignKey(
name: "FK_Keys_AspNetUsers_CreatedById",
table: "Keys");
migrationBuilder.DropForeignKey(
name: "FK_Keys_AspNetUsers_UpdatedById",
table: "Keys");
migrationBuilder.DropForeignKey(
name: "FK_Media_AspNetUsers_CreatedById",
table: "Media");
migrationBuilder.DropForeignKey(
name: "FK_Media_AspNetUsers_UpdatedById",
table: "Media");
migrationBuilder.DropForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_CreatedById",
table: "MultiplayerModes");
migrationBuilder.DropForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_UpdatedById",
table: "MultiplayerModes");
migrationBuilder.DropForeignKey(
name: "FK_PlaySessions_AspNetUsers_CreatedById",
table: "PlaySessions");
migrationBuilder.DropForeignKey(
name: "FK_PlaySessions_AspNetUsers_UpdatedById",
table: "PlaySessions");
migrationBuilder.DropForeignKey(
name: "FK_PlaySessions_Games_GameId",
table: "PlaySessions");
migrationBuilder.DropForeignKey(
name: "FK_Redistributables_AspNetUsers_CreatedById",
table: "Redistributables");
migrationBuilder.DropForeignKey(
name: "FK_Redistributables_AspNetUsers_UpdatedById",
table: "Redistributables");
migrationBuilder.DropForeignKey(
name: "FK_SavePaths_AspNetUsers_CreatedById",
table: "SavePaths");
migrationBuilder.DropForeignKey(
name: "FK_SavePaths_AspNetUsers_UpdatedById",
table: "SavePaths");
migrationBuilder.DropForeignKey(
name: "FK_SavePaths_Games_GameId",
table: "SavePaths");
migrationBuilder.DropForeignKey(
name: "FK_Scripts_AspNetUsers_CreatedById",
table: "Scripts");
migrationBuilder.DropForeignKey(
name: "FK_Scripts_AspNetUsers_UpdatedById",
table: "Scripts");
migrationBuilder.DropForeignKey(
name: "FK_ServerConsoles_AspNetUsers_CreatedById",
table: "ServerConsoles");
migrationBuilder.DropForeignKey(
name: "FK_ServerConsoles_AspNetUsers_UpdatedById",
table: "ServerConsoles");
migrationBuilder.DropForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_CreatedById",
table: "ServerHttpPath");
migrationBuilder.DropForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_UpdatedById",
table: "ServerHttpPath");
migrationBuilder.DropForeignKey(
name: "FK_Servers_AspNetUsers_CreatedById",
table: "Servers");
migrationBuilder.DropForeignKey(
name: "FK_Servers_AspNetUsers_UpdatedById",
table: "Servers");
migrationBuilder.DropForeignKey(
name: "FK_Servers_Games_GameId",
table: "Servers");
migrationBuilder.DropForeignKey(
name: "FK_Tags_AspNetUsers_CreatedById",
table: "Tags");
migrationBuilder.DropForeignKey(
name: "FK_Tags_AspNetUsers_UpdatedById",
table: "Tags");
migrationBuilder.AlterColumn<Guid>(
name: "GameId",
table: "PlaySessions",
type: "TEXT",
nullable: true,
oldClrType: typeof(Guid),
oldType: "TEXT");
migrationBuilder.AlterColumn<Guid>(
name: "GameId",
table: "GameSaves",
type: "TEXT",
nullable: true,
oldClrType: typeof(Guid),
oldType: "TEXT");
migrationBuilder.AddForeignKey(
name: "FK_Actions_AspNetUsers_CreatedById",
table: "Actions",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Actions_AspNetUsers_UpdatedById",
table: "Actions",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Archive_AspNetUsers_CreatedById",
table: "Archive",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Archive_AspNetUsers_UpdatedById",
table: "Archive",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Categories_AspNetUsers_CreatedById",
table: "Categories",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Categories_AspNetUsers_UpdatedById",
table: "Categories",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Companies_AspNetUsers_CreatedById",
table: "Companies",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Companies_AspNetUsers_UpdatedById",
table: "Companies",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Games_AspNetUsers_CreatedById",
table: "Games",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Games_AspNetUsers_UpdatedById",
table: "Games",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_GameSaves_AspNetUsers_CreatedById",
table: "GameSaves",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_GameSaves_AspNetUsers_UpdatedById",
table: "GameSaves",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_GameSaves_Games_GameId",
table: "GameSaves",
column: "GameId",
principalTable: "Games",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Genres_AspNetUsers_CreatedById",
table: "Genres",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Genres_AspNetUsers_UpdatedById",
table: "Genres",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Keys_AspNetUsers_CreatedById",
table: "Keys",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Keys_AspNetUsers_UpdatedById",
table: "Keys",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Media_AspNetUsers_CreatedById",
table: "Media",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Media_AspNetUsers_UpdatedById",
table: "Media",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_CreatedById",
table: "MultiplayerModes",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_UpdatedById",
table: "MultiplayerModes",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_PlaySessions_AspNetUsers_CreatedById",
table: "PlaySessions",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_PlaySessions_AspNetUsers_UpdatedById",
table: "PlaySessions",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_PlaySessions_Games_GameId",
table: "PlaySessions",
column: "GameId",
principalTable: "Games",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Redistributables_AspNetUsers_CreatedById",
table: "Redistributables",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Redistributables_AspNetUsers_UpdatedById",
table: "Redistributables",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_SavePaths_AspNetUsers_CreatedById",
table: "SavePaths",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_SavePaths_AspNetUsers_UpdatedById",
table: "SavePaths",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_SavePaths_Games_GameId",
table: "SavePaths",
column: "GameId",
principalTable: "Games",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Scripts_AspNetUsers_CreatedById",
table: "Scripts",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Scripts_AspNetUsers_UpdatedById",
table: "Scripts",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_ServerConsoles_AspNetUsers_CreatedById",
table: "ServerConsoles",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_ServerConsoles_AspNetUsers_UpdatedById",
table: "ServerConsoles",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_CreatedById",
table: "ServerHttpPath",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_UpdatedById",
table: "ServerHttpPath",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Servers_AspNetUsers_CreatedById",
table: "Servers",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Servers_AspNetUsers_UpdatedById",
table: "Servers",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Servers_Games_GameId",
table: "Servers",
column: "GameId",
principalTable: "Games",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Tags_AspNetUsers_CreatedById",
table: "Tags",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Tags_AspNetUsers_UpdatedById",
table: "Tags",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Actions_AspNetUsers_CreatedById",
table: "Actions");
migrationBuilder.DropForeignKey(
name: "FK_Actions_AspNetUsers_UpdatedById",
table: "Actions");
migrationBuilder.DropForeignKey(
name: "FK_Archive_AspNetUsers_CreatedById",
table: "Archive");
migrationBuilder.DropForeignKey(
name: "FK_Archive_AspNetUsers_UpdatedById",
table: "Archive");
migrationBuilder.DropForeignKey(
name: "FK_Categories_AspNetUsers_CreatedById",
table: "Categories");
migrationBuilder.DropForeignKey(
name: "FK_Categories_AspNetUsers_UpdatedById",
table: "Categories");
migrationBuilder.DropForeignKey(
name: "FK_Companies_AspNetUsers_CreatedById",
table: "Companies");
migrationBuilder.DropForeignKey(
name: "FK_Companies_AspNetUsers_UpdatedById",
table: "Companies");
migrationBuilder.DropForeignKey(
name: "FK_Games_AspNetUsers_CreatedById",
table: "Games");
migrationBuilder.DropForeignKey(
name: "FK_Games_AspNetUsers_UpdatedById",
table: "Games");
migrationBuilder.DropForeignKey(
name: "FK_GameSaves_AspNetUsers_CreatedById",
table: "GameSaves");
migrationBuilder.DropForeignKey(
name: "FK_GameSaves_AspNetUsers_UpdatedById",
table: "GameSaves");
migrationBuilder.DropForeignKey(
name: "FK_GameSaves_Games_GameId",
table: "GameSaves");
migrationBuilder.DropForeignKey(
name: "FK_Genres_AspNetUsers_CreatedById",
table: "Genres");
migrationBuilder.DropForeignKey(
name: "FK_Genres_AspNetUsers_UpdatedById",
table: "Genres");
migrationBuilder.DropForeignKey(
name: "FK_Keys_AspNetUsers_CreatedById",
table: "Keys");
migrationBuilder.DropForeignKey(
name: "FK_Keys_AspNetUsers_UpdatedById",
table: "Keys");
migrationBuilder.DropForeignKey(
name: "FK_Media_AspNetUsers_CreatedById",
table: "Media");
migrationBuilder.DropForeignKey(
name: "FK_Media_AspNetUsers_UpdatedById",
table: "Media");
migrationBuilder.DropForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_CreatedById",
table: "MultiplayerModes");
migrationBuilder.DropForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_UpdatedById",
table: "MultiplayerModes");
migrationBuilder.DropForeignKey(
name: "FK_PlaySessions_AspNetUsers_CreatedById",
table: "PlaySessions");
migrationBuilder.DropForeignKey(
name: "FK_PlaySessions_AspNetUsers_UpdatedById",
table: "PlaySessions");
migrationBuilder.DropForeignKey(
name: "FK_PlaySessions_Games_GameId",
table: "PlaySessions");
migrationBuilder.DropForeignKey(
name: "FK_Redistributables_AspNetUsers_CreatedById",
table: "Redistributables");
migrationBuilder.DropForeignKey(
name: "FK_Redistributables_AspNetUsers_UpdatedById",
table: "Redistributables");
migrationBuilder.DropForeignKey(
name: "FK_SavePaths_AspNetUsers_CreatedById",
table: "SavePaths");
migrationBuilder.DropForeignKey(
name: "FK_SavePaths_AspNetUsers_UpdatedById",
table: "SavePaths");
migrationBuilder.DropForeignKey(
name: "FK_SavePaths_Games_GameId",
table: "SavePaths");
migrationBuilder.DropForeignKey(
name: "FK_Scripts_AspNetUsers_CreatedById",
table: "Scripts");
migrationBuilder.DropForeignKey(
name: "FK_Scripts_AspNetUsers_UpdatedById",
table: "Scripts");
migrationBuilder.DropForeignKey(
name: "FK_ServerConsoles_AspNetUsers_CreatedById",
table: "ServerConsoles");
migrationBuilder.DropForeignKey(
name: "FK_ServerConsoles_AspNetUsers_UpdatedById",
table: "ServerConsoles");
migrationBuilder.DropForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_CreatedById",
table: "ServerHttpPath");
migrationBuilder.DropForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_UpdatedById",
table: "ServerHttpPath");
migrationBuilder.DropForeignKey(
name: "FK_Servers_AspNetUsers_CreatedById",
table: "Servers");
migrationBuilder.DropForeignKey(
name: "FK_Servers_AspNetUsers_UpdatedById",
table: "Servers");
migrationBuilder.DropForeignKey(
name: "FK_Servers_Games_GameId",
table: "Servers");
migrationBuilder.DropForeignKey(
name: "FK_Tags_AspNetUsers_CreatedById",
table: "Tags");
migrationBuilder.DropForeignKey(
name: "FK_Tags_AspNetUsers_UpdatedById",
table: "Tags");
migrationBuilder.AlterColumn<Guid>(
name: "GameId",
table: "PlaySessions",
type: "TEXT",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "GameId",
table: "GameSaves",
type: "TEXT",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Actions_AspNetUsers_CreatedById",
table: "Actions",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Actions_AspNetUsers_UpdatedById",
table: "Actions",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Archive_AspNetUsers_CreatedById",
table: "Archive",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Archive_AspNetUsers_UpdatedById",
table: "Archive",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Categories_AspNetUsers_CreatedById",
table: "Categories",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Categories_AspNetUsers_UpdatedById",
table: "Categories",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Companies_AspNetUsers_CreatedById",
table: "Companies",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Companies_AspNetUsers_UpdatedById",
table: "Companies",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Games_AspNetUsers_CreatedById",
table: "Games",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Games_AspNetUsers_UpdatedById",
table: "Games",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_GameSaves_AspNetUsers_CreatedById",
table: "GameSaves",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_GameSaves_AspNetUsers_UpdatedById",
table: "GameSaves",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_GameSaves_Games_GameId",
table: "GameSaves",
column: "GameId",
principalTable: "Games",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Genres_AspNetUsers_CreatedById",
table: "Genres",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Genres_AspNetUsers_UpdatedById",
table: "Genres",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Keys_AspNetUsers_CreatedById",
table: "Keys",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Keys_AspNetUsers_UpdatedById",
table: "Keys",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Media_AspNetUsers_CreatedById",
table: "Media",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Media_AspNetUsers_UpdatedById",
table: "Media",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_CreatedById",
table: "MultiplayerModes",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_MultiplayerModes_AspNetUsers_UpdatedById",
table: "MultiplayerModes",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_PlaySessions_AspNetUsers_CreatedById",
table: "PlaySessions",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_PlaySessions_AspNetUsers_UpdatedById",
table: "PlaySessions",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_PlaySessions_Games_GameId",
table: "PlaySessions",
column: "GameId",
principalTable: "Games",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Redistributables_AspNetUsers_CreatedById",
table: "Redistributables",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Redistributables_AspNetUsers_UpdatedById",
table: "Redistributables",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_SavePaths_AspNetUsers_CreatedById",
table: "SavePaths",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_SavePaths_AspNetUsers_UpdatedById",
table: "SavePaths",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_SavePaths_Games_GameId",
table: "SavePaths",
column: "GameId",
principalTable: "Games",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Scripts_AspNetUsers_CreatedById",
table: "Scripts",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Scripts_AspNetUsers_UpdatedById",
table: "Scripts",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ServerConsoles_AspNetUsers_CreatedById",
table: "ServerConsoles",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ServerConsoles_AspNetUsers_UpdatedById",
table: "ServerConsoles",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_CreatedById",
table: "ServerHttpPath",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ServerHttpPath_AspNetUsers_UpdatedById",
table: "ServerHttpPath",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Servers_AspNetUsers_CreatedById",
table: "Servers",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Servers_AspNetUsers_UpdatedById",
table: "Servers",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Servers_Games_GameId",
table: "Servers",
column: "GameId",
principalTable: "Games",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Tags_AspNetUsers_CreatedById",
table: "Tags",
column: "CreatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Tags_AspNetUsers_UpdatedById",
table: "Tags",
column: "UpdatedById",
principalTable: "AspNetUsers",
principalColumn: "Id");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddSavePathRegexFlag : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsRegex",
table: "SavePaths",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsRegex",
table: "SavePaths");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class FixScriptDisplayBounds : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$Display.Width', '$Display.Bounds.Width')");
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$Display.Height', '$Display.Bounds.Height')");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,124 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddCollections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Collections",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Collections", x => x.Id);
table.ForeignKey(
name: "FK_Collections_AspNetUsers_CreatedById",
column: x => x.CreatedById,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_Collections_AspNetUsers_UpdatedById",
column: x => x.UpdatedById,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "CollectionGame",
columns: table => new
{
CollectionId = table.Column<Guid>(type: "TEXT", nullable: false),
GameId = table.Column<Guid>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CollectionGame", x => new { x.CollectionId, x.GameId });
table.ForeignKey(
name: "FK_CollectionGame_Collections_CollectionId",
column: x => x.CollectionId,
principalTable: "Collections",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_CollectionGame_Games_GameId",
column: x => x.GameId,
principalTable: "Games",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RoleCollection",
columns: table => new
{
CollectionId = table.Column<Guid>(type: "TEXT", nullable: false),
RoleId = table.Column<Guid>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RoleCollection", x => new { x.CollectionId, x.RoleId });
table.ForeignKey(
name: "FK_RoleCollection_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RoleCollection_Collections_CollectionId",
column: x => x.CollectionId,
principalTable: "Collections",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_CollectionGame_GameId",
table: "CollectionGame",
column: "GameId");
migrationBuilder.CreateIndex(
name: "IX_Collections_CreatedById",
table: "Collections",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_Collections_UpdatedById",
table: "Collections",
column: "UpdatedById");
migrationBuilder.CreateIndex(
name: "IX_RoleCollection_RoleId",
table: "RoleCollection",
column: "RoleId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CollectionGame");
migrationBuilder.DropTable(
name: "RoleCollection");
migrationBuilder.DropTable(
name: "Collections");
}
}
}

View File

@ -16,7 +16,7 @@ namespace LANCommander.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.11")
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true);
@ -36,6 +36,21 @@ namespace LANCommander.Migrations
b.ToTable("CategoryGame");
});
modelBuilder.Entity("CollectionGame", b =>
{
b.Property<Guid>("CollectionId")
.HasColumnType("TEXT");
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.HasKey("CollectionId", "GameId");
b.HasIndex("GameId");
b.ToTable("CollectionGame");
});
modelBuilder.Entity("GameDeveloper", b =>
{
b.Property<Guid>("DeveloperId")
@ -257,6 +272,37 @@ namespace LANCommander.Migrations
b.ToTable("Categories");
});
modelBuilder.Entity("LANCommander.Data.Models.Collection", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("UpdatedById");
b.ToTable("Collections");
});
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
{
b.Property<Guid>("Id")
@ -355,7 +401,7 @@ namespace LANCommander.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<Guid>("GameId")
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
@ -570,6 +616,49 @@ namespace LANCommander.Migrations
b.ToTable("MultiplayerModes");
});
modelBuilder.Entity("LANCommander.Data.Models.PlaySession", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime?>("End")
.HasColumnType("TEXT");
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime?>("Start")
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("GameId");
b.HasIndex("UpdatedById");
b.HasIndex("UserId");
b.ToTable("PlaySessions");
});
modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b =>
{
b.Property<Guid>("Id")
@ -649,6 +738,9 @@ namespace LANCommander.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<bool>("IsRegex")
.HasColumnType("INTEGER");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("TEXT");
@ -749,16 +841,9 @@ namespace LANCommander.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<bool>("EnableHTTP")
.HasColumnType("INTEGER");
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<string>("HTTPRootPath")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -859,6 +944,51 @@ namespace LANCommander.Migrations
b.ToTable("ServerConsoles");
});
modelBuilder.Entity("LANCommander.Data.Models.ServerHttpPath", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("LocalPath")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("ServerId")
.HasColumnType("TEXT");
b.Property<Guid?>("ServerId1")
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("ServerId");
b.HasIndex("ServerId1");
b.HasIndex("UpdatedById");
b.ToTable("ServerHttpPath");
});
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
{
b.Property<Guid>("Id")
@ -1073,6 +1203,21 @@ namespace LANCommander.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("RoleCollection", b =>
{
b.Property<Guid>("CollectionId")
.HasColumnType("TEXT");
b.Property<Guid>("RoleId")
.HasColumnType("TEXT");
b.HasKey("CollectionId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("RoleCollection");
});
modelBuilder.Entity("CategoryGame", b =>
{
b.HasOne("LANCommander.Data.Models.Category", null)
@ -1088,6 +1233,21 @@ namespace LANCommander.Migrations
.IsRequired();
});
modelBuilder.Entity("CollectionGame", b =>
{
b.HasOne("LANCommander.Data.Models.Collection", null)
.WithMany()
.HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GameDeveloper", b =>
{
b.HasOne("LANCommander.Data.Models.Company", null)
@ -1167,7 +1327,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Actions")
@ -1177,7 +1338,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1190,7 +1352,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Archives")
@ -1208,7 +1371,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1225,7 +1389,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Category", "Parent")
.WithMany("Children")
@ -1233,7 +1398,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1242,15 +1408,34 @@ namespace LANCommander.Migrations
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Collection", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1261,11 +1446,13 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1276,17 +1463,18 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("GameSaves")
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "User")
.WithMany("GameSaves")
@ -1307,11 +1495,13 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1326,7 +1516,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Keys")
@ -1336,7 +1527,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("ClaimedByUser");
@ -1351,7 +1543,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Media")
@ -1361,7 +1554,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1374,7 +1568,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("MultiplayerModes")
@ -1384,7 +1579,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1393,15 +1589,49 @@ namespace LANCommander.Migrations
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.PlaySession", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("PlaySessions")
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "User")
.WithMany("PlaySessions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy");
b.Navigation("Game");
b.Navigation("UpdatedBy");
b.Navigation("User");
});
modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1412,15 +1642,18 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("SavePaths")
.HasForeignKey("GameId");
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1433,7 +1666,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Scripts")
@ -1447,7 +1681,8 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1462,16 +1697,18 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Game", "Game")
.WithMany("Servers")
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.NoAction);
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1484,7 +1721,8 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Server", "Server")
.WithMany()
@ -1498,7 +1736,37 @@ namespace LANCommander.Migrations
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
b.Navigation("Server");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.ServerHttpPath", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.Server", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Data.Models.Server", null)
.WithMany("HttpPaths")
.HasForeignKey("ServerId1");
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1511,11 +1779,13 @@ namespace LANCommander.Migrations
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
.HasForeignKey("UpdatedById")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("CreatedBy");
@ -1573,6 +1843,21 @@ namespace LANCommander.Migrations
.IsRequired();
});
modelBuilder.Entity("RoleCollection", b =>
{
b.HasOne("LANCommander.Data.Models.Collection", null)
.WithMany()
.HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Data.Models.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LANCommander.Data.Models.Category", b =>
{
b.Navigation("Children");
@ -1592,6 +1877,8 @@ namespace LANCommander.Migrations
b.Navigation("MultiplayerModes");
b.Navigation("PlaySessions");
b.Navigation("SavePaths");
b.Navigation("Scripts");
@ -1608,12 +1895,16 @@ namespace LANCommander.Migrations
modelBuilder.Entity("LANCommander.Data.Models.Server", b =>
{
b.Navigation("HttpPaths");
b.Navigation("ServerConsoles");
});
modelBuilder.Entity("LANCommander.Data.Models.User", b =>
{
b.Navigation("GameSaves");
b.Navigation("PlaySessions");
});
#pragma warning restore 612, 618
}

View File

@ -0,0 +1,7 @@
namespace LANCommander.Models
{
public class AddToCollectionOptions
{
public IEnumerable<Guid> GameIds { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace LANCommander.Models
{
public class RoleViewModel
{
public string Name { get; set; }
public int Users { get; set; }
public int Collections { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using LANCommander.Data.Enums;
namespace LANCommander.Models
{
public class ScriptEditorOptions
{
public Guid ScriptId { get; set; }
public Guid GameId { get; set; }
public Guid RedistributableId { get; set; }
public Guid ArchiveId { get; set; }
public IEnumerable<ScriptType> AllowedTypes { get; set; }
}
}

View File

@ -0,0 +1,145 @@
@page "/Collections/{id:guid}"
@page "/Collections/Add"
@using LANCommander.Data.Enums;
@using LANCommander.Extensions
@attribute [Authorize(Roles = "Administrator")]
@inject CollectionService CollectionService
@inject IMessageService MessageService
@inject NavigationManager NavigationManager
<PageHeader>
<PageHeaderTitle>
<Input Size="@InputSize.Large" @bind-Value="@Collection.Name" />
</PageHeaderTitle>
<PageHeaderExtra>
<Button Type="@ButtonType.Primary" OnClick="Save">Save</Button>
</PageHeaderExtra>
</PageHeader>
<Table TItem="Game" DataSource="@Collection.Games" Responsive>
<Column TData="string" Title="Icon">
<Image Src="@GetIcon(context)" Height="32" Width="32" Preview="false"></Image>
</Column>
<PropertyColumn Property="g => g.Title" Sortable Filterable />
<PropertyColumn Property="g => g.ReleasedOn" Format="MM/dd/yyyy" Sortable Filterable />
<PropertyColumn Property="g => g.Singleplayer" Sortable Filterable>
<Checkbox Disabled="true" Checked="context.Singleplayer" />
</PropertyColumn>
<Column TData="bool" Title="Multiplayer">
<Checkbox Disabled="true" Checked="context.MultiplayerModes?.Count > 0" />
</Column>
<Column TData="string[]" Title="Developers">
@foreach (var dev in context.Developers)
{
<Tag>@dev.Name</Tag>
}
</Column>
<Column TData="string[]" Title="Publishers">
@foreach (var pub in context.Publishers)
{
<Tag>@pub.Name</Tag>
}
</Column>
<Column TData="string[]" Title="Genres">
@foreach (var genre in context.Genres)
{
<Tag>@genre.Name</Tag>
}
</Column>
<Column TData="Data.Enums.MultiplayerType[]" Title="Multiplayer Modes">
@foreach (var mode in context.MultiplayerModes.Select(mm => mm.Type).Distinct())
{
<Tag>@mode.GetDisplayName()</Tag>
}
</Column>
<ActionColumn Title="" Style="text-align: right">
<ChildContent>
<Space Direction="DirectionVHType.Horizontal">
<SpaceItem>
<Popconfirm OnConfirm="() => RemoveGame(context)" Title="Are you sure you want to remove this game from the collection?">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
</SpaceItem>
</Space>
</ChildContent>
</ActionColumn>
</Table>
@code {
[Parameter] public Guid Id { get; set; }
Collection Collection;
protected override async Task OnInitializedAsync()
{
if (Id == Guid.Empty)
Collection = new Collection();
else
Collection = await CollectionService.Get(Id);
}
private async Task Save()
{
try
{
if (Collection.Id != Guid.Empty)
{
Collection = await CollectionService.Update(Collection);
await MessageService.Success("Collection updated!");
}
else
{
Collection = await CollectionService.Add(Collection);
NavigationManager.LocationChanged += NotifyCollectionAdded;
NavigationManager.NavigateTo($"/Collections/{Collection.Id}");
}
}
catch (Exception ex)
{
await MessageService.Error("Could not save!");
}
}
private void NotifyCollectionAdded(object? sender, LocationChangedEventArgs e)
{
NavigationManager.LocationChanged -= NotifyCollectionAdded;
MessageService.Success("Collection added!");
}
private async Task RemoveGame(Game game)
{
try
{
Collection.Games.Remove(game);
await CollectionService.Update(Collection);
}
catch (Exception ex)
{
MessageService.Error("Game could not be removed from the collection!");
}
}
private string GetIcon(Game game)
{
var media = game?.Media?.FirstOrDefault(m => m.Type == Data.Enums.MediaType.Icon);
if (media != null)
return $"/api/Media/{media.Id}/Download?fileId={media.FileId}";
else
return "/favicon.ico";
}
}

View File

@ -0,0 +1,110 @@
@page "/Collections"
@using Microsoft.EntityFrameworkCore;
@attribute [Authorize(Roles = "Administrator")]
@inject CollectionService CollectionService
@inject NavigationManager NavigationManager
@inject IMessageService MessageService
<PageHeader Title="Collections">
<PageHeaderExtra>
<Space Direction="DirectionVHType.Horizontal">
<SpaceItem>
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="150" OnChange="() => LoadData()" />
</SpaceItem>
</Space>
</PageHeaderExtra>
</PageHeader>
<TableColumnPicker @ref="Picker" Key="Collections" @bind-Visible="ColumnPickerVisible" />
<Table TItem="Collection" DataSource="@Collections" Loading="@Loading" PageSize="25" Responsive>
<PropertyColumn Property="r => r.Name" Sortable Hidden="@(Picker.IsColumnHidden("Name"))" />
<PropertyColumn Property="s => s.CreatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Hidden="@(Picker.IsColumnHidden("Created On"))" />
<PropertyColumn Property="s => s.CreatedBy" Sortable Hidden="@(Picker.IsColumnHidden("Created By"))">
@context.CreatedBy?.UserName
</PropertyColumn>
<PropertyColumn Property="g => g.UpdatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Hidden="@(Picker.IsColumnHidden("Updated On"))" />
<PropertyColumn Property="g => g.UpdatedBy" Sortable Hidden="@(Picker.IsColumnHidden("Updated By"))">
@context.UpdatedBy?.UserName
</PropertyColumn>
<Column Title="Games" TData="int">
@context.Games.Count
</Column>
<ActionColumn Title="" Style="text-align: right; white-space: nowrap">
<TitleTemplate>
<div style="text-align: right">
<Button Icon="@IconType.Outline.Edit" Type="@ButtonType.Text" OnClick="() => OpenColumnPicker()" />
</div>
</TitleTemplate>
<ChildContent>
<Space Direction="DirectionVHType.Horizontal">
<SpaceItem>
<a href="/Collections/@(context.Id)" class="ant-btn ant-btn-primary">Edit</a>
</SpaceItem>
<SpaceItem>
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this Collection?">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
</SpaceItem>
</Space>
</ChildContent>
</ActionColumn>
</Table>
@code {
IEnumerable<Collection> Collections { get; set; } = new List<Collection>();
bool Loading = true;
string Search = "";
TableColumnPicker Picker;
bool ColumnPickerVisible = false;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
LoadData();
Loading = false;
StateHasChanged();
}
}
private async Task LoadData()
{
var fuzzySearch = Search.ToLower().Trim();
Collections = await CollectionService.Get(r => r.Name.ToLower().Contains(fuzzySearch)).OrderBy(r => r.Name).ToListAsync();
}
private void Add()
{
NavigationManager.NavigateTo("/Collections/Add");
}
private async Task Delete(Collection Collection)
{
Collections = new List<Collection>();
Loading = true;
await CollectionService.Delete(Collection);
Collections = await CollectionService.Get(x => true).OrderBy(r => r.Name).ToListAsync();
Loading = false;
}
private async Task OpenColumnPicker()
{
ColumnPickerVisible = true;
}
private async Task CloseColumnPicker()
{
ColumnPickerVisible = false;
}
}

View File

@ -1,94 +0,0 @@
@using System.Diagnostics;
@using LANCommander.Extensions;
@using AntDesign.Charts;
@using System.Collections.Concurrent;
<Spin Spinning="Loading">
<Area @ref="Chart" Config="Config" />
</Spin>
@code {
[Parameter] public int TimerHistory { get; set; }
[Parameter] public int TimerInterval { get; set; }
IChartComponent? Chart;
System.Timers.Timer Timer;
bool Loading = true;
Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
string JsConfig = @"{
meta: {
value: {
alias: 'Speed',
formatter: (v) => humanFileSize(v, true) + '/s'
}
}
}";
AreaConfig Config = new AreaConfig
{
Name = "Network Download Rate",
Padding = "auto",
SeriesField = "series",
YField = "value",
XField = "index",
Animation = false,
XAxis = new ValueCatTimeAxis
{
Visible = false
}
};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (Timer == null)
{
Timer = new System.Timers.Timer();
Timer.Interval = TimerInterval;
Timer.Elapsed += async (s, e) =>
{
await RefreshData();
};
}
await Chart.UpdateChart(Config, null, null, JsConfig);
Timer.Start();
Loading = false;
StateHasChanged();
}
}
private async Task RefreshData()
{
#if WINDOWS
var category = new PerformanceCounterCategory("Network Interface");
foreach (var instance in category.GetInstanceNames())
{
if (!Data.ContainsKey(instance))
Data[instance] = new double[TimerHistory];
if (!PerformanceCounters.ContainsKey(instance))
PerformanceCounters[instance] = new PerformanceCounter("Network Interface", "Bytes Received/sec", instance);
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
}
try
{
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
}
catch { }
#endif
}
}

View File

@ -1,93 +0,0 @@
@using System.Diagnostics;
@using LANCommander.Extensions;
@using AntDesign.Charts;
@using System.Collections.Concurrent;
<Spin Spinning="Loading">
<Area @ref="Chart" Config="Config" />
</Spin>
@code {
[Parameter] public int TimerHistory { get; set; }
[Parameter] public int TimerInterval { get; set; }
IChartComponent? Chart;
System.Timers.Timer Timer;
bool Loading = true;
Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
string JsConfig = @"{
meta: {
value: {
alias: 'Speed',
formatter: (v) => humanFileSize(v, true) + '/s'
}
}
}";
AreaConfig Config = new AreaConfig
{
Name = "Network Upload Rate",
Padding = "auto",
SeriesField = "series",
YField = "value",
XField = "index",
Animation = false,
XAxis = new ValueCatTimeAxis
{
Visible = false
}
};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (Timer == null)
{
Timer = new System.Timers.Timer();
Timer.Interval = TimerInterval;
Timer.Elapsed += async (s, e) =>
{
await RefreshData();
};
}
await Chart.UpdateChart(Config, null, null, JsConfig);
Timer.Start();
Loading = false;
StateHasChanged();
}
}
private async Task RefreshData()
{
#if WINDOWS
var category = new PerformanceCounterCategory("Network Interface");
foreach (var instance in category.GetInstanceNames())
{
if (!Data.ContainsKey(instance))
Data[instance] = new double[TimerHistory];
if (!PerformanceCounters.ContainsKey(instance))
PerformanceCounters[instance] = new PerformanceCounter("Network Interface", "Bytes Sent/sec", instance);
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
}
try
{
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
}
catch { }
#endif
}
}

View File

@ -0,0 +1,65 @@
@using AntDesign.Charts
@using ByteSizeLib
@inject PlaySessionService PlaySessionService
<Spin Spinning="Loading">
<Pie Data="Data" Config="Config" JsConfig="@JsConfig" />
</Spin>
@code {
object[] Data;
bool Loading = true;
string JsConfig = @"{
meta: {
value: {
alias: 'Overall Playtime',
formatter: (v) => new Date(v * 1000).toISOString().slice(11, 19)
}
},
label: {
visible: true,
type: 'outer-center'
}
}";
PieConfig Config = new PieConfig
{
Radius = 0.8,
AngleField = "value",
ColorField = "type",
};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Dictionary<string, TimeSpan> playtimes = new Dictionary<string, TimeSpan>();
var sessions = await PlaySessionService.Get();
foreach (var gameSessions in sessions.Where(s => s.GameId.HasValue && s.GameId.Value != Guid.Empty).GroupBy(s => s.GameId))
{
var total = new TimeSpan();
foreach (var session in gameSessions.Where(gs => gs.Start != null && gs.End != null))
{
total = total.Add(session.End.Value.Subtract(session.Start.Value));
}
playtimes[gameSessions.First().Game.Title] = total;
}
Data = playtimes.Select(pt => new
{
type = pt.Key,
value = (int)pt.Value.TotalSeconds
}).ToArray();
Loading = false;
StateHasChanged();
}
}
}

Some files were not shown because too many files have changed in this diff Show More