Compare commits
No commits in common. "main" and "blazor" have entirely different histories.
|
@ -1,45 +0,0 @@
|
|||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: LANCommander Playnite Plugin
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'LANCommander.Playnite.Extension/**'
|
||||
- 'LANCommander.SDK/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'LANCommander.Playnite.Extension/**'
|
||||
- 'LANCommander.SDK/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v1.1.1
|
||||
- name: Restore NuGet packages
|
||||
run: nuget restore LANCommander.sln
|
||||
- name: Build and Publish Library
|
||||
run: msbuild LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj /p:Configuration=Release /p:OutputPath=Build
|
||||
- name: Download Playnite Release
|
||||
uses: robinraju/release-downloader@v1.7
|
||||
with:
|
||||
repository: JosefNemec/Playnite
|
||||
tag: 10.13
|
||||
fileName: Playnite1013.zip
|
||||
- name: Extract Playnite
|
||||
run: Expand-Archive -Path Playnite1013.zip -DestinationPath Playnite
|
||||
- name: Run Playnite Toolbox
|
||||
run: Playnite/Toolbox.exe pack LANCommander.Playnite.Extension/Build .
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
path: LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_1_0.pext
|
|
@ -1,84 +0,0 @@
|
|||
name: LANCommander Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: frabert/replace-string-action@v2
|
||||
name: Trim Tag Ref
|
||||
id: trim_tag_ref
|
||||
with:
|
||||
string: '${{ github.ref }}'
|
||||
pattern: 'refs/tags/v'
|
||||
replace-with: ''
|
||||
# Server
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3.8.1
|
||||
- run: cd LANCommander/wwwroot/scripts; npm install
|
||||
- name: Build
|
||||
run: dotnet build "./LANCommander/LANCommander.csproj" --no-restore /p:Version="${{ steps.trim_tag_ref.outputs.replaced }}"
|
||||
- name: Publish
|
||||
run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build --self-contained --os win -p:PublishSingleFile=true
|
||||
- name: Sign Windows Binary
|
||||
uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
|
||||
with:
|
||||
certificate: "${{ secrets.CERTIFICATE }}"
|
||||
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
|
||||
filepath: "./_Build/LANCommander.exe"
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: LANCommander-v${{ steps.trim_tag_ref.outputs.replaced }}
|
||||
path: "./_Build"
|
||||
# Client
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v1.1.1
|
||||
- name: Restore NuGet packages
|
||||
run: nuget restore LANCommander.sln
|
||||
- name: Build and Publish Library
|
||||
run: msbuild LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj /p:Configuration=Release /p:OutputPath=Build /p:Version="${{ steps.trim_tag_ref.outputs.replaced }}"
|
||||
- name: Sign Windows Binary
|
||||
uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
|
||||
with:
|
||||
certificate: "${{ secrets.CERTIFICATE }}"
|
||||
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
|
||||
filepath: "./LANCommander.Playnite.Extension/Build/LANCommander.PlaynitePlugin.dll"
|
||||
- name: Download Playnite Release
|
||||
uses: robinraju/release-downloader@v1.7
|
||||
with:
|
||||
repository: JosefNemec/Playnite
|
||||
tag: 10.18
|
||||
fileName: Playnite1018.zip
|
||||
- name: Extract Playnite
|
||||
run: Expand-Archive -Path Playnite1018.zip -DestinationPath Playnite
|
||||
- name: Update Manifest Versioning
|
||||
uses: fjogeleit/yaml-update-action@main
|
||||
with:
|
||||
valueFile: "LANCommander.Playnite.Extension/Build/extension.yaml"
|
||||
propertyPath: "Version"
|
||||
value: "${{ steps.trim_tag_ref.outputs.replaced }}"
|
||||
commitChange: false
|
||||
- name: Run Playnite Toolbox
|
||||
run: Playnite/Toolbox.exe pack LANCommander.Playnite.Extension/Build .
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: LANCommander.PlaynitePlugin-v${{ steps.trim_tag_ref.outputs.replaced }}
|
||||
path: LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_*.pext
|
||||
# Release
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: LANCommander
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'LANCommander/**'
|
||||
- 'LANCommander.PCGamingWiki/**'
|
||||
- 'LANCommander.SDK/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'LANCommander/**'
|
||||
- 'LANCommander.PCGamingWiki/**'
|
||||
- 'LANCommander.SDK/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3.8.1
|
||||
- run: cd LANCommander/wwwroot/scripts; npm install
|
||||
- name: Build
|
||||
run: dotnet build "./LANCommander/LANCommander.csproj" --no-restore
|
||||
- name: Publish
|
||||
run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build --self-contained --os win -p:PublishSingleFile=true
|
||||
- name: Sign Windows Binary
|
||||
uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
|
||||
with:
|
||||
certificate: "${{ secrets.CERTIFICATE }}"
|
||||
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
|
||||
filepath: "./_Build/LANCommander.exe"
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: LANCommander
|
||||
path: "./_Build"
|
|
@ -351,6 +351,3 @@ MigrationBackup/
|
|||
Upload/
|
||||
LANCommander/Icon/
|
||||
LANCommander/Settings.yml
|
||||
LANCommander/Saves/
|
||||
LANCommander/Media/
|
||||
LANCommander/Uploads/
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.53" />
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.3" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -48,9 +48,6 @@ namespace LANCommander.PCGamingWiki
|
|||
{
|
||||
var results = new Dictionary<string, int>();
|
||||
|
||||
if (url == null)
|
||||
return results;
|
||||
|
||||
HtmlWeb web = new HtmlWeb();
|
||||
HtmlDocument dom = web.Load(url);
|
||||
|
||||
|
|
|
@ -1,211 +1,203 @@
|
|||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.Models;
|
||||
using LANCommander.SDK.PowerShell;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Models;
|
||||
using Playnite.SDK.Plugins;
|
||||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using ICSharpCode.SharpZipLib.Core;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using LANCommander.SDK.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
public class LANCommanderInstallController : InstallController
|
||||
{
|
||||
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)
|
||||
{
|
||||
Logger.Trace("Game install triggered, checking connection...");
|
||||
|
||||
while (!Plugin.ValidateConnection())
|
||||
{
|
||||
Logger.Trace("User not authenticated. Opening auth window...");
|
||||
|
||||
Plugin.ShowAuthenticationWindow();
|
||||
}
|
||||
|
||||
var gameId = Guid.Parse(Game.GameId);
|
||||
|
||||
string installDirectory = null;
|
||||
var game = Plugin.LANCommander.GetGame(gameId);
|
||||
var manifest = Plugin.LANCommander.GetGameManifest(gameId);
|
||||
|
||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
var tempFile = Download(game);
|
||||
|
||||
var installDirectory = Extract(game, tempFile);
|
||||
|
||||
var installInfo = new GameInstallationData()
|
||||
{
|
||||
var gameManager = new GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory);
|
||||
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
stopwatch.Start();
|
||||
|
||||
var lastTotalSize = 0d;
|
||||
var speed = 0d;
|
||||
|
||||
gameManager.OnArchiveExtractionProgress += (long pos, long len) =>
|
||||
{
|
||||
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();
|
||||
}
|
||||
InstallDirectory = installDirectory
|
||||
};
|
||||
|
||||
gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) =>
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
gameManager.CancelInstall();
|
||||
PlayniteGame.InstallDirectory = installDirectory;
|
||||
|
||||
progress.IsIndeterminate = true;
|
||||
WriteManifest(manifest, installDirectory);
|
||||
|
||||
SaveScript(game, installDirectory, ScriptType.Install);
|
||||
SaveScript(game, installDirectory, ScriptType.Uninstall);
|
||||
SaveScript(game, installDirectory, ScriptType.NameChange);
|
||||
SaveScript(game, installDirectory, ScriptType.KeyChange);
|
||||
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.NameChange, Plugin.Settings.PlayerName);
|
||||
|
||||
var key = Plugin.LANCommander.GetAllocatedKey(game.Id);
|
||||
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.KeyChange, $"\"{key}\"");
|
||||
}
|
||||
};
|
||||
catch { }
|
||||
|
||||
installDirectory = gameManager.Install(gameId);
|
||||
|
||||
stopwatch.Stop();
|
||||
},
|
||||
new GlobalProgressOptions($"Preparing to download {Game.Name}")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = true,
|
||||
});
|
||||
|
||||
// Install any redistributables
|
||||
var game = Plugin.LANCommanderClient.GetGame(gameId);
|
||||
|
||||
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
||||
{
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
var redistributableManager = new RedistributableManager(Plugin.LANCommanderClient);
|
||||
|
||||
redistributableManager.Install(game);
|
||||
},
|
||||
new GlobalProgressOptions("Installing redistributables...")
|
||||
{
|
||||
IsIndeterminate = true,
|
||||
Cancelable = false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
|
||||
Plugin.UpdateGame(manifest);
|
||||
|
||||
var installInfo = new GameInstallationData
|
||||
{
|
||||
InstallDirectory = installDirectory,
|
||||
};
|
||||
|
||||
RunInstallScript(installDirectory);
|
||||
RunNameChangeScript(installDirectory);
|
||||
RunKeyChangeScript(installDirectory);
|
||||
Plugin.UpdateGame(manifest, gameId);
|
||||
|
||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
}
|
||||
else if (result.Canceled)
|
||||
|
||||
private string Download(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
var dbGame = Plugin.PlayniteApi.Database.Games.Get(Game.Id);
|
||||
string tempFile = String.Empty;
|
||||
|
||||
dbGame.IsInstalling = false;
|
||||
dbGame.IsInstalled = false;
|
||||
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)
|
||||
{
|
||||
|
||||
Plugin.PlayniteApi.Database.Games.Update(dbGame);
|
||||
}
|
||||
else if (result.Error != null)
|
||||
throw result.Error;
|
||||
}
|
||||
|
||||
private int RunInstallScript(string installDirectory)
|
||||
tempFile = destination;
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {game.Title}...")
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install);
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
|
||||
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.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install));
|
||||
|
||||
return script.Execute();
|
||||
return tempFile;
|
||||
}
|
||||
else
|
||||
throw new Exception("Game failed to download!");
|
||||
}
|
||||
|
||||
return 0;
|
||||
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 =>
|
||||
{
|
||||
ZipFile file = null;
|
||||
|
||||
try
|
||||
{
|
||||
FileStream fs = File.OpenRead(archivePath);
|
||||
|
||||
file = new ZipFile(fs);
|
||||
|
||||
progress.ProgressMaxValue = file.Count;
|
||||
|
||||
foreach (ZipEntry entry in file)
|
||||
{
|
||||
if (!entry.IsFile)
|
||||
continue;
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
var zipStream = file.GetInputStream(entry);
|
||||
|
||||
var entryDestination = Path.Combine(destination, entry.Name);
|
||||
var entryDirectory = Path.GetDirectoryName(entryDestination);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(entryDirectory))
|
||||
Directory.CreateDirectory(entryDirectory);
|
||||
|
||||
using (FileStream streamWriter = File.Create(entryDestination))
|
||||
{
|
||||
StreamUtils.Copy(zipStream, streamWriter, buffer);
|
||||
}
|
||||
|
||||
private int RunNameChangeScript(string installDirectory)
|
||||
progress.CurrentProgressValue = entry.ZipFileIndex;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange);
|
||||
|
||||
if (File.Exists(path))
|
||||
if (file != null)
|
||||
{
|
||||
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();
|
||||
file.IsStreamOwner = true;
|
||||
file.Close();
|
||||
}
|
||||
|
||||
return 0;
|
||||
File.Delete(archivePath);
|
||||
}
|
||||
},
|
||||
new GlobalProgressOptions($"Extracting {game.Title}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private int RunKeyChangeScript(string installDirectory)
|
||||
private void WriteManifest(SDK.GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange);
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var script = new PowerShellScript();
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
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();
|
||||
File.WriteAllText(Path.Combine(installDirectory, "_manifest.yml"), yaml);
|
||||
}
|
||||
|
||||
return 0;
|
||||
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);
|
||||
|
||||
File.WriteAllText(filename, script.Contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -35,29 +34,20 @@
|
|||
<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="ByteSize, Version=2.1.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ByteSize.2.1.1\lib\net45\ByteSize.dll</HintPath>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=1.4.1.12, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpZipLib.1.4.1\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<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 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.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 Include="Playnite.SDK, Version=6.8.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PlayniteSDK.6.8.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.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>
|
||||
|
@ -67,24 +57,21 @@
|
|||
<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.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 Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Memory.4.5.4\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 Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\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 Include="System.Text.Encodings.Web, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Encodings.Web.5.0.0\lib\net461\System.Text.Encodings.Web.dll</HintPath>
|
||||
</Reference>
|
||||
<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=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 Include="System.Text.Json, Version=5.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Json.5.0.1\lib\net461\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>
|
||||
|
@ -101,20 +88,17 @@
|
|||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<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 Include="YamlDotNet, Version=12.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.12.3.1\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
|
||||
<Compile Include="PlayniteLogger.cs" />
|
||||
<Compile Include="SaveController.cs" />
|
||||
<Compile Include="PowerShellRuntime.cs" />
|
||||
<Compile Include="UninstallController.cs" />
|
||||
<Compile Include="InstallController.cs" />
|
||||
<Compile Include="LANCommanderClient.cs" />
|
||||
<Compile Include="LANCommanderLibraryClient.cs" />
|
||||
<Compile Include="LANCommanderLibraryPlugin.cs" />
|
||||
<Compile Include="ViewModels\LANCommanderSettingsViewModel.cs" />
|
||||
<Compile Include="Views\LANCommanderSettingsView.xaml.cs">
|
||||
|
@ -155,15 +139,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>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,208 @@
|
|||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Models;
|
||||
using RestSharp;
|
||||
using RestSharp.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
internal class LANCommanderClient
|
||||
{
|
||||
public readonly RestClient Client;
|
||||
public AuthToken Token;
|
||||
|
||||
public LANCommanderClient(string baseUrl)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(baseUrl))
|
||||
Client = new RestClient(baseUrl);
|
||||
}
|
||||
|
||||
private T PostRequest<T>(string route, object body)
|
||||
{
|
||||
var request = new RestRequest(route)
|
||||
.AddJsonBody(body)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var response = Client.Post<T>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
private T GetRequest<T>(string route)
|
||||
{
|
||||
var request = new RestRequest(route)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var response = Client.Get<T>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
private string DownloadRequest(string route, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
route = route.TrimStart('/');
|
||||
|
||||
var client = new WebClient();
|
||||
var tempFile = Path.GetTempFileName();
|
||||
|
||||
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
||||
client.DownloadProgressChanged += (s, e) => progressHandler(e);
|
||||
client.DownloadFileCompleted += (s, e) => completeHandler(e);
|
||||
|
||||
client.DownloadFileAsync(new Uri($"{Client.BaseUrl}{route}"), tempFile);
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public AuthResponse Authenticate(string username, string password)
|
||||
{
|
||||
var response = Client.Post<AuthResponse>(new RestRequest("/api/Auth").AddJsonBody(new AuthRequest()
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
}));
|
||||
|
||||
if (String.IsNullOrWhiteSpace(response.Data.AccessToken))
|
||||
throw new WebException("Invalid username or password");
|
||||
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.OK:
|
||||
return response.Data;
|
||||
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.BadRequest:
|
||||
throw new WebException("Invalid username or password");
|
||||
|
||||
default:
|
||||
throw new WebException("Could not communicate with the server");
|
||||
}
|
||||
}
|
||||
|
||||
public AuthResponse RefreshToken(AuthToken token)
|
||||
{
|
||||
var request = new RestRequest("/api/Auth/Refresh")
|
||||
.AddJsonBody(token);
|
||||
|
||||
var response = Client.Post<AuthResponse>(request);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
throw new WebException(response.ErrorMessage);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public bool ValidateToken(AuthToken token)
|
||||
{
|
||||
var request = new RestRequest("/api/Auth/Validate")
|
||||
.AddHeader("Authorization", $"Bearer {token.AccessToken}");
|
||||
|
||||
var response = Client.Post(request);
|
||||
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
public IEnumerable<Game> GetGames()
|
||||
{
|
||||
return GetRequest<IEnumerable<Game>>("/api/Games");
|
||||
}
|
||||
|
||||
public Game GetGame(Guid id)
|
||||
{
|
||||
return GetRequest<Game>($"/api/Games/{id}");
|
||||
}
|
||||
|
||||
public GameManifest GetGameManifest(Guid id)
|
||||
{
|
||||
return GetRequest<GameManifest>($"/api/Games/{id}/Manifest");
|
||||
}
|
||||
|
||||
public string DownloadGame(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Games/{id}/Download", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public string DownloadArchive(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public string GetKey(Guid id)
|
||||
{
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
var request = new KeyRequest()
|
||||
{
|
||||
GameId = id,
|
||||
MacAddress = macAddress,
|
||||
ComputerName = Environment.MachineName,
|
||||
IpAddress = GetIpAddress(),
|
||||
};
|
||||
|
||||
var response = PostRequest<Key>($"/api/Keys/Get", request);
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
public string GetAllocatedKey(Guid id)
|
||||
{
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
var request = new KeyRequest()
|
||||
{
|
||||
GameId = id,
|
||||
MacAddress = macAddress,
|
||||
ComputerName = Environment.MachineName,
|
||||
IpAddress = GetIpAddress(),
|
||||
};
|
||||
|
||||
var response = PostRequest<Key>($"/api/Keys/GetAllocated/{id}", request);
|
||||
|
||||
if (response == null)
|
||||
return String.Empty;
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
public string GetNewKey(Guid id)
|
||||
{
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
var request = new KeyRequest()
|
||||
{
|
||||
GameId = id,
|
||||
MacAddress = macAddress,
|
||||
ComputerName = Environment.MachineName,
|
||||
IpAddress = GetIpAddress(),
|
||||
};
|
||||
|
||||
var response = PostRequest<Key>($"/api/Keys/Allocate/{id}", request);
|
||||
|
||||
if (response == null)
|
||||
return String.Empty;
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
private string GetMacAddress()
|
||||
{
|
||||
return NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||
.Select(nic => nic.GetPhysicalAddress().ToString())
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private string GetIpAddress()
|
||||
{
|
||||
return Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Playnite.SDK;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
public class LANCommanderLibraryClient : LibraryClient
|
||||
{
|
||||
public override bool IsInstalled => true;
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
|
||||
System.Diagnostics.Process.Start("https://localhost:7087");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using LANCommander.PlaynitePlugin.Extensions;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.PowerShell;
|
||||
using LANCommander.SDK;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Events;
|
||||
using Playnite.SDK.Models;
|
||||
|
@ -8,12 +7,18 @@ using Playnite.SDK.Plugins;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using PN = Playnite;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
|
@ -22,13 +27,12 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
internal LANCommanderSettingsViewModel Settings { get; set; }
|
||||
internal LANCommander.SDK.Client LANCommanderClient { get; set; }
|
||||
internal LANCommanderSaveController SaveController { get; set; }
|
||||
internal LANCommanderClient LANCommander { get; set; }
|
||||
internal PowerShellRuntime PowerShellRuntime { get; set; }
|
||||
|
||||
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
|
||||
public override string Name => "LANCommander";
|
||||
|
||||
internal Dictionary<Guid, string> DownloadCache = new Dictionary<Guid, string>();
|
||||
public override LibraryClient Client { get; } = new LANCommanderLibraryClient();
|
||||
|
||||
public LANCommanderLibraryPlugin(IPlayniteAPI api) : base(api)
|
||||
{
|
||||
|
@ -39,102 +43,66 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
Settings = new LANCommanderSettingsViewModel(this);
|
||||
|
||||
LANCommanderClient = new SDK.Client(Settings.ServerAddress, new PlayniteLogger(Logger));
|
||||
LANCommanderClient.UseToken(new SDK.Models.AuthToken()
|
||||
LANCommander = new LANCommanderClient(Settings.ServerAddress);
|
||||
LANCommander.Token = new SDK.Models.AuthToken()
|
||||
{
|
||||
AccessToken = Settings.AccessToken,
|
||||
RefreshToken = Settings.RefreshToken,
|
||||
});
|
||||
};
|
||||
|
||||
// GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
||||
PowerShellRuntime = new PowerShellRuntime();
|
||||
}
|
||||
|
||||
api.UriHandler.RegisterSource("lancommander", args =>
|
||||
public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
|
||||
{
|
||||
if (args.Arguments.Length == 0)
|
||||
return;
|
||||
|
||||
Guid gameId;
|
||||
|
||||
switch (args.Arguments[0].ToLower())
|
||||
{
|
||||
case "install":
|
||||
if (args.Arguments.Length == 1)
|
||||
break;
|
||||
|
||||
if (Guid.TryParse(args.Arguments[1], out gameId))
|
||||
PlayniteApi.InstallGame(gameId);
|
||||
break;
|
||||
|
||||
case "run":
|
||||
if (args.Arguments.Length == 1)
|
||||
break;
|
||||
|
||||
if (Guid.TryParse(args.Arguments[1], out gameId))
|
||||
PlayniteApi.StartGame(gameId);
|
||||
break;
|
||||
|
||||
case "connect":
|
||||
if (args.Arguments.Length == 1)
|
||||
if (LANCommander.Token == null || !LANCommander.ValidateToken(LANCommander.Token))
|
||||
{
|
||||
ShowAuthenticationWindow();
|
||||
break;
|
||||
}
|
||||
|
||||
ShowAuthenticationWindow(HttpUtility.UrlDecode(args.Arguments[1]));
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public bool ValidateConnection()
|
||||
{
|
||||
return LANCommanderClient.ValidateToken();
|
||||
}
|
||||
|
||||
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
|
||||
{
|
||||
var gameMetadata = new List<GameMetadata>();
|
||||
|
||||
if (!ValidateConnection())
|
||||
{
|
||||
Logger.Trace("Authentication invalid, showing auth window...");
|
||||
ShowAuthenticationWindow();
|
||||
|
||||
if (!ValidateConnection())
|
||||
{
|
||||
Logger.Trace("User cancelled authentication.");
|
||||
|
||||
throw new Exception("You must set up a valid connection to a LANCommander server.");
|
||||
}
|
||||
}
|
||||
|
||||
var games = LANCommanderClient
|
||||
.GetGames()
|
||||
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
|
||||
|
||||
foreach (var game in games)
|
||||
if (LANCommander.Token == null || !LANCommander.ValidateToken(LANCommander.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
|
||||
var response = LANCommander.RefreshToken(LANCommander.Token);
|
||||
|
||||
var manifest = LANCommanderClient.GetGameManifest(game.Id);
|
||||
Logger.Trace("Successfully grabbed game manifest");
|
||||
LANCommander.Token.AccessToken = response.AccessToken;
|
||||
LANCommander.Token.RefreshToken = response.RefreshToken;
|
||||
|
||||
if (!LANCommander.ValidateToken(LANCommander.Token))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ShowAuthenticationWindow();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var games = LANCommander
|
||||
.GetGames()
|
||||
.Where(g => g.Archives != null && g.Archives.Count() > 0);
|
||||
|
||||
foreach (var game in games)
|
||||
{
|
||||
var manifest = LANCommander.GetGameManifest(game.Id);
|
||||
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
|
||||
|
||||
if (existingGame != null)
|
||||
{
|
||||
Logger.Trace("Game already exists in library, updating metadata...");
|
||||
|
||||
UpdateGame(manifest);
|
||||
UpdateGame(manifest, game.Id);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.Trace("Game does not exist in the library, importing metadata...");
|
||||
|
||||
var metadata = new GameMetadata()
|
||||
{
|
||||
IsInstalled = false,
|
||||
|
@ -144,6 +112,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
GameId = game.Id.ToString(),
|
||||
ReleaseDate = new ReleaseDate(manifest.ReleasedOn),
|
||||
//Version = game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault().Version,
|
||||
Icon = new MetadataFile($"{Settings.ServerAddress}{manifest.Icon}"),
|
||||
GameActions = game.Actions.OrderBy(a => a.SortOrder).Select(a => new PN.SDK.Models.GameAction()
|
||||
{
|
||||
Name = a.Name,
|
||||
|
@ -180,22 +149,14 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (manifest.OnlineMultiplayer != null)
|
||||
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(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(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(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
||||
|
||||
gameMetadata.Add(metadata);
|
||||
};
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Could not update game \"{game.Title}\" in library");
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return gameMetadata;
|
||||
}
|
||||
|
@ -218,43 +179,25 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public override IEnumerable<GameMenuItem> GetGameMenuItems(GetGameMenuItemsArgs args)
|
||||
{
|
||||
Logger.Trace("Populating game menu items...");
|
||||
|
||||
if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory))
|
||||
{
|
||||
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);
|
||||
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
|
||||
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
|
||||
|
||||
if (File.Exists(nameChangeScriptPath))
|
||||
{
|
||||
Logger.Trace($"Name change script found at path {nameChangeScriptPath}");
|
||||
|
||||
yield return new GameMenuItem
|
||||
{
|
||||
Description = "Change Player Name",
|
||||
Action = (nameChangeArgs) =>
|
||||
{
|
||||
var oldName = Settings.PlayerName;
|
||||
|
||||
var result = PlayniteApi.Dialogs.SelectString("Enter your player name", "Change Player Name", Settings.PlayerName);
|
||||
|
||||
if (result.Result == true)
|
||||
{
|
||||
var game = nameChangeArgs.Games.First();
|
||||
|
||||
RunNameChangeScript(game.InstallDirectory, oldName, result.SelectedString);
|
||||
|
||||
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||
}
|
||||
PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}""");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (File.Exists(keyChangeScriptPath))
|
||||
{
|
||||
Logger.Trace($"Key change script found at path {keyChangeScriptPath}");
|
||||
|
||||
yield return new GameMenuItem
|
||||
{
|
||||
Description = "Change Game Key",
|
||||
|
@ -265,12 +208,12 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
|
||||
{
|
||||
// NUKIEEEE
|
||||
var newKey = LANCommanderClient.GetNewKey(gameId);
|
||||
var newKey = LANCommander.GetNewKey(gameId);
|
||||
|
||||
if (String.IsNullOrEmpty(newKey))
|
||||
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
|
||||
else
|
||||
RunKeyChangeScript(keyChangeArgs.Games.First().InstallDirectory, newKey);
|
||||
PowerShellRuntime.RunScript(keyChangeArgs.Games.First(), SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -279,26 +222,6 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (File.Exists(installScriptPath))
|
||||
{
|
||||
Logger.Trace($"Install script found at path {installScriptPath}");
|
||||
|
||||
yield return new GameMenuItem
|
||||
{
|
||||
Description = "Run Install Script",
|
||||
Action = (installArgs) =>
|
||||
{
|
||||
Guid gameId;
|
||||
|
||||
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
|
||||
RunInstallScript(installArgs.Games.First().InstallDirectory);
|
||||
else
|
||||
PlayniteApi.Dialogs.ShowErrorMessage("This game could not be found on the server. Your game may be corrupted.");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To add new main menu items override GetMainMenuItems
|
||||
|
@ -312,61 +235,6 @@ namespace LANCommander.PlaynitePlugin
|
|||
ShowNameChangeWindow();
|
||||
}
|
||||
};
|
||||
|
||||
yield return new MainMenuItem
|
||||
{
|
||||
Description = "Clear Download Cache",
|
||||
Action = (args2) =>
|
||||
{
|
||||
foreach (var gameId in DownloadCache.Keys)
|
||||
{
|
||||
File.Delete(DownloadCache[gameId]);
|
||||
DownloadCache.Remove(gameId);
|
||||
}
|
||||
|
||||
PlayniteApi.Dialogs.ShowMessage("The download cache has been cleared and any temporary files have been deleted.", "Cache Cleared!", MessageBoxButton.OK);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnGameStarting(OnGameStartingEventArgs args)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()
|
||||
|
@ -382,8 +250,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
Padding = new Thickness(10, 0, 10, 0),
|
||||
|
||||
},
|
||||
Activated = () =>
|
||||
{
|
||||
Activated = () => {
|
||||
ShowNameChangeWindow();
|
||||
}
|
||||
};
|
||||
|
@ -401,58 +268,31 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public void ShowNameChangeWindow()
|
||||
{
|
||||
Logger.Trace("Showing name change dialog!");
|
||||
|
||||
var result = PlayniteApi.Dialogs.SelectString("Enter your new player name. This will change your name across all installed games!", "Enter Name", Settings.PlayerName);
|
||||
|
||||
if (result.Result == true)
|
||||
{
|
||||
Logger.Trace($"New name entered was \"{result.SelectedString}\"");
|
||||
|
||||
// Check to make sure they're staying in ASCII encoding
|
||||
if (String.IsNullOrEmpty(result.SelectedString) || result.SelectedString.Any(c => c > sbyte.MaxValue))
|
||||
{
|
||||
PlayniteApi.Dialogs.ShowErrorMessage("The name you supplied is invalid. Try again.");
|
||||
|
||||
Logger.Trace("An invalid name was specified. Showing the name dialog again...");
|
||||
|
||||
ShowNameChangeWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldName = Settings.PlayerName;
|
||||
|
||||
Settings.PlayerName = result.SelectedString;
|
||||
|
||||
Logger.Trace($"New player name of \"{Settings.PlayerName}\" has been set!");
|
||||
|
||||
Logger.Trace("Saving plugin settings!");
|
||||
SavePluginSettings(Settings);
|
||||
|
||||
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
|
||||
|
||||
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||
|
||||
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");
|
||||
|
||||
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();
|
||||
PowerShellRuntime.RunScripts(games, SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Logger.Trace("Name change was cancelled");
|
||||
}
|
||||
|
||||
public Window ShowAuthenticationWindow(string serverAddress = null, EventHandler onClose = null)
|
||||
public Window ShowAuthenticationWindow()
|
||||
{
|
||||
Window window = null;
|
||||
Application.Current.Dispatcher.Invoke((Action)delegate
|
||||
|
@ -468,25 +308,20 @@ namespace LANCommander.PlaynitePlugin
|
|||
window.Content = new Views.Authentication(this);
|
||||
window.DataContext = new ViewModels.Authentication()
|
||||
{
|
||||
ServerAddress = serverAddress ?? Settings?.ServerAddress
|
||||
ServerAddress = Settings?.ServerAddress
|
||||
};
|
||||
|
||||
window.Owner = PlayniteApi.Dialogs.GetCurrentAppWindow();
|
||||
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
window.ResizeMode = ResizeMode.NoResize;
|
||||
|
||||
if (onClose != null)
|
||||
window.Closed += onClose;
|
||||
|
||||
window.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
|
||||
window.ShowDialog();
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public void UpdateGame(SDK.GameManifest manifest)
|
||||
public void UpdateGame(SDK.GameManifest manifest, Guid gameId)
|
||||
{
|
||||
var game = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == manifest?.Id.ToString());
|
||||
var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString());
|
||||
|
||||
if (game == null)
|
||||
return;
|
||||
|
@ -496,8 +331,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
else
|
||||
game.GameActions.Clear();
|
||||
|
||||
if (manifest.Actions == null)
|
||||
throw new Exception("The game has no actions defined.");
|
||||
game.Icon = $"{Settings.ServerAddress}{manifest.Icon}";
|
||||
|
||||
foreach (var action in manifest.Actions.OrderBy(a => a.SortOrder))
|
||||
{
|
||||
|
@ -568,77 +402,5 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
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.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
internal class PowerShellRuntime
|
||||
{
|
||||
[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 RunScript(string path, bool asAdmin = false, string arguments = null)
|
||||
{
|
||||
var wow64Value = IntPtr.Zero;
|
||||
|
||||
Wow64DisableWow64FsRedirection(ref wow64Value);
|
||||
|
||||
var process = new Process();
|
||||
|
||||
process.StartInfo.FileName = "powershell.exe";
|
||||
process.StartInfo.Arguments = $@"-ExecutionPolicy Unrestricted -File ""{path}""";
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
|
||||
if (arguments != null)
|
||||
process.StartInfo.Arguments += " " + arguments;
|
||||
|
||||
if (asAdmin)
|
||||
process.StartInfo.Verb = "runas";
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
Wow64RevertWow64FsRedirection(ref wow64Value);
|
||||
}
|
||||
|
||||
public void RunScriptsAsAdmin(IEnumerable<string> paths, string arguments = null)
|
||||
{
|
||||
// Concatenate scripts
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
var contents = File.ReadAllText(path);
|
||||
|
||||
sb.AppendLine(contents);
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
var scriptPath = Path.GetTempFileName();
|
||||
|
||||
File.WriteAllText(scriptPath, sb.ToString());
|
||||
|
||||
RunScript(scriptPath, true, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +1,42 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.PowerShell;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Models;
|
||||
using Playnite.SDK.Plugins;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using ICSharpCode.SharpZipLib.Core;
|
||||
using LANCommander.SDK.Enums;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
public class LANCommanderUninstallController : UninstallController
|
||||
{
|
||||
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
|
||||
{
|
||||
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");
|
||||
PowerShellRuntime.RunScript(Game, ScriptType.Uninstall);
|
||||
}
|
||||
catch { }
|
||||
|
||||
gameManager.Uninstall(Game.InstallDirectory);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "There was an error uninstalling the game");
|
||||
}
|
||||
if (!String.IsNullOrWhiteSpace(Game.InstallDirectory) && Directory.Exists(Game.InstallDirectory))
|
||||
Directory.Delete(Game.InstallDirectory, true);
|
||||
|
||||
InvokeOnUninstalled(new GameUninstalledEventArgs());
|
||||
}
|
||||
|
|
|
@ -34,10 +34,6 @@ namespace LANCommander.PlaynitePlugin
|
|||
InstallDirectory = settings.InstallDirectory;
|
||||
PlayerName = settings.PlayerName;
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallDirectory = "C:\\Games";
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginEdit()
|
||||
|
|
|
@ -202,7 +202,6 @@
|
|||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
|
@ -215,20 +214,6 @@
|
|||
<TextBox Grid.Row="1" Grid.Column="1" Name="Username" Text="{Binding UserName}" KeyDown="TextBox_KeyDown" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Password" />
|
||||
<PasswordBox Grid.Row="2" Grid.Column="1" Name="Password" PasswordChanged="Password_PasswordChanged" KeyDown="TextBox_KeyDown" />
|
||||
|
||||
<GridSplitter Grid.Row="4" />
|
||||
|
||||
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Row="0" Grid.Column="0" Click="LoginButton_Click" x:Name="LoginButton">Login</Button>
|
||||
<Button Grid.Row="0" Grid.Column="1" Click="RegisterButton_Click" x:Name="RegisterButton">Register</Button>
|
||||
</Grid>
|
||||
<Button Grid.Row="3" Grid.ColumnSpan="2" Click="LoginButton_Click" x:Name="LoginButton">Login</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
@ -21,8 +20,6 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
{
|
||||
public partial class Authentication : UserControl
|
||||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
|
||||
private LANCommanderLibraryPlugin Plugin;
|
||||
private ViewModels.Authentication Context { get { return (ViewModels.Authentication)DataContext; } }
|
||||
|
||||
|
@ -34,21 +31,14 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
|
||||
var probe = new Probe("LANCommander");
|
||||
|
||||
Logger.Trace("Attempting to find a LANCommander server on the local network...");
|
||||
|
||||
probe.BeaconsUpdated += beacons => Dispatcher.BeginInvoke((System.Action)(() =>
|
||||
{
|
||||
var beacon = beacons.First();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(beacon.Data) && Uri.TryCreate(beacon.Data, UriKind.Absolute, out var beaconUri))
|
||||
Context.ServerAddress = beaconUri.ToString();
|
||||
else
|
||||
Context.ServerAddress = $"http://{beacon.Address.Address}:{beacon.Address.Port}";
|
||||
|
||||
this.ServerAddress.Text = Context.ServerAddress;
|
||||
|
||||
Logger.Trace($"The beacons have been lit! LANCommander calls for aid! {Context.ServerAddress}");
|
||||
|
||||
probe.Stop();
|
||||
}));
|
||||
|
||||
|
@ -69,11 +59,6 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
Authenticate();
|
||||
}
|
||||
|
||||
private void RegisterButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Register();
|
||||
}
|
||||
|
||||
private void Password_PasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext != null)
|
||||
|
@ -82,39 +67,21 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
}
|
||||
}
|
||||
|
||||
public RelayCommand<object> LoginCommand
|
||||
{
|
||||
get => new RelayCommand<object>(async (a) =>
|
||||
{
|
||||
await Authenticate();
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Authenticate()
|
||||
private void Authenticate()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoginButton.Dispatcher.Invoke(new System.Action(() =>
|
||||
{
|
||||
LoginButton.IsEnabled = false;
|
||||
LoginButton.Content = "Logging in...";
|
||||
}));
|
||||
|
||||
if (Plugin.LANCommanderClient == null)
|
||||
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
|
||||
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
|
||||
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
|
||||
else
|
||||
Plugin.LANCommanderClient.UseServerAddress(Context.ServerAddress);
|
||||
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
|
||||
|
||||
var response = await Plugin.LANCommanderClient.AuthenticateAsync(Context.UserName, Context.Password);
|
||||
var response = Plugin.LANCommander.Authenticate(Context.UserName, Context.Password);
|
||||
|
||||
Plugin.Settings.ServerAddress = Context.ServerAddress;
|
||||
Plugin.Settings.AccessToken = response.AccessToken;
|
||||
Plugin.Settings.RefreshToken = response.RefreshToken;
|
||||
|
||||
var profile = Plugin.LANCommanderClient.GetProfile();
|
||||
|
||||
Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias;
|
||||
|
||||
// Probably unneeded, but why not be more secure?
|
||||
Context.Password = String.Empty;
|
||||
|
||||
|
@ -124,49 +91,7 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, ex.Message);
|
||||
|
||||
Plugin.PlayniteApi.Dialogs.ShowErrorMessage(ex.Message);
|
||||
|
||||
LoginButton.Dispatcher.Invoke(new System.Action(() =>
|
||||
{
|
||||
LoginButton.IsEnabled = true;
|
||||
LoginButton.Content = "Login";
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Register()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoginButton.IsEnabled = false;
|
||||
RegisterButton.IsEnabled = false;
|
||||
RegisterButton.Content = "Working...";
|
||||
|
||||
if (Plugin.LANCommanderClient == null)
|
||||
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
|
||||
|
||||
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;
|
||||
|
||||
Context.Password = String.Empty;
|
||||
|
||||
Plugin.SavePluginSettings(Plugin.Settings);
|
||||
|
||||
Window.GetWindow(this).Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.PlayniteApi.Dialogs.ShowErrorMessage(ex.Message);
|
||||
|
||||
LoginButton.IsEnabled = true;
|
||||
RegisterButton.IsEnabled = true;
|
||||
RegisterButton.Content = "Register";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,44 +15,29 @@
|
|||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="75" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="Install Directory" />
|
||||
<TextBox Name="PART_InstallDirectory" Grid.Column="2" Text="{Binding InstallDirectory}" />
|
||||
<Button Grid.Column="4" Content="Browse" Click="SelectInstallDirectory_Click" VerticalAlignment="Center" Grid.ColumnSpan="2" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="75" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="Server Address" />
|
||||
<TextBox Name="PART_ServerAddress" Grid.Column="2" Text="{Binding ServerAddress}" IsEnabled="false" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Margin="0,10,0,0">
|
||||
<Grid Height="40" Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Name="PART_AuthenticationButton" Content="Authenticate" Grid.Column="0" Click="AuthenticateButton_Click" VerticalAlignment="Center" />
|
||||
<Button Name="PART_DisconnectButton" Content="Disconnect" Grid.Column="0" Click="DisconnectButton_Click" VerticalAlignment="Center" />
|
||||
<Label Name="PART_AuthenticateLabel" Margin="20,0,0,0" VerticalAlignment="Center" Grid.Column="3" />
|
||||
<Label Name="PART_AuthenticateLabel" Margin="20,0,0,0" VerticalAlignment="Center" Grid.Column="1" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -39,7 +39,6 @@ namespace LANCommander.PlaynitePlugin
|
|||
PART_AuthenticateLabel.Content = "Checking authentication status...";
|
||||
PART_AuthenticationButton.IsEnabled = false;
|
||||
PART_InstallDirectory.Text = Settings.InstallDirectory;
|
||||
PART_ServerAddress.Text = Settings.ServerAddress;
|
||||
|
||||
var token = new AuthToken()
|
||||
{
|
||||
|
@ -47,7 +46,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
RefreshToken = Settings.RefreshToken,
|
||||
};
|
||||
|
||||
var task = Task.Run(() => Plugin.LANCommanderClient.ValidateToken(token))
|
||||
var task = Task.Run(() => Plugin.LANCommander.ValidateToken(token))
|
||||
.ContinueWith(antecedent =>
|
||||
{
|
||||
try
|
||||
|
@ -58,17 +57,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
PART_AuthenticateLabel.Content = "Authentication failed!";
|
||||
PART_AuthenticationButton.IsEnabled = true;
|
||||
PART_AuthenticationButton.Visibility = Visibility.Visible;
|
||||
PART_DisconnectButton.IsEnabled = false;
|
||||
PART_DisconnectButton.Visibility = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
PART_AuthenticateLabel.Content = "Connection established!";
|
||||
PART_AuthenticationButton.IsEnabled = false;
|
||||
PART_AuthenticationButton.Visibility = Visibility.Hidden;
|
||||
PART_DisconnectButton.IsEnabled = true;
|
||||
PART_DisconnectButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -81,22 +74,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
private void AuthenticateButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var authWindow = Plugin.ShowAuthenticationWindow(Settings.ServerAddress, AuthWindow_Closed);
|
||||
}
|
||||
var authWindow = Plugin.ShowAuthenticationWindow();
|
||||
|
||||
private void DisconnectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Plugin.Settings.AccessToken = String.Empty;
|
||||
Plugin.Settings.RefreshToken = String.Empty;
|
||||
Plugin.LANCommanderClient.UseToken(null);
|
||||
|
||||
Plugin.SavePluginSettings(Plugin.Settings);
|
||||
|
||||
PART_AuthenticateLabel.Content = "Not Authenticated";
|
||||
PART_AuthenticationButton.IsEnabled = true;
|
||||
PART_AuthenticationButton.Visibility = Visibility.Visible;
|
||||
PART_DisconnectButton.IsEnabled = false;
|
||||
PART_DisconnectButton.Visibility = Visibility.Hidden;
|
||||
authWindow.Closed += AuthWindow_Closed;
|
||||
}
|
||||
|
||||
private void AuthWindow_Closed(object sender, EventArgs e)
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.3" newVersion="7.0.0.3" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.1" newVersion="5.0.0.1" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
|
@ -18,18 +18,6 @@
|
|||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 9.1 KiB |
|
@ -1,50 +0,0 @@
|
|||
AddonId: LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc
|
||||
Packages:
|
||||
- Version: 0.1.2
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-09-15
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.1.2/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc-v0.1.2.pext
|
||||
Changelog:
|
||||
- Initial submission to Playnite addon database
|
||||
- Version: 0.1.3
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-09-21
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.1.3/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_v0.1.3.pext
|
||||
Changelog:
|
||||
- Added the ability to cancel installs
|
||||
- Version: 0.2.0
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-10-28
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.0/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_0.pext
|
||||
Changelog:
|
||||
- Player name is now persisted across machines and is stored as the user's profile name
|
||||
- Added support for servers to bundle redistributables with games
|
||||
- Added support for opening authentication window using Playnite API
|
||||
- Version: 0.2.1
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-11-03
|
||||
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
|
|
@ -1,24 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<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="Microsoft.Bcl.AsyncInterfaces" version="5.0.0" targetFramework="net462" />
|
||||
<package id="NuGet.CommandLine" version="4.4.3" targetFramework="net462" developmentDependency="true" />
|
||||
<package id="PlayniteSDK" version="6.8.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.34.2" targetFramework="net462" />
|
||||
<package id="SharpZipLib" version="1.4.1" 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.Memory" version="4.5.4" 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.Text.Encodings.Web" version="8.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Json" version="8.0.0" targetFramework="net462" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encodings.Web" version="5.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Json" version="5.0.1" 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" />
|
||||
<package id="YamlDotNet" version="12.3.1" targetFramework="net462" />
|
||||
</packages>
|
|
@ -1,64 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
<?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>
|
|
@ -1,20 +0,0 @@
|
|||
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")]
|
|
@ -1,16 +0,0 @@
|
|||
<?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>
|
|
@ -1,45 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<?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.
|
@ -1,36 +0,0 @@
|
|||
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")]
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net472" />
|
||||
</packages>
|
|
@ -1,410 +0,0 @@
|
|||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RestSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class Client
|
||||
{
|
||||
private readonly ILogger Logger;
|
||||
|
||||
private RestClient ApiClient;
|
||||
private AuthToken Token;
|
||||
|
||||
public string BaseUrl;
|
||||
|
||||
public Client(string 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)
|
||||
{
|
||||
var request = new RestRequest(route)
|
||||
.AddJsonBody(body)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private T GetRequest<T>(string route)
|
||||
{
|
||||
var request = new RestRequest(route)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var response = ApiClient.Get<T>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
private string DownloadRequest(string route, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
route = route.TrimStart('/');
|
||||
|
||||
var client = new WebClient();
|
||||
var tempFile = Path.GetTempFileName();
|
||||
|
||||
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
||||
client.DownloadProgressChanged += (s, e) => progressHandler(e);
|
||||
client.DownloadFileCompleted += (s, e) => completeHandler(e);
|
||||
|
||||
client.DownloadFileAsync(new Uri($"{ApiClient.BaseUrl}{route}"), tempFile);
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
private TrackableStream StreamRequest(string route)
|
||||
{
|
||||
route = route.TrimStart('/');
|
||||
|
||||
var client = new WebClient();
|
||||
var tempFile = Path.GetTempFileName();
|
||||
|
||||
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var ws = client.OpenRead(new Uri($"{ApiClient.BaseUrl}{route}"));
|
||||
|
||||
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
|
||||
}
|
||||
|
||||
public async Task<AuthToken> AuthenticateAsync(string username, string password)
|
||||
{
|
||||
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
}));
|
||||
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.OK:
|
||||
Token = new AuthToken
|
||||
{
|
||||
AccessToken = response.Data.AccessToken,
|
||||
RefreshToken = response.Data.RefreshToken,
|
||||
Expiration = response.Data.Expiration
|
||||
};
|
||||
|
||||
return Token;
|
||||
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.BadRequest:
|
||||
case HttpStatusCode.Unauthorized:
|
||||
throw new WebException("Invalid username or password");
|
||||
|
||||
default:
|
||||
throw new WebException("Could not communicate with the server");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuthToken> RegisterAsync(string username, string password)
|
||||
{
|
||||
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
}));
|
||||
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.OK:
|
||||
Token = new AuthToken
|
||||
{
|
||||
AccessToken = response.Data.AccessToken,
|
||||
RefreshToken = response.Data.RefreshToken,
|
||||
Expiration = response.Data.Expiration
|
||||
};
|
||||
|
||||
return Token;
|
||||
|
||||
case HttpStatusCode.BadRequest:
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.Unauthorized:
|
||||
throw new WebException(response.Data.Message);
|
||||
|
||||
default:
|
||||
throw new WebException("Could not communicate with the server");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PingAsync()
|
||||
{
|
||||
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
|
||||
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
public AuthToken RefreshToken(AuthToken token)
|
||||
{
|
||||
Logger?.LogTrace("Refreshing token...");
|
||||
|
||||
var request = new RestRequest("/api/Auth/Refresh")
|
||||
.AddJsonBody(token);
|
||||
|
||||
var response = ApiClient.Post<AuthResponse>(request);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
throw new WebException(response.ErrorMessage);
|
||||
|
||||
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?.LogTrace("Validating token...");
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
Logger?.LogTrace("Token is null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var request = new RestRequest("/api/Auth/Validate")
|
||||
.AddHeader("Authorization", $"Bearer {token.AccessToken}");
|
||||
|
||||
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
|
||||
{
|
||||
Logger?.LogTrace("Token is empty!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var response = ApiClient.Post(request);
|
||||
|
||||
var valid = response.StatusCode == HttpStatusCode.OK;
|
||||
|
||||
if (valid)
|
||||
Logger?.LogTrace("Token is valid!");
|
||||
else
|
||||
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");
|
||||
}
|
||||
|
||||
public Game GetGame(Guid id)
|
||||
{
|
||||
return GetRequest<Game>($"/api/Games/{id}");
|
||||
}
|
||||
|
||||
public GameManifest GetGameManifest(Guid id)
|
||||
{
|
||||
return GetRequest<GameManifest>($"/api/Games/{id}/Manifest");
|
||||
}
|
||||
|
||||
public string DownloadGame(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Games/{id}/Download", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public TrackableStream StreamGame(Guid id)
|
||||
{
|
||||
return StreamRequest($"/api/Games/{id}/Download");
|
||||
}
|
||||
|
||||
public string DownloadArchive(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public TrackableStream StreamRedistributable(Guid id)
|
||||
{
|
||||
return StreamRequest($"/api/Redistributables/{id}/Download");
|
||||
}
|
||||
|
||||
public string DownloadSave(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Saves/Download/{id}", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public string DownloadLatestSave(Guid gameId, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Saves/DownloadLatest/{gameId}", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public GameSave UploadSave(string gameId, byte[] data)
|
||||
{
|
||||
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 = ApiClient.Post<GameSave>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public string GetMediaUrl(Media media)
|
||||
{
|
||||
return (new Uri(ApiClient.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
|
||||
}
|
||||
|
||||
public string GetKey(Guid id)
|
||||
{
|
||||
Logger?.LogTrace("Requesting key allocation...");
|
||||
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
var request = new KeyRequest()
|
||||
{
|
||||
GameId = id,
|
||||
MacAddress = macAddress,
|
||||
ComputerName = Environment.MachineName,
|
||||
IpAddress = GetIpAddress(),
|
||||
};
|
||||
|
||||
var response = PostRequest<Key>($"/api/Keys/Get", request);
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
public string GetAllocatedKey(Guid id)
|
||||
{
|
||||
Logger?.LogTrace("Requesting allocated key...");
|
||||
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
var request = new KeyRequest()
|
||||
{
|
||||
GameId = id,
|
||||
MacAddress = macAddress,
|
||||
ComputerName = Environment.MachineName,
|
||||
IpAddress = GetIpAddress(),
|
||||
};
|
||||
|
||||
var response = PostRequest<Key>($"/api/Keys/GetAllocated/{id}", request);
|
||||
|
||||
if (response == null)
|
||||
return String.Empty;
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
public string GetNewKey(Guid id)
|
||||
{
|
||||
Logger?.LogTrace("Requesting new key allocation...");
|
||||
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
var request = new KeyRequest()
|
||||
{
|
||||
GameId = id,
|
||||
MacAddress = macAddress,
|
||||
ComputerName = Environment.MachineName,
|
||||
IpAddress = GetIpAddress(),
|
||||
};
|
||||
|
||||
var response = PostRequest<Key>($"/api/Keys/Allocate/{id}", request);
|
||||
|
||||
if (response == null)
|
||||
return String.Empty;
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
public User GetProfile()
|
||||
{
|
||||
Logger?.LogTrace("Requesting player's profile...");
|
||||
|
||||
return GetRequest<User>("/api/Profile");
|
||||
}
|
||||
|
||||
public string ChangeAlias(string 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()
|
||||
.Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||
.Select(nic => nic.GetPhysicalAddress().ToString())
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private string GetIpAddress()
|
||||
{
|
||||
return Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Enums
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
Icon,
|
||||
Cover,
|
||||
Background
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Enums
|
||||
{
|
||||
public enum SavePathType
|
||||
{
|
||||
File,
|
||||
Registry
|
||||
}
|
||||
}
|
|
@ -5,9 +5,6 @@
|
|||
Install,
|
||||
Uninstall,
|
||||
NameChange,
|
||||
KeyChange,
|
||||
SaveUpload,
|
||||
SaveDownload,
|
||||
DetectInstall
|
||||
KeyChange
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
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; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
internal class ExtractionResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public bool Canceled { get; set; }
|
||||
public string Directory { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.SDK.Helpers
|
||||
{
|
||||
internal static class RetryHelper
|
||||
{
|
||||
internal static readonly ILogger Logger;
|
||||
|
||||
internal static T RetryOnException<T>(int maxAttempts, TimeSpan delay, T @default, Func<T> action)
|
||||
{
|
||||
int attempts = 0;
|
||||
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
||||
|
||||
attempts++;
|
||||
return action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, $"Attempt failed!");
|
||||
|
||||
if (attempts >= maxAttempts)
|
||||
return @default;
|
||||
|
||||
Task.Delay(delay).Wait();
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,7 @@
|
|||
<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>
|
||||
|
|
|
@ -9,6 +9,5 @@ namespace LANCommander.SDK.Models
|
|||
public string AccessToken { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,5 @@ namespace LANCommander.SDK.Models
|
|||
{
|
||||
public string AccessToken { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,11 @@ 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; }
|
||||
public virtual Company Developer { get; set; }
|
||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||
public virtual IEnumerable<Media> Media { get; set; }
|
||||
public virtual IEnumerable<Redistributable> Redistributables { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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; }
|
||||
|
@ -16,12 +14,12 @@ namespace LANCommander.SDK
|
|||
public IEnumerable<string> Publishers { get; set; }
|
||||
public IEnumerable<string> Developers { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public IEnumerable<GameAction> Actions { get; set; }
|
||||
public bool Singleplayer { get; set; }
|
||||
public MultiplayerInfo LocalMultiplayer { get; set; }
|
||||
public MultiplayerInfo LanMultiplayer { get; set; }
|
||||
public MultiplayerInfo OnlineMultiplayer { get; set; }
|
||||
public IEnumerable<SavePath> SavePaths { get; set; }
|
||||
|
||||
public GameManifest() { }
|
||||
}
|
||||
|
@ -41,19 +39,4 @@ namespace LANCommander.SDK
|
|||
public int MinPlayers { get; set; }
|
||||
public int MaxPlayers { get; set; }
|
||||
}
|
||||
|
||||
public class SavePath
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class GameSave : BaseModel
|
||||
{
|
||||
public Guid GameId { get; set; }
|
||||
public virtual Game Game { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public virtual User User { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class Media : BaseModel
|
||||
{
|
||||
public Guid FileId { get; set; }
|
||||
public MediaType Type { get; set; }
|
||||
public string SourceUrl { get; set; }
|
||||
public string MimeType { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class Redistributable : BaseModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public DateTime ReleasedOn { get; set; }
|
||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class SavePath : BaseModel
|
||||
{
|
||||
public SavePathType Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool IsRegex { get; set; }
|
||||
public virtual Game Game { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,6 +6,5 @@ namespace LANCommander.SDK.Models
|
|||
{
|
||||
public Guid Id { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Alias { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,340 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class TrackableStream : MemoryStream, IDisposable
|
||||
{
|
||||
public delegate void OnProgressDelegate(long Position, long Length);
|
||||
public event OnProgressDelegate OnProgress = delegate { };
|
||||
private long internalStreamProgress = 0;
|
||||
private Stream internalStream;
|
||||
private bool disposeStream = false;
|
||||
private long? streamLength;
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the TrackableStream class with an expandable
|
||||
// capacity initialized to zero.
|
||||
public TrackableStream() : base() { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the TrackableStream class with the contents of stream.
|
||||
// capacity initialized to zero.
|
||||
public TrackableStream(Stream stream, bool disposeStream = false, long? streamLength = null) : base()
|
||||
{
|
||||
internalStream = stream;
|
||||
this.disposeStream = disposeStream;
|
||||
this.streamLength = streamLength;
|
||||
}
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the TrackableStream class with an expandable
|
||||
// capacity initialized as specified.
|
||||
//
|
||||
// Parameters:
|
||||
// capacity:
|
||||
// The initial size of the internal array in bytes.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// capacity is negative.
|
||||
public TrackableStream(int capacity) : base(capacity) { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new non-resizable instance of the TrackableStream class
|
||||
// based on the specified byte array.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// The array of unsigned bytes from which to create the current stream.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
public TrackableStream(byte[] buffer) : base(buffer) { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new non-resizable instance of the TrackableStream class
|
||||
// based on the specified byte array with the TrackableStream.CanWrite property
|
||||
// set as specified.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// The array of unsigned bytes from which to create this stream.
|
||||
//
|
||||
// writable:
|
||||
// The setting of the TrackableStream.CanWrite property, which determines
|
||||
// whether the stream supports writing.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
public TrackableStream(byte[] buffer, bool writable) : base(buffer, writable) { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new non-resizable instance of the TrackableStream class
|
||||
// based on the specified region (index) of a byte array.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// The array of unsigned bytes from which to create this stream.
|
||||
//
|
||||
// index:
|
||||
// The index into buffer at which the stream begins.
|
||||
//
|
||||
// count:
|
||||
// The length of the stream in bytes.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
//
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// index or count is less than zero.
|
||||
//
|
||||
// T:System.ArgumentException:
|
||||
// The buffer length minus index is less than count.
|
||||
public TrackableStream(byte[] buffer, int index, int count) : base(buffer, index, count) { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new non-resizable instance of the TrackableStream class
|
||||
// based on the specified region of a byte array, with the TrackableStream.CanWrite
|
||||
// property set as specified.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// The array of unsigned bytes from which to create this stream.
|
||||
//
|
||||
// index:
|
||||
// The index in buffer at which the stream begins.
|
||||
//
|
||||
// count:
|
||||
// The length of the stream in bytes.
|
||||
//
|
||||
// writable:
|
||||
// The setting of the TrackableStream.CanWrite property, which determines
|
||||
// whether the stream supports writing.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
//
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// index or count are negative.
|
||||
//
|
||||
// T:System.ArgumentException:
|
||||
// The buffer length minus index is less than count.
|
||||
public TrackableStream(byte[] buffer, int index, int count, bool writable) : base(buffer, index, count, writable) { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the TrackableStream class based on the specified
|
||||
// region of a byte array, with the TrackableStream.CanWrite property set
|
||||
// as specified, and the ability to call TrackableStream.GetBuffer set as
|
||||
// specified.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// The array of unsigned bytes from which to create this stream.
|
||||
//
|
||||
// index:
|
||||
// The index into buffer at which the stream begins.
|
||||
//
|
||||
// count:
|
||||
// The length of the stream in bytes.
|
||||
//
|
||||
// writable:
|
||||
// The setting of the TrackableStream.CanWrite property, which determines
|
||||
// whether the stream supports writing.
|
||||
//
|
||||
// publiclyVisible:
|
||||
// true to enable TrackableStream.GetBuffer, which returns the unsigned byte
|
||||
// array from which the stream was created; otherwise, false.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
//
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// index or count is negative.
|
||||
//
|
||||
// T:System.ArgumentException:
|
||||
// The buffer length minus index is less than count.
|
||||
public TrackableStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) : base(buffer, index, count, writable, publiclyVisible) { }
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Writes a block of bytes to the current stream using data read from a buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// The buffer to write data from.
|
||||
//
|
||||
// offset:
|
||||
// The zero-based byte offset in buffer at which to begin copying bytes to the current
|
||||
// stream.
|
||||
//
|
||||
// count:
|
||||
// The maximum number of bytes to write.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
//
|
||||
// T:System.NotSupportedException:
|
||||
// The stream does not support writing. For additional information see System.IO.Stream.CanWrite.-or-
|
||||
// The current position is closer than count bytes to the end of the stream, and
|
||||
// the capacity cannot be modified.
|
||||
//
|
||||
// T:System.ArgumentException:
|
||||
// offset subtracted from the buffer length is less than count.
|
||||
//
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// offset or count are negative.
|
||||
//
|
||||
// T:System.IO.IOException:
|
||||
// An I/O error occurs.
|
||||
//
|
||||
// T:System.ObjectDisposedException:
|
||||
// The current stream instance is closed.
|
||||
public override void Write(byte[] array, int offset, int count)
|
||||
{
|
||||
if (internalStream is Stream)
|
||||
{
|
||||
internalStream.Write(array, offset, count);
|
||||
OnProgress(internalStream.Position, internalStream.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Write(array, offset, count);
|
||||
OnProgress(this.Position, this.Length);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Writes a byte to the current stream at the current position.
|
||||
//
|
||||
// Parameters:
|
||||
// value:
|
||||
// The byte to write.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.NotSupportedException:
|
||||
// The stream does not support writing. For additional information see System.IO.Stream.CanWrite.-or-
|
||||
// The current position is at the end of the stream, and the capacity cannot be
|
||||
// modified.
|
||||
//
|
||||
// T:System.ObjectDisposedException:
|
||||
// The current stream is closed.
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
if (internalStream is Stream)
|
||||
{
|
||||
internalStream.WriteByte(value);
|
||||
OnProgress(internalStream.Position, internalStream.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.WriteByte(value);
|
||||
OnProgress(this.Position, this.Length);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Reads a block of bytes from the current stream and writes the data to a buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// buffer:
|
||||
// When this method returns, contains the specified byte array with the values between
|
||||
// offset and (offset + count - 1) replaced by the characters read from the current
|
||||
// stream.
|
||||
//
|
||||
// offset:
|
||||
// The zero-based byte offset in buffer at which to begin storing data from the
|
||||
// current stream.
|
||||
//
|
||||
// count:
|
||||
// The maximum number of bytes to read.
|
||||
//
|
||||
// Returns:
|
||||
// The total number of bytes written into the buffer. This can be less than the
|
||||
// number of bytes requested if that number of bytes are not currently available,
|
||||
// or zero if the end of the stream is reached before any bytes are read.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// buffer is null.
|
||||
//
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// offset or count is negative.
|
||||
//
|
||||
// T:System.ArgumentException:
|
||||
// offset subtracted from the buffer length is less than count.
|
||||
//
|
||||
// T:System.ObjectDisposedException:
|
||||
// The current stream instance is closed.
|
||||
public override int Read(byte[] array, int offset, int count)
|
||||
{
|
||||
int r;
|
||||
if (internalStream is Stream)
|
||||
{
|
||||
r = internalStream.Read(array, offset, count);
|
||||
internalStreamProgress += r;
|
||||
OnProgress(internalStreamProgress, this.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = base.Read(array, offset, count);
|
||||
OnProgress(this.Position, this.Length);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Reads a byte from the current stream.
|
||||
//
|
||||
// Returns:
|
||||
// The byte cast to a System.Int32, or -1 if the end of the stream has been reached.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ObjectDisposedException:
|
||||
// The current stream instance is closed.
|
||||
public override int ReadByte()
|
||||
{
|
||||
int r;
|
||||
if (internalStream is Stream)
|
||||
{
|
||||
r = internalStream.ReadByte();
|
||||
internalStreamProgress += r;
|
||||
OnProgress(internalStreamProgress, this.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = base.ReadByte();
|
||||
OnProgress(this.Position, this.Length);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposeStream)
|
||||
{
|
||||
internalStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public long Length => streamLength ?? internalStream.Length;
|
||||
}
|
||||
}
|
|
@ -11,10 +11,6 @@ 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
|
||||
|
@ -37,14 +33,6 @@ 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
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
@page
|
||||
@using LANCommander.Models;
|
||||
@using LANCommander.Services;
|
||||
@model FirstTimeSetupModel
|
||||
@{
|
||||
Layout = "/Views/Shared/_LayoutBasic.cshtml";
|
||||
|
@ -9,20 +7,11 @@
|
|||
ViewData["Title"] = "First Time Setup";
|
||||
}
|
||||
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="position: absolute; top:0; left: 0; right: 0; bottom: 0;">
|
||||
<div class="ant-col ant-col-10">
|
||||
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
@switch (SettingService.GetSettings().Theme)
|
||||
{
|
||||
case LANCommanderTheme.Light:
|
||||
<img src="~/static/logo.svg" />
|
||||
break;
|
||||
|
||||
case LANCommanderTheme.Dark:
|
||||
<img src="~/static/logo-dark.svg" />
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ant-card ant-card-bordered">
|
||||
|
|
|
@ -136,9 +136,6 @@ namespace LANCommander.Areas.Identity.Pages.Account
|
|||
{
|
||||
var user = CreateUser();
|
||||
|
||||
user.Approved = true;
|
||||
user.ApprovedOn = DateTime.Now;
|
||||
|
||||
await _userStore.SetUserNameAsync(user, Input.UserName, CancellationToken.None);
|
||||
var result = await _userManager.CreateAsync(user, Input.Password);
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
@page
|
||||
@using LANCommander.Models;
|
||||
@using LANCommander.Services;
|
||||
@model LoginModel
|
||||
@{ Layout = "/Views/Shared/_LayoutBasic.cshtml"; }
|
||||
|
||||
|
@ -8,38 +6,19 @@
|
|||
ViewData["Title"] = "Log in";
|
||||
}
|
||||
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="position: absolute; top:0; left: 0; right: 0; bottom: 0;">
|
||||
<div class="ant-col ant-col-10">
|
||||
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
@switch (SettingService.GetSettings().Theme)
|
||||
{
|
||||
case LANCommanderTheme.Light:
|
||||
<img src="~/static/logo.svg" />
|
||||
break;
|
||||
|
||||
case LANCommanderTheme.Dark:
|
||||
<img src="~/static/logo-dark.svg" />
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
@foreach (var error in ModelState.SelectMany(x => x.Value.Errors))
|
||||
{
|
||||
<div data-show="true" class="ant-alert ant-alert-error ant-alert-no-icon" style="margin-bottom: 16px">
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">@error.ErrorMessage</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="ant-card ant-card-bordered">
|
||||
<div class="ant-card-head">
|
||||
<div class="ant-card-head-wrapper">
|
||||
<div class="ant-card-head-title">Login</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="account" method="post" class="ant-card-body" autocomplete="off">
|
||||
<div class="ant-form ant-form-vertical">
|
||||
<div class="ant-form-item">
|
||||
|
|
|
@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Identity.UI.Services;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LANCommander.Services;
|
||||
|
||||
namespace LANCommander.Areas.Identity.Pages.Account
|
||||
{
|
||||
|
@ -127,19 +126,6 @@ namespace LANCommander.Areas.Identity.Pages.Account
|
|||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
if (settings.Authentication.RequireApproval)
|
||||
{
|
||||
var user = await _userManager.FindByNameAsync(Input.UserName);
|
||||
|
||||
if (user != null && !user.Approved)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Your account must be approved by an administrator.");
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
// This doesn't count login failures towards account lockout
|
||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||
var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);
|
||||
|
|
|
@ -15,20 +15,29 @@ 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()
|
||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
||||
{
|
||||
await SignInManager.SignOutAsync();
|
||||
|
||||
return LocalRedirect("/");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,15 @@
|
|||
@page
|
||||
@using LANCommander.Models;
|
||||
@using LANCommander.Services;
|
||||
@model RegisterModel
|
||||
@{ Layout = "/Views/Shared/_LayoutBasic.cshtml"; }
|
||||
@{
|
||||
ViewData["Title"] = "Register";
|
||||
}
|
||||
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="position: absolute; top:0; left: 0; right: 0; bottom: 0;">
|
||||
<div class="ant-col ant-col-10">
|
||||
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
@switch (SettingService.GetSettings().Theme)
|
||||
{
|
||||
case LANCommanderTheme.Light:
|
||||
<img src="~/static/logo.svg" />
|
||||
break;
|
||||
|
||||
case LANCommanderTheme.Dark:
|
||||
<img src="~/static/logo-dark.svg" />
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ant-card ant-card-bordered">
|
||||
|
|
|
@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LANCommander.Services;
|
||||
|
||||
namespace LANCommander.Areas.Identity.Pages.Account
|
||||
{
|
||||
|
@ -107,20 +106,12 @@ namespace LANCommander.Areas.Identity.Pages.Account
|
|||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
{
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
returnUrl ??= Url.Content("~/");
|
||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var user = CreateUser();
|
||||
|
||||
if (!settings.Authentication.RequireApproval)
|
||||
{
|
||||
user.Approved = false;
|
||||
user.ApprovedOn = DateTime.Now;
|
||||
}
|
||||
|
||||
await _userStore.SetUserNameAsync(user, Input.UserName, CancellationToken.None);
|
||||
var result = await _userManager.CreateAsync(user, Input.Password);
|
||||
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
@using LANCommander.Data.Models
|
||||
@using LANCommander.Extensions
|
||||
@using LANCommander.Models;
|
||||
@using System.IO.Compression;
|
||||
@inject ModalService ModalService
|
||||
|
||||
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%">
|
||||
<SpaceItem>
|
||||
<Table TItem="Data.Models.Action" DataSource="@OrderedActions" HidePagination="true" Style="border: 1px solid #f0f0f0">
|
||||
<PropertyColumn Property="a => a.Name">
|
||||
<Input Type="text" @bind-Value="context.Name" />
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.Path">
|
||||
<Space Style="display: flex">
|
||||
<SpaceItem Style="flex-grow: 1">
|
||||
<Input Type="text" @bind-Value="context.Path" />
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Button OnClick="() => BrowseForActionPath(context)" Type="@ButtonType.Primary" Icon="@IconType.Outline.FolderOpen" />
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.Arguments">
|
||||
<Input Type="text" @bind-Value="context.Arguments" />
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.WorkingDirectory" Title="Working Dir">
|
||||
<Input Type="text" @bind-Value="context.WorkingDirectory" />
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.PrimaryAction" Title="Primary" Style="text-align: center">
|
||||
<Checkbox @bind-Checked="context.PrimaryAction" />
|
||||
</PropertyColumn>
|
||||
<ActionColumn>
|
||||
<Space Style="display: flex; justify-content: end">
|
||||
<SpaceItem>
|
||||
<Button OnClick="() => MoveUp(context)" Icon="@IconType.Outline.Up" Type="@ButtonType.Text" />
|
||||
<Button OnClick="() => MoveDown(context)" Icon="@IconType.Outline.Down" Type="@ButtonType.Text" />
|
||||
|
||||
<Popconfirm OnConfirm="() => RemoveAction(context)" Title="Are you sure you want to remove this action?">
|
||||
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||
</Popconfirm>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
</Table>
|
||||
</SpaceItem>
|
||||
|
||||
<SpaceItem>
|
||||
<GridRow Justify="end">
|
||||
<GridCol>
|
||||
<Button OnClick="AddAction" Type="@ButtonType.Primary">Add Action</Button>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
|
||||
@code {
|
||||
[Parameter] public Game Game { get; set; }
|
||||
|
||||
private List<Data.Models.Action> OrderedActions { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Game.Actions == null)
|
||||
Game.Actions = new List<Data.Models.Action>();
|
||||
|
||||
OrderedActions = Game.Actions.OrderBy(a => a.SortOrder).ToList();
|
||||
FixSortOrder();
|
||||
}
|
||||
|
||||
private async Task AddAction()
|
||||
{
|
||||
if (OrderedActions == null)
|
||||
OrderedActions = new List<Data.Models.Action>();
|
||||
|
||||
OrderedActions.Add(new Data.Models.Action()
|
||||
{
|
||||
PrimaryAction = OrderedActions.Count == 0,
|
||||
SortOrder = OrderedActions.Count
|
||||
});
|
||||
}
|
||||
|
||||
private async Task RemoveAction(Data.Models.Action action)
|
||||
{
|
||||
OrderedActions.Remove(action);
|
||||
}
|
||||
|
||||
private async Task MoveUp(Data.Models.Action action)
|
||||
{
|
||||
if (action.SortOrder > 0)
|
||||
OrderedActions.Move(action, action.SortOrder - 1);
|
||||
|
||||
FixSortOrder();
|
||||
}
|
||||
|
||||
private async Task MoveDown(Data.Models.Action action)
|
||||
{
|
||||
if (action.SortOrder < OrderedActions.Count + 1)
|
||||
OrderedActions.Move(action, action.SortOrder + 1);
|
||||
|
||||
FixSortOrder();
|
||||
}
|
||||
|
||||
private async void BrowseForActionPath(Data.Models.Action action)
|
||||
{
|
||||
var modalOptions = new ModalOptions()
|
||||
{
|
||||
Title = "Choose Action Executable",
|
||||
Maximizable = false,
|
||||
DefaultMaximized = true,
|
||||
Closable = true,
|
||||
OkText = "Select File"
|
||||
};
|
||||
|
||||
var browserOptions = new ArchiveBrowserOptions()
|
||||
{
|
||||
ArchiveId = Game.Archives.FirstOrDefault().Id,
|
||||
Select = true,
|
||||
Multiple = false
|
||||
};
|
||||
|
||||
var modalRef = await ModalService.CreateModalAsync<ArchiveBrowserDialog, ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>(modalOptions, browserOptions);
|
||||
|
||||
modalRef.OnOk = (results) =>
|
||||
{
|
||||
action.Path = results.FirstOrDefault().FullName;
|
||||
|
||||
var parts = action.Path.Split('/');
|
||||
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
action.Path = parts.Last();
|
||||
action.WorkingDirectory = "{InstallDir}/" + String.Join('/', parts.Take(parts.Length - 1));
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
private void FixSortOrder()
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
foreach (var action in OrderedActions)
|
||||
{
|
||||
action.SortOrder = i;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
Game.Actions = OrderedActions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
@using AntDesign.TableModels;
|
||||
@using ByteSizeLib;
|
||||
@using LANCommander.Services;
|
||||
@using System.IO.Compression;
|
||||
@inject ArchiveService ArchiveService;
|
||||
|
||||
<GridRow Style="position: fixed; height: calc(100vh - 55px - 53px); top: 55px; left: 0; width: 100%">
|
||||
<GridCol Span="6" Style="height: 100%; overflow-y: scroll; padding: 24px">
|
||||
<Tree TItem="ArchiveDirectory"
|
||||
DataSource="Directories"
|
||||
TitleExpression="x => x.DataItem.Name"
|
||||
ChildrenExpression="x => x.DataItem.Children"
|
||||
IsLeafExpression="x => !x.DataItem.HasChildren"
|
||||
OnClick="(args) => ChangeDirectory(args.Node.DataItem)">
|
||||
</Tree>
|
||||
</GridCol>
|
||||
|
||||
<GridCol Span="18" Style="height: 100%">
|
||||
<Table
|
||||
@ref="FileTable"
|
||||
TItem="ZipArchiveEntry"
|
||||
DataSource="CurrentPathEntries"
|
||||
HidePagination="true"
|
||||
Loading="Entries == null"
|
||||
RowSelectable="@(x => x.FullName != null && !x.FullName.EndsWith('/'))"
|
||||
OnRowClick="OnRowClicked"
|
||||
SelectedRowsChanged="SelectedFilesChanged"
|
||||
ScrollY="calc(100vh - 55px - 55px - 53px)">
|
||||
|
||||
@if (Select)
|
||||
{
|
||||
<Selection Key="@context.FullName" Type="@(Multiple ? "checkbox" : "radio")" Disabled="@(context.FullName != null && context.FullName.EndsWith('/'))" />
|
||||
}
|
||||
<Column TData="string" Width="32">
|
||||
<Icon Type="@GetIcon(context)" Theme="outline" />
|
||||
</Column>
|
||||
<PropertyColumn Property="e => e.FullName" Sortable Title="Name">
|
||||
@GetFileName(context)
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="e => e.Length" Sortable Title="Size">
|
||||
@ByteSize.FromBytes(context.Length)
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="e => e.LastWriteTime" Format="MM/dd/yyyy hh:mm tt" Sortable Title="Modified" />
|
||||
|
||||
</Table>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
|
||||
<style>
|
||||
.select-file-button {
|
||||
opacity: 0;
|
||||
transition: .1s opacity;
|
||||
}
|
||||
|
||||
.archive-browser tr:hover .select-file-button {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
[Parameter] public Guid ArchiveId { get; set; }
|
||||
[Parameter] public bool Select { get; set; }
|
||||
[Parameter] public bool Multiple { get; set; }
|
||||
|
||||
[Parameter] public IEnumerable<ZipArchiveEntry> SelectedFiles { get; set; }
|
||||
[Parameter] public EventCallback<IEnumerable<ZipArchiveEntry>> SelectedFilesChanged { get; set; }
|
||||
|
||||
ITable? FileTable;
|
||||
|
||||
private IEnumerable<ZipArchiveEntry> Entries { get; set; }
|
||||
private IEnumerable<ZipArchiveEntry> CurrentPathEntries { get; set; }
|
||||
private string CurrentPath { get; set; }
|
||||
private HashSet<ArchiveDirectory> Directories { get; set; }
|
||||
private ArchiveDirectory SelectedDirectory { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Entries = await ArchiveService.GetContents(ArchiveId);
|
||||
Directories = new HashSet<ArchiveDirectory>();
|
||||
|
||||
var root = new ArchiveDirectory()
|
||||
{
|
||||
Name = "/",
|
||||
FullName = "",
|
||||
IsExpanded = true
|
||||
};
|
||||
|
||||
root.PopulateChildren(Entries);
|
||||
|
||||
Directories.Add(root);
|
||||
|
||||
ChangeDirectory(root);
|
||||
}
|
||||
|
||||
private void OnRowClicked(RowData<ZipArchiveEntry> row)
|
||||
{
|
||||
FileTable.SetSelection(new string[] { row.Data.FullName });
|
||||
}
|
||||
|
||||
private void ChangeDirectory(ArchiveDirectory selectedDirectory)
|
||||
{
|
||||
SelectedDirectory = selectedDirectory;
|
||||
|
||||
if (SelectedDirectory.FullName == "")
|
||||
CurrentPathEntries = Entries.Where(e => !e.FullName.TrimEnd('/').Contains('/'));
|
||||
else
|
||||
CurrentPathEntries = Entries.Where(e => e.FullName.StartsWith(SelectedDirectory.FullName) && e.FullName != SelectedDirectory.FullName);
|
||||
}
|
||||
|
||||
private string GetFileName(ZipArchiveEntry entry)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(entry.Name) && entry.Length == 0)
|
||||
{
|
||||
return entry.FullName.TrimEnd('/').Split('/').Last();
|
||||
}
|
||||
else
|
||||
return entry.Name;
|
||||
}
|
||||
|
||||
private string GetIcon(ZipArchiveEntry entry)
|
||||
{
|
||||
switch (Path.GetExtension(entry.FullName))
|
||||
{
|
||||
case "":
|
||||
return "folder";
|
||||
|
||||
case ".exe":
|
||||
return "code";
|
||||
|
||||
case ".zip":
|
||||
case ".rar":
|
||||
case ".7z":
|
||||
case ".gz":
|
||||
case ".tar":
|
||||
return "file-zip";
|
||||
|
||||
case ".wad":
|
||||
case ".pk3":
|
||||
case ".pak":
|
||||
case ".cab":
|
||||
return "file-zip";
|
||||
|
||||
case ".txt":
|
||||
case ".cfg":
|
||||
case ".config":
|
||||
case ".ini":
|
||||
case ".yml":
|
||||
case ".yaml":
|
||||
case ".log":
|
||||
case ".doc":
|
||||
case ".nfo":
|
||||
return "file-text";
|
||||
|
||||
case ".bat":
|
||||
case ".ps1":
|
||||
case ".json":
|
||||
return "code";
|
||||
|
||||
case ".bik":
|
||||
case ".avi":
|
||||
case ".mov":
|
||||
case ".mp4":
|
||||
case ".m4v":
|
||||
case ".mkv":
|
||||
case ".wmv":
|
||||
case ".mpg":
|
||||
case ".mpeg":
|
||||
case ".flv":
|
||||
return "video-camera";
|
||||
|
||||
case ".dll":
|
||||
return "api";
|
||||
|
||||
case ".hlp":
|
||||
return "file-unknown";
|
||||
|
||||
case ".png":
|
||||
case ".bmp":
|
||||
case ".jpeg":
|
||||
case ".jpg":
|
||||
case ".gif":
|
||||
return "file-image";
|
||||
|
||||
default:
|
||||
return "file";
|
||||
}
|
||||
}
|
||||
|
||||
public class ArchiveDirectory
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public bool IsExpanded { get; set; } = false;
|
||||
public bool HasChildren => Children != null && Children.Count > 0;
|
||||
public HashSet<ArchiveDirectory> Children { get; set; } = new HashSet<ArchiveDirectory>();
|
||||
|
||||
public void PopulateChildren(IEnumerable<ZipArchiveEntry> entries)
|
||||
{
|
||||
var childPaths = entries.Where(e => e.FullName.StartsWith(FullName) && e.FullName.EndsWith('/'));
|
||||
var directChildren = childPaths.Where(p => p.FullName != FullName && p.FullName.Substring(FullName.Length + 1).TrimEnd('/').Split('/').Length == 1);
|
||||
|
||||
foreach (var directChild in directChildren)
|
||||
{
|
||||
var child = new ArchiveDirectory()
|
||||
{
|
||||
FullName = directChild.FullName,
|
||||
Name = directChild.FullName.Substring(FullName.Length).TrimEnd('/')
|
||||
};
|
||||
|
||||
child.PopulateChildren(entries);
|
||||
|
||||
Children.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
@inherits FeedbackComponent<ArchiveBrowserOptions, IEnumerable<ZipArchiveEntry>>
|
||||
@using System.IO.Compression;
|
||||
@using LANCommander.Models;
|
||||
|
||||
<ArchiveBrowser ArchiveId="Options.ArchiveId" @bind-SelectedFiles="SelectedFiles" Select="Options.Select" Multiple="Options.Multiple" />
|
||||
|
||||
@code {
|
||||
private IEnumerable<ZipArchiveEntry> SelectedFiles { get; set; }
|
||||
|
||||
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||
{
|
||||
await base.OkCancelRefWithResult!.OnOk(SelectedFiles);
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
@using System.Net;
|
||||
@using System.Diagnostics;
|
||||
@using Hangfire;
|
||||
@using LANCommander.Jobs.Background;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@inject HttpClient HttpClient
|
||||
@inject NavigationManager Navigator
|
||||
@inject ArchiveService ArchiveService
|
||||
@inject IMessageService MessageService
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
|
||||
<SpaceItem>
|
||||
<Table TItem="Archive" DataSource="@Archives" HidePagination="true" Responsive>
|
||||
<PropertyColumn Property="a => a.Version" />
|
||||
<PropertyColumn Property="a => a.CompressedSize">
|
||||
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.CreatedBy">
|
||||
@context.CreatedBy?.UserName
|
||||
</PropertyColumn>
|
||||
<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>
|
||||
<a href="/Download/Archive/@context.Id" target="_blank" class="ant-btn ant-btn-text ant-btn-icon-only">
|
||||
<Icon Type="@IconType.Outline.Download" />
|
||||
</a>
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Popconfirm Title="Are you sure you want to delete this archive?" OnConfirm="() => Delete(context)">
|
||||
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||
</Popconfirm>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
</Table>
|
||||
</SpaceItem>
|
||||
|
||||
<SpaceItem>
|
||||
<GridRow Justify="end">
|
||||
<GridCol>
|
||||
<Button OnClick="UploadArchive" Type="@ButtonType.Primary">Upload Archive</Button>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
|
||||
<ArchiveUploader @ref="Uploader" GameId="GameId" RedistributableId="RedistributableId" OnArchiveUploaded="LoadData" />
|
||||
|
||||
@code {
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
|
||||
ICollection<Archive> Archives { get; set; }
|
||||
|
||||
ArchiveUploader Uploader;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
|
||||
HttpClient.BaseAddress = new Uri(Navigator.BaseUri);
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
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)
|
||||
{
|
||||
string url = $"/Download/Game/{archive.Id}";
|
||||
|
||||
await JS.InvokeAsync<object>("open", url, "_blank");
|
||||
}
|
||||
|
||||
private async Task UploadArchive()
|
||||
{
|
||||
await Uploader.Open();
|
||||
}
|
||||
|
||||
private async Task Delete(Archive archive)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ArchiveService.Delete(archive);
|
||||
|
||||
await LoadData();
|
||||
|
||||
await MessageService.Success("Archive deleted!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MessageService.Error("Archive could not be deleted.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,52 @@
|
|||
@using System.Net;
|
||||
@using System.Diagnostics;
|
||||
@using Hangfire;
|
||||
@using LANCommander.Jobs.Background;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@inject HttpClient HttpClient
|
||||
@inject NavigationManager Navigator
|
||||
@inject ArchiveService ArchiveService
|
||||
@inject IMessageService MessageService
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
|
||||
<SpaceItem>
|
||||
<Table TItem="Archive" DataSource="@Game.Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true">
|
||||
<PropertyColumn Property="a => a.Version" />
|
||||
<PropertyColumn Property="a => a.CompressedSize">
|
||||
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.CreatedBy">
|
||||
@context.CreatedBy?.UserName
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" />
|
||||
<ActionColumn Title="">
|
||||
<Space Style="display: flex; justify-content: end">
|
||||
<SpaceItem>
|
||||
<Popconfirm Title="Are you sure you want to delete this archive?" OnConfirm="() => Delete(context)">
|
||||
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||
</Popconfirm>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
</Table>
|
||||
</SpaceItem>
|
||||
|
||||
<SpaceItem>
|
||||
<GridRow Justify="end">
|
||||
<GridCol>
|
||||
<Button OnClick="AddArchive" Type="@ButtonType.Primary">Upload Archive</Button>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
|
||||
@{
|
||||
RenderFragment Footer =
|
||||
@<Template>
|
||||
<Button OnClick="UploadArchiveJS" Disabled="@(File == null || Uploading)" Type="@ButtonType.Primary">Upload</Button>
|
||||
<Button OnClick="UploadArchive" Disabled="@(File == null || Uploading)" Type="@ButtonType.Primary">Upload</Button>
|
||||
<Button OnClick="Clear" Disabled="File == null || Uploading" Danger>Clear</Button>
|
||||
<Button OnClick="Cancel">Cancel</Button>
|
||||
</Template>;
|
||||
}
|
||||
|
||||
<Modal Visible="@Visible" Title="Upload Archive" OnOk="UploadArchiveJS" OnCancel="Cancel" Footer="@Footer">
|
||||
<Modal Visible="@ModalVisible" Title="Upload Archive" OnOk="UploadArchive" OnCancel="Cancel" Footer="@Footer">
|
||||
<Form Model="@Archive" Layout="@FormLayout.Vertical">
|
||||
<FormItem Label="Version">
|
||||
<Input @bind-Value="@context.Version" />
|
||||
|
@ -29,75 +57,83 @@
|
|||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Space Direction="DirectionVHType.Horizontal">
|
||||
<SpaceItem>
|
||||
<InputFile @ref="FileInput" id="FileInput" OnChange="FileSelected" hidden />
|
||||
<InputFile id="FileInput" OnChange="FileSelected" hidden />
|
||||
|
||||
<Upload Name="files" FileList="FileList">
|
||||
<label class="ant-btn" for="FileInput">
|
||||
<Icon Type="upload" />
|
||||
@if (File == null)
|
||||
{
|
||||
<Text>Select File</Text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Text>Change File</Text>
|
||||
}
|
||||
Select Archive
|
||||
</label>
|
||||
</Upload>
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
@if (File != null)
|
||||
{
|
||||
<Text>@File.Name (@ByteSizeLib.ByteSize.FromBytes(File.Size))</Text>
|
||||
}
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Progress Percent="Progress" Status="@CurrentProgressStatus" Class="uploader-progress" />
|
||||
<Text Class="uploader-progress-rate"></Text>
|
||||
<Progress Percent="Progress" />
|
||||
<Text>@ByteSizeLib.ByteSize.FromBytes(Speed)/s</Text>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
[Parameter] public EventCallback<Guid> OnArchiveUploaded { get; set; }
|
||||
[Parameter] public Game Game { get; set; }
|
||||
|
||||
Archive Archive;
|
||||
|
||||
InputFile FileInput;
|
||||
IBrowserFile File { get; set; }
|
||||
List<UploadFileItem> FileList = new List<UploadFileItem>();
|
||||
|
||||
bool IsValid = false;
|
||||
bool Visible = false;
|
||||
bool ModalVisible = false;
|
||||
|
||||
private static string DefaultDragClass = "relative rounded-lg border-2 border-dashed pa-4 mt-4 mud-width-full mud-height-full z-10";
|
||||
private string DragClass = DefaultDragClass;
|
||||
|
||||
const int ChunkSize = 1024 * 1024 * 10;
|
||||
|
||||
int Progress = 0;
|
||||
bool Uploading = false;
|
||||
bool Finished = false;
|
||||
double Speed = 0;
|
||||
|
||||
string Filename;
|
||||
|
||||
ProgressStatus CurrentProgressStatus {
|
||||
get
|
||||
{
|
||||
if (Finished)
|
||||
return ProgressStatus.Success;
|
||||
else if (Uploading)
|
||||
return ProgressStatus.Active;
|
||||
else
|
||||
return ProgressStatus.Normal;
|
||||
}
|
||||
}
|
||||
Stopwatch Watch;
|
||||
long WatchBytesTransferred = 0;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Game.Archives == null)
|
||||
Game.Archives = new List<Archive>();
|
||||
|
||||
HttpClient.BaseAddress = new Uri(Navigator.BaseUri);
|
||||
|
||||
Archive = new Archive()
|
||||
{
|
||||
GameId = Game.Id,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
}
|
||||
|
||||
private void AddArchive()
|
||||
{
|
||||
Archive = new Archive()
|
||||
{
|
||||
GameId = Game.Id,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
ModalVisible = true;
|
||||
}
|
||||
|
||||
private async Task Delete(Archive archive)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ArchiveService.Delete(archive);
|
||||
|
||||
await MessageService.Success("Archive deleted!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
await MessageService.Error("Archive could not be deleted.");
|
||||
}
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
|
@ -108,112 +144,84 @@
|
|||
private void Cancel()
|
||||
{
|
||||
File = null;
|
||||
Visible = false;
|
||||
ModalVisible = false;
|
||||
}
|
||||
|
||||
private async void FileSelected(InputFileChangeEventArgs args)
|
||||
private void FileSelected(InputFileChangeEventArgs args)
|
||||
{
|
||||
File = args.File;
|
||||
}
|
||||
|
||||
public async Task Open(Guid? archiveId = null)
|
||||
private async Task UploadArchive()
|
||||
{
|
||||
if (archiveId.HasValue && archiveId != Guid.Empty)
|
||||
{
|
||||
Archive = await ArchiveService.Get(archiveId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Archive = new Archive();
|
||||
long uploadedBytes = 0;
|
||||
long totalBytes = File.Size;
|
||||
|
||||
if (GameId != Guid.Empty)
|
||||
Archive.GameId = GameId;
|
||||
else if (RedistributableId != Guid.Empty)
|
||||
Archive.RedistributableId = RedistributableId;
|
||||
}
|
||||
Watch = new Stopwatch();
|
||||
|
||||
Visible = true;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
var i = 0;
|
||||
|
||||
// Check every 10 seconds to see if the file input is available
|
||||
while (i < 20)
|
||||
{
|
||||
if (FileInput != null)
|
||||
{
|
||||
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", "");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UploadArchiveJS()
|
||||
using (var stream = File.OpenReadStream(long.MaxValue))
|
||||
{
|
||||
Uploading = true;
|
||||
|
||||
var dotNetReference = DotNetObjectReference.Create(this);
|
||||
Watch.Start();
|
||||
|
||||
await JS.InvokeVoidAsync("Uploader.Upload", dotNetReference);
|
||||
while (Uploading)
|
||||
{
|
||||
byte[] chunk;
|
||||
|
||||
if (totalBytes - uploadedBytes < ChunkSize)
|
||||
chunk = new byte[totalBytes - uploadedBytes];
|
||||
else
|
||||
chunk = new byte[ChunkSize];
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
// This feels hacky, why do we need to do this?
|
||||
// Only 32256 bytes of the file get read unless we
|
||||
// loop through like this. Probably kills performance.
|
||||
while (bytesRead < chunk.Length)
|
||||
{
|
||||
bytesRead += await stream.ReadAsync(chunk, bytesRead, chunk.Length - bytesRead);
|
||||
}
|
||||
|
||||
using (FileStream fs = new FileStream(Path.Combine("Upload", Archive.Id.ToString()), FileMode.Append))
|
||||
{
|
||||
await fs.WriteAsync(chunk);
|
||||
}
|
||||
|
||||
uploadedBytes += chunk.Length;
|
||||
WatchBytesTransferred += chunk.Length;
|
||||
|
||||
Progress = (int)(uploadedBytes * 100 / totalBytes);
|
||||
|
||||
if (Watch.Elapsed.TotalSeconds >= 1)
|
||||
{
|
||||
Speed = WatchBytesTransferred * (1 / Watch.Elapsed.TotalSeconds);
|
||||
WatchBytesTransferred = 0;
|
||||
Watch.Restart();
|
||||
}
|
||||
|
||||
if (Progress >= 100)
|
||||
{
|
||||
Watch.Stop();
|
||||
Uploading = false;
|
||||
await UploadComplete();
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async void OnUploadComplete(string data)
|
||||
private async Task UploadComplete()
|
||||
{
|
||||
if (Guid.TryParse(data, out var objectKey))
|
||||
{
|
||||
Uploading = false;
|
||||
Finished = true;
|
||||
|
||||
Archive.ObjectKey = objectKey.ToString();
|
||||
Archive.ObjectKey = Archive.Id.ToString();
|
||||
Archive.CompressedSize = File.Size;
|
||||
|
||||
if (Archive.Id != Guid.Empty)
|
||||
Archive = await ArchiveService.Update(Archive);
|
||||
else
|
||||
Archive = await ArchiveService.Add(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.Id);
|
||||
ModalVisible = false;
|
||||
|
||||
await MessageService.Success("Archive uploaded!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Visible = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await MessageService.Error("Archive failed to upload!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,522 +0,0 @@
|
|||
@using AntDesign.TableModels;
|
||||
@using LANCommander.Components.FileManagerComponents
|
||||
@inject ArchiveService ArchiveService
|
||||
@inject IMessageService MessageService
|
||||
@namespace LANCommander.Components
|
||||
|
||||
<div class="file-manager">
|
||||
<GridRow Class="file-manager-nav">
|
||||
<Space>
|
||||
@if (Features.HasFlag(FileManagerFeatures.NavigationBack))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="Back" MouseEnterDelay="2">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.ArrowLeft" OnClick="NavigateBack" Disabled="@(Past.Count == 0)" />
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.NavigationForward))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="Forward" MouseEnterDelay="2">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.ArrowRight" OnClick="NavigateForward" Disabled="@(Future.Count == 0)" />
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.UpALevel))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="Up a Level" MouseEnterDelay="2">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.ArrowUp" OnClick="NavigateUp" Disabled="@(Path.Parent == null)" />
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.Refresh))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="Refresh" MouseEnterDelay="2">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Reload" OnClick="Refresh" />
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.Breadcrumbs))
|
||||
{
|
||||
<SpaceItem Class="file-manager-nav-breadcrumbs">
|
||||
<Breadcrumb>
|
||||
@foreach (var breadcrumb in Breadcrumbs)
|
||||
{
|
||||
<BreadcrumbItem OnClick="() => ChangeDirectory(breadcrumb, false)">@breadcrumb.Name</BreadcrumbItem>
|
||||
}
|
||||
</Breadcrumb>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.NewFolder))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="New Folder" MouseEnterDelay="2">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.FolderAdd" OnClick="() => NewFolderModal.Open()" />
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.UploadFile))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="Upload File" MouseEnterDelay="2">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Upload" OnClick="() => UploadModal.Open()" />
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
|
||||
@if (Features.HasFlag(FileManagerFeatures.Delete))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Tooltip Title="Delete" MouseEnterDelay="2">
|
||||
<Popconfirm OnConfirm="Delete">
|
||||
<TitleTemplate>
|
||||
Are you sure you want to delete the selected file@(Selected?.Count() == 1 ? "" : "s")?
|
||||
</TitleTemplate>
|
||||
<ChildContent>
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Delete" Disabled="@(Selected?.Count() == 0)" />
|
||||
</ChildContent>
|
||||
</Popconfirm>
|
||||
</Tooltip>
|
||||
</SpaceItem>
|
||||
}
|
||||
</Space>
|
||||
</GridRow>
|
||||
|
||||
<GridRow Class="file-manager-body">
|
||||
<GridCol Span="6" Class="file-manager-tree">
|
||||
<Tree TItem="FileManagerDirectory"
|
||||
DataSource="Directories"
|
||||
SwitcherIcon="@IconType.Outline.Down"
|
||||
TitleExpression="x => x.DataItem.Name"
|
||||
ChildrenExpression="x => x.DataItem.Children"
|
||||
IsLeafExpression="x => !x.DataItem.HasChildren"
|
||||
IconExpression="x => x.Expanded ? IconType.Outline.FolderOpen : IconType.Outline.Folder"
|
||||
DefaultExpandParent="true"
|
||||
OnClick="(args) => ChangeDirectory(args.Node.DataItem, false)"
|
||||
OnNodeLoadDelayAsync="ExpandTree">
|
||||
<SwitcherIconTemplate>
|
||||
<Icon Type="@IconType.Outline.Down" />
|
||||
</SwitcherIconTemplate>
|
||||
<TitleIconTemplate>
|
||||
@if (context.Expanded)
|
||||
{
|
||||
<Icon Type="@IconType.Outline.FolderOpen" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<Icon Type="@IconType.Outline.Folder" />
|
||||
}
|
||||
</TitleIconTemplate>
|
||||
</Tree>
|
||||
</GridCol>
|
||||
|
||||
<GridCol Span="18" Class="file-manager-list">
|
||||
<Table TItem="IFileManagerEntry"
|
||||
DataSource="Entries"
|
||||
HidePagination="true"
|
||||
Loading="Entries == null"
|
||||
OnRow="OnRow"
|
||||
SelectedRowsChanged="SelectedChanged"
|
||||
RowSelectable="EntrySelectable"
|
||||
Size="@TableSize.Small">
|
||||
<Selection Key="@context.Path" Type="@(SelectMultiple ? "checkbox" : "radio")" Disabled="!EntrySelectable.Invoke(context)" Class="@(EntrySelectable.Invoke(context) ? "" : "file-manager-selector-hidden")" />
|
||||
<Column TData="string" Width="32">
|
||||
@if (context is FileManagerFile)
|
||||
{
|
||||
<Icon Type="@(((FileManagerFile)context).GetIcon())" Theme="outline" />
|
||||
}
|
||||
else if (context is FileManagerDirectory)
|
||||
{
|
||||
<Icon Type="@IconType.Outline.Folder" />
|
||||
}
|
||||
</Column>
|
||||
<PropertyColumn Property="e => e.Path" Sortable Title="Name">
|
||||
@GetEntryName(context)
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="e => e.Size" Sortable Title="Size">
|
||||
@ByteSizeLib.ByteSize.FromBytes(context.Size)
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="e => e.ModifiedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Title="Modified" />
|
||||
</Table>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</div>
|
||||
|
||||
<NewFolderModal @ref="NewFolderModal" OnFolderNameEntered="AddFolder" />
|
||||
<UploadModal @ref="UploadModal" Path="@Path.Path" OnUploadCompleted="() => Refresh()" />
|
||||
|
||||
@code {
|
||||
[Parameter] public Guid ArchiveId { get; set; }
|
||||
[Parameter] public string WorkingDirectory { get; set; }
|
||||
[Parameter] public bool SelectMultiple { get; set; } = true;
|
||||
[Parameter] public FileManagerFeatures Features { get; set; } = FileManagerFeatures.NavigationBack | FileManagerFeatures.NavigationForward | FileManagerFeatures.UpALevel | FileManagerFeatures.Refresh | FileManagerFeatures.Breadcrumbs | FileManagerFeatures.NewFolder | FileManagerFeatures.UploadFile | FileManagerFeatures.Delete;
|
||||
[Parameter] public IEnumerable<IFileManagerEntry> Selected { get; set; } = new List<IFileManagerEntry>();
|
||||
[Parameter] public EventCallback<IEnumerable<IFileManagerEntry>> SelectedChanged { get; set; }
|
||||
[Parameter] public Func<IFileManagerEntry, bool> EntrySelectable { get; set; } = _ => true;
|
||||
[Parameter] public Func<IFileManagerEntry, bool> EntryVisible { get; set; } = _ => true;
|
||||
|
||||
FileManagerSource Source = FileManagerSource.FileSystem;
|
||||
|
||||
FileManagerDirectory Path { get; set; } = new FileManagerDirectory();
|
||||
|
||||
List<FileManagerDirectory> Past { get; set; } = new List<FileManagerDirectory>();
|
||||
List<FileManagerDirectory> Future { get; set; } = new List<FileManagerDirectory>();
|
||||
List<FileManagerDirectory> Breadcrumbs = new List<FileManagerDirectory>();
|
||||
|
||||
List<IFileManagerEntry> Entries { get; set; } = new List<IFileManagerEntry>();
|
||||
HashSet<FileManagerDirectory> Directories { get; set; } = new HashSet<FileManagerDirectory>();
|
||||
|
||||
NewFolderModal NewFolderModal;
|
||||
UploadModal UploadModal;
|
||||
|
||||
Dictionary<string, object> OnRow(RowData<IFileManagerEntry> row) => new()
|
||||
{
|
||||
["data-path"] = row.Data.Path,
|
||||
["ondblclick"] = ((System.Action)delegate
|
||||
{
|
||||
if (row.Data is FileManagerDirectory)
|
||||
ChangeDirectory((FileManagerDirectory)row.Data, true);
|
||||
})
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(WorkingDirectory))
|
||||
Source = FileManagerSource.FileSystem;
|
||||
else if (ArchiveId != Guid.Empty)
|
||||
Source = FileManagerSource.Archive;
|
||||
|
||||
Directories = await GetDirectoriesAsync();
|
||||
}
|
||||
|
||||
async Task<HashSet<FileManagerDirectory>> GetDirectoriesAsync()
|
||||
{
|
||||
switch (Source)
|
||||
{
|
||||
case FileManagerSource.FileSystem:
|
||||
return await GetFileSystemDirectoriesAsync(WorkingDirectory);
|
||||
case FileManagerSource.Archive:
|
||||
return await GetArchiveDirectoriesAsync(ArchiveId);
|
||||
}
|
||||
|
||||
return new HashSet<FileManagerDirectory>();
|
||||
}
|
||||
|
||||
async Task<HashSet<FileManagerDirectory>> GetFileSystemDirectoriesAsync(string path)
|
||||
{
|
||||
var paths = Directory.EnumerateDirectories(path, "*", new EnumerationOptions
|
||||
{
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = true,
|
||||
MaxRecursionDepth = 1
|
||||
});
|
||||
|
||||
var root = new FileManagerDirectory
|
||||
{
|
||||
Name = path,
|
||||
Path = path,
|
||||
IsExpanded = true
|
||||
};
|
||||
|
||||
root.PopulateChildren(paths);
|
||||
|
||||
await ChangeDirectory(root, true);
|
||||
|
||||
return new HashSet<FileManagerDirectory>
|
||||
{
|
||||
root
|
||||
};
|
||||
}
|
||||
|
||||
async Task<HashSet<FileManagerDirectory>> GetArchiveDirectoriesAsync(Guid archiveId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entries = await ArchiveService.GetContents(archiveId);
|
||||
var directories = new HashSet<FileManagerDirectory>();
|
||||
|
||||
var root = new FileManagerDirectory
|
||||
{
|
||||
Name = "Root",
|
||||
Path = "",
|
||||
IsExpanded = true
|
||||
};
|
||||
|
||||
root.PopulateChildren(entries);
|
||||
|
||||
await ChangeDirectory(root, true);
|
||||
|
||||
return new HashSet<FileManagerDirectory>
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(entry.Name) && entry.Size == 0)
|
||||
{
|
||||
return entry.Path.TrimEnd('/').Split('/').Last();
|
||||
}
|
||||
else
|
||||
return entry.Name;
|
||||
}
|
||||
|
||||
async Task ChangeDirectory(FileManagerDirectory directory, bool clearFuture)
|
||||
{
|
||||
if (Path != null && !String.IsNullOrWhiteSpace(Path.Path) && directory.Path != Path.Path && Past.LastOrDefault()?.Path != directory.Path)
|
||||
Past.Add(Path);
|
||||
|
||||
Path = directory;
|
||||
|
||||
await UpdateEntries();
|
||||
UpdateBreadcrumbs();
|
||||
|
||||
if (clearFuture)
|
||||
Future.Clear();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
async Task ExpandTree(TreeEventArgs<FileManagerDirectory> args)
|
||||
{
|
||||
if (Source == FileManagerSource.FileSystem)
|
||||
{
|
||||
var directory = (FileManagerDirectory)args.Node.DataItem;
|
||||
|
||||
foreach (var child in directory.Children)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var paths = Directory.EnumerateDirectories(child.Path, "*", new EnumerationOptions
|
||||
{
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = true,
|
||||
MaxRecursionDepth = 1
|
||||
});
|
||||
|
||||
child.PopulateChildren(paths);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateEntries()
|
||||
{
|
||||
Entries.Clear();
|
||||
|
||||
switch (Source)
|
||||
{
|
||||
case FileManagerSource.FileSystem:
|
||||
await Task.Run(UpdateFileSystemEntries);
|
||||
break;
|
||||
|
||||
case FileManagerSource.Archive:
|
||||
await UpdateArchiveEntries();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateFileSystemEntries()
|
||||
{
|
||||
var entries = Directory.EnumerateFileSystemEntries(Path.Path);
|
||||
var separator = System.IO.Path.DirectorySeparatorChar;
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (Directory.Exists(entry))
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new DirectoryInfo(entry);
|
||||
var directory = new FileManagerDirectory
|
||||
{
|
||||
Path = entry,
|
||||
Name = entry.Substring(Path.Path.Length).TrimStart(separator),
|
||||
ModifiedOn = info.LastWriteTime,
|
||||
CreatedOn = info.CreationTime,
|
||||
Parent = Path
|
||||
};
|
||||
|
||||
if (EntryVisible.Invoke(directory))
|
||||
Entries.Add(directory);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new FileInfo(entry);
|
||||
var file = new FileManagerFile
|
||||
{
|
||||
Path = entry,
|
||||
Name = System.IO.Path.GetFileName(entry),
|
||||
ModifiedOn = info.LastWriteTime,
|
||||
CreatedOn = info.CreationTime,
|
||||
Size = info.Length,
|
||||
Parent = Path
|
||||
};
|
||||
|
||||
if (EntryVisible.Invoke(file))
|
||||
Entries.Add(file);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateArchiveEntries()
|
||||
{
|
||||
var entries = await ArchiveService.GetContents(ArchiveId);
|
||||
var separator = '/';
|
||||
|
||||
foreach (var entry in entries.Where(e => e.FullName != Path.Path && e.FullName.StartsWith(Path.Path) && !e.FullName.Substring(Path.Path.Length).TrimEnd(separator).Contains(separator)))
|
||||
{
|
||||
if (entry.FullName.EndsWith(separator))
|
||||
{
|
||||
var directory = new FileManagerDirectory
|
||||
{
|
||||
Path = entry.FullName,
|
||||
Name = entry.Name,
|
||||
ModifiedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||
CreatedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||
Size = entry.Length,
|
||||
Parent = Path
|
||||
};
|
||||
|
||||
if (EntryVisible.Invoke(directory))
|
||||
Entries.Add(directory);
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = new FileManagerFile
|
||||
{
|
||||
Path = entry.FullName,
|
||||
Name = entry.Name,
|
||||
ModifiedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||
CreatedOn = entry.LastWriteTime.UtcDateTime.ToLocalTime(),
|
||||
Size = entry.Length,
|
||||
Parent = Path
|
||||
};
|
||||
|
||||
if (EntryVisible.Invoke(file))
|
||||
Entries.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateBreadcrumbs()
|
||||
{
|
||||
Breadcrumbs.Clear();
|
||||
|
||||
var currentPath = Path;
|
||||
|
||||
while (currentPath != null)
|
||||
{
|
||||
Breadcrumbs.Add(currentPath);
|
||||
|
||||
currentPath = currentPath.Parent;
|
||||
}
|
||||
|
||||
Breadcrumbs.Reverse();
|
||||
}
|
||||
|
||||
async Task NavigateBack()
|
||||
{
|
||||
if (Past.Count > 0)
|
||||
{
|
||||
Future.Add(Path);
|
||||
await ChangeDirectory(Past.Last(), false);
|
||||
Past = Past.Take(Past.Count - 1).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
async Task NavigateForward()
|
||||
{
|
||||
if (Future.Count > 0)
|
||||
{
|
||||
Past.Add(Path);
|
||||
await ChangeDirectory(Future.First(), false);
|
||||
Future = Future.Skip(1).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
async Task NavigateUp()
|
||||
{
|
||||
if (Path.Parent != null)
|
||||
await ChangeDirectory(Path.Parent, true);
|
||||
}
|
||||
|
||||
async Task Refresh()
|
||||
{
|
||||
await ChangeDirectory(Path, false);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
async Task AddFolder(string name)
|
||||
{
|
||||
if (Source == FileManagerSource.Archive)
|
||||
throw new NotImplementedException();
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(System.IO.Path.Combine(Path.Path, name));
|
||||
|
||||
await Refresh();
|
||||
|
||||
await MessageService.Success("Folder created!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
await MessageService.Error("Error creating folder!");
|
||||
}
|
||||
}
|
||||
|
||||
async Task Delete()
|
||||
{
|
||||
if (Source == FileManagerSource.Archive)
|
||||
throw new NotImplementedException();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var entry in Selected)
|
||||
{
|
||||
if (entry is FileManagerDirectory)
|
||||
Directory.Delete(entry.Path);
|
||||
else if (entry is FileManagerFile)
|
||||
File.Delete(entry.Path);
|
||||
}
|
||||
|
||||
Selected = new List<IFileManagerEntry>();
|
||||
MessageService.Success("Deleted!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageService.Error("Error deleting!");
|
||||
}
|
||||
|
||||
await Refresh();
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
using System.IO.Compression;
|
||||
|
||||
namespace LANCommander.Components.FileManagerComponents
|
||||
{
|
||||
public class FileManagerDirectory : FileManagerEntry
|
||||
{
|
||||
public bool IsExpanded { get; set; } = false;
|
||||
public bool HasChildren => Children != null && Children.Count > 0;
|
||||
public HashSet<FileManagerDirectory> Children { get; set; } = new HashSet<FileManagerDirectory>();
|
||||
|
||||
public void PopulateChildren(IEnumerable<ZipArchiveEntry> entries)
|
||||
{
|
||||
var path = Path == "/" ? "" : Path;
|
||||
var childPaths = entries.Where(e => e.FullName.EndsWith('/'));
|
||||
var directChildren = childPaths.Where(p => p.FullName != path && p.FullName.StartsWith(path) && p.FullName.Substring(path.Length).TrimEnd('/').Split('/').Length == 1);
|
||||
|
||||
foreach (var directChild in directChildren)
|
||||
{
|
||||
var child = new FileManagerDirectory()
|
||||
{
|
||||
Path = directChild.FullName,
|
||||
Name = directChild.FullName.Substring(path.Length).TrimEnd('/'),
|
||||
Parent = this
|
||||
};
|
||||
|
||||
child.PopulateChildren(entries);
|
||||
|
||||
Children.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
public void PopulateChildren(IEnumerable<string> entries)
|
||||
{
|
||||
var separator = System.IO.Path.DirectorySeparatorChar;
|
||||
var childPaths = entries.Where(e => e.StartsWith(Path));
|
||||
var directChildren = childPaths.Where(p => p != Path && p.Substring(Path.Length + 1).Split(separator).Length == 1);
|
||||
|
||||
foreach (var directChild in directChildren)
|
||||
{
|
||||
if (!Children.Any(c => c.Path == directChild))
|
||||
{
|
||||
var child = new FileManagerDirectory()
|
||||
{
|
||||
Path = directChild,
|
||||
Name = directChild.Substring(Path.Length).TrimStart(separator),
|
||||
Parent = this
|
||||
};
|
||||
|
||||
child.PopulateChildren(entries);
|
||||
|
||||
Children.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace LANCommander.Components.FileManagerComponents
|
||||
{
|
||||
public abstract class FileManagerEntry : IFileManagerEntry
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public long Size { get; set; }
|
||||
public FileManagerDirectory Parent { get; set; }
|
||||
public DateTime ModifiedOn { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
namespace LANCommander.Components.FileManagerComponents
|
||||
{
|
||||
[Flags]
|
||||
public enum FileManagerFeatures
|
||||
{
|
||||
NavigationBack = 0,
|
||||
NavigationForward = 1,
|
||||
UpALevel = 2,
|
||||
Refresh = 4,
|
||||
Breadcrumbs = 8,
|
||||
NewFolder = 16,
|
||||
UploadFile = 32,
|
||||
Delete = 64,
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
namespace LANCommander.Components.FileManagerComponents
|
||||
{
|
||||
public class FileManagerFile : FileManagerEntry
|
||||
{
|
||||
public string Extension => Name.Contains('.') ? Name.Split('.').Last() : Name;
|
||||
|
||||
public string GetIcon()
|
||||
{
|
||||
switch (Extension)
|
||||
{
|
||||
case "":
|
||||
return "folder";
|
||||
|
||||
case "exe":
|
||||
return "code";
|
||||
|
||||
case "zip":
|
||||
case "rar":
|
||||
case "7z":
|
||||
case "gz":
|
||||
case "tar":
|
||||
return "file-zip";
|
||||
|
||||
case "wad":
|
||||
case "pk3":
|
||||
case "pak":
|
||||
case "cab":
|
||||
return "file-zip";
|
||||
|
||||
case "txt":
|
||||
case "cfg":
|
||||
case "config":
|
||||
case "ini":
|
||||
case "yml":
|
||||
case "yaml":
|
||||
case "log":
|
||||
case "doc":
|
||||
case "nfo":
|
||||
return "file-text";
|
||||
|
||||
case "bat":
|
||||
case "ps1":
|
||||
case "json":
|
||||
return "code";
|
||||
|
||||
case "bik":
|
||||
case "avi":
|
||||
case "mov":
|
||||
case "mp4":
|
||||
case "m4v":
|
||||
case "mkv":
|
||||
case "wmv":
|
||||
case "mpg":
|
||||
case "mpeg":
|
||||
case "flv":
|
||||
return "video-camera";
|
||||
|
||||
case "dll":
|
||||
return "api";
|
||||
|
||||
case "hlp":
|
||||
return "file-unknown";
|
||||
|
||||
case "png":
|
||||
case "bmp":
|
||||
case "jpeg":
|
||||
case "jpg":
|
||||
case "gif":
|
||||
return "file-image";
|
||||
|
||||
default:
|
||||
return "file";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace LANCommander.Components.FileManagerComponents
|
||||
{
|
||||
public enum FileManagerSource
|
||||
{
|
||||
FileSystem,
|
||||
Archive
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace LANCommander.Components.FileManagerComponents
|
||||
{
|
||||
public interface IFileManagerEntry
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public long Size { get; set; }
|
||||
public FileManagerDirectory Parent { get; set; }
|
||||
public DateTime ModifiedOn { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<Modal Title="New Folder" Visible="@Visible" Draggable="true" DragInViewport="false" OnOk="OnOk" OnCancel="Close">
|
||||
<Input @bind-Value="@Name" />
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<string> OnFolderNameEntered { get; set; }
|
||||
|
||||
bool Visible { get; set; } = false;
|
||||
string Name { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Name = "";
|
||||
}
|
||||
|
||||
public void Open()
|
||||
{
|
||||
Name = "";
|
||||
Visible = true;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
async Task OnOk(MouseEventArgs e)
|
||||
{
|
||||
if (OnFolderNameEntered.HasDelegate)
|
||||
await OnFolderNameEntered.InvokeAsync(Name);
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<Modal Title="Upload Files" Visible="@Visible" Draggable="true" DragInViewport="false" OnCancel="Close">
|
||||
<Upload Action="/Upload/File" Name="file" Drag Multiple Data="Data" OnCompleted="OnCompleted">
|
||||
<p class="ant-upload-drag-icon">
|
||||
<Icon Type="@IconType.Outline.Upload" />
|
||||
</p>
|
||||
<p class="ant-upload-text">Click or Drag Files</p>
|
||||
</Upload>
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
[Parameter] public string Path { get; set; }
|
||||
[Parameter] public EventCallback OnUploadCompleted { get; set; }
|
||||
|
||||
bool Visible = false;
|
||||
|
||||
Dictionary<string, object> Data = new Dictionary<string, object>();
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
Data["Path"] = Path;
|
||||
}
|
||||
|
||||
public void Open()
|
||||
{
|
||||
Visible = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Visible = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
async Task OnCompleted()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (OnUploadCompleted.HasDelegate)
|
||||
await OnUploadCompleted.InvokeAsync();
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
@using LANCommander.Components.FileManagerComponents;
|
||||
@using LANCommander.Models;
|
||||
@using System.IO.Compression;
|
||||
@inject ModalService ModalService
|
||||
@inject ArchiveService ArchiveService
|
||||
|
||||
<Space Style="display: flex">
|
||||
<SpaceItem Style="flex-grow: 1">
|
||||
<Input Type="text" @bind-Value="Value" OnChange="ValueChanged" />
|
||||
</SpaceItem>
|
||||
@if (ArchiveId != Guid.Empty) {
|
||||
<SpaceItem>
|
||||
<Button OnClick="BrowseForFile" Type="@ButtonType.Primary" Icon="@IconType.Outline.FolderOpen" Disabled="!ArchiveExists" />
|
||||
</SpaceItem>
|
||||
}
|
||||
else if (!String.IsNullOrWhiteSpace(Root))
|
||||
{
|
||||
<SpaceItem>
|
||||
<Button OnClick="BrowseForFile" Type="@ButtonType.Primary" Icon="@IconType.Outline.FolderOpen" />
|
||||
</SpaceItem>
|
||||
}
|
||||
</Space>
|
||||
|
||||
@code {
|
||||
[Parameter] public string Value { get; set; }
|
||||
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||
[Parameter] public EventCallback<string> OnSelected { get; set; }
|
||||
[Parameter] public Guid ArchiveId { get; set; }
|
||||
[Parameter] public string Title { get; set; } = "Choose File";
|
||||
[Parameter] public string OkText { get; set; } = "Select File";
|
||||
[Parameter] public bool AllowDirectories { get; set; } = false;
|
||||
[Parameter] public string Prefix { get; set; }
|
||||
[Parameter] public string Root { get; set; }
|
||||
[Parameter] public Func<IFileManagerEntry, bool> EntrySelectable { get; set; } = _ => true;
|
||||
[Parameter] public Func<IFileManagerEntry, bool> EntryVisible { get; set; } = _ => true;
|
||||
|
||||
bool ArchiveExists { get; set; } = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (ArchiveId != Guid.Empty)
|
||||
ArchiveExists = await ArchiveService.Exists(ArchiveId);
|
||||
}
|
||||
|
||||
private async void BrowseForFile()
|
||||
{
|
||||
var modalOptions = new ModalOptions()
|
||||
{
|
||||
Title = Title,
|
||||
Maximizable = false,
|
||||
DefaultMaximized = true,
|
||||
Closable = true,
|
||||
OkText = OkText,
|
||||
WrapClassName = "file-picker-dialog"
|
||||
};
|
||||
|
||||
var browserOptions = new FilePickerOptions()
|
||||
{
|
||||
ArchiveId = ArchiveId,
|
||||
Root = Root,
|
||||
Select = true,
|
||||
Multiple = false,
|
||||
EntrySelectable = EntrySelectable,
|
||||
EntryVisible = EntryVisible
|
||||
};
|
||||
|
||||
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
||||
|
||||
modalRef.OnOk = async (results) =>
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(Prefix))
|
||||
Value = Prefix + results?.FirstOrDefault()?.Path;
|
||||
else
|
||||
Value = results?.FirstOrDefault()?.Path;
|
||||
|
||||
if (ValueChanged.HasDelegate)
|
||||
await ValueChanged.InvokeAsync(Value);
|
||||
|
||||
if (OnSelected.HasDelegate)
|
||||
await OnSelected.InvokeAsync(Value);
|
||||
|
||||
StateHasChanged();
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
@inherits FeedbackComponent<FilePickerOptions, IEnumerable<IFileManagerEntry>>
|
||||
@using System.IO.Compression;
|
||||
@using LANCommander.Components.FileManagerComponents;
|
||||
@using LANCommander.Models;
|
||||
|
||||
<FileManager ArchiveId="@Options.ArchiveId" WorkingDirectory="@Options.Root" @bind-Selected="SelectedFiles" EntrySelectable="Options.EntrySelectable" EntryVisible="Options.EntryVisible" SelectMultiple="Options.Multiple" Features="@(FileManagerFeatures.NavigationBack | FileManagerFeatures.NavigationForward | FileManagerFeatures.UpALevel | FileManagerFeatures.Breadcrumbs)" />
|
||||
|
||||
@code {
|
||||
|
||||
private IEnumerable<IFileManagerEntry> SelectedFiles { get; set; }
|
||||
|
||||
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||
{
|
||||
await base.OkCancelRefWithResult!.OnOk(SelectedFiles);
|
||||
}
|
||||
}
|
|
@ -6,27 +6,25 @@
|
|||
@inject CompanyService CompanyService
|
||||
@inject GenreService GenreService
|
||||
@inject TagService TagService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@{
|
||||
RenderFragment Footer =
|
||||
@<Template>
|
||||
<Button OnClick="SelectGame" Disabled="@(Loading || (SelectedResults != null && SelectedResults.Count() == 0) || Results.Count() == 0)" Type="@ButtonType.Primary">Select</Button>
|
||||
<Button OnClick="() => ModalVisible = false">Cancel</Button>
|
||||
<Button OnClick="SelectGame" Disabled="@(Results == null || Results.Count() == 0)" Type="@ButtonType.Primary">Select</Button>
|
||||
<Button OnClick="() => ModalVisible= false">Cancel</Button>
|
||||
</Template>;
|
||||
}
|
||||
|
||||
<Modal Visible="ModalVisible" Title="Game Metadata Lookup" Footer="@Footer" Class="game-metadata-lookup-modal">
|
||||
<Modal Visible="ModalVisible" Title="Game Metadata Lookup" Footer="@Footer">
|
||||
<Table
|
||||
@ref="ResultsTable"
|
||||
TItem="Game"
|
||||
DataSource="Results"
|
||||
HidePagination="true"
|
||||
Loading="Loading"
|
||||
Loading="Results == null"
|
||||
OnRowClick="OnRowClicked"
|
||||
@bind-SelectedRows="SelectedResults"
|
||||
ScrollY="calc(100vh - 55px - 55px - 53px)"
|
||||
Responsive>
|
||||
ScrollY="calc(100vh - 55px - 55px - 53px)">
|
||||
|
||||
<Selection Key="@context.IGDBId.ToString()" Type="radio" />
|
||||
<PropertyColumn Property="g => g.Title" Title="Title" />
|
||||
|
@ -37,21 +35,8 @@
|
|||
</Table>
|
||||
</Modal>
|
||||
|
||||
@if (IGDBService.Authenticated)
|
||||
{
|
||||
<Button OnClick="() => SearchForGame(GameTitle)" Type="@ButtonType.Primary" Disabled="@(String.IsNullOrWhiteSpace(GameTitle))">@ButtonText</Button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Popconfirm OnConfirm="() => NavigateToSettings()" Title="Invalid IGDB credentials. Setup now?">
|
||||
<Button Type="@ButtonType.Primary">@ButtonText</Button>
|
||||
</Popconfirm>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<GameLookupResult> OnResultSelected { get; set; }
|
||||
[Parameter] public string ButtonText { get; set; }
|
||||
[Parameter] public string GameTitle { get; set; }
|
||||
|
||||
ITable? ResultsTable;
|
||||
|
||||
|
@ -59,7 +44,6 @@ else
|
|||
IEnumerable<Game> SelectedResults { get; set; }
|
||||
PCGamingWikiClient PCGamingWikiClient { get; set; }
|
||||
bool ModalVisible { get; set; } = false;
|
||||
bool Loading = true;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
@ -71,16 +55,13 @@ else
|
|||
ResultsTable.SetSelection(new string[] { row.Data.IGDBId.ToString() });
|
||||
}
|
||||
|
||||
private async Task SearchForGame(string title)
|
||||
public async Task SearchForGame(string title)
|
||||
{
|
||||
Loading = true;
|
||||
ModalVisible = true;
|
||||
Results = null;
|
||||
|
||||
var results = await IGDBService.Search(title, "involved_companies.*", "involved_companies.company.*");
|
||||
|
||||
Loading = false;
|
||||
|
||||
if (results == null)
|
||||
Results = new List<Game>();
|
||||
else
|
||||
|
@ -110,8 +91,6 @@ else
|
|||
|
||||
private async Task SelectGame()
|
||||
{
|
||||
Loading = true;
|
||||
|
||||
var result = new GameLookupResult();
|
||||
|
||||
result.IGDBMetadata = await IGDBService.Get(SelectedResults.First().IGDBId.GetValueOrDefault(), "genres.*", "game_modes.*", "multiplayer_modes.*", "release_dates.*", "platforms.*", "keywords.*", "involved_companies.*", "involved_companies.company.*", "cover.*");
|
||||
|
@ -163,9 +142,4 @@ else
|
|||
|
||||
return multiplayerModes;
|
||||
}
|
||||
|
||||
private void NavigateToSettings()
|
||||
{
|
||||
NavigationManager.NavigateTo("/Settings/General", true);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<div class="image-picker">
|
||||
<div class="image-picker-images">
|
||||
@foreach (var image in Images)
|
||||
{
|
||||
<div class="image-picker-image" style="width: @(Size)px; max-height: @(Size)px">
|
||||
<input type="radio" id="image-picker-image-@image.Key" checked="@(Value == image.Key)" name="SelectedResult" @onchange="@(() => SelectionChanged(image.Key))" />
|
||||
<label for="image-picker-image-@image.Key"></label>
|
||||
<img src="@image.Value" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public double Size { get; set; }
|
||||
[Parameter] public string Value { get; set; }
|
||||
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||
[Parameter] public Dictionary<string, string> Images { get; set; }
|
||||
|
||||
async Task SelectionChanged(string key)
|
||||
{
|
||||
Value = key;
|
||||
|
||||
if (ValueChanged.HasDelegate)
|
||||
await ValueChanged.InvokeAsync(key);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<AntDesign.Input Type="text" @bind-Value="Path" OnBlur="() => OnChanged()">
|
||||
<AddOnBefore>
|
||||
<SimpleSelect @bind-Value="Hive" Style="width: auto;" OnSelectedItemsChanged="() => OnChanged()">
|
||||
<SelectOptions>
|
||||
@foreach (var hive in AvailableHives)
|
||||
{
|
||||
<SimpleSelectOption Value="@hive" Label="@hive"></SimpleSelectOption>
|
||||
}
|
||||
</SelectOptions>
|
||||
</SimpleSelect>
|
||||
</AddOnBefore>
|
||||
</AntDesign.Input>
|
||||
|
||||
@code {
|
||||
[Parameter] public string Value { get; set; } = "";
|
||||
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||
|
||||
string Hive = "HKCU:\\";
|
||||
string Path = "SOFTWARE";
|
||||
|
||||
string[] AvailableHives = new string[]
|
||||
{
|
||||
"HKCR:\\",
|
||||
"HKCU:\\",
|
||||
"HKLM:\\",
|
||||
"HKU:\\",
|
||||
"HKCC:\\"
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Hive == null)
|
||||
Hive = "HKCU:\\";
|
||||
|
||||
if (Value == null)
|
||||
Value = Hive;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(Value))
|
||||
Hive = AvailableHives.FirstOrDefault(h => Value != null && Value.StartsWith(h));
|
||||
|
||||
Path = Value.Substring(Hive.Length);
|
||||
}
|
||||
|
||||
private async void OnChanged()
|
||||
{
|
||||
Value = Hive + Path;
|
||||
|
||||
if (ValueChanged.HasDelegate)
|
||||
await ValueChanged.InvokeAsync(Value);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
@inject KeyService KeyService
|
||||
@inject IMessageService MessageService
|
||||
|
||||
<Row>
|
||||
<Col Span="8">
|
||||
<Statistic Title="Available" Value="Game.Keys.Count - AllocatedKeys" Style="text-align: center;" />
|
||||
</Col>
|
||||
<Col Span="8">
|
||||
<Statistic Title="Allocated" Value="AllocatedKeys" Style="text-align: center;" />
|
||||
</Col>
|
||||
<Col Span="8">
|
||||
<Statistic Title="Total" Value="Game.Keys.Count" Style="text-align: center;" />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Modal Title="View Keys" Visible="ViewModalVisible" Maximizable="false" DefaultMaximized="true" OnCancel="() => ViewModalVisible = false" OnOk="() => ViewModalVisible = false">
|
||||
<Table TItem="Key" DataSource="@Game.Keys" Bordered>
|
||||
<PropertyColumn Property="k => k.Value">
|
||||
<InputPassword @bind-Value="@context.Value" />
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="k => k.AllocationMethod" />
|
||||
<Column TData="string">
|
||||
@switch (context.AllocationMethod)
|
||||
{
|
||||
case KeyAllocationMethod.MacAddress:
|
||||
<text>@context.ClaimedByMacAddress</text>
|
||||
break;
|
||||
|
||||
case KeyAllocationMethod.UserAccount:
|
||||
<text>@context.ClaimedByUser?.UserName</text>
|
||||
break;
|
||||
}
|
||||
</Column>
|
||||
<PropertyColumn Property="g => g.ClaimedOn" Format="MM/dd/yyyy hh:mm tt" Sortable />
|
||||
<ActionColumn Title="">
|
||||
<Space>
|
||||
<SpaceItem>
|
||||
@if (context.IsAllocated())
|
||||
{
|
||||
<Button OnClick="() => Release(context)">Release</Button>
|
||||
}
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
</Table>
|
||||
</Modal>
|
||||
|
||||
<Modal Title="Edit Keys" Visible="EditModalVisible" Maximizable="false" DefaultMaximized="true" OnCancel="() => EditModalVisible = false" OnOk="Save">
|
||||
<StandaloneCodeEditor @ref="Editor" Id="editor" ConstructionOptions="EditorConstructionOptions" />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.monaco-editor-container {
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
[Parameter] public Game Game { get; set; }
|
||||
|
||||
int AllocatedKeys;
|
||||
|
||||
bool ViewModalVisible = false;
|
||||
bool EditModalVisible = false;
|
||||
|
||||
private StandaloneCodeEditor? Editor;
|
||||
|
||||
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
|
||||
{
|
||||
return new StandaloneEditorConstructionOptions
|
||||
{
|
||||
AutomaticLayout = true,
|
||||
Language = "text",
|
||||
Value = String.Join('\n', Game.Keys.Select(k => k.Value)),
|
||||
Theme = "vs-dark",
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Game.Keys == null)
|
||||
Game.Keys = new List<Key>();
|
||||
|
||||
AllocatedKeys = Game.Keys.Count(k => k.IsAllocated());
|
||||
}
|
||||
|
||||
public void Edit()
|
||||
{
|
||||
EditModalVisible = true;
|
||||
}
|
||||
|
||||
public void View()
|
||||
{
|
||||
ViewModalVisible = true;
|
||||
}
|
||||
|
||||
private async Task Release(Key key)
|
||||
{
|
||||
key = await KeyService.Release(key);
|
||||
|
||||
await MessageService.Success("Key was unallocated!");
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var value = await Editor.GetValue();
|
||||
var keys = value.Split("\n").Select(k => k.Trim()).Where(k => !String.IsNullOrWhiteSpace(k));
|
||||
|
||||
var keysDeleted = Game.Keys.Where(k => !keys.Contains(k.Value));
|
||||
var keysAdded = keys.Where(k => !Game.Keys.Any(gk => gk.Value == k));
|
||||
|
||||
foreach (var key in keysDeleted)
|
||||
KeyService.Delete(key);
|
||||
|
||||
foreach (var key in keysAdded)
|
||||
await KeyService.Add(new Key()
|
||||
{
|
||||
Game = Game,
|
||||
Value = key
|
||||
});
|
||||
|
||||
EditModalVisible = false;
|
||||
|
||||
await MessageService.Success("Keys updated!");
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<Header>
|
||||
<div class="logo" style="background: url('/static/logo-dark.svg'); width: 143px; height: 31px; margin: 16px 24px 16px 0; float: left; background-size: contain;" />
|
||||
|
||||
<Menu Theme="MenuTheme.Dark" Mode="MenuMode.Horizontal">
|
||||
@ChildContent
|
||||
</Menu>
|
||||
</Header>
|
||||
|
||||
<MobileMenu>
|
||||
@ChildContent
|
||||
</MobileMenu>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
@inherits FeedbackComponent<MediaGrabberOptions, MediaGrabberResult>
|
||||
@using LANCommander.Data.Enums;
|
||||
@using LANCommander.Models;
|
||||
@inject IMediaGrabberService MediaGrabberService
|
||||
|
||||
<GridRow Justify="space-between">
|
||||
<GridCol Span="6">
|
||||
<Search @bind-Value="Search" OnSearch="(x) => GetResults(Type, x)" DefaultValue="@Search" />
|
||||
</GridCol>
|
||||
<GridCol Span="12"></GridCol>
|
||||
<GridCol Span="6">
|
||||
<Slider TValue="double" @bind-Value="Size" DefaultValue="200" Min="50" Max="400" />
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
|
||||
@foreach (var group in Results)
|
||||
{
|
||||
<div class="media-grabber-group">
|
||||
<h2>@group.First().Group</h2>
|
||||
|
||||
<ImagePicker Size="Size" Images="@group.ToDictionary(r => r.Id, r => r.ThumbnailUrl)" ValueChanged="OnImageSelected" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string Search { get; set; }
|
||||
[Parameter] public MediaType Type { get; set; }
|
||||
|
||||
MediaGrabberResult Media { get; set; }
|
||||
|
||||
double Size { get; set; } = 200;
|
||||
|
||||
IEnumerable<IEnumerable<MediaGrabberResult>> Results = new List<List<MediaGrabberResult>>();
|
||||
Dictionary<string, string> Images { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
protected override async Task OnFirstAfterRenderAsync()
|
||||
{
|
||||
Type = Options.Type;
|
||||
Search = Options.Search;
|
||||
|
||||
await GetResults(Type, Search);
|
||||
}
|
||||
|
||||
private async Task GetResults(MediaType type, string search)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
Results = (await MediaGrabberService.SearchAsync(type, search)).GroupBy(r => r.Group);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnImageSelected(string key)
|
||||
{
|
||||
Media = Results.SelectMany(g => g).FirstOrDefault(r => r.Id == key);
|
||||
}
|
||||
|
||||
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||
{
|
||||
await base.OkCancelRefWithResult!.OnOk(Media);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div class="mobile-menu">
|
||||
<Header Class="mobile-header">
|
||||
<div class="logo" style="background: url('/static/logo-dark.svg'); width: 143px; height: 31px; background-size: contain;" />
|
||||
<Button Icon="@IconType.Outline.Menu" Type="@ButtonType.Text" OnClick="ToggleMenu" />
|
||||
</Header>
|
||||
|
||||
<Drawer Closable="true" Visible="@MenuDrawerOpen" Placement="@("top")" Class="menu-drawer">
|
||||
<Menu Theme="MenuTheme.Dark" Mode="MenuMode.Vertical">
|
||||
@ChildContent
|
||||
</Menu>
|
||||
</Drawer>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
|
||||
bool MenuDrawerOpen = false;
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
NavigationManager.LocationChanged += CloseMenu;
|
||||
}
|
||||
|
||||
void ToggleMenu()
|
||||
{
|
||||
MenuDrawerOpen = !MenuDrawerOpen;
|
||||
}
|
||||
|
||||
void CloseMenu(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
MenuDrawerOpen = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue