Compare commits

..

No commits in common. "main" and "client" have entirely different histories.
main ... client

2571 changed files with 40654 additions and 646302 deletions

View file

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Put relevant logs here in a code block
**Versions:**
- LANCommander.Server version: [e.g. 1.1.4]
- LANCommander.Launcher version: [e.g. 1.1.4]
**Additional Info**
- Running in Docker: [e.g. yes/no]
- Behind a reverse proxy: [e.g. none, traefik, nginx]
**Additional context**
Add any other context about the problem here.

View file

@ -1,84 +0,0 @@
name: 'Build LANCommander.Launcher'
inputs:
version_semver:
description: "Semantic Version"
required: true
type: string
version_tag:
description: 'Version Tag'
required: true
type: string
build_dotnet_version:
description: 'Build .NET Version'
required: true
type: string
build_runtime:
description: 'Build Runtime'
required: false
type: string
default: 'win-x64'
build_arch:
description: 'Build Architecture'
required: false
type: string
default: 'x64'
build_platform:
description: 'Build Platform'
required: false
type: string
default: 'Windows'
build_configuration:
description: 'Build Configuration (Debug/Release)'
required: false
type: string
default: 'Release'
runs:
using: "composite"
steps:
- name: Publish Components
run: |
dotnet publish "./LANCommander.AutoUpdater/LANCommander.AutoUpdater.csproj" -c ${{ inputs.build_configuration }} --self-contained --runtime ${{ inputs.build_runtime }} -p:Version="${{ inputs.version_tag }}" -p:AssemblyVersion="${{ inputs.version_semver }}"
dotnet publish "./LANCommander.Launcher/LANCommander.Launcher.csproj" -c ${{ inputs.build_configuration }} --self-contained --runtime ${{ inputs.build_runtime }} -p:Version="${{ inputs.version_tag }}" -p:AssemblyVersion="${{ inputs.version_semver }}"
dotnet publish "./LANCommander.Launcher.CLI/LANCommander.Launcher.CLI.csproj" -c ${{ inputs.build_configuration }} --self-contained --runtime ${{ inputs.build_runtime }} -p:Version="${{ inputs.version_tag }}" -p:AssemblyVersion="${{ inputs.version_semver }}"
- name: Bundle and Clean
run: |
Copy-Item -Force -Recurse -Verbose LANCommander.AutoUpdater/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/* LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/
Copy-Item -Force -Recurse -Verbose LANCommander.Launcher.CLI/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/* LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/
# Remove unnecessary files in a single operation
$PathsToRemove = @(
'wwwroot/_content/BootstrapBlazor.PdfReader/compat',
'wwwroot/_content/BootstrapBlazor.PdfReader/2.*',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/pdf.sandbox.js',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/*.map',
'wwwroot/_content/BootstrapBlazor.PdfReader/web/*.map',
'wwwroot/_content/AntDesign/less',
'wwwroot/_content/BlazorMonaco/lib/monaco-editor/min-maps',
'wwwroot/Identity/lib/bootstrap',
'LANCommander.ico',
'LANCommanderDark.ico',
'package-lock.json',
'package.json',
'*.pdb',
'hostfxr.dll.bak',
'Libraries/locales'
)
$BasePath = "LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish"
foreach ($path in $PathsToRemove) {
Remove-Item -Recurse -Force -ErrorAction Continue "$BasePath/$path"
}
- name: Compress Build Output
run: |
$compress = @{
Path = "LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/*"
DestinationPath = "LANCommander.Launcher-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
- name: Output artifact path
run: echo "artifact=LANCommander.Launcher-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip >> $GITHUB_OUTPUT"

View file

@ -1,86 +0,0 @@
name: 'Build LANCommander.Server'
inputs:
version_semver:
description: "Semantic Version"
required: true
type: string
version_tag:
description: 'Version Tag'
required: true
type: string
build_dotnet_version:
description: 'Build .NET Version'
required: true
type: string
build_runtime:
description: 'Build Runtime'
required: false
type: string
default: 'win-x64'
build_arch:
description: 'Build Architecture'
required: false
type: string
default: 'x64'
build_platform:
description: 'Build Platform'
required: false
type: string
default: 'Windows'
build_configuration:
description: 'Build Configuration (Debug/Release)'
required: false
type: string
default: 'Release'
outputs:
artifact:
description: "Output artifact"
runs:
using: "composite"
steps:
- name: Publish Updater and Server
run: |
dotnet publish "./LANCommander.AutoUpdater/LANCommander.AutoUpdater.csproj" -c ${{ inputs.build_configuration }} --self-contained --runtime ${{ inputs.build_runtime }} -p:Version="${{ inputs.version_tag }}" -p:AssemblyVersion="${{ inputs.version_semver }}"
dotnet publish "./LANCommander.Server/LANCommander.Server.csproj" -c ${{ inputs.build_configuration }} --self-contained --runtime ${{ inputs.build_runtime }} -p:Version="${{ inputs.version_tag }}" -p:AssemblyVersion="${{ inputs.version_semver }}"
- name: Bundle and Clean
run: |
Copy-Item -Force -Recurse -Verbose LANCommander.AutoUpdater/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/* LANCommander.Server/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/
# Remove unnecessary files in a single operation
$PathsToRemove = @(
'wwwroot/_content/BootstrapBlazor.PdfReader/compat',
'wwwroot/_content/BootstrapBlazor.PdfReader/2.*',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/pdf.sandbox.js',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/*.map',
'wwwroot/_content/BootstrapBlazor.PdfReader/web/*.map',
'wwwroot/_content/AntDesign/less',
'wwwroot/_content/BlazorMonaco/lib/monaco-editor/min-maps',
'wwwroot/Identity/lib/bootstrap',
'LANCommander.ico',
'LANCommanderDark.ico',
'package-lock.json',
'package.json',
'*.pdb',
'hostfxr.dll.bak',
'Libraries/locales'
)
$BasePath = "LANCommander.Server/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish"
foreach ($path in $PathsToRemove) {
Remove-Item -Recurse -Force -ErrorAction Continue "$BasePath/$path"
}
- name: Compress Build Output
run: |
$compress = @{
Path = "LANCommander.Server/bin/${{ inputs.build_configuration }}/net9.0/${{ inputs.build_runtime }}/publish/*"
DestinationPath = "LANCommander.Server-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
- name: Output artifact path
run: echo "artifact=LANCommander.Server-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip >> $GITHUB_OUTPUT"

View file

@ -1,100 +0,0 @@
name: LANCommander Server Restore
on:
workflow_dispatch:
workflow_call:
inputs:
build_dotnet_version:
description: 'Build .NET Version'
required: true
type: string
permissions:
contents: write
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/package
jobs:
build:
runs-on: windows-latest
steps:
- uses: frabert/replace-string-action@v2
name: Swap Path Backslashes
id: swap_path_backslashes
with:
string: '${{ github.workspace }}'
pattern: '\\'
replace-with: '/'
flags: g
# Checkout code
- uses: actions/checkout@v3
with:
submodules: true
# .NET Setup and Caching
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.build_dotnet_version }}
- name: Restore dependencies
run: dotnet restore --locked-mode
- name: NuGet Cache
uses: actions/cache/save@v4
with:
path: ${{ env.NUGET_PACKAGES }}
key: ${{ runner.os }}-dotnet-${{ hashFiles('**/*.csproj') }}
# Node.js Setup and Caching
- name: Setup Node.js
uses: actions/setup-node@v3.8.1
with:
node-version: '20'
- name: Generate PowerShell Completions
run: dotnet run --project ./LANCommander.CompletionGenerator/LANCommander.CompletionGenerator.csproj -- ./LANCommander.UI/Components/MonacoCodeEditor/PowerShellCompletions.g.ts
# Parallel UI builds
- name: Build UI Components
run: |
cd ./LANCommander.UI
npm install
npm run package
- name: Cache UI npm packages
uses: actions/cache/save@v4
with:
path: ./LANCommander.UI/node_modules
key: LANCommander.UI-npm-${{ hashFiles('**/package-lock.json') }}
- name: Cache UI build
uses: actions/cache/save@v4
with:
path: ./LANCommander.UI/wwwroot
key: LANCommander.UI-${{ hashFiles('**/package-lock.json') }}
- name: Install Server Node Dependencies
run: |
cd ./LANCommander.Server
npm install
- name: Cache Server npm packages
uses: actions/cache/save@v4
with:
path: ./LANCommander.Server/node_modules
key: LANCommander.Server-npm-${{ hashFiles('**/package-lock.json') }}
- name: Install Launcher Node Dependencies
run: |
cd ./LANCommander.Launcher
npm install
- name: Cache Launcher npm packages
uses: actions/cache/save@v4
with:
path: ./LANCommander.Launcher/node_modules
key: LANCommander.Launcher-npm-${{ hashFiles('**/package-lock.json') }}

View file

@ -1,88 +0,0 @@
name: LANCommander Debug Build
on:
push:
tags:
- 'v*-debug'
permissions:
contents: read
jobs:
deploy_server_win-x64:
runs-on: [self-hosted, LANCommander Server]
env:
DOTNET_VERSION: '9.0.x' # Define the .NET version here
PUBLISH_DIR: 'C:\LANCommander' # Define the output directory
RUNTIME: 'win-x64' # Define the runtime platform
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Extract Version from Tag
id: extract_version
run: |
$tag = "${{ github.ref }}".Replace("refs/tags/v", "")
$version = $tag -replace "-debug", ""
echo "version=$version" >> $env:GITHUB_ENV
shell: pwsh
- name: Generate Build Number and Timestamp
id: build_info
run: |
$buildNumber = "${{ github.run_number }}"
$timestamp = Get-Date -Format "yyyyMMddHHmm"
echo "build_number=$buildNumber" >> $env:GITHUB_ENV
echo "timestamp=$timestamp" >> $env:GITHUB_ENV
shell: pwsh
- name: Stop LANCommander Server Service
run: |
$serviceName = "LANCommander"
if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) {
Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 5
}
shell: pwsh
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# Node.js
- name: Setup Node.js environment
uses: actions/setup-node@v3.8.1
# UI
- name: Generate PowerShell Completions
run: dotnet run --project ./LANCommander.CompletionGenerator/LANCommander.CompletionGenerator.csproj -- ./LANCommander.UI/Components/MonacoCodeEditor/PowerShellCompletions.g.ts
- run: cd ./LANCommander.UI; npm install; npm run package
- run: cd ./LANCommander.Server; npm install
- name: Restore dependencies
run: dotnet restore
- name: Publish LANCommander.Server
run: |
dotnet publish LANCommander.Server/LANCommander.Server.csproj `
-c Release `
-r ${{ env.RUNTIME }} `
--self-contained `
-p:Version=${{ env.version }} `
-p:FileVersion=${{ env.version }}.${{ env.build_number }} `
-p:InformationalVersion=${{ env.version }}+${{ env.timestamp }} `
-o "${{ env.PUBLISH_DIR }}"
shell: pwsh
- name: Start LANCommander Server Service
run: |
$serviceName = "LANCommander"
if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) {
Start-Service -Name $serviceName
}
shell: pwsh
- name: Verify build output
run: dir "${{ env.PUBLISH_DIR }}"

View file

@ -1,254 +0,0 @@
name: LANCommander Development
on:
push:
branches:
- development
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: lancommander/lancommander
permissions:
contents: write
packages: write
id-token: write
attestations: write
jobs:
# --------------------------------------------------------------------------
# 1) PREP JOB: figure out the latest semver, build development version,
# check if there are commits since the last development tag. If none,
# skip the rest of the workflow.
# --------------------------------------------------------------------------
prep:
runs-on: ubuntu-latest
outputs:
version_semver: ${{ steps.set_version.outputs.VERSION_SEMVER }}
version_tag: ${{ steps.set_version.outputs.VERSION_TAG }}
changed: ${{ steps.check_diff.outputs.changed }}
build_dotnet_version: 9.0.102
steps:
- name: Check out code
uses: actions/checkout@v4
with:
# Ensure we get all tags so we can find the latest
fetch-depth: 0
- name: Determine last semver and build development version
id: set_version
shell: bash
run: |
# Fetch all tags
git fetch --tags
# Grab the last semver-ish tag (e.g., "v1.2.3", "v1.2.3-debug", or "1.2.3-development")
LAST_SEMVER_TAG="$(git tag --list --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+' | head -n1)"
if [ -z "$LAST_SEMVER_TAG" ]; then
echo "No semver tag found; defaulting to 0.0.0"
LAST_SEMVER_TAG="0.0.0"
fi
# Remove prefix 'v' and any suffix like "-debug", "-development", etc.
CLEAN_TAG="$(echo "${LAST_SEMVER_TAG#v}" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')"
# Build a development version string, e.g., "1.2.3-development.20250127"
DATE=$(date +'%Y%m%d')
FINAL_VERSION="${CLEAN_TAG}-development.${DATE}"
echo "Last semver tag: $LAST_SEMVER_TAG"
echo "Clean semver tag: $CLEAN_TAG"
echo "Development version: $FINAL_VERSION"
# Set output variables for GitHub Actions
echo "VERSION_SEMVER=$CLEAN_TAG" >> $GITHUB_ENV
echo "VERSION_TAG=$FINAL_VERSION" >> $GITHUB_ENV
echo "::set-output name=VERSION_SEMVER::$CLEAN_TAG"
echo "::set-output name=VERSION_TAG::$FINAL_VERSION"
- name: Check if commits since last development tag
id: check_diff
shell: bash
run: |
# Fetch the last development tag
LAST_DEVELOPMENT_TAG="$(git tag --list --sort=-v:refname | grep -E 'development\.20[0-9]+' | head -n1 || true)"
if [ -z "$LAST_DEVELOPMENT_TAG" ]; then
echo "No previous development tag found. Marking as changed."
echo "::set-output name=changed::true"
exit 0
fi
# Check for commits since that tag
set +e # Disable exit on error temporarily
COMMITS=$(git log "${LAST_DEVELOPMENT_TAG}"..HEAD --oneline 2>/dev/null || true)
set -e # Re-enable exit on error
if [ -z "$COMMITS" ]; then
echo "No commits since last development tag: $LAST_DEVELOPMENT_TAG"
echo "::set-output name=changed::false"
else
echo "Found commits since $LAST_DEVELOPMENT_TAG"
echo "::set-output name=changed::true"
fi
# --------------------------------------------------------------------------
# 2) BUILD JOBS (Server/Launcher) - only run if changes == 'true'
# Each calls your local workflow YAML with correct indentation
# --------------------------------------------------------------------------
build_server_linux_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Debug
build_server_win_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Debug
# --------------------------------------------------------------------------
# 3) FINALIZE: if changes == 'true', gather artifacts + push Docker:development
# --------------------------------------------------------------------------
publish_docker_image:
needs:
- prep
- build_server_linux_x64
- build_server_win_x64
if: needs.prep.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
# 3c) Build and push Docker image with tag "development"
- name: Checkout Repo for Docker build
uses: actions/checkout@v4
- name: Log in to the container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=development
type=raw,value=${{ needs.prep.outputs.version_tag }}
- name: Download Server x64 Artifacts
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: ./
- name: Extract Server Artifacts
run: |
mkdir -p ./LANCommander.Server/published
unzip ./LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip -d ./LANCommander.Server/published
# - name: Download Server arm64 Artifacts
# uses: actions/download-artifact@v4
# with:
# name: LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
# path: ./published
# - name: Set up QEMU
# uses: docker/setup-qemu-action@v2
- name: Setup buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: ./LANCommander.Server
file: ./LANCommander.Server/Dockerfile
push: true
platforms: linux/amd64
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}:development
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ needs.prep.outputs.version_tag }}
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
provenance: true
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
- name: Save version to artifact
run: echo "${{ needs.prep.outputs.version_tag }}" > version.txt
- name: Upload version artifact
uses: actions/upload-artifact@v4
with:
name: version.${{ needs.prep.outputs.version_tag }}
path: version.txt
publish_development_release:
runs-on: ubuntu-latest
needs:
- prep
- publish_docker_image
steps:
- name: Create Temp Directory
run: mkdir -p artifacts
- name: Download Server Linux x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Server Windows x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Delete existing release assets
uses: dev-drprasad/delete-tag-and-release@v1.1
with:
tag_name: development
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Create development release
uses: softprops/action-gh-release@v2
with:
tag_name: development
name: Development Build v${{ needs.prep.outputs.version_tag }}
draft: false
prerelease: true
generate_release_notes: true
body: This is the latest development build. These builds are generated automatically and should be considered unstable.
files: |
artifacts/LANCommander.Server-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -0,0 +1,37 @@
name: Publish Docker Image
on:
release:
types: [published]
workflow_dispatch:
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: lancommander/lancommander
- name: Build and push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: ./Docker
file: ./Docker/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{github.ref_name}}

View file

@ -1,28 +0,0 @@
name: Publish Documentation Updates
on:
push:
branches: [main]
paths:
- "LANCommander.Documentation/**"
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Dispatch to Docusaurus repo
env:
DISPATCH_TOKEN: ${{ secrets.DOCUMENTATION_DISPATCH_TOKEN }}
run: |
curl -sS -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${DISPATCH_TOKEN}" \
https://api.github.com/repos/LANCommander/LANCommander.Documentation/dispatches \
-d '{
"event_type": "docs-sources-updated",
"client_payload": {
"repo": "'"${GITHUB_REPOSITORY}"'",
"sha": "'"${GITHUB_SHA}"'",
"ref": "'"${GITHUB_REF}"'"
}
}'

View file

@ -1,125 +0,0 @@
name: LANCommander.Launcher.Legacy
on:
push:
branches: [main, feature/legacy-launcher]
paths:
- 'LANCommander.Launcher.Legacy/**'
- 'LANCommander.SDK.Cpp/**'
- '.github/workflows/LANCommander.Launcher.Legacy.yml'
pull_request:
branches: [main]
paths:
- 'LANCommander.Launcher.Legacy/**'
- 'LANCommander.SDK.Cpp/**'
- '.github/workflows/LANCommander.Launcher.Legacy.yml'
workflow_dispatch:
jobs:
build-win9x:
name: Build (Win9x / MinGW-w64 i686)
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- name: Checkout
uses: actions/checkout@v4
# ---------------------------------------------------------------
# MSYS2 with MinGW32 (i686) toolchain
# ---------------------------------------------------------------
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: MINGW32
update: false
install: >-
mingw-w64-i686-gcc
mingw-w64-i686-cmake
mingw-w64-i686-make
make
# ---------------------------------------------------------------
# Vendor dependencies (cJSON, miniz, Allegro 4 source)
# ---------------------------------------------------------------
- name: Download vendor dependencies
shell: pwsh
run: ./setup-vendor.ps1
# ---------------------------------------------------------------
# Build Allegro 4 from source (static, no addons)
# ---------------------------------------------------------------
- name: Build Allegro 4
run: |
ALLEGRO_SRC="LANCommander.Launcher.Legacy/vendor/allegro4/allegro5-4.4.3.1"
ALLEGRO_BUILD="LANCommander.Launcher.Legacy/build-allegro-win9x"
ALLEGRO_PREFIX="$(pwd)/LANCommander.Launcher.Legacy/allegro4-win9x"
cmake -S "$ALLEGRO_SRC" -B "$ALLEGRO_BUILD" \
-G "MinGW Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$ALLEGRO_PREFIX" \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DSHARED=OFF \
-DWANT_EXAMPLES=OFF \
-DWANT_TOOLS=OFF \
-DWANT_TESTS=OFF \
-DWANT_ALLEGROGL=OFF \
-DWANT_LOADPNG=OFF \
-DWANT_LOGG=OFF \
-DWANT_JPGALLEG=OFF \
-DWANT_FRAMEWORKS=OFF
mingw32-make -C "$ALLEGRO_BUILD" -j$(nproc)
mingw32-make -C "$ALLEGRO_BUILD" install
# ---------------------------------------------------------------
# Build the launcher
# ---------------------------------------------------------------
- name: Build launcher
run: |
LAUNCHER_DIR="LANCommander.Launcher.Legacy"
LAUNCHER_BUILD="$LAUNCHER_DIR/build-win9x"
ALLEGRO_PREFIX="$(pwd)/$LAUNCHER_DIR/allegro4-win9x"
cmake -S "$LAUNCHER_DIR" -B "$LAUNCHER_BUILD" \
-G "MinGW Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DALLEGRO_STATIC=ON \
-DTARGET_WIN9X=ON \
-DALLEGRO_ROOT="$ALLEGRO_PREFIX"
mingw32-make -C "$LAUNCHER_BUILD" -j$(nproc)
# ---------------------------------------------------------------
# Package
# ---------------------------------------------------------------
- name: Package artifacts
run: |
mkdir -p out-win9x
LAUNCHER_EXE=$(find LANCommander.Launcher.Legacy/build-win9x -name "launcher.exe" | head -1)
cp "$LAUNCHER_EXE" out-win9x/LANCommander.exe
strip out-win9x/LANCommander.exe
# Bundle GDI+ redistributable (MinGW CRT is statically linked)
if [ -f "$MINGW_PREFIX/bin/gdiplus.dll" ]; then
cp "$MINGW_PREFIX/bin/gdiplus.dll" out-win9x/
echo "Bundled: gdiplus.dll"
fi
# Show PE info for verification
objdump -p out-win9x/LANCommander.exe | grep -i "Version\|Subsystem" || true
echo ""
ls -lh out-win9x/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: LANCommander-Legacy-Win9x
path: out-win9x/
if-no-files-found: error

View file

@ -1,127 +0,0 @@
name: LANCommander Launcher Tests — PR Visual Diff Comment
on:
workflow_call:
inputs:
pr_number:
description: 'Pull request number to comment on'
required: true
type: string
artifact_run_id:
description: 'Workflow run ID that produced the launcher-visual-artifacts artifact'
required: true
type: string
permissions:
contents: write # push diff PNGs to the gh-visual-diffs branch
pull-requests: write # post / update the comment
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout for branch push
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download visual artifacts
uses: actions/download-artifact@v4
with:
name: launcher-visual-artifacts
path: visual
run-id: ${{ inputs.artifact_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Read regression manifest
id: manifest
run: |
if [ ! -f visual/regressions.json ]; then
echo "no manifest — assuming no visual run"
echo "count=0" >> "$GITHUB_OUTPUT"
exit 0
fi
count=$(jq '.regressed_count' visual/regressions.json)
echo "count=$count" >> "$GITHUB_OUTPUT"
- name: Skip when no regressions
if: steps.manifest.outputs.count == '0'
run: echo "No visual regressions; nothing to comment."
# Push diff/actual/baseline PNGs to an orphan branch so raw.githubusercontent.com
# URLs render inline in the PR comment. One folder per (PR, run).
- name: Push diffs to gh-visual-diffs branch
if: steps.manifest.outputs.count != '0'
env:
PR: ${{ inputs.pr_number }}
RUN: ${{ inputs.artifact_run_id }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Worktree off an orphan branch so we never touch the source tree.
if git ls-remote --exit-code --heads origin gh-visual-diffs >/dev/null; then
git fetch origin gh-visual-diffs
git worktree add /tmp/visual gh-visual-diffs
else
git worktree add --detach /tmp/visual
cd /tmp/visual
git checkout --orphan gh-visual-diffs
git rm -rf . 2>/dev/null || true
cd -
fi
DEST="/tmp/visual/pr-${PR}/run-${RUN}"
mkdir -p "$DEST"
cp -r visual/diffs "$DEST/" 2>/dev/null || true
cp -r visual/screenshots "$DEST/" 2>/dev/null || true
cp -r visual/baselines "$DEST/" 2>/dev/null || true
cd /tmp/visual
git add -A
git -c user.name=github-actions -c user.email=github-actions@github.com \
commit -m "Visual diffs for PR #${PR} run ${RUN}"
git push origin gh-visual-diffs
- name: Post / update PR comment
if: steps.manifest.outputs.count != '0'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ inputs.pr_number }}
RUN: ${{ inputs.artifact_run_id }}
REPO: ${{ github.repository }}
MARKER: '<!-- launcher-visual-diff-comment -->'
run: |
set -euo pipefail
base="https://raw.githubusercontent.com/${REPO}/gh-visual-diffs/pr-${PR}/run-${RUN}"
body=$(mktemp)
{
echo "$MARKER"
echo "## :art: Launcher visual regressions"
echo
echo "$(jq '.regressed_count' visual/regressions.json) baseline(s) drifted in [run ${RUN}](https://github.com/${REPO}/actions/runs/${RUN})."
echo
jq -r '.regressions[] | .name' visual/regressions.json | while read -r name; do
echo "<details><summary><strong>${name}</strong></summary>"
echo
echo "| Baseline | Actual | Diff |"
echo "| --- | --- | --- |"
echo "| ![baseline](${base}/baselines/${name}.png) | ![actual](${base}/screenshots/${name}.png) | ![diff](${base}/diffs/${name}.diff.png) |"
echo
echo "</details>"
echo
done
echo "_If these changes are intentional, run the **Update Visual Baselines** workflow to refresh._"
} >> "$body"
# Find an existing comment with our marker; update it if present.
existing=$(gh api repos/${REPO}/issues/${PR}/comments --paginate \
--jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" | head -1)
if [ -n "$existing" ]; then
gh api -X PATCH "repos/${REPO}/issues/comments/${existing}" --field "body=@${body}"
else
gh api -X POST "repos/${REPO}/issues/${PR}/comments" --field "body=@${body}"
fi

View file

@ -1,91 +0,0 @@
name: LANCommander Launcher Tests — Update Visual Baselines
# Operator-triggered: re-renders every visual test on the CI host and commits the
# new screenshots over the committed Baselines/. Only runs when a human dispatches
# it from the Actions tab — never on push or PR — so accidental drift can't slip in.
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to commit refreshed baselines to'
required: true
type: string
default: 'main'
build_dotnet_version:
description: 'Build .NET Version'
required: false
type: string
default: '9.0.102'
permissions:
contents: write
jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.build_dotnet_version }}
- name: Install rendering prereqs
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
libfontconfig1 libfreetype6 libice6 libsm6 libx11-6 libxcb1 libxext6
- name: Restore + build visual test project
run: |
dotnet restore LANCommander.Launcher.Tests/LANCommander.Launcher.Tests.csproj
dotnet build LANCommander.Launcher.Tests/LANCommander.Launcher.Tests.csproj -c Debug --no-restore
# Run visual tests, ignore failures (regressions are expected here — that's
# the whole point of refreshing). Then promote every captured screenshot
# to be the new baseline.
- name: Capture fresh screenshots
continue-on-error: true
run: |
dotnet test LANCommander.Launcher.Tests/LANCommander.Launcher.Tests.csproj \
-c Debug --no-build --no-restore \
--logger "console;verbosity=normal"
- name: Promote captures to baselines
run: |
set -euo pipefail
src=LANCommander.Launcher.Tests/bin/Debug/net10.0/Screenshots
dst=LANCommander.Launcher.Tests/Baselines
if [ ! -d "$src" ]; then
echo "::error::No screenshots captured at $src — visual tests didn't run."
exit 1
fi
mkdir -p "$dst"
cp -v "$src"/*.png "$dst"/
echo "## Updated baselines" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
ls -1 "$dst"/*.png | xargs -n1 basename | sed 's/^/- /' >> "$GITHUB_STEP_SUMMARY"
- name: Commit + push refreshed baselines
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git diff --quiet -- LANCommander.Launcher.Tests/Baselines; then
echo "Baselines unchanged — nothing to commit."
exit 0
fi
git add LANCommander.Launcher.Tests/Baselines
git commit -m "Refresh launcher visual baselines from CI run ${GITHUB_RUN_ID}"
git push origin HEAD:${{ inputs.branch }}

View file

@ -1,170 +0,0 @@
name: LANCommander Launcher Tests
on:
workflow_call:
inputs:
build_dotnet_version:
description: 'Build .NET Version'
required: true
type: string
outputs:
visual_diff_count:
description: 'Number of visual baselines that regressed in this run'
value: ${{ jobs.test.outputs.visual_diff_count }}
workflow_dispatch:
inputs:
build_dotnet_version:
description: 'Build .NET Version'
required: false
type: string
default: '9.0.102'
permissions:
contents: read
jobs:
test:
name: Run Launcher Tests
runs-on: ubuntu-latest
outputs:
visual_diff_count: ${{ steps.summarize.outputs.visual_diff_count }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.build_dotnet_version }}
# Avalonia headless rendering uses Skia + the Inter font. Install
# font + GPU prereqs so screenshots render consistently.
- name: Install rendering prereqs
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
libfontconfig1 libfreetype6 libice6 libsm6 libx11-6 libxcb1 libxext6
- name: Restore
run: dotnet restore
# ---- Unit + integration test projects ---------------------------------
# Services.Tests and IntegrationTests build cleanly without the
# Server/UI npm+pwsh chain. Launcher.Tests likewise just needs Skia.
- name: Build test projects
run: |
dotnet build LANCommander.Launcher.Services.Tests/LANCommander.Launcher.Services.Tests.csproj -c Debug --no-restore
dotnet build LANCommander.Launcher.IntegrationTests/LANCommander.Launcher.IntegrationTests.csproj -c Debug --no-restore
dotnet build LANCommander.Launcher.Tests/LANCommander.Launcher.Tests.csproj -c Debug --no-restore
- name: Run Services unit tests
run: |
dotnet test LANCommander.Launcher.Services.Tests/LANCommander.Launcher.Services.Tests.csproj \
-c Debug --no-build --no-restore \
--logger "trx;LogFileName=services.trx" \
--logger "console;verbosity=normal" \
--results-directory test-results \
--collect:"XPlat Code Coverage"
- name: Run integration tests
run: |
dotnet test LANCommander.Launcher.IntegrationTests/LANCommander.Launcher.IntegrationTests.csproj \
-c Debug --no-build --no-restore \
--logger "trx;LogFileName=integration.trx" \
--logger "console;verbosity=normal" \
--results-directory test-results \
--collect:"XPlat Code Coverage"
# Visual tests are continued-on-error so a baseline regression doesn't
# mask any earlier failure and so we always reach the artifact-upload
# + summary steps below. The summary step re-asserts the failure for CI.
- name: Run visual tests
id: visual_tests
continue-on-error: true
run: |
dotnet test LANCommander.Launcher.Tests/LANCommander.Launcher.Tests.csproj \
-c Debug --no-build --no-restore \
--logger "trx;LogFileName=visual.trx" \
--logger "console;verbosity=normal" \
--results-directory test-results
# ---- Visual artifact collection --------------------------------------
# Aggregate every screenshot + diff PNG into top-level folders so the
# PR-comment job downstream can find them by predictable name.
- name: Collect visual artifacts
if: always()
run: |
mkdir -p visual/screenshots visual/diffs visual/baselines
cp -r LANCommander.Launcher.Tests/bin/Debug/net10.0/Screenshots/. visual/screenshots/ 2>/dev/null || true
cp -r LANCommander.Launcher.Tests/bin/Debug/net10.0/Diffs/. visual/diffs/ 2>/dev/null || true
cp -r LANCommander.Launcher.Tests/Baselines/. visual/baselines/ 2>/dev/null || true
- name: Summarize visual diffs
id: summarize
if: always()
run: |
shopt -s nullglob
diffs=(visual/diffs/*.diff.png)
count=${#diffs[@]}
echo "visual_diff_count=$count" >> "$GITHUB_OUTPUT"
echo "## Visual test summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "$count baseline(s) regressed." >> "$GITHUB_STEP_SUMMARY"
# JSON manifest the PR-comment job consumes
python3 - "$count" <<'PY' > visual/regressions.json
import json, os, sys
count = int(sys.argv[1])
diffs = sorted(os.listdir("visual/diffs")) if os.path.isdir("visual/diffs") else []
data = {
"regressed_count": count,
"regressions": [
{
"name": d.replace(".diff.png", ""),
"diff": f"visual/diffs/{d}",
"actual": f"visual/screenshots/{d.replace('.diff.png', '.png')}",
"baseline": f"visual/baselines/{d.replace('.diff.png', '.png')}",
}
for d in diffs
],
}
print(json.dumps(data, indent=2))
PY
cat visual/regressions.json
- name: Upload visual artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: launcher-visual-artifacts
path: |
visual/screenshots/**
visual/diffs/**
visual/baselines/**
visual/regressions.json
if-no-files-found: warn
retention-days: 14
- name: Upload TRX + coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: launcher-test-results
path: test-results/**
if-no-files-found: warn
retention-days: 14
# Re-assert the visual test outcome so CI fails when baselines diverge,
# after artifacts are guaranteed uploaded.
- name: Fail if visual tests regressed
if: always() && steps.visual_tests.outcome == 'failure'
run: |
echo "::error::Visual tests reported regressions. See the launcher-visual-artifacts artifact."
exit 1

View file

@ -1,314 +0,0 @@
name: LANCommander Launcher Build
on:
workflow_dispatch:
workflow_call:
inputs:
version_semver:
description: "Semantic Version"
required: true
type: string
version_tag:
description: 'Version Tag'
required: true
type: string
build_dotnet_version:
description: 'Build .NET Version'
required: true
type: string
build_runtime:
description: 'Build Runtime'
required: false
type: string
default: 'win-x64'
build_arch:
description: 'Build Architecture'
required: false
type: string
default: 'x64'
build_platform:
description: 'Build Platform'
required: false
type: string
default: 'Windows'
build_configuration:
description: 'Build Configuration (Debug/Release)'
required: false
type: string
default: 'Release'
permissions:
contents: write
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/package
jobs:
build:
runs-on: ${{ inputs.build_platform == 'Linux' && inputs.build_arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
# Checkout code
- uses: actions/checkout@v4
with:
submodules: true
# .NET Setup
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.build_dotnet_version }}
- name: Restore dependencies
run: dotnet restore --locked-mode
- name: Publish Launcher
run: |
# Strip leading 'v' if present
RAW_VERSION="${{ inputs.version_tag }}" # e.g. v2.0.0-rc1
SEMVER="${RAW_VERSION#v}" # 2.0.0-rc1
# Numeric part only for Assembly/FileVersion
NUMERIC="${SEMVER%%-*}" # 2.0.0
ASSEMBLY_VERSION="${NUMERIC}.0" # 2.0.0.0
echo "SEMVER=$SEMVER"
echo "ASSEMBLY_VERSION=$ASSEMBLY_VERSION"
dotnet publish "./LANCommander.Launcher/LANCommander.Launcher.csproj" \
-c "${{ inputs.build_configuration }}" \
--self-contained \
--runtime "${{ inputs.build_runtime }}" \
-p:Version="$SEMVER" \
-p:AssemblyVersion="$ASSEMBLY_VERSION" \
-p:FileVersion="$ASSEMBLY_VERSION" \
-p:InformationalVersion="$SEMVER" \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:IncludeAllContentForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \
-p:DebugType=embedded
- name: Bundle libvlc (Linux)
if: inputs.build_platform == 'Linux'
shell: bash
run: |
PUBLISH_DIR="LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish"
VLC_DIR="$PUBLISH_DIR/libvlc/${{ inputs.build_runtime }}"
mkdir -p "$VLC_DIR"
if [ "${{ inputs.build_arch }}" = "arm64" ]; then
# Enable arm64 multiarch and add Ubuntu Ports repository so apt can
# download arm64 packages on this x64 runner.
sudo dpkg --add-architecture arm64
CODENAME=$(lsb_release -cs)
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports ${CODENAME} main restricted universe" \
| sudo tee /etc/apt/sources.list.d/ubuntu-ports-arm64.list > /dev/null
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports ${CODENAME}-updates main restricted universe" \
| sudo tee -a /etc/apt/sources.list.d/ubuntu-ports-arm64.list > /dev/null
sudo apt-get update -qq
sudo apt-get download libvlc5:arm64 libvlccore9:arm64 vlc-plugin-base:arm64
mkdir -p vlc-extracted
for deb in libvlc5_*arm64.deb libvlccore9_*arm64.deb vlc-plugin-base_*arm64.deb; do
dpkg -x "$deb" vlc-extracted/
done
LIB_DIR="vlc-extracted/usr/lib/aarch64-linux-gnu"
PLUGINS_SRC="$LIB_DIR/vlc/plugins"
else
sudo apt-get install -y --no-install-recommends libvlc5 libvlccore9 vlc-plugin-base
LIB_DIR="/usr/lib/x86_64-linux-gnu"
PLUGINS_SRC="$LIB_DIR/vlc/plugins"
fi
# Copy the two core shared libraries, dereferencing symlinks so their
# content is preserved correctly when zipped.
for lib in libvlc.so.5 libvlccore.so.9; do
src=$(find "$LIB_DIR" -maxdepth 1 -name "${lib}*" | sort | head -1)
if [ -n "$src" ]; then
cp -L "$src" "$VLC_DIR/$lib"
echo "Bundled: $lib"
else
echo "WARNING: $lib not found in $LIB_DIR" >&2
fi
done
# Copy VLC plugins (vlc-plugin-base covers common codecs and demuxers)
if [ -d "$PLUGINS_SRC" ]; then
mkdir -p "$VLC_DIR/plugins"
cp -rL "$PLUGINS_SRC/." "$VLC_DIR/plugins/"
echo "Bundled $(find "$VLC_DIR/plugins" -name "*.so" | wc -l) plugin(s)"
fi
- name: Bundle and Clean
shell: pwsh
run: |
$BasePath = "LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish"
# Remove unnecessary files
$PathsToRemove = @(
'*.pdb'
)
foreach ($path in $PathsToRemove) {
Remove-Item -Recurse -Force -ErrorAction Continue "$BasePath/$path"
}
- name: Bundle macOS .app
if: inputs.build_platform == 'macOS'
shell: bash
run: |
set -euo pipefail
# Strip leading 'v' and reduce to numeric x.y.z for the bundle version keys.
RAW_VERSION="${{ inputs.version_tag }}"
SEMVER="${RAW_VERSION#v}"
NUMERIC="${SEMVER%%-*}"
APP_NAME="LANCommander Launcher"
EXECUTABLE="LANCommander.Launcher"
BUNDLE_ID="app.lancommander.launcher"
ICON_SRC="LANCommander.Launcher/Assets/icon.icns"
PUBLISH_DIR="LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish"
APP_DIR="${EXECUTABLE}.app"
# Lay out the bundle skeleton.
mkdir -p "$APP_DIR/Contents/MacOS" "$APP_DIR/Contents/Resources"
cp -a "$PUBLISH_DIR/." "$APP_DIR/Contents/MacOS/"
cp "$ICON_SRC" "$APP_DIR/Contents/Resources/AppIcon.icns"
# Write Info.plist.
cat > "$APP_DIR/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${BUNDLE_ID}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE}</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>${NUMERIC}</string>
<key>CFBundleVersion</key>
<string>${SEMVER}</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
PLIST
# Ensure the entrypoint is executable, then zip while preserving the
# permission bits (Compress-Archive drops the exec bit).
chmod +x "$APP_DIR/Contents/MacOS/${EXECUTABLE}"
zip -ry "LANCommander.Launcher-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip" "$APP_DIR"
- name: Build AppImage (Linux)
if: inputs.build_platform == 'Linux'
shell: bash
run: |
set -euo pipefail
PUBLISH_DIR="LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish"
APP_NAME="LANCommander.Launcher"
APPDIR="AppDir"
# appimagetool / runtime architecture identifiers
if [ "${{ inputs.build_arch }}" = "arm64" ]; then
AI_ARCH="aarch64"
else
AI_ARCH="x86_64"
fi
export ARCH="$AI_ARCH"
# --- Assemble the AppDir ---------------------------------------------
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/share/applications" \
"$APPDIR/usr/share/icons/hicolor/scalable/apps"
cp -r "$PUBLISH_DIR/." "$APPDIR/usr/bin/"
chmod +x "$APPDIR/usr/bin/$APP_NAME"
# Icon (use the prebuilt 256x256 project icon)
ICON_SRC="LANCommander.Launcher/Assets/icon.png"
ICON_DIR="$APPDIR/usr/share/icons/hicolor/256x256/apps"
mkdir -p "$ICON_DIR"
cp "$ICON_SRC" "$ICON_DIR/lancommander.png"
cp "$ICON_DIR/lancommander.png" "$APPDIR/lancommander.png"
ln -sf lancommander.png "$APPDIR/.DirIcon"
# Desktop entry
cat > "$APPDIR/usr/share/applications/lancommander.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Name=LANCommander Launcher
Comment=LANCommander Launcher
Exec=LANCommander.Launcher
Icon=lancommander
Categories=Game;
Terminal=false
EOF
cp "$APPDIR/usr/share/applications/lancommander.desktop" "$APPDIR/lancommander.desktop"
# AppRun entry point
cat > "$APPDIR/AppRun" <<'EOF'
#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"
export PATH="${HERE}/usr/bin:${PATH}"
export LD_LIBRARY_PATH="${HERE}/usr/bin:${LD_LIBRARY_PATH:-}"
exec "${HERE}/usr/bin/LANCommander.Launcher" "$@"
EOF
chmod +x "$APPDIR/AppRun"
# --- Fetch appimagetool ----------------------------------------------
TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${AI_ARCH}.AppImage"
curl -L -o appimagetool "$TOOL_URL"
chmod +x appimagetool
# --- Build the AppImage ----------------------------------------------
OUT="LANCommander.Launcher-Linux-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.AppImage"
./appimagetool --appimage-extract-and-run "$APPDIR" "$OUT"
echo "Produced $OUT"
- name: Upload AppImage Artifact (Linux)
if: inputs.build_platform == 'Linux'
uses: actions/upload-artifact@v4
with:
path: LANCommander.Launcher-Linux-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.AppImage
name: LANCommander.Launcher-Linux-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.AppImage
- name: Compress Build Output
if: inputs.build_platform != 'macOS'
shell: pwsh
run: |
$compress = @{
Path = "LANCommander.Launcher/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish/*"
DestinationPath = "LANCommander.Launcher-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
path: LANCommander.Launcher-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip
name: LANCommander.Launcher-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip

View file

@ -1,482 +0,0 @@
name: LANCommander Nightly
on:
workflow_dispatch:
schedule:
# Runs every day at 3AM CST
# CST is UTC-6, so midnight CST = 09:00 UTC
- cron: '0 9 * * *'
env:
REGISTRY: ghcr.io
IMAGE_NAME: lancommander/lancommander
permissions:
contents: write
packages: write
id-token: write
attestations: write
jobs:
# --------------------------------------------------------------------------
# 1) PREP JOB: figure out the latest semver, build nightly version,
# check if there are commits since the last nightly tag. If none,
# skip the rest of the workflow.
# --------------------------------------------------------------------------
prep:
runs-on: ubuntu-latest
outputs:
version_semver: ${{ steps.set_version.outputs.VERSION_SEMVER }}
version_tag: ${{ steps.set_version.outputs.VERSION_TAG }}
changed: ${{ steps.check_diff.outputs.changed }}
build_dotnet_version: 10.0.100
steps:
- name: Check out code
uses: actions/checkout@v4
with:
# Ensure we get all tags so we can find the latest
fetch-depth: 0
- name: Determine last semver and build nightly version
id: set_version
shell: bash
run: |
# Fetch all tags
git fetch --tags
# Grab the last semver-ish tag (e.g., "v1.2.3", "v1.2.3-debug", or "1.2.3-nightly")
LAST_SEMVER_TAG="$(git tag --list --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+' | head -n1)"
if [ -z "$LAST_SEMVER_TAG" ]; then
echo "No semver tag found; defaulting to 0.0.0"
LAST_SEMVER_TAG="0.0.0"
fi
# Remove prefix 'v' and any suffix like "-debug", "-nightly", etc.
CLEAN_TAG="$(echo "${LAST_SEMVER_TAG#v}" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')"
# Build a nightly version string, e.g., "1.2.3-nightly.20250127"
DATE=$(date +'%Y%m%d')
FINAL_VERSION="${CLEAN_TAG}-nightly.${DATE}"
echo "Last semver tag: $LAST_SEMVER_TAG"
echo "Clean semver tag: $CLEAN_TAG"
echo "Nightly version: $FINAL_VERSION"
# Set output variables for GitHub Actions
echo "VERSION_SEMVER=$CLEAN_TAG" >> $GITHUB_ENV
echo "VERSION_TAG=$FINAL_VERSION" >> $GITHUB_ENV
echo "::set-output name=VERSION_SEMVER::$CLEAN_TAG"
echo "::set-output name=VERSION_TAG::$FINAL_VERSION"
- name: Check if commits since last nightly tag
id: check_diff
shell: bash
run: |
# Fetch the last nightly tag
LAST_NIGHTLY_TAG="$(git tag --list --sort=-v:refname | grep -E 'nightly\.20[0-9]+' | head -n1 || true)"
if [ -z "$LAST_NIGHTLY_TAG" ]; then
echo "No previous nightly tag found. Marking as changed."
echo "::set-output name=changed::true"
exit 0
fi
# Check for commits since that tag
set +e # Disable exit on error temporarily
COMMITS=$(git log "${LAST_NIGHTLY_TAG}"..HEAD --oneline 2>/dev/null || true)
set -e # Re-enable exit on error
if [ -z "$COMMITS" ]; then
echo "No commits since last nightly tag: $LAST_NIGHTLY_TAG"
echo "::set-output name=changed::false"
else
echo "Found commits since $LAST_NIGHTLY_TAG"
echo "::set-output name=changed::true"
fi
# --------------------------------------------------------------------------
# 2) BUILD JOBS (Server/Launcher) - only run if changes == 'true'
# Each calls your local workflow YAML with correct indentation
# --------------------------------------------------------------------------
build_server_linux_arm64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-arm64
build_arch: arm64
build_platform: Linux
build_configuration: Debug
build_server_linux_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Debug
build_server_osx_arm64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-arm64
build_arch: arm64
build_platform: macOS
build_configuration: Debug
build_server_osx_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-x64
build_arch: x64
build_platform: macOS
build_configuration: Debug
build_server_win_arm64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-arm64
build_arch: arm64
build_platform: Windows
build_configuration: Debug
build_server_win_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Debug
build_launcher_linux_arm64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-arm64
build_arch: arm64
build_platform: Linux
build_configuration: Debug
build_launcher_linux_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Debug
build_launcher_osx_arm64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-arm64
build_arch: arm64
build_platform: macOS
build_configuration: Debug
build_launcher_osx_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-x64
build_arch: x64
build_platform: macOS
build_configuration: Debug
build_launcher_win_arm64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-arm64
build_arch: arm64
build_platform: Windows
build_configuration: Debug
build_launcher_win_x64:
needs: [prep]
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Debug
# --------------------------------------------------------------------------
# 3) FINALIZE: if changes == 'true', gather artifacts + push Docker:nightly
# --------------------------------------------------------------------------
publish_docker_image:
needs:
- prep
- build_server_linux_arm64
- build_server_linux_x64
- build_server_osx_arm64
- build_server_osx_x64
- build_server_win_arm64
- build_server_win_x64
if: needs.prep.outputs.changed == 'true' && github.repository_owner == 'LANCommander'
runs-on: ubuntu-latest
steps:
# 3c) Build and push Docker image with tag "nightly"
- name: Checkout Repo for Docker build
uses: actions/checkout@v4
- name: Log in to the container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=nightly
type=raw,value=${{ needs.prep.outputs.version_tag }}
- name: Download Server x64 Artifacts
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: ./
- name: Download Server arm64 Artifacts
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: ./
- name: Extract Server Artifacts x64
run: |
mkdir -p ./LANCommander.Server/published-amd64
unzip ./LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip -d ./LANCommander.Server/published-amd64
- name: Extract Server Artifacts arm64
run: |
mkdir -p ./LANCommander.Server/published-arm64
unzip ./LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip -d ./LANCommander.Server/published-arm64
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Setup buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: ./LANCommander.Server
file: ./LANCommander.Server/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}:nightly
${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}:v${{ needs.prep.outputs.version_tag }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ needs.prep.outputs.version_tag }}
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
provenance: true
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
- name: Save version to artifact
run: echo "${{ needs.prep.outputs.version_tag }}" > version.txt
- name: Upload version artifact
uses: actions/upload-artifact@v4
with:
name: version.${{ needs.prep.outputs.version_tag }}
path: version.txt
publish_nightly_release:
runs-on: ubuntu-latest
needs:
- prep
- publish_docker_image
- build_launcher_linux_arm64
- build_launcher_linux_x64
- build_launcher_osx_arm64
- build_launcher_osx_x64
- build_launcher_win_arm64
- build_launcher_win_x64
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Create Temp Directory
run: mkdir -p artifacts
- name: Download Server Linux ARM64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Server Linux x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Server macOS ARM64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-macOS-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Server macOS x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-macOS-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Server Windows ARM64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Windows-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Server Windows x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher Linux ARM64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher Linux x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher macOS ARM64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-macOS-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher macOS x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-macOS-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher Windows ARM64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Windows-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher Windows x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher Linux ARM64 AppImage
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.AppImage
path: artifacts
- name: Download Launcher Linux x64 AppImage
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Linux-x64-v${{ needs.prep.outputs.version_tag }}.AppImage
path: artifacts
- name: Create or update nightly release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create the release if it doesn't exist, otherwise update its metadata
if gh release view nightly > /dev/null 2>&1; then
# Remove all existing assets so only this nightly's files remain
gh release view nightly --json assets -q '.assets[].name' | while read -r asset; do
gh release delete-asset nightly "$asset" -y
done
gh release edit nightly \
--prerelease \
--title "Nightly Build v${{ needs.prep.outputs.version_tag }}" \
--notes "This is the latest nightly build. These builds are generated automatically and should be considered unstable."
else
gh release create nightly \
--prerelease \
--title "Nightly Build v${{ needs.prep.outputs.version_tag }}" \
--notes "This is the latest nightly build. These builds are generated automatically and should be considered unstable."
fi
# Upload new artifacts
gh release upload nightly artifacts/*

View file

@ -1,229 +0,0 @@
name: LANCommander Pull Request
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
workflow_dispatch:
permissions:
contents: write
packages: read
checks: write
pull-requests: write
jobs:
prep:
runs-on: ubuntu-latest
outputs:
version_semver: ${{ steps.set_version.outputs.version_semver }}
version_tag: ${{ steps.set_version.outputs.version_tag }}
build_dotnet_version: 9.0.102
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine build metadata
id: set_version
shell: bash
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
RUN_NUMBER: ${{ github.run_number }}
run: |
TIMESTAMP=$(date -u +"%Y%m%d%H%M")
TIME_COMPONENT=$(date -u +"%H%M")
TIME_COMPONENT=$((10#$TIME_COMPONENT))
if [ -n "$PR_NUMBER" ]; then
BUILD_COMPONENT=$((PR_NUMBER % 65535))
if [ "$BUILD_COMPONENT" -eq 0 ]; then
BUILD_COMPONENT=1
fi
VERSION_SEMVER="0.0.${BUILD_COMPONENT}.${TIME_COMPONENT}"
VERSION_TAG="0.0.${BUILD_COMPONENT}-pr.${PR_NUMBER}.${TIMESTAMP}"
else
BUILD_COMPONENT=$((RUN_NUMBER % 65535))
if [ "$BUILD_COMPONENT" -eq 0 ]; then
BUILD_COMPONENT=1
fi
VERSION_SEMVER="0.0.${BUILD_COMPONENT}.${TIME_COMPONENT}"
VERSION_TAG="0.0.${BUILD_COMPONENT}-ci.${RUN_NUMBER}.${TIMESTAMP}"
fi
echo "version_semver=$VERSION_SEMVER" >> $GITHUB_OUTPUT
echo "version_tag=$VERSION_TAG" >> $GITHUB_OUTPUT
ui_tests:
needs: [prep]
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ needs.prep.outputs.build_dotnet_version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Node packages
run: |
npm install --prefix ./LANCommander.UI
npm install --prefix ./LANCommander.Server
# The Monaco editor's PowerShell completions are generated (gitignored) and
# required by the frontend webpack build. The in-build MSBuild target uses
# Windows-style paths, so generate explicitly here for the Linux runner.
- name: Generate PowerShell Completions
run: dotnet run --project ./LANCommander.CompletionGenerator/LANCommander.CompletionGenerator.csproj -- ./LANCommander.UI/Components/MonacoCodeEditor/PowerShellCompletions.g.ts
- name: Restore dependencies
run: dotnet restore LANCommander.Server.UI.Tests
- name: Build test project
run: dotnet build LANCommander.Server.UI.Tests --no-restore --configuration Release
- name: Install Playwright browsers
run: pwsh LANCommander.Server.UI.Tests/bin/Release/net10.0/playwright.ps1 install --with-deps chromium
- name: Run UI tests
run: dotnet test LANCommander.Server.UI.Tests --no-build --configuration Release --logger "trx;LogFileName=ui-test-results.trx" --results-directory ./TestResults
env:
SCREENSHOT_DIR: ${{ github.workspace }}/TestResults/Screenshots
- name: Test report
if: always()
uses: dorny/test-reporter@v1
with:
name: UI Test Results
path: ./TestResults/ui-test-results.trx
reporter: dotnet-trx
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: ui-test-results
path: ./TestResults
retention-days: 7
build_server_linux_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-arm64
build_arch: arm64
build_platform: Linux
build_configuration: Debug
build_server_linux_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Debug
build_server_osx_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-arm64
build_arch: arm64
build_platform: macOS
build_configuration: Debug
build_server_osx_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-x64
build_arch: x64
build_platform: macOS
build_configuration: Debug
build_server_win_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-arm64
build_arch: arm64
build_platform: Windows
build_configuration: Debug
build_server_win_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Debug
build_launcher_linux_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Debug
build_launcher_win_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Debug
launcher_tests:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.Tests.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
launcher_tests_pr_comment:
# Always run after the test job — even on failure — so visual regressions
# show up as a PR comment instead of being buried in the artifact.
needs: [launcher_tests]
if: always() && needs.launcher_tests.result != 'cancelled' && github.event.pull_request.number != null
uses: ./.github/workflows/LANCommander.Launcher.Tests.PRComment.yml
with:
pr_number: ${{ github.event.pull_request.number }}
artifact_run_id: ${{ github.run_id }}

View file

@ -1,100 +0,0 @@
name: LANCommander Packager Build
on:
workflow_dispatch:
workflow_call:
inputs:
version_semver:
description: "Semantic Version"
required: true
type: string
version_tag:
description: 'Version Tag'
required: true
type: string
build_dotnet_version:
description: 'Build .NET Version'
required: false
type: string
default: '10.0.x'
build_configuration:
description: 'Build Configuration (Debug/Release)'
required: false
type: string
default: 'Release'
permissions:
contents: write
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/package
jobs:
build:
runs-on: windows-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.build_dotnet_version }}
- name: Restore dependencies
run: dotnet restore
- name: Publish Packager
shell: pwsh
run: |
# Strip leading 'v' if present
$RawVersion = "${{ inputs.version_tag }}"
$Semver = $RawVersion -replace '^v', ''
# Numeric part only for Assembly/FileVersion
$Numeric = ($Semver -split '-')[0]
$AssemblyVersion = "$Numeric.0"
Write-Host "SEMVER=$Semver"
Write-Host "ASSEMBLY_VERSION=$AssemblyVersion"
dotnet publish "./LANCommander.Packager/LANCommander.Packager.csproj" `
-c "${{ inputs.build_configuration }}" `
--self-contained `
--runtime win-x86 `
-p:Version="$Semver" `
-p:AssemblyVersion="$AssemblyVersion" `
-p:FileVersion="$AssemblyVersion" `
-p:InformationalVersion="$Semver" `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:IncludeAllContentForSelfExtract=true `
-p:EnableCompressionInSingleFile=true `
-p:DebugType=embedded
- name: Clean
shell: pwsh
run: |
$BasePath = "LANCommander.Packager/bin/${{ inputs.build_configuration }}/net10.0/win-x86/publish"
Remove-Item -Recurse -Force -ErrorAction Continue "$BasePath/*.pdb"
- name: Compress Build Output
shell: pwsh
run: |
$compress = @{
Path = "LANCommander.Packager/bin/${{ inputs.build_configuration }}/net10.0/win-x86/publish/*"
DestinationPath = "LANCommander.Packager-Windows-x86-v${{ inputs.version_tag }}.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
path: LANCommander.Packager-Windows-x86-v${{ inputs.version_tag }}.zip
name: LANCommander.Packager-Windows-x86-v${{ inputs.version_tag }}.zip

View file

@ -3,437 +3,131 @@ name: LANCommander Release
on: on:
push: push:
tags: tags:
- 'v*.*.*' - 'v*'
- 'v*.*.*-*'
env:
REGISTRY: docker.io
IMAGE_NAME: lancommander/lancommander
permissions: permissions:
contents: write contents: write
packages: write
id-token: write
attestations: write
jobs: jobs:
# -------------------------------------------------------------------------- build:
# 1) PREP JOB: figure out the latest semver, build nightly version, runs-on: windows-latest
# check if there are commits since the last nightly tag. If none,
# skip the rest of the workflow.
# --------------------------------------------------------------------------
prep:
runs-on: ubuntu-latest
outputs:
version_tag: ${{ steps.trim_tag_ref.outputs.replaced }}
version_semver: ${{ steps.trim_tag_ref.outputs.replaced }}
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
build_dotnet_version: 9.0.102
steps:
- name: Check out code
uses: actions/checkout@v4
with:
# Ensure we get all tags so we can find the latest
fetch-depth: 0
- uses: frabert/replace-string-action@v2 env:
name: Trim Tag Ref GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
id: trim_tag_ref
with:
string: '${{ github.ref }}'
pattern: 'refs/tags/v'
replace-with: ''
- name: Check if pre-release
id: check_prerelease
run: |
VERSION="${{ steps.trim_tag_ref.outputs.replaced }}"
if [[ "$VERSION" == *-* ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
# Server
build_server_linux_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-arm64
build_arch: arm64
build_platform: Linux
build_configuration: Release
build_server_linux_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Release
build_server_osx_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-arm64
build_arch: arm64
build_platform: macOS
build_configuration: Release
build_server_osx_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-x64
build_arch: x64
build_platform: macOS
build_configuration: Release
build_server_win_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-arm64
build_arch: arm64
build_platform: Windows
build_configuration: Release
build_server_win_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Server.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Release
# Launcher
build_launcher_linux_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-arm64
build_arch: arm64
build_platform: Linux
build_configuration: Release
build_launcher_linux_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: linux-x64
build_arch: x64
build_platform: Linux
build_configuration: Release
build_launcher_osx_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-arm64
build_arch: arm64
build_platform: macOS
build_configuration: Release
build_launcher_osx_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: osx-x64
build_arch: x64
build_platform: macOS
build_configuration: Release
build_launcher_win_arm64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-arm64
build_arch: arm64
build_platform: Windows
build_configuration: Release
build_launcher_win_x64:
needs: [prep]
uses: ./.github/workflows/LANCommander.Launcher.yml
with:
build_dotnet_version: ${{ needs.prep.outputs.build_dotnet_version }}
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_runtime: win-x64
build_arch: x64
build_platform: Windows
build_configuration: Release
# Packager (Windows x86 only)
build_packager:
needs: [prep]
uses: ./.github/workflows/LANCommander.Packager.yml
with:
build_dotnet_version: '10.0.x'
version_semver: ${{ needs.prep.outputs.version_semver }}
version_tag: ${{ needs.prep.outputs.version_tag }}
build_configuration: Release
build_release:
runs-on: ubuntu-latest
needs:
- prep
- build_server_linux_arm64
- build_server_linux_x64
- build_server_osx_arm64
- build_server_osx_x64
- build_server_win_arm64
- build_server_win_x64
- build_launcher_linux_arm64
- build_launcher_linux_x64
- build_launcher_osx_arm64
- build_launcher_osx_x64
- build_launcher_win_arm64
- build_launcher_win_x64
- build_packager
steps: steps:
- name: Create Temp Directory - uses: frabert/replace-string-action@v2
run: mkdir -p artifacts name: Trim Tag Ref
id: trim_tag_ref
with:
string: '${{ github.ref }}'
pattern: 'refs/tags/v'
replace-with: ''
- name: Download Server Linux ARM64 - uses: frabert/replace-string-action@v2
uses: actions/download-artifact@v4 name: Swap Path Backslashes
with: id: swap_path_backslashes
name: LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip with:
path: artifacts string: '${{ github.workspace }}'
pattern: '\\'
replace-with: '/'
flags: g
- name: Download Server Linux x64 # Server
uses: actions/download-artifact@v4 - uses: actions/checkout@v3
with: - name: Setup .NET
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip uses: actions/setup-dotnet@v3
path: artifacts with:
dotnet-version: 8.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: Download Server macOS ARM64 # Windows
uses: actions/download-artifact@v4 - name: Publish Windows
with: run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build_Windows --self-contained --os win -p:PublishSingleFile=true -p:Version="${{ steps.trim_tag_ref.outputs.replaced }}" -p:AssemblyVersion="${{ steps.trim_tag_ref.outputs.replaced }}"
name: LANCommander.Server-macOS-arm64-v${{ needs.prep.outputs.version_tag }}.zip - name: Publish Windows Updater
path: artifacts run: dotnet publish "./LANCommander.AutoUpdater/LANCommander.AutoUpdater.csproj" -c Release -o _Build_Windows --self-contained --os win -p:Version="${{ steps.trim_tag_ref.outputs.replaced }}" -p:AssemblyVersion="${{ steps.trim_tag_ref.outputs.replaced }}"
- name: Rename Windows Updater
run: mv ./_Build_Windows/LANCommander.AutoUpdater.exe ./_Build_Windows/LANCommander.AutoUpdater.exe.Update
- name: Remove Windows Extra Files
run: rm -Recurse -Force ./_Build_Windows/wwwroot/_content/AntDesign/less
- name: Download Server macOS x64 # Sign Windows Binary
uses: actions/download-artifact@v4 - name: Sign Windows Binary
with: uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
name: LANCommander.Server-macOS-x64-v${{ needs.prep.outputs.version_tag }}.zip with:
path: artifacts certificate: "${{ secrets.CERTIFICATE }}"
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
filepath: "./_Build_Windows/LANCommander.exe"
- name: Download Server Windows ARM64 # Create Windows ZIP file
uses: actions/download-artifact@v4 - name: Package Windows Build
with: uses: vimtor/action-zip@v1.2
name: LANCommander.Server-Windows-arm64-v${{ needs.prep.outputs.version_tag }}.zip with:
path: artifacts files: _Build_Windows/
recursive: true
dest: LANCommander-Windows-x64-v${{ steps.trim_tag_ref.outputs.replaced }}.zip
- name: Download Server Windows x64 # Linux
uses: actions/download-artifact@v4 - name: Publish Linux
with: run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build_Linux --self-contained --os linux -p:PublishSingleFile=true -p:Version="${{ steps.trim_tag_ref.outputs.replaced }}" -p:AssemblyVersion="${{ steps.trim_tag_ref.outputs.replaced }}"
name: LANCommander.Server-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip - name: Remove Linux Extra Files
path: artifacts run: rm -Recurse -Force ./_Build_Linux/wwwroot/_content/AntDesign/less
# Launcher artifacts # Create Linux ZIP file
- name: Download Launcher Linux ARM64 - name: Package Linux Build
uses: actions/download-artifact@v4 uses: vimtor/action-zip@v1.2
with: with:
name: LANCommander.Launcher-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip files: _Build_Linux/
path: artifacts recursive: true
dest: LANCommander-Linux-x64-v${{ steps.trim_tag_ref.outputs.replaced }}.zip
- name: Download Launcher Linux x64 # Client
uses: actions/download-artifact@v4 - name: Setup MSBuild
with: uses: microsoft/setup-msbuild@v1.3.1
name: LANCommander.Launcher-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip - name: Setup NuGet
path: artifacts 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 }}" /p:InformationalVersion="${{ 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.32
fileName: Playnite1032.zip
- name: Extract Playnite
run: Expand-Archive -Path Playnite1032.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: Download Launcher macOS ARM64 - name: List contents of workspace
uses: actions/download-artifact@v4 run: Get-ChildItem -Path ${{ steps.swap_path_backslashes.outputs.replaced }} -Recurse
with:
name: LANCommander.Launcher-macOS-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher macOS x64 - name: Draft Release
uses: actions/download-artifact@v4 uses: softprops/action-gh-release@v2
with: with:
name: LANCommander.Launcher-macOS-x64-v${{ needs.prep.outputs.version_tag }}.zip name: v${{ steps.trim_tag_ref.outputs.replaced }}
path: artifacts generate_release_notes: true
draft: true
- name: Download Launcher Windows ARM64 files: |
uses: actions/download-artifact@v4 ${{ steps.swap_path_backslashes.outputs.replaced }}/LANCommander-Windows-x64-*.zip
with: ${{ steps.swap_path_backslashes.outputs.replaced }}/LANCommander-Linux-x64-*.zip
name: LANCommander.Launcher-Windows-arm64-v${{ needs.prep.outputs.version_tag }}.zip LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_*.pext
path: artifacts
- name: Download Launcher Windows x64
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Download Launcher Linux ARM64 AppImage
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.AppImage
path: artifacts
- name: Download Launcher Linux x64 AppImage
uses: actions/download-artifact@v4
with:
name: LANCommander.Launcher-Linux-x64-v${{ needs.prep.outputs.version_tag }}.AppImage
path: artifacts
- name: Download Packager Windows x86
uses: actions/download-artifact@v4
with:
name: LANCommander.Packager-Windows-x86-v${{ needs.prep.outputs.version_tag }}.zip
path: artifacts
- name: Debug - List Artifact Files
run: |
echo "Contents of ./artifacts:"
ls -la ./artifacts
- name: Draft Release
uses: softprops/action-gh-release@v2
with:
name: v${{ needs.prep.outputs.version_tag }}
generate_release_notes: true
draft: true
prerelease: ${{ needs.prep.outputs.is_prerelease == 'true' }}
files: |
artifacts/LANCommander.Server-Windows-arm64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Server-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Server-macOS-arm64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Server-macOS-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-macOS-arm64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-macOS-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-Windows-arm64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-Windows-x64-v${{ needs.prep.outputs.version_tag }}.zip
artifacts/LANCommander.Launcher-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.AppImage
artifacts/LANCommander.Launcher-Linux-x64-v${{ needs.prep.outputs.version_tag }}.AppImage
artifacts/LANCommander.Packager-Windows-x86-v${{ needs.prep.outputs.version_tag }}.zip
- name: Checkout Repo for Docker build
uses: actions/checkout@v4
- name: Download Server x64 Artifacts
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: ./
- name: Extract Server Artifacts
run: |
mkdir -p ./LANCommander.Server/published
unzip ./LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip -d ./LANCommander.Server/published
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Download Server x64 Artifacts
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip
path: ./
- name: Download Server arm64 Artifacts
uses: actions/download-artifact@v4
with:
name: LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip
path: ./
- name: Extract Server Artifacts x64
run: |
mkdir -p ./LANCommander.Server/published-amd64
unzip ./LANCommander.Server-Linux-x64-v${{ needs.prep.outputs.version_tag }}.zip -d ./LANCommander.Server/published-amd64
- name: Extract Server Artifacts arm64
run: |
mkdir -p ./LANCommander.Server/published-arm64
unzip ./LANCommander.Server-Linux-arm64-v${{ needs.prep.outputs.version_tag }}.zip -d ./LANCommander.Server/published-arm64
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Setup buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: ./LANCommander.Server
file: ./LANCommander.Server/Dockerfile
push: true
platforms: linux/amd64
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}:v${{ needs.prep.outputs.version_tag }}
${{ needs.prep.outputs.is_prerelease == 'true' && format('{0}/{1}:prerelease', env.REGISTRY, env.IMAGE_NAME) || format('{0}/{1}:latest', env.REGISTRY, env.IMAGE_NAME) }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ needs.prep.outputs.version_tag }}
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
provenance: true
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

View file

@ -1,56 +0,0 @@
name: LANCommander SDK Release
on:
release:
types: [published]
permissions:
contents: write
jobs:
prep:
runs-on: ubuntu-latest
outputs:
version_tag: ${{ steps.trim_tag_ref.outputs.replaced }}
version_semver: ${{ steps.extract_semver.outputs.replaced }}
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: ''
- uses: frabert/replace-string-action@v2
name: Extract SemVer (strip prerelease suffix for AssemblyVersion)
id: extract_semver
with:
string: '${{ steps.trim_tag_ref.outputs.replaced }}'
pattern: '-.*$'
replace-with: ''
publish:
needs: prep
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.0.102'
- name: Restore dependencies
run: dotnet restore LANCommander.SDK/LANCommander.SDK.csproj
- name: Build project
run: dotnet build --configuration Release LANCommander.SDK/LANCommander.SDK.csproj -p:Version="${{ needs.prep.outputs.version_tag }}" -p:AssemblyVersion="${{ needs.prep.outputs.version_semver }}"
- name: Pack NuGet package with symbols
run: dotnet pack LANCommander.SDK/LANCommander.SDK.csproj --no-build --configuration Release -p:PackageVersion=${{ needs.prep.outputs.version_tag }} -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -o ./artifacts
- name: Publish NuGet package
run: |
dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json

View file

@ -1,241 +0,0 @@
name: LANCommander Server Build
on:
workflow_dispatch:
workflow_call:
inputs:
version_semver:
description: "Semantic Version"
required: true
type: string
version_tag:
description: 'Version Tag'
required: true
type: string
build_dotnet_version:
description: 'Build .NET Version'
required: true
type: string
build_runtime:
description: 'Build Runtime'
required: false
type: string
default: 'win-x64'
build_arch:
description: 'Build Architecture'
required: false
type: string
default: 'x64'
build_platform:
description: 'Build Platform'
required: false
type: string
default: 'Windows'
build_configuration:
description: 'Build Configuration (Debug/Release)'
required: false
type: string
default: 'Release'
permissions:
contents: write
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/package
jobs:
build:
runs-on: ${{ inputs.build_platform == 'Linux' && inputs.build_arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: frabert/replace-string-action@v2
name: Swap Path Backslashes
id: swap_path_backslashes
with:
string: '${{ github.workspace }}'
pattern: '\\'
replace-with: '/'
flags: g
# Checkout code
- uses: actions/checkout@v3
with:
submodules: true
# .NET Setup and Caching
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.build_dotnet_version }}
- name: Restore dependencies
run: dotnet restore --locked-mode
# Node.js Setup and Caching
- name: Setup Node.js
uses: actions/setup-node@v3.8.1
with:
node-version: '20'
- name: Install Node Packages
run: |
npm install --prefix ./LANCommander.UI
npm install --prefix ./LANCommander.Server
- name: Generate PowerShell Completions
run: dotnet run --project ./LANCommander.CompletionGenerator/LANCommander.CompletionGenerator.csproj -- ./LANCommander.UI/Components/MonacoCodeEditor/PowerShellCompletions.g.ts
- name: Package Frontend
run: |
npm run package --prefix ./LANCommander.UI
npm run package --prefix ./LANCommander.Server
# .NET builds
- name: Publish Updater and Server
run: |
# Strip leading 'v' if present
RAW_VERSION="${{ inputs.version_tag }}" # e.g. v2.0.0-rc1
SEMVER="${RAW_VERSION#v}" # 2.0.0-rc1
# Numeric part only for Assembly/FileVersion
NUMERIC="${SEMVER%%-*}" # 2.0.0
ASSEMBLY_VERSION="${NUMERIC}.0" # 2.0.0.0
echo "SEMVER=$SEMVER"
echo "ASSEMBLY_VERSION=$ASSEMBLY_VERSION"
dotnet publish "./LANCommander.AutoUpdater/LANCommander.AutoUpdater.csproj" \
-c "${{ inputs.build_configuration }}" \
--self-contained \
--runtime "${{ inputs.build_runtime }}" \
-p:Version="$SEMVER" \
-p:AssemblyVersion="$ASSEMBLY_VERSION" \
-p:FileVersion="$ASSEMBLY_VERSION" \
-p:InformationalVersion="$SEMVER"
# Disable NetBeauty on ARM64 runners — nbeauty2 only ships x64 native binaries
DISABLE_BEAUTY="False"
if [ "${{ inputs.build_arch }}" = "arm64" ] && [ "${{ inputs.build_platform }}" = "Linux" ]; then
DISABLE_BEAUTY="True"
fi
dotnet publish "./LANCommander.Server/LANCommander.Server.csproj" \
-c "${{ inputs.build_configuration }}" \
--self-contained \
--runtime "${{ inputs.build_runtime }}" \
-p:Version="$SEMVER" \
-p:AssemblyVersion="$ASSEMBLY_VERSION" \
-p:FileVersion="$ASSEMBLY_VERSION" \
-p:InformationalVersion="$SEMVER" \
-p:DisableBeauty="$DISABLE_BEAUTY"
- name: Bundle and Clean
shell: pwsh
run: |
Copy-Item -Force -Recurse -Verbose LANCommander.AutoUpdater/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish/* LANCommander.Server/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish/
# Remove unnecessary files in a single operation
$PathsToRemove = @(
'wwwroot/_content/BootstrapBlazor.PdfReader/compat',
'wwwroot/_content/BootstrapBlazor.PdfReader/2.*',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/pdf.sandbox.js',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/*.map',
'wwwroot/_content/BootstrapBlazor.PdfReader/web/*.map',
'wwwroot/_content/AntDesign/less',
'wwwroot/_content/BlazorMonaco/lib/monaco-editor/min-maps',
'wwwroot/Identity/lib/bootstrap',
'LANCommander.ico',
'LANCommanderDark.ico',
'package-lock.json',
'package.json',
'*.pdb',
'hostfxr.dll.bak',
'Libraries/locales'
)
$BasePath = "LANCommander.Server/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish"
foreach ($path in $PathsToRemove) {
Remove-Item -Recurse -Force -ErrorAction Continue "$BasePath/$path"
}
- name: Bundle macOS .app
if: inputs.build_platform == 'macOS'
shell: bash
run: |
set -euo pipefail
# Strip leading 'v' and reduce to numeric x.y.z for the bundle version keys.
RAW_VERSION="${{ inputs.version_tag }}"
SEMVER="${RAW_VERSION#v}"
NUMERIC="${SEMVER%%-*}"
APP_NAME="LANCommander Server"
EXECUTABLE="LANCommander.Server"
BUNDLE_ID="app.lancommander.server"
ICON_SRC="LANCommander.Server/icon.icns"
PUBLISH_DIR="LANCommander.Server/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish"
APP_DIR="${EXECUTABLE}.app"
# Lay out the bundle skeleton.
mkdir -p "$APP_DIR/Contents/MacOS" "$APP_DIR/Contents/Resources"
cp -a "$PUBLISH_DIR/." "$APP_DIR/Contents/MacOS/"
cp "$ICON_SRC" "$APP_DIR/Contents/Resources/AppIcon.icns"
# Write Info.plist.
cat > "$APP_DIR/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${BUNDLE_ID}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE}</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>${NUMERIC}</string>
<key>CFBundleVersion</key>
<string>${SEMVER}</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
PLIST
# Ensure the entrypoint is executable, then zip while preserving the
# permission bits (Compress-Archive drops the exec bit).
chmod +x "$APP_DIR/Contents/MacOS/${EXECUTABLE}"
zip -ry "LANCommander.Server-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip" "$APP_DIR"
- name: Compress Build Output
if: inputs.build_platform != 'macOS'
shell: pwsh
run: |
$compress = @{
Path = "LANCommander.Server/bin/${{ inputs.build_configuration }}/net10.0/${{ inputs.build_runtime }}/publish/*"
DestinationPath = "LANCommander.Server-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
path: LANCommander.Server-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip
name: LANCommander.Server-${{ inputs.build_platform }}-${{ inputs.build_arch }}-v${{ inputs.version_tag }}.zip

View file

@ -1,121 +0,0 @@
name: Publish to WinGet
on:
release:
types: [published]
jobs:
create-installers:
runs-on: windows-latest
strategy:
matrix:
app: ['Server', 'Launcher']
arch: ['x64', 'arm64']
steps:
- uses: actions/checkout@v4
- name: Get version
id: get_version
shell: pwsh
run: |
$tag = '${{ github.ref_name }}'
$version = $tag.TrimStart('v')
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Download release artifact
shell: pwsh
run: |
$releaseAsset = "LANCommander.${{ matrix.app }}-Windows-${{ matrix.arch }}-v${{ env.VERSION }}.zip"
gh release download ${{ github.ref_name }} -p $releaseAsset
Expand-Archive $releaseAsset -DestinationPath ".\extracted"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Install Inno Setup
- name: Install Inno Setup
run: |
curl -L -o innosetup.exe https://github.com/jrsoftware/issrc/releases/download/is-6_7_3/innosetup-6.7.3.exe
.\innosetup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART
shell: cmd
# Create Inno Setup script
- name: Create installer script
run: |
$appId = if ('${{ matrix.app }}' -eq 'Server') {
'2C58E237-1D69-42A0-B702-F995B75B8A5E'
} else {
'A3D4F8E1-7B2C-4E5D-9F1A-6C8B3D7E2F4A'
}
@"
#define MyAppName "LANCommander ${{ matrix.app }}"
#define MyAppVersion "${{ env.VERSION }}"
#define MyAppPublisher "LANCommander"
#define MyAppURL "https://github.com/${{ github.repository }}"
#define MyAppExeName "LANCommander.${{ matrix.app }}.exe"
#define Architecture "${{ matrix.arch }}"
[Setup]
AppId={{$appId}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\LANCommander\{#MyAppName}
DisableProgramGroupPage=yes
OutputBaseFilename=LANCommander.${{ matrix.app }}-{#MyAppVersion}-{#Architecture}-Setup
Compression=lzma
SolidCompression=yes
ArchitecturesAllowed=${{ matrix.arch == 'x64' && 'x64' || 'arm64' }}
ArchitecturesInstallIn64BitMode=${{ matrix.arch == 'x64' && 'x64' || 'arm64' }}
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "extracted\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
"@ | Out-File -FilePath "installer.iss" -Encoding UTF8
shell: pwsh
# Build installer
- name: Build installer
run: |
& 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' installer.iss
shell: pwsh
# Upload installer to release
- name: Upload installer to release
run: |
gh release upload ${{ github.ref_name }} "Output/LANCommander.${{ matrix.app }}-${{ env.VERSION }}-${{ matrix.arch }}-Setup.exe"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-winget:
needs: create-installers
runs-on: windows-latest
strategy:
matrix:
app: ['Server', 'Launcher']
steps:
- name: Get version
shell: pwsh
run: |
$tag = '${{ github.ref_name }}'
$version = $tag.TrimStart('v')
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install wingetcreate
shell: pwsh
run: |
Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest" -OutFile wingetcreate.exe
- name: Submit package to Windows Package Manager Community Repository
run: |
$x64Url = "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/LANCommander.${{ matrix.app }}-${env:VERSION}-x64-Setup.exe"
$arm64Url = "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/LANCommander.${{ matrix.app }}-${env:VERSION}-arm64-Setup.exe"
.\wingetcreate.exe update --submit --token "${{ secrets.WINGET_TOKEN }}" --urls $x64Url $arm64Url --version ${env:VERSION} LANCommander.${{ matrix.app }}
shell: pwsh

35
.gitignore vendored
View file

@ -17,6 +17,10 @@
mono_crash.* mono_crash.*
# Build results # Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/ x64/
x86/ x86/
[Aa][Rr][Mm]/ [Aa][Rr][Mm]/
@ -351,34 +355,3 @@ LANCommander/Saves/
LANCommander/Media/ LANCommander/Media/
LANCommander/Uploads/ LANCommander/Uploads/
LANCommander/Updates/ LANCommander/Updates/
LANCommander/Launcher
LANCommander/dist
LANCommander/wwwroot/scripts/dist
LANCommander.Server/Media
LANCommander.Server/Uploads
LANCommander.Server/Updates
LANCommander.Server/Saves
LANCommander.Server/Launcher
LANCommander.Server/Settings.yml
LANCommander.Server/LANCommander.db*
LANCommander.Server/wwwroot/css/custom.css
LANCommander.Server/wwwroot/css/app.css*
LANCommander.Launcher/wwwroot/css/app.css*
LANCommander.Launcher/wwwroot/css/main.js
LANCommander.Server/wwwroot/css/main.js
.aspire
LANCommander.Server/config/
LANCommander.UI/wwwroot/
LANCommander.UI/Components/MonacoCodeEditor/PowerShellCompletions.g.ts
LANCommander.Server/Data/
LANCommander.Launcher.Avalonia/Data/
Data/
# Visual test output — screenshots & diffs are CI artifacts, not committed.
# Baselines/ is committed; everything else is transient.
LANCommander.Launcher.Avalonia.Tests/Screenshots/
LANCommander.Launcher.Avalonia.Tests/Diffs/
visual-test-output/

View file

@ -1,94 +0,0 @@
# Contributing to LANCommander
Thanks for your interest in contributing to LANCommander! This project is primarily developed by a single developer, so community contributions are greatly appreciated.
## Getting Started
### Prerequisites
- [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- [Node.js](https://nodejs.org/) (for the server UI's TypeScript/SCSS assets)
- A code editor such as [Visual Studio](https://visualstudio.microsoft.com/), [Rider](https://www.jetbrains.com/rider/), or [VS Code](https://code.visualstudio.com/)
### Building the Project
1. Clone the repository:
```bash
git clone https://github.com/LANCommander/LANCommander.git
cd LANCommander
```
2. Restore dependencies:
```bash
dotnet restore
```
3. Build the server:
```bash
dotnet build LANCommander.Server
```
4. Build the launcher:
```bash
dotnet build LANCommander.Launcher
```
### Running Locally
To run the server in development mode:
```bash
dotnet run --project LANCommander.Server
```
The server will be available at `http://localhost:1337` by default.
## How to Contribute
### Reporting Bugs
Use the [GitHub Issues](https://github.com/LANCommander/LANCommander/issues) page with the bug report template. Include:
- Steps to reproduce the issue
- Expected vs. actual behavior
- Your OS and LANCommander version
- Relevant logs or screenshots
### Submitting Changes
1. Fork the repository
2. Create a feature branch from `main` (`git checkout -b my-feature`)
3. Make your changes
4. Test your changes locally
5. Commit with a clear, descriptive message
6. Push to your fork and open a Pull Request
### What to Work On
- Check [open issues](https://github.com/LANCommander/LANCommander/issues) for bugs or feature requests
- Documentation improvements are always welcome at our [documentation site](https://docs.lancommander.app/)
- Game packaging scripts and guides for the community
### Code Guidelines
- Follow existing code style and conventions in the project
- Keep PRs focused, one feature or fix per PR when possible
- Include screenshots in your PR if you're changing UI
## Project Structure
| Directory | Description |
|-----------|-------------|
| `LANCommander.Server` | ASP.NET Blazor web application (server/admin) |
| `LANCommander.Launcher` | Avalonia desktop client (launcher) |
| `LANCommander.Packager` | Game packaging tool |
| `LANCommander.SDK` | .NET SDK for building custom clients |
| `LANCommander.Server.Data` | Entity Framework data models and migrations |
| `LANCommander.Server.Services` | Server business logic |
| `LANCommander.Documentation` | Docusaurus documentation site |
## Community
- [Discord](https://discord.gg/vDEEWVt8EM): Best place for discussion, help, and sharing game packages
## License
By contributing to LANCommander, you agree that your contributions will be licensed under the [MIT License](LICENSE).

View file

@ -1,191 +0,0 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label="Aspire">
<PackageVersion Include="Aspire.Hosting.AppHost" Version="9.5.0" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.1.1" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="EasyMDE.Blazor" Version="1.0.5" />
<PackageVersion Include="HotAvalonia" Version="3.1.0" />
<PackageVersion Include="LiveChartsCore" Version="2.0.0-rc5.4" />
<PackageVersion Include="LiveChartsCore.SkiaSharpView" Version="2.0.0-rc5.4" />
<PackageVersion Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0-rc5.4" />
<PackageVersion Include="Markdig" Version="0.44.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.8" />
<PackageVersion Include="Notify.NET" Version="1.1.0" />
<PackageVersion Include="Svrooij.PowerShell.DI" Version="1.3.4" />
</ItemGroup>
<ItemGroup Label="AutoMapper">
<PackageVersion Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup Label="AntDesign">
<PackageVersion Include="AntDesign" Version="1.5.0" />
<PackageVersion Include="AntDesign.Charts" Version="0.8.0" />
</ItemGroup>
<ItemGroup Label="Blazor">
<PackageVersion Include="BlazorMonaco" Version="3.4.0" />
<PackageVersion Include="BootstrapBlazor.PdfReader" Version="9.0.1" />
<PackageVersion Include="PSC.Blazor.Components.MarkdownEditor" Version="8.0.8" />
<PackageVersion Include="Razor.Templating.Core" Version="2.1.0" />
<PackageVersion Include="XtermBlazor" Version="2.1.2" />
</ItemGroup>
<ItemGroup Label="Utility">
<PackageVersion Include="ByteSize" Version="2.1.2" />
<PackageVersion Include="CaseConverter" Version="2.0.1" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.11.72" />
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
<PackageVersion Include="Emzi0767.NtfsDataStreams" Version="1.0.0" />
<PackageVersion Include="PeanutButter.INI" Version="3.0.396" />
<PackageVersion Include="Superpower" Version="3.1.0" />
<PackageVersion Include="RegParserDotNet" Version="1.1.1" />
<PackageVersion Include="nulastudio.NetBeauty" Version="2.1.4.6" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
</ItemGroup>
<ItemGroup Label="ASP.NET Core">
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client.SourceGenerator" Version="7.0.0-preview.7.22376.6" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.0.11" />
</ItemGroup>
<ItemGroup Label="Code Analysis">
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Features" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Scripting.Common" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0" />
</ItemGroup>
<ItemGroup Label="Testing">
<PackageVersion Include="bunit" Version="1.40.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.Playwright" Version="1.51.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageVersion Include="Microsoft.TypeScript.MSBuild" Version="5.7.1" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageVersion Include="Syncfusion.PdfToImageConverter.Net" Version="28.2.4" />
<PackageVersion Include="Xunit.Extensions.TestDependency" Version="1.1.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
</ItemGroup>
<ItemGroup Label="External">
<PackageVersion Include="CoreRCON" Version="5.0.5" />
<PackageVersion Include="LANCommander.HQ.SDK" Version="1.0.1" />
<PackageVersion Include="craftersmine.SteamGridDB.Net" Version="1.1.7" />
<PackageVersion Include="YoutubeExplode" Version="6.4.3" />
<PackageVersion Include="Docker.DotNet.Enhanced" Version="3.129.0" />
<PackageVersion Include="Facepunch.Steamworks" Version="2.3.3" />
<PackageVersion Include="IGDB" Version="6.1.0" />
<PackageVersion Include="IPXRelayDotNet" Version="1.1.3" />
<PackageVersion Include="LANCommander.Interposer" Version="1.0.7" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="RestSharp" Version="112.1.0" />
<PackageVersion Include="rix0rrr.BeaconLib" Version="1.0.2" />
<PackageVersion Include="Semver" Version="3.0.0" />
<PackageVersion Include="SharpCompress" Version="0.50.0" />
<PackageVersion Include="SteamWebAPI2" Version="4.4.1" />
</ItemGroup>
<ItemGroup Label="Entity Framework Core">
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup>
<ItemGroup Label="Hangfire">
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" />
<PackageVersion Include="Hangfire.Core" Version="1.8.17" />
<PackageVersion Include="Hangfire.InMemory" Version="1.0.0" />
</ItemGroup>
<ItemGroup Label="PowerShell">
<PackageVersion Include="Microsoft.PowerShell.Commands.Diagnostics" Version="7.4.7" />
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.4.7" />
<PackageVersion Include="System.Management.Automation" Version="7.4.7" />
</ItemGroup>
<ItemGroup Label="Microsoft Extensions">
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.7.0" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.4.0" />
</ItemGroup>
<ItemGroup Label="OpenTelemetry">
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
</ItemGroup>
<ItemGroup Label="LibVLC">
<PackageVersion Include="LibVLCSharp" Version="3.8.5" />
<PackageVersion Include="LibVLCSharp.Avalonia" Version="3.8.5" />
<PackageVersion Include="VideoLAN.LibVLC.Windows" Version="3.0.23.1" />
<PackageVersion Include="VideoLAN.LibVLC.Mac" Version="3.1.3.1" />
</ItemGroup>
<ItemGroup Label="SDL">
<PackageVersion Include="ppy.SDL3-CS" Version="2026.520.0" />
</ItemGroup>
<ItemGroup Label="Photino">
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
<PackageVersion Include="Avalonia" Version="11.2.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.3" />
<PackageVersion Include="Avalonia.Headless" Version="11.2.3" />
<PackageVersion Include="Avalonia.Headless.XUnit" Version="11.2.3" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.2.3" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.2.3" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.3" />
<PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.2.0.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="PDFtoImage" Version="4.1.1" />
<PackageVersion Include="Photino.Blazor" Version="4.0.13" />
<PackageVersion Include="Photino.Blazor.CustomWindow" Version="1.3.1" />
<PackageVersion Include="Photino.NET" Version="4.0.16" />
</ItemGroup>
<ItemGroup Label="System">
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.3.1" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.1" />
</ItemGroup>
<ItemGroup Label="Caching">
<PackageVersion Include="ZiggyCreatures.FusionCache" Version="2.1.0" />
</ItemGroup>
<ItemGroup Label="Overlay">
<PackageVersion Include="Avalonia.Skia" Version="11.2.3" />
<PackageVersion Include="Reloaded.Hooks" Version="4.3.0" />
<PackageVersion Include="DNNE" Version="2.1.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
</ItemGroup>
</Project>

52
Docker/Dockerfile Normal file
View file

@ -0,0 +1,52 @@
# syntax=docker/dockerfile:1
FROM ghcr.io/linuxserver/baseimage-ubuntu:jammy
# set version label
ARG BUILD_DATE
ARG VERSION
LABEL build_version="LANCommander version:- ${VERSION} Build-date:- ${BUILD_DATE}, container based on github.com/LinuxServer/baseimage-ubuntu"
LABEL maintainer="bashNinja"
# copy local files
COPY root/ /
# grab app
ADD "https://github.com/LANCommander/LANCommander/releases/download/${VERSION}/LANCommander-Linux-x64-${VERSION}.zip" "/tmp"
# install app
RUN \
echo "**** install packages ****" && \
apt-get update && \
apt-get install -y --no-install-recommends \
jq \
libicu70 \
sqlite3 \
unzip && \
echo "**** setup lancommander ****" && \
unzip "/tmp/LANCommander-Linux-x64-${VERSION}.zip" -d /app/LANCommander && \
chmod +x /app/LANCommander/LANCommander && \
ls -alh /app/LANCommander && \
echo "**** setup symlinks ****" && \
ln -s /config/Uploads/ /app/LANCommander/Uploads && \
ln -s /config/Updates/ /app/LANCommander/Updates && \
ln -s /config/Saves/ /app/LANCommander/Saves && \
ln -s /config/Media/ /app/LANCommander/Media && \
ln -s /config/Logs/ /app/LANCommander/Logs && \
ln -s /config/Backups/ /app/LANCommander/Backups && \
ln -s /config/Settings.yml /app/LANCommander/Settings.yml && \
ln -s /config/LANCommander.db /app/LANCommander/LANCommander.db && \
ln -s /config/internal.log /app/LANCommander/internal.log && \
echo "**** cleanup ****" && \
apt-get -y autoremove && \
apt-get clean && \
rm -rf \
/tmp/* \
/var/lib/apt/lists/* \
/var/tmp/*
# ports and volumes
EXPOSE 1337/tcp
EXPOSE 35891/udp
VOLUME /config

View file

@ -0,0 +1,32 @@
Port: 1337
DatabaseConnectionString: Data Source=LANCommander.db;Cache=Shared
IGDBClientId: ''
IGDBClientSecret: ''
Beacon:
Enabled: true
Address: ''
Authentication:
TokenSecret: ''
TokenLifetime: 30
PasswordRequireDigit: true
PasswordRequiredLength: 8
Roles: {}
UserSaves:
StoragePath: /config/Saves
MaxSize: 25
Archives:
StoragePath: /config/Uploads
Media:
SteamGridDbApiKey: ''
StoragePath: /config/Media
MaxSize: 25
IPXRelay:
Host: ''
Port: 213
Wiki:
Username: ''
Password: ''
Update:
StoragePath: /config/Updates
Logs:
StoragePath: /config/Logs

View file

@ -0,0 +1,21 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
# make our folders for LANCommander
mkdir -p \
/config/{Uploads,Updates,Saves,Media,Logs,Backups}
# files needed by LANCommander
if [[ ! -f /config/Settings.yml ]]; then
cp /defaults/Settings.yml /config/Settings.yml
fi
if [[ ! -f /config/LANCommander.db ]]; then
sqlite3 /config/LANCommander.db "VACUUM;"
fi
if [[ ! -f /config/internal.log ]]; then
touch /config/internal.log
fi
# permissions
lsiown -R abc:abc \
/config

View file

@ -0,0 +1 @@
oneshot

View file

@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-files-folders-config/run

View file

@ -0,0 +1,9 @@
#!/bin/bash
PORT=$(grep '^Port:' /config/Settings.yml | awk '{ print $2 }')
if [[ $(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${PORT:-1337}/") -eq 200 ]]; then
exit 0
else
exit 1
fi

View file

@ -0,0 +1,6 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
exec \
s6-notifyoncheck -d -n 300 -w 1000 \
cd /app/LANCommander s6-setuidgid abc /app/LANCommander/LANCommander

View file

@ -0,0 +1 @@
longrun

BIN
Docs/AddGame.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
Docs/ArchiveUploading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
Docs/ChangeKey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

BIN
Docs/Dashboard.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
Docs/EditingScript.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
Docs/GamesList.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
Docs/InstallingGames.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

BIN
Docs/KeyManagement.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View file

@ -1,14 +0,0 @@
using Projects;
var builder = DistributedApplication.CreateBuilder(args);
var server = builder
.AddProject<LANCommander_Server>("Server")
.WithExternalHttpEndpoints();
var launcher = builder
.AddProject<LANCommander_Launcher>("Launcher")
.WithReference(server)
.WaitFor(server);
builder.Build().Run();

View file

@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.4.0"/>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>0899617b-b319-465d-aaaf-aac7ea333029</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.Launcher\LANCommander.Launcher.csproj" />
<ProjectReference Include="..\LANCommander.Server\LANCommander.Server.csproj" />
</ItemGroup>
</Project>

View file

@ -1,29 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17134;http://localhost:15274",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21013",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22279"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15274",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19171",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20001"
}
}
}
}

View file

@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}

View file

@ -2,10 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile> <PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -16,13 +16,11 @@ Console.WriteLine("Waiting for LANCommander to stop...");
int delay = 500; int delay = 500;
int maxAttempts = 30 * (1000 / delay); int maxAttempts = 30 * (1000 / delay);
string processName = Path.GetFileNameWithoutExtension(arguments["Executable"]);
for (int i = 0; i < maxAttempts; i++) for (int i = 0; i < maxAttempts; i++)
{ {
var stillRunning = Process.GetProcessesByName(processName).Any(); var processes = Process.GetProcessesByName("LANCommander");
if (!stillRunning) if (processes.Length == 0)
break; break;
else if (i == maxAttempts - 1) else if (i == maxAttempts - 1)
throw new Exception("Cannot update! LANCommander is still running!"); throw new Exception("Cannot update! LANCommander is still running!");
@ -53,12 +51,7 @@ finally
{ {
Console.WriteLine("Starting LANCommander..."); Console.WriteLine("Starting LANCommander...");
var executable = "LANCommander.exe"; Process.Start(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LANCommander.exe"));
if (arguments.ContainsKey("Executable"))
executable = arguments["Executable"];
Process.Start(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, executable));
Console.WriteLine("LANCommander has been started! My job here is done"); Console.WriteLine("LANCommander has been started! My job here is done");
} }

View file

@ -2,7 +2,7 @@
"profiles": { "profiles": {
"LANCommander.AutoUpdater": { "LANCommander.AutoUpdater": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "-Version \"0.4.1\" -Path \"Updates\" -Executable \"LANCommander.Server.exe\"" "commandLineArgs": "-Version \"0.4.1\" -Path \"Updates\""
} }
} }
} }

View file

@ -1,60 +0,0 @@
function Build-Launcher {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[SemVer] $Version,
[String] $Runtime,
[String] $Architecture,
[String] $Platform,
[String] $Configuration
)
$AssemblyVersion = "$($Version.Major).$($Version.Minor).$($Version.Patch)"
$RuntimeIdentifier = "$Runtime-$Architecture"
dotnet restore
npm install --prefix ./LANCommander.UI
npm install --prefix ./LANCommander.Launcher
dotnet publish ./LANCommander.AutoUpdater/LANCommander.AutoUpdater.csproj -c $Configuration --self-contained --runtime $RuntimeIdentifier -p:Version="$Version" -p:AssemblyVersion="$AssemblyVersion"
dotnet publish ./LANCommander.Launcher/LANCommander.Launcher.csproj -c $Configuration --self-contained --runtime $RuntimeIdentifier -p:Version="$Version" -p:AssemblyVersion="$AssemblyVersion"
dotnet publish ./LANCommander.Launcher.CLI/LANCommander.Launcher.CLI.csproj -c $Configuration --self-contained --runtime $RuntimeIdentifier -p:Version="$Version" -p:AssemblyVersion="$AssemblyVersion"
Copy-Item -Force -Recurse -Verbose LANCommander.AutoUpdater/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/* LANCommander.Launcher/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/
Copy-Item -Force -Recurse -Verbose LANCommander.Launcher.CLI/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/* LANCommander.Launcher/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/
$PathsToRemove = @(
'wwwroot/_content/BootstrapBlazor.PdfReader/compat',
'wwwroot/_content/BootstrapBlazor.PdfReader/2.*',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/pdf.sandbox.js',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/*.map',
'wwwroot/_content/BootstrapBlazor.PdfReader/web/*.map',
'wwwroot/_content/AntDesign/less',
'wwwroot/_content/BlazorMonaco/lib/monaco-editor/min-maps',
'wwwroot/Identity/lib/bootstrap',
'LANCommander.ico',
'LANCommanderDark.ico',
'package-lock.json',
'package.json',
'*.pdb',
'hostfxr.dll.bak',
'Libraries/locales'
)
$BasePath = "LANCommander.Launcher/bin/$Configuration/net9.0/$RuntimeIdentifier/publish"
foreach ($path in $PathsToRemove) {
Remove-Item -Force -Recurse -ErrorAction SilentlyContinue "$BasePath/$path"
}
$Compress = @{
Path = "LANCommander.Launcher/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/*"
DestinationPath = "LANCommander.Launcher-$Platform-$Architecture-v$Version.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
$Compress.Path
}

View file

@ -1,58 +0,0 @@
function Build-Server {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[SemVer] $Version,
[String] $Runtime,
[String] $Architecture,
[String] $Platform,
[String] $Configuration
)
$AssemblyVersion = "$($Version.Major).$($Version.Minor).$($Version.Patch)"
$RuntimeIdentifier = "$Runtime-$Architecture"
dotnet restore
npm install --prefix ./LANCommander.UI
npm install --prefix ./LANCommander.Server
dotnet publish ./LANCommander.AutoUpdater/LANCommander.AutoUpdater.csproj -c $Configuration --self-contained --runtime $RuntimeIdentifier -p:Version="$Version" -p:AssemblyVersion="$AssemblyVersion"
dotnet publish ./LANCommander.Server/LANCommander.Server.csproj -c $Configuration --self-contained --runtime $RuntimeIdentifier -p:Version="$Version" -p:AssemblyVersion="$AssemblyVersion"
Copy-Item -Force -Recurse -Verbose LANCommander.AutoUpdater/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/* LANCommander.Server/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/
$PathsToRemove = @(
'wwwroot/_content/BootstrapBlazor.PdfReader/compat',
'wwwroot/_content/BootstrapBlazor.PdfReader/2.*',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/pdf.sandbox.js',
'wwwroot/_content/BootstrapBlazor.PdfReader/build/*.map',
'wwwroot/_content/BootstrapBlazor.PdfReader/web/*.map',
'wwwroot/_content/AntDesign/less',
'wwwroot/_content/BlazorMonaco/lib/monaco-editor/min-maps',
'wwwroot/Identity/lib/bootstrap',
'LANCommander.ico',
'LANCommanderDark.ico',
'package-lock.json',
'package.json',
'*.pdb',
'hostfxr.dll.bak',
'Libraries/locales'
)
$BasePath = "LANCommander.Server/bin/$Configuration/net9.0/$RuntimeIdentifier/publish"
foreach ($path in $PathsToRemove) {
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$BasePath/$path"
}
$Compress = @{
Path = "LANCommander.Server/bin/$Configuration/net9.0/$RuntimeIdentifier/publish/*"
DestinationPath = "LANCommander.Server-$Platform-$Architecture-v$Version.zip"
CompressionLevel = "Fastest"
}
Compress-Archive @compress
$Compress.Path
}

View file

@ -1,10 +0,0 @@
function Get-LastSemVerTag {
[OutputType([semver])]
param()
git fetch --tags
$lastTag = git tag --list | Where-Object { $_ -match '^v?\d+\.\d+\.\d+$' } | Sort-Object -Descending | Select-Object -First 1
return [semver]$lastTag.TrimStart('v')
}

View file

@ -1,21 +0,0 @@
. ./LANCommander.Build/Include/Get-LastSemVerTag.ps1
. ./LANCommander.Build/Include/Build-Server.ps1
. ./LANCommander.Build/Include/Build-Launcher.ps1
$Version = Get-LastSemVerTag
$Version = [semver]"$($Version.Major).$($Version.Minor).$($Version.Patch)-nightly.$(Get-Date -Format "yyyyMMddHHmm")"
$BuildTargets =
@{ Platform = 'Windows'; Architecture = 'x64'; Runtime = 'win' },
@{ Platform = 'Windows'; Architecture = 'arm64'; Runtime = 'win' },
@{ Platform = 'Linux'; Architecture = 'x64'; Runtime = 'linux' },
@{ Platform = 'Linux'; Architecture = 'arm64'; Runtime = 'linux' },
@{ Platform = 'macOS'; Architecture = 'x64'; Runtime = 'osx' },
@{ Platform = 'macOS'; Architecture = 'arm64'; Runtime = 'osx' }
Write-Host "Building Targets"
foreach ($target in $BuildTargets) {
Build-Server -Version $Version -Runtime $target.Runtime -Architecture $target.Architecture -Platform $target.Platform -Configuration 'Debug'
Build-Launcher -Version $Version -Runtime $target.Runtime -Architecture $target.Architecture -Platform $target.Platform -Configuration 'Debug'
}

View file

@ -1,54 +1,23 @@
using LANCommander.Launcher.Data.Interceptors; using LANCommander.Client.Data.Models;
using LANCommander.Launcher.Data.Models;
using LANCommander.SDK;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Data namespace LANCommander.Client.Data
{ {
public class DatabaseContext : DbContext public class DatabaseContext : DbContext
{ {
private readonly ILoggerFactory LoggerFactory;
public DatabaseContext() { } public DatabaseContext() { }
public DatabaseContext(ILoggerFactory loggerFactory, DbContextOptions options) public DatabaseContext(DbContextOptions options)
: base(options) : base(options)
{ {
LoggerFactory = loggerFactory;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
// Skip the SQLite default when the caller has already chosen a provider var dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LANCommander.db");
// (e.g. tests using EF InMemory) — EF errors out if two providers are registered.
if (optionsBuilder.IsConfigured)
return;
var dbPath = AppPaths.GetConfigPath("LANCommander.db"); optionsBuilder.UseSqlite($"Data Source={dbPath};Cache=Shared");
optionsBuilder.UseLazyLoadingProxies();
var connectionString = new SqliteConnectionStringBuilder
{
DataSource = dbPath,
Cache = SqliteCacheMode.Shared,
}.ToString();
optionsBuilder.AddInterceptors(new AuditingInterceptor());
optionsBuilder.UseLoggerFactory(LoggerFactory);
optionsBuilder.UseSqlite(connectionString, options =>
{
options.CommandTimeout(30);
});
}
/// <summary>
/// Enables WAL mode for concurrent reads during background writes.
/// Should be called once after the database is created/migrated.
/// </summary>
public async Task EnableWalModeAsync()
{
await Database.ExecuteSqlRawAsync("PRAGMA journal_mode=WAL;");
} }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
@ -72,33 +41,7 @@ namespace LANCommander.Launcher.Data
.HasMany(t => t.Games) .HasMany(t => t.Games)
.WithMany(g => g.Tags); .WithMany(g => g.Tags);
builder.Entity<Platform>()
.HasMany(p => p.Games)
.WithMany(g => g.Platforms);
builder.Entity<Library>()
.HasOne(l => l.User)
.WithOne(u => u.Library)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Library>()
.HasMany(l => l.Games)
.WithMany(l => l.Libraries)
.UsingEntity<Dictionary<string, object>>(
"LibraryGame",
lg => lg.HasOne<Game>().WithMany().HasForeignKey("GameId").OnDelete(DeleteBehavior.Cascade),
lg => lg.HasOne<Library>().WithMany().HasForeignKey("LibraryId").OnDelete(DeleteBehavior.Cascade)
);
builder.Entity<User>()
.HasOne(u => u.Avatar)
.WithOne(a => a.User)
.OnDelete(DeleteBehavior.Cascade);
#region Game Relationships #region Game Relationships
builder.Entity<Game>()
.Property(g => g.BaseGameId)
.HasConversion(new GuidToNullConverter());
builder.Entity<Game>() builder.Entity<Game>()
.HasMany(g => g.MultiplayerModes) .HasMany(g => g.MultiplayerModes)
@ -139,14 +82,6 @@ namespace LANCommander.Launcher.Data
gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId") gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId")
); );
builder.Entity<Game>()
.HasMany(g => g.Tools)
.WithMany(t => t.Games)
.UsingEntity<GameTool>(
gt => gt.HasOne(x => x.Tool).WithMany(t => t.GameTools).HasForeignKey(x => x.ToolId).OnDelete(DeleteBehavior.Cascade),
gt => gt.HasOne(x => x.Game).WithMany(g => g.GameTools).HasForeignKey(x => x.GameId).OnDelete(DeleteBehavior.Cascade),
gt => gt.HasKey(x => new { x.GameId, x.ToolId }));
builder.Entity<Game>() builder.Entity<Game>()
.HasMany(g => g.DependentGames) .HasMany(g => g.DependentGames)
.WithOne(g => g.BaseGame) .WithOne(g => g.BaseGame)
@ -158,12 +93,6 @@ namespace LANCommander.Launcher.Data
.WithMany(e => e.Games) .WithMany(e => e.Games)
.IsRequired(false) .IsRequired(false)
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
builder.Entity<Game>()
.HasMany(g => g.ExternalIds)
.WithOne(e => e.Game)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
#endregion #endregion
#region Collection Relationships #region Collection Relationships
@ -178,7 +107,6 @@ namespace LANCommander.Launcher.Data
#endregion #endregion
} }
public DbSet<Engine>? Engines { get; set; }
public DbSet<Game>? Games { get; set; } public DbSet<Game>? Games { get; set; }
public DbSet<Genre>? Genres { get; set; } public DbSet<Genre>? Genres { get; set; }
@ -192,7 +120,5 @@ namespace LANCommander.Launcher.Data
public DbSet<Redistributable>? Redistributables { get; set; } public DbSet<Redistributable>? Redistributables { get; set; }
public DbSet<Media>? Media { get; set; } public DbSet<Media>? Media { get; set; }
public DbSet<Library>? Libraries { get; set; }
public DbSet<User>? Users { get; set; }
} }
} }

View file

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace LANCommander.Client.Data.Enums
{
public enum GameType
{
[Display(Name = "Main Game")]
MainGame,
Expansion,
[Display(Name = "Standalone Expansion")]
StandaloneExpansion,
Mod,
[Display(Name = "Standalone Mod")]
StandaloneMod
}
}

View file

@ -0,0 +1,10 @@
namespace LANCommander.Client.Data.Enums
{
public enum MediaType
{
Icon,
Cover,
Background,
Avatar
}
}

View file

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj" />
</ItemGroup>
</Project>

View file

@ -1,6 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LANCommander.Launcher.Data; using LANCommander.Client.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20240522012114_InitialSeed")] [Migration("20240522012114_InitialSeed")]
@ -129,7 +129,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("GameTag"); b.ToTable("GameTag");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -155,7 +155,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Categories"); b.ToTable("Categories");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Collection", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -176,7 +176,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Collections"); b.ToTable("Collections");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Company", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -197,7 +197,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Companies"); b.ToTable("Companies");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -218,7 +218,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Engines"); b.ToTable("Engines");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -279,7 +279,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Games"); b.ToTable("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Genre", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Genre", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -300,7 +300,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Genres"); b.ToTable("Genres");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -341,7 +341,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Media"); b.ToTable("Media");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -382,7 +382,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("MultiplayerModes"); b.ToTable("MultiplayerModes");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Redistributable", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -409,7 +409,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Redistributables"); b.ToTable("Redistributables");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Tag", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -432,13 +432,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CategoryGame", b => modelBuilder.Entity("CategoryGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", null) b.HasOne("LANCommander.Client.Data.Models.Category", null)
.WithMany() .WithMany()
.HasForeignKey("CategoriesId") .HasForeignKey("CategoriesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -447,13 +447,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CollectionGame", b => modelBuilder.Entity("CollectionGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Collection", null) b.HasOne("LANCommander.Client.Data.Models.Collection", null)
.WithMany() .WithMany()
.HasForeignKey("CollectionId") .HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -462,13 +462,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameDeveloper", b => modelBuilder.Entity("GameDeveloper", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("DeveloperId") .HasForeignKey("DeveloperId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -477,13 +477,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameGenre", b => modelBuilder.Entity("GameGenre", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Genre", null) b.HasOne("LANCommander.Client.Data.Models.Genre", null)
.WithMany() .WithMany()
.HasForeignKey("GenresId") .HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -492,13 +492,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GamePublisher", b => modelBuilder.Entity("GamePublisher", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("PublisherId") .HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -507,13 +507,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameRedistributable", b => modelBuilder.Entity("GameRedistributable", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Redistributable", null) b.HasOne("LANCommander.Client.Data.Models.Redistributable", null)
.WithMany() .WithMany()
.HasForeignKey("RedistributableId") .HasForeignKey("RedistributableId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -522,36 +522,36 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameTag", b => modelBuilder.Entity("GameTag", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Tag", null) b.HasOne("LANCommander.Client.Data.Models.Tag", null)
.WithMany() .WithMany()
.HasForeignKey("TagsId") .HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", "Parent") b.HasOne("LANCommander.Client.Data.Models.Category", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
b.Navigation("Parent"); b.Navigation("Parent");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "BaseGame") b.HasOne("LANCommander.Client.Data.Models.Game", "BaseGame")
.WithMany("DependentGames") .WithMany("DependentGames")
.HasForeignKey("BaseGameId") .HasForeignKey("BaseGameId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Launcher.Data.Models.Engine", "Engine") b.HasOne("LANCommander.Client.Data.Models.Engine", "Engine")
.WithMany("Games") .WithMany("Games")
.HasForeignKey("EngineId") .HasForeignKey("EngineId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
@ -561,9 +561,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Engine"); b.Navigation("Engine");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("Media") .WithMany("Media")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -571,9 +571,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("MultiplayerModes") .WithMany("MultiplayerModes")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -582,17 +582,17 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Navigation("Children"); b.Navigation("Children");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Navigation("Games"); b.Navigation("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Navigation("DependentGames"); b.Navigation("DependentGames");

View file

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class InitialSeed : Migration public partial class InitialSeed : Migration

View file

@ -1,6 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LANCommander.Launcher.Data; using LANCommander.Client.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20240524000735_MultiplayerModeOptionalGame")] [Migration("20240524000735_MultiplayerModeOptionalGame")]
@ -129,7 +129,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("GameTag"); b.ToTable("GameTag");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -155,7 +155,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Categories"); b.ToTable("Categories");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Collection", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -176,7 +176,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Collections"); b.ToTable("Collections");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Company", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -197,7 +197,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Companies"); b.ToTable("Companies");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -218,7 +218,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Engines"); b.ToTable("Engines");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -279,7 +279,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Games"); b.ToTable("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Genre", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Genre", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -300,7 +300,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Genres"); b.ToTable("Genres");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -341,7 +341,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Media"); b.ToTable("Media");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -381,7 +381,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("MultiplayerModes"); b.ToTable("MultiplayerModes");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Redistributable", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -408,7 +408,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Redistributables"); b.ToTable("Redistributables");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Tag", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -431,13 +431,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CategoryGame", b => modelBuilder.Entity("CategoryGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", null) b.HasOne("LANCommander.Client.Data.Models.Category", null)
.WithMany() .WithMany()
.HasForeignKey("CategoriesId") .HasForeignKey("CategoriesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -446,13 +446,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CollectionGame", b => modelBuilder.Entity("CollectionGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Collection", null) b.HasOne("LANCommander.Client.Data.Models.Collection", null)
.WithMany() .WithMany()
.HasForeignKey("CollectionId") .HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -461,13 +461,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameDeveloper", b => modelBuilder.Entity("GameDeveloper", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("DeveloperId") .HasForeignKey("DeveloperId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -476,13 +476,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameGenre", b => modelBuilder.Entity("GameGenre", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Genre", null) b.HasOne("LANCommander.Client.Data.Models.Genre", null)
.WithMany() .WithMany()
.HasForeignKey("GenresId") .HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -491,13 +491,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GamePublisher", b => modelBuilder.Entity("GamePublisher", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("PublisherId") .HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -506,13 +506,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameRedistributable", b => modelBuilder.Entity("GameRedistributable", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Redistributable", null) b.HasOne("LANCommander.Client.Data.Models.Redistributable", null)
.WithMany() .WithMany()
.HasForeignKey("RedistributableId") .HasForeignKey("RedistributableId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -521,36 +521,36 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameTag", b => modelBuilder.Entity("GameTag", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Tag", null) b.HasOne("LANCommander.Client.Data.Models.Tag", null)
.WithMany() .WithMany()
.HasForeignKey("TagsId") .HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", "Parent") b.HasOne("LANCommander.Client.Data.Models.Category", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
b.Navigation("Parent"); b.Navigation("Parent");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "BaseGame") b.HasOne("LANCommander.Client.Data.Models.Game", "BaseGame")
.WithMany("DependentGames") .WithMany("DependentGames")
.HasForeignKey("BaseGameId") .HasForeignKey("BaseGameId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Launcher.Data.Models.Engine", "Engine") b.HasOne("LANCommander.Client.Data.Models.Engine", "Engine")
.WithMany("Games") .WithMany("Games")
.HasForeignKey("EngineId") .HasForeignKey("EngineId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
@ -560,9 +560,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Engine"); b.Navigation("Engine");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("Media") .WithMany("Media")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -570,9 +570,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("MultiplayerModes") .WithMany("MultiplayerModes")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -580,17 +580,17 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Navigation("Children"); b.Navigation("Children");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Navigation("Games"); b.Navigation("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Navigation("DependentGames"); b.Navigation("DependentGames");

View file

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class MultiplayerModeOptionalGame : Migration public partial class MultiplayerModeOptionalGame : Migration

View file

@ -1,6 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LANCommander.Launcher.Data; using LANCommander.Client.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20240525185852_AddGameLatestVersion")] [Migration("20240525185852_AddGameLatestVersion")]
@ -129,7 +129,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("GameTag"); b.ToTable("GameTag");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -155,7 +155,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Categories"); b.ToTable("Categories");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Collection", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -176,7 +176,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Collections"); b.ToTable("Collections");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Company", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -197,7 +197,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Companies"); b.ToTable("Companies");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -218,7 +218,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Engines"); b.ToTable("Engines");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -282,7 +282,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Games"); b.ToTable("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Genre", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Genre", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -303,7 +303,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Genres"); b.ToTable("Genres");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -344,7 +344,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Media"); b.ToTable("Media");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -384,7 +384,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("MultiplayerModes"); b.ToTable("MultiplayerModes");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Redistributable", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -411,7 +411,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Redistributables"); b.ToTable("Redistributables");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Tag", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -434,13 +434,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CategoryGame", b => modelBuilder.Entity("CategoryGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", null) b.HasOne("LANCommander.Client.Data.Models.Category", null)
.WithMany() .WithMany()
.HasForeignKey("CategoriesId") .HasForeignKey("CategoriesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -449,13 +449,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CollectionGame", b => modelBuilder.Entity("CollectionGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Collection", null) b.HasOne("LANCommander.Client.Data.Models.Collection", null)
.WithMany() .WithMany()
.HasForeignKey("CollectionId") .HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -464,13 +464,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameDeveloper", b => modelBuilder.Entity("GameDeveloper", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("DeveloperId") .HasForeignKey("DeveloperId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -479,13 +479,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameGenre", b => modelBuilder.Entity("GameGenre", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Genre", null) b.HasOne("LANCommander.Client.Data.Models.Genre", null)
.WithMany() .WithMany()
.HasForeignKey("GenresId") .HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -494,13 +494,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GamePublisher", b => modelBuilder.Entity("GamePublisher", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("PublisherId") .HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -509,13 +509,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameRedistributable", b => modelBuilder.Entity("GameRedistributable", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Redistributable", null) b.HasOne("LANCommander.Client.Data.Models.Redistributable", null)
.WithMany() .WithMany()
.HasForeignKey("RedistributableId") .HasForeignKey("RedistributableId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -524,36 +524,36 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameTag", b => modelBuilder.Entity("GameTag", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Tag", null) b.HasOne("LANCommander.Client.Data.Models.Tag", null)
.WithMany() .WithMany()
.HasForeignKey("TagsId") .HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", "Parent") b.HasOne("LANCommander.Client.Data.Models.Category", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
b.Navigation("Parent"); b.Navigation("Parent");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "BaseGame") b.HasOne("LANCommander.Client.Data.Models.Game", "BaseGame")
.WithMany("DependentGames") .WithMany("DependentGames")
.HasForeignKey("BaseGameId") .HasForeignKey("BaseGameId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Launcher.Data.Models.Engine", "Engine") b.HasOne("LANCommander.Client.Data.Models.Engine", "Engine")
.WithMany("Games") .WithMany("Games")
.HasForeignKey("EngineId") .HasForeignKey("EngineId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
@ -563,9 +563,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Engine"); b.Navigation("Engine");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("Media") .WithMany("Media")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -573,9 +573,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("MultiplayerModes") .WithMany("MultiplayerModes")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -583,17 +583,17 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Navigation("Children"); b.Navigation("Children");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Navigation("Games"); b.Navigation("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Navigation("DependentGames"); b.Navigation("DependentGames");

View file

@ -2,7 +2,7 @@
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class AddGameLatestVersion : Migration public partial class AddGameLatestVersion : Migration

View file

@ -1,6 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LANCommander.Launcher.Data; using LANCommander.Client.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20240603031059_AddLocalPlaySessions")] [Migration("20240603031059_AddLocalPlaySessions")]
@ -129,7 +129,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("GameTag"); b.ToTable("GameTag");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -155,7 +155,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Categories"); b.ToTable("Categories");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Collection", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -176,7 +176,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Collections"); b.ToTable("Collections");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Company", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -197,7 +197,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Companies"); b.ToTable("Companies");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -218,7 +218,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Engines"); b.ToTable("Engines");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -282,7 +282,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Games"); b.ToTable("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Genre", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Genre", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -303,7 +303,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Genres"); b.ToTable("Genres");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -344,7 +344,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Media"); b.ToTable("Media");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -384,7 +384,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("MultiplayerModes"); b.ToTable("MultiplayerModes");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b => modelBuilder.Entity("LANCommander.Client.Data.Models.PlaySession", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -415,7 +415,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("PlaySessions"); b.ToTable("PlaySessions");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Redistributable", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -442,7 +442,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Redistributables"); b.ToTable("Redistributables");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Tag", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -465,13 +465,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CategoryGame", b => modelBuilder.Entity("CategoryGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", null) b.HasOne("LANCommander.Client.Data.Models.Category", null)
.WithMany() .WithMany()
.HasForeignKey("CategoriesId") .HasForeignKey("CategoriesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -480,13 +480,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CollectionGame", b => modelBuilder.Entity("CollectionGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Collection", null) b.HasOne("LANCommander.Client.Data.Models.Collection", null)
.WithMany() .WithMany()
.HasForeignKey("CollectionId") .HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -495,13 +495,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameDeveloper", b => modelBuilder.Entity("GameDeveloper", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("DeveloperId") .HasForeignKey("DeveloperId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -510,13 +510,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameGenre", b => modelBuilder.Entity("GameGenre", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Genre", null) b.HasOne("LANCommander.Client.Data.Models.Genre", null)
.WithMany() .WithMany()
.HasForeignKey("GenresId") .HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -525,13 +525,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GamePublisher", b => modelBuilder.Entity("GamePublisher", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("PublisherId") .HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -540,13 +540,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameRedistributable", b => modelBuilder.Entity("GameRedistributable", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Redistributable", null) b.HasOne("LANCommander.Client.Data.Models.Redistributable", null)
.WithMany() .WithMany()
.HasForeignKey("RedistributableId") .HasForeignKey("RedistributableId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -555,36 +555,36 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameTag", b => modelBuilder.Entity("GameTag", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Tag", null) b.HasOne("LANCommander.Client.Data.Models.Tag", null)
.WithMany() .WithMany()
.HasForeignKey("TagsId") .HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", "Parent") b.HasOne("LANCommander.Client.Data.Models.Category", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
b.Navigation("Parent"); b.Navigation("Parent");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "BaseGame") b.HasOne("LANCommander.Client.Data.Models.Game", "BaseGame")
.WithMany("DependentGames") .WithMany("DependentGames")
.HasForeignKey("BaseGameId") .HasForeignKey("BaseGameId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Launcher.Data.Models.Engine", "Engine") b.HasOne("LANCommander.Client.Data.Models.Engine", "Engine")
.WithMany("Games") .WithMany("Games")
.HasForeignKey("EngineId") .HasForeignKey("EngineId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
@ -594,9 +594,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Engine"); b.Navigation("Engine");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("Media") .WithMany("Media")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -604,9 +604,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("MultiplayerModes") .WithMany("MultiplayerModes")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -614,26 +614,26 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b => modelBuilder.Entity("LANCommander.Client.Data.Models.PlaySession", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("PlaySessions") .WithMany("PlaySessions")
.HasForeignKey("GameId"); .HasForeignKey("GameId");
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Navigation("Children"); b.Navigation("Children");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Navigation("Games"); b.Navigation("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Navigation("DependentGames"); b.Navigation("DependentGames");

View file

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class AddLocalPlaySessions : Migration public partial class AddLocalPlaySessions : Migration

View file

@ -1,21 +1,18 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LANCommander.Launcher.Data; using LANCommander.Client.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable #nullable disable
namespace LANCommander.Launcher.Data.Migrations namespace LANCommander.Client.Data.Migrations
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20240620051957_AddMediaName")] partial class DatabaseContextModelSnapshot : ModelSnapshot
partial class AddMediaName
{ {
/// <inheritdoc /> protected override void BuildModel(ModelBuilder modelBuilder)
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
@ -129,7 +126,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("GameTag"); b.ToTable("GameTag");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -155,7 +152,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Categories"); b.ToTable("Categories");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Collection", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -176,7 +173,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Collections"); b.ToTable("Collections");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Company", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -197,7 +194,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Companies"); b.ToTable("Companies");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -218,7 +215,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Engines"); b.ToTable("Engines");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -282,7 +279,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Games"); b.ToTable("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Genre", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Genre", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -303,7 +300,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Genres"); b.ToTable("Genres");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -327,10 +324,6 @@ namespace LANCommander.Launcher.Data.Migrations
.HasMaxLength(255) .HasMaxLength(255)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("SourceUrl") b.Property<string>("SourceUrl")
.HasMaxLength(2048) .HasMaxLength(2048)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -348,7 +341,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Media"); b.ToTable("Media");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -388,7 +381,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("MultiplayerModes"); b.ToTable("MultiplayerModes");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b => modelBuilder.Entity("LANCommander.Client.Data.Models.PlaySession", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -419,7 +412,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("PlaySessions"); b.ToTable("PlaySessions");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Redistributable", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -446,7 +439,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.ToTable("Redistributables"); b.ToTable("Redistributables");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Tag", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -469,13 +462,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CategoryGame", b => modelBuilder.Entity("CategoryGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", null) b.HasOne("LANCommander.Client.Data.Models.Category", null)
.WithMany() .WithMany()
.HasForeignKey("CategoriesId") .HasForeignKey("CategoriesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -484,13 +477,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("CollectionGame", b => modelBuilder.Entity("CollectionGame", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Collection", null) b.HasOne("LANCommander.Client.Data.Models.Collection", null)
.WithMany() .WithMany()
.HasForeignKey("CollectionId") .HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -499,13 +492,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameDeveloper", b => modelBuilder.Entity("GameDeveloper", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("DeveloperId") .HasForeignKey("DeveloperId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -514,13 +507,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameGenre", b => modelBuilder.Entity("GameGenre", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Genre", null) b.HasOne("LANCommander.Client.Data.Models.Genre", null)
.WithMany() .WithMany()
.HasForeignKey("GenresId") .HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -529,13 +522,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GamePublisher", b => modelBuilder.Entity("GamePublisher", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Company", null) b.HasOne("LANCommander.Client.Data.Models.Company", null)
.WithMany() .WithMany()
.HasForeignKey("PublisherId") .HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -544,13 +537,13 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameRedistributable", b => modelBuilder.Entity("GameRedistributable", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Redistributable", null) b.HasOne("LANCommander.Client.Data.Models.Redistributable", null)
.WithMany() .WithMany()
.HasForeignKey("RedistributableId") .HasForeignKey("RedistributableId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -559,36 +552,36 @@ namespace LANCommander.Launcher.Data.Migrations
modelBuilder.Entity("GameTag", b => modelBuilder.Entity("GameTag", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", null) b.HasOne("LANCommander.Client.Data.Models.Game", null)
.WithMany() .WithMany()
.HasForeignKey("GamesId") .HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Tag", null) b.HasOne("LANCommander.Client.Data.Models.Tag", null)
.WithMany() .WithMany()
.HasForeignKey("TagsId") .HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Category", "Parent") b.HasOne("LANCommander.Client.Data.Models.Category", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
b.Navigation("Parent"); b.Navigation("Parent");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "BaseGame") b.HasOne("LANCommander.Client.Data.Models.Game", "BaseGame")
.WithMany("DependentGames") .WithMany("DependentGames")
.HasForeignKey("BaseGameId") .HasForeignKey("BaseGameId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Launcher.Data.Models.Engine", "Engine") b.HasOne("LANCommander.Client.Data.Models.Engine", "Engine")
.WithMany("Games") .WithMany("Games")
.HasForeignKey("EngineId") .HasForeignKey("EngineId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
@ -598,9 +591,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Engine"); b.Navigation("Engine");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Media", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("Media") .WithMany("Media")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -608,9 +601,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b => modelBuilder.Entity("LANCommander.Client.Data.Models.MultiplayerMode", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("MultiplayerModes") .WithMany("MultiplayerModes")
.HasForeignKey("GameId") .HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -618,26 +611,26 @@ namespace LANCommander.Launcher.Data.Migrations
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b => modelBuilder.Entity("LANCommander.Client.Data.Models.PlaySession", b =>
{ {
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game") b.HasOne("LANCommander.Client.Data.Models.Game", "Game")
.WithMany("PlaySessions") .WithMany("PlaySessions")
.HasForeignKey("GameId"); .HasForeignKey("GameId");
b.Navigation("Game"); b.Navigation("Game");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Category", b =>
{ {
b.Navigation("Children"); b.Navigation("Children");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Engine", b =>
{ {
b.Navigation("Games"); b.Navigation("Games");
}); });
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => modelBuilder.Entity("LANCommander.Client.Data.Models.Game", b =>
{ {
b.Navigation("DependentGames"); b.Navigation("DependentGames");

View file

@ -1,16 +1,12 @@
using LANCommander.SDK.Models; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace LANCommander.Launcher.Data.Models namespace LANCommander.Client.Data.Models
{ {
public abstract class BaseModel : IKeyedModel public abstract class BaseModel
{ {
[Key] [Key]
public Guid Id { get; set; } public Guid Id { get; set; }
[Display(Name = "Imported On")]
public DateTime ImportedOn { get; set; }
[Display(Name = "Created On")] [Display(Name = "Created On")]
public DateTime CreatedOn { get; set; } public DateTime CreatedOn { get; set; }

View file

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace LANCommander.Client.Data.Models
{
[Table("Categories")]
public class Category : BaseModel
{
public string Name { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
public virtual ICollection<Game> Games { get; set; }
}
}

View file

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

View file

@ -1,14 +1,14 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace LANCommander.Launcher.Data.Models namespace LANCommander.Client.Data.Models
{ {
public class Company : BaseModel public class Company : BaseModel
{ {
public string Name { get; set; } public string Name { get; set; }
[JsonIgnore] [JsonIgnore]
public virtual ICollection<Game> PublishedGames { get; set; } = new List<Game>(); public virtual ICollection<Game> PublishedGames { get; set; }
[JsonIgnore] [JsonIgnore]
public virtual ICollection<Game> DevelopedGames { get; set; } = new List<Game>(); public virtual ICollection<Game> DevelopedGames { get; set; }
} }
} }

View file

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace LANCommander.Client.Data.Models
{
[Table("Engines")]
public class Engine : BaseModel
{
public string Name { get; set; }
public virtual ICollection<Game> Games { get; set; }
}
}

View file

@ -1,13 +1,13 @@
using System.ComponentModel.DataAnnotations; using LANCommander.Client.Data.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using LANCommander.SDK.Enums;
namespace LANCommander.Launcher.Data.Models namespace LANCommander.Client.Data.Models
{ {
[Table("Games")] [Table("Games")]
public class Game : BaseModel public class Game : BaseModel
{ {
public virtual ICollection<GameExternalId>? ExternalIds { get; set; } = new List<GameExternalId>(); public long? IGDBId { get; set; }
public string Title { get; set; } public string Title { get; set; }
[Display(Name = "Sort Title")] [Display(Name = "Sort Title")]
public string? SortTitle { get; set; } public string? SortTitle { get; set; }
@ -18,7 +18,6 @@ namespace LANCommander.Launcher.Data.Models
public bool Installed { get; set; } public bool Installed { get; set; }
public string? InstallDirectory { get; set; } public string? InstallDirectory { get; set; }
public string? InstalledVersion { get; set; } public string? InstalledVersion { get; set; }
public DateTime? InstalledOn { get; set; }
public string? LatestVersion { get; set; } public string? LatestVersion { get; set; }
[Display(Name = "Released On")] [Display(Name = "Released On")]
@ -41,14 +40,10 @@ namespace LANCommander.Launcher.Data.Models
public virtual ICollection<Category>? Categories { get; set; } = new List<Category>(); public virtual ICollection<Category>? Categories { get; set; } = new List<Category>();
public virtual ICollection<Company>? Publishers { get; set; } = new List<Company>(); public virtual ICollection<Company>? Publishers { get; set; } = new List<Company>();
public virtual ICollection<Company>? Developers { get; set; } = new List<Company>(); public virtual ICollection<Company>? Developers { get; set; } = new List<Company>();
public virtual ICollection<Platform>? Platforms { get; set; } = new List<Platform>();
public virtual ICollection<Redistributable>? Redistributables { get; set; } = new List<Redistributable>(); public virtual ICollection<Redistributable>? Redistributables { get; set; } = new List<Redistributable>();
public virtual ICollection<Tool>? Tools { get; set; } = new List<Tool>();
public virtual ICollection<GameTool> GameTools { get; set; } = new List<GameTool>();
public virtual ICollection<Media>? Media { get; set; } = new List<Media>(); public virtual ICollection<Media>? Media { get; set; } = new List<Media>();
public virtual ICollection<Collection> Collections { get; set; } = new List<Collection>(); public virtual ICollection<Collection> Collections { get; set; } = new List<Collection>();
public virtual ICollection<Game> DependentGames { get; set; } = new List<Game>(); public virtual ICollection<Game> DependentGames { get; set; } = new List<Game>();
public virtual ICollection<PlaySession> PlaySessions { get; set; } = new List<PlaySession>(); public virtual ICollection<PlaySession> PlaySessions { get; set; } = new List<PlaySession>();
public virtual ICollection<Library> Libraries { get; set; } = new List<Library>();
} }
} }

View file

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

View file

@ -0,0 +1,29 @@
using LANCommander.Client.Data.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace LANCommander.Client.Data.Models
{
[Table("Media")]
public class Media : BaseModel
{
public Guid FileId { get; set; }
public MediaType Type { get; set; }
[MaxLength(2048)]
public string? SourceUrl { get; set; }
[MaxLength(255)]
public string? MimeType { get; set; }
[MaxLength(8)]
public string Crc32 { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(GameId))]
[InverseProperty("Media")]
public virtual Game? Game { get; set; }
}
}

View file

@ -1,8 +1,9 @@
using LANCommander.SDK.Enums; using LANCommander.Client.Data.Enums;
using LANCommander.SDK.Enums;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace LANCommander.Server.Data.Models namespace LANCommander.Client.Data.Models
{ {
[Table("MultiplayerModes")] [Table("MultiplayerModes")]
public class MultiplayerMode : BaseModel public class MultiplayerMode : BaseModel
@ -17,6 +18,6 @@ namespace LANCommander.Server.Data.Models
[JsonIgnore] [JsonIgnore]
[ForeignKey(nameof(GameId))] [ForeignKey(nameof(GameId))]
[InverseProperty("MultiplayerModes")] [InverseProperty("MultiplayerModes")]
public Game? Game { get; set; } public virtual Game? Game { get; set; }
} }
} }

View file

@ -2,7 +2,7 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace LANCommander.Launcher.Data.Models namespace LANCommander.Client.Data.Models
{ {
[Table("PlaySessions")] [Table("PlaySessions")]
public class PlaySession : BaseModel public class PlaySession : BaseModel

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace LANCommander.Launcher.Data.Models namespace LANCommander.Client.Data.Models
{ {
[Table("Redistributables")] [Table("Redistributables")]
public class Redistributable : BaseModel public class Redistributable : BaseModel
@ -8,6 +8,6 @@ namespace LANCommander.Launcher.Data.Models
public string Name { get; set; } public string Name { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public string? Notes { get; set; } public string? Notes { get; set; }
public virtual ICollection<Game>? Games { get; set; } = new List<Game>(); public virtual ICollection<Game>? Games { get; set; }
} }
} }

View file

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

View file

@ -0,0 +1,70 @@
using LANCommander.Client.Data.Models;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
namespace LANCommander.Client.Data
{
public class Repository<T> : IDisposable where T : BaseModel
{
private DbContext Context;
public Repository(DatabaseContext context)
{
Context = context;
}
private DbSet<T> DbSet
{
get { return Context.Set<T>(); }
}
public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
{
return DbSet.AsQueryable().Where(predicate);
}
public async Task<T> Find(Guid id)
{
return await DbSet.FindAsync(id);
}
public T FirstOrDefault(Expression<Func<T, bool>> predicate)
{
return Get(predicate).FirstOrDefault();
}
public async Task<T> Add(T entity)
{
entity.CreatedOn = DateTime.Now;
entity.UpdatedOn = DateTime.Now;
await Context.AddAsync(entity);
return entity;
}
public T Update(T entity)
{
entity.UpdatedOn = DateTime.Now;
Context.Update(entity);
return entity;
}
public void Delete(T entity)
{
Context.Remove(entity);
}
public async Task SaveChanges()
{
await Context.SaveChangesAsync();
}
public void Dispose()
{
}
}
}

View file

@ -0,0 +1,11 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(UI.MainLayout)" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(UI.MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Enums
{
public enum DownloadStatus
{
Idle,
Downloading,
InstallingRedistributables,
InstallingMods,
InstallingExpansions,
RunningScripts,
DownloadingSaves,
Canceled,
Failed,
Complete
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Extensions
{
public static class DateTimeExtensions
{
public static string ToRelativeDate(this DateTime dateTime)
{
var today = DateTime.Today;
var yesterday = today.AddDays(-1);
if (dateTime.Day == today.Day && dateTime.Month == today.Month && dateTime.Year == today.Year)
return "Today";
else if (dateTime.Day == yesterday.Day && dateTime.Month == yesterday.Month && dateTime.Year == yesterday.Year)
return "Yesterday";
else
return dateTime.ToString("MMMM d");
}
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LANCommander.Client.Extensions
{
internal static class IEnumerableExtensions
{
static Regex TitleComparisonExpression = new Regex(@"^(?:a|the|an)\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
internal static IOrderedEnumerable<T> OrderByTitle<T>(this IEnumerable<T> items, Func<T, string> keySelector)
{
return items.OrderBy(i =>
{
var key = keySelector.Invoke(i);
return TitleComparisonExpression.Replace(key, "");
});
}
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Extensions
{
public static class TimeSpanExtensions
{
public static string ToShortTime(this TimeSpan timeSpan)
{
if (timeSpan.TotalHours >= 1)
return timeSpan.ToString("hh\\:mm\\:ss");
else
return timeSpan.ToString("mm\\:ss");
}
}
}

File diff suppressed because it is too large Load diff

View file

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

View file

@ -1,15 +1,15 @@
using LANCommander.Launcher.Data.Models; using LANCommander.Client.Data.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LANCommander.Launcher.Models namespace LANCommander.Client.Models
{ {
public class ActionSelectorDialogOptions public class ActionSelectorDialogOptions
{ {
public IEnumerable<SDK.Models.Manifest.Action> Actions { get; set; } public IEnumerable<SDK.Models.Action> Actions { get; set; }
public string Title { get; set; } public string Title { get; set; }
public Game Game { get; set; } public Game Game { get; set; }
} }

View file

@ -0,0 +1,71 @@
using LANCommander.Client.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Models
{
internal class DownloadQueueGame : IDownloadQueueItem
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Version { get; set; }
public Guid CoverId { get; set; }
public Guid IconId { get; set; }
public DateTime QueuedOn { get; set; }
public DateTime? CompletedOn { get; set; }
public bool IsUpdate { get; set; }
public bool State {
get
{
switch (Status)
{
case DownloadStatus.Downloading:
case DownloadStatus.InstallingRedistributables:
case DownloadStatus.InstallingMods:
case DownloadStatus.InstallingExpansions:
case DownloadStatus.RunningScripts:
case DownloadStatus.DownloadingSaves:
return true;
default:
return false;
}
}
}
public DownloadStatus Status { get; set; }
public SDK.Models.Game Game { get; set; }
public float Progress {
get
{
return BytesDownloaded / (float)TotalBytes;
}
set { }
}
public double TransferSpeed { get; set; }
public long BytesDownloaded { get; set; }
public long TotalBytes { get; set; }
public DownloadQueueGame(SDK.Models.Game game)
{
Game = game;
Id = game.Id;
Title = game.Title;
Version = game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault()?.Version;
QueuedOn = DateTime.Now;
Status = DownloadStatus.Idle;
var cover = game.Media.FirstOrDefault(m => m.Type == SDK.Enums.MediaType.Cover);
if (cover != null)
CoverId = cover.Id;
var icon = game.Media.FirstOrDefault(m => m.Type == SDK.Enums.MediaType.Icon);
if (icon != null)
IconId = icon.Id;
}
}
}

View file

@ -0,0 +1,54 @@
using LANCommander.Client.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Models
{
public class DownloadQueueRedistributable : IDownloadQueueItem
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Version { get; set; }
public Guid CoverId { get; set; }
public Guid IconId { get; set; }
public DateTime QueuedOn { get; set; }
public DateTime? CompletedOn { get; set; }
public bool IsUpdate { get; set; }
public bool State
{
get
{
switch (Status)
{
case DownloadStatus.Downloading:
case DownloadStatus.InstallingRedistributables:
case DownloadStatus.InstallingMods:
case DownloadStatus.InstallingExpansions:
case DownloadStatus.RunningScripts:
case DownloadStatus.DownloadingSaves:
return true;
default:
return false;
}
}
}
public DownloadStatus Status { get; set; }
public SDK.Models.Redistributable Redistributable { get; set; }
public float Progress { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public double TransferSpeed { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public long BytesDownloaded { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public long TotalBytes { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public DownloadQueueRedistributable(SDK.Models.Redistributable redistributable)
{
Redistributable = redistributable;
Id = redistributable.Id;
Title = redistributable.Name;
Version = redistributable.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault()?.Version;
}
}
}

View file

@ -1,6 +1,6 @@
using LANCommander.Launcher.Data.Models; using LANCommander.Client.Data.Models;
namespace LANCommander.Launcher.Models namespace LANCommander.Client.Models
{ {
public class ExistingEntityResult<T> where T : BaseModel public class ExistingEntityResult<T> where T : BaseModel
{ {

View file

@ -1,29 +1,27 @@
using LANCommander.SDK.Enums; using LANCommander.Client.Enums;
using LANCommander.SDK.Models; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Launcher.Models namespace LANCommander.Client.Models
{ {
public interface IInstallQueueItem public interface IDownloadQueueItem
{ {
Guid Id { get; set; } Guid Id { get; set; }
string Title { get; set; } string Title { get; set; }
string Version { get; set; } string Version { get; set; }
string InstallDirectory { get; set; }
Guid CoverId { get; set; } Guid CoverId { get; set; }
Guid IconId { get; set; } Guid IconId { get; set; }
DateTime QueuedOn { get; set; } DateTime QueuedOn { get; set; }
DateTime? CompletedOn { get; set; } DateTime? CompletedOn { get; set; }
bool IsUpdate { get; set; } bool IsUpdate { get; set; }
bool State { get; } bool State { get; }
InstallStatus Status { get; set; } DownloadStatus Status { get; set; }
float Progress { get; set; } float Progress { get; set; }
double TransferSpeed { get; set; } double TransferSpeed { get; set; }
long BytesDownloaded { get; set; } long BytesDownloaded { get; set; }
long TotalBytes { get; set; } long TotalBytes { get; set; }
CancellationTokenSource CancellationToken { get; set; }
InstallPlanItemType ItemType { get; }
Guid? DependsOnId { get; set; }
List<InstallTaskDefinition> Tasks { get; set; }
Guid? CurrentTaskId { get; set; }
} }
} }

View file

@ -0,0 +1,76 @@
using LANCommander.Client.Data.Models;
using LANCommander.Client.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Models
{
public enum LibraryItemState
{
NotInstalled,
Installed,
Queued,
Installing,
UpdateAvailable
}
public enum LibraryItemType
{
Game,
Redistributable
}
public class LibraryItem
{
public Guid Key { get; set; }
public Guid IconId { get; set; }
public LibraryItemType Type { get; set; }
public LibraryItemState State { get; set; }
public string Name { get; set; }
public string SortName { get; set; }
public object DataItem { get; set; }
public ObservableCollection<LibraryItem> Children { get; set; }
public LibraryItem(Collection collection)
{
Key = collection.Id;
Type = LibraryItemType.Game;
Name = collection.Name;
DataItem = collection;
Children = new ObservableCollection<LibraryItem>(collection.Games.Select(g => new LibraryItem(g)).OrderByTitle(g => g.SortName ?? g.Name).ToList());
}
public LibraryItem(Game game)
{
Key = game.Id;
Type = LibraryItemType.Game;
Name = game.Title;
SortName = game.SortTitle;
DataItem = game;
if (game.Installed && !String.IsNullOrWhiteSpace(game.LatestVersion) && game.InstalledVersion != game.LatestVersion)
State = LibraryItemState.UpdateAvailable;
else if (game.Installed)
State = LibraryItemState.Installed;
else
State = LibraryItemState.NotInstalled;
var icon = game.Media.FirstOrDefault(m => m.Type == Data.Enums.MediaType.Icon);
if (icon != null)
IconId = icon.Id;
}
public LibraryItem(Redistributable redistributable)
{
Key = redistributable.Id;
Type = LibraryItemType.Redistributable;
Name = redistributable.Name;
DataItem = redistributable;
}
}
}

View file

@ -0,0 +1,55 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Models
{
public class Settings
{
public DatabaseSettings Database { get; set; } = new DatabaseSettings();
public AuthenticationSettings Authentication { get; set; } = new AuthenticationSettings();
public GameSettings Games { get; set; } = new GameSettings();
public MediaSettings Media { get; set; } = new MediaSettings();
public ProfileSettings Profile { get; set; } = new ProfileSettings();
public DebugSettings Debug { get; set; } = new DebugSettings();
}
public class DatabaseSettings
{
public string ConnectionString { get; set; } = "Data Source=LANCommander.db;Cache=Shared";
}
public class AuthenticationSettings
{
public string ServerAddress { get; set; } = "";
public string AccessToken { get; set; } = "";
public string RefreshToken { get; set; } = "";
}
public class GameSettings
{
public string DefaultInstallDirectory { get; set; } = "C:\\Games";
}
public class MediaSettings
{
public string StoragePath { get; set; } = "Media";
}
public class ProfileSettings
{
public Guid Id { get; set; }
public string Alias { get; set; }
public string Avatar { get; set; }
}
public class DebugSettings
{
public bool EnableScriptDebugging { get; set; } = false;
public LogLevel LoggingLevel { get; set; } = LogLevel.Warning;
public string LoggingPath { get; set; } = "Logs";
}
}

View file

@ -0,0 +1,151 @@
using LANCommander.Client.Data;
using LANCommander.Client.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using Photino.Blazor;
using Photino.Blazor.CustomWindow.Extensions;
using Photino.NET;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
namespace LANCommander.Client
{
class Program
{
[STAThread]
static void Main(string[] args)
{
var settings = SettingService.GetSettings();
var builder = PhotinoBlazorAppBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddLogging();
builder.Services.AddCustomWindow();
builder.Services.AddAntDesign();
builder.Services.AddDbContext<DbContext, DatabaseContext>();
NLog.GlobalDiagnosticsContext.Set("StoragePath", settings.Debug.LoggingPath);
builder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.SetMinimumLevel(settings.Debug.LoggingLevel);
loggingBuilder.AddNLog();
});
#region Register Client
var client = new SDK.Client(settings.Authentication.ServerAddress, settings.Games.DefaultInstallDirectory);
client.UseToken(new SDK.Models.AuthToken
{
AccessToken = settings.Authentication.AccessToken,
RefreshToken = settings.Authentication.RefreshToken,
});
builder.Services.AddSingleton(client);
#endregion
#region Register Services
builder.Services.AddScoped<CollectionService>();
builder.Services.AddScoped<CompanyService>();
builder.Services.AddScoped<EngineService>();
builder.Services.AddScoped<GameService>();
builder.Services.AddScoped<GenreService>();
builder.Services.AddScoped<MultiplayerModeService>();
builder.Services.AddScoped<TagService>();
builder.Services.AddScoped<MediaService>();
builder.Services.AddScoped<ProfileService>();
builder.Services.AddScoped<PlaySessionService>();
builder.Services.AddScoped<RedistributableService>();
builder.Services.AddScoped<SaveService>();
builder.Services.AddScoped<ImportService>();
builder.Services.AddScoped<LibraryService>();
builder.Services.AddScoped<DownloadService>();
#endregion
var app = builder.Build();
app.MainWindow
.SetTitle("LANCommander")
.SetUseOsDefaultLocation(true)
.SetChromeless(true)
.SetResizable(true)
.RegisterCustomSchemeHandler("media", (object sender, string scheme, string url, out string contentType) =>
{
var uri = new Uri(url);
var query = HttpUtility.ParseQueryString(uri.Query);
var filePath = Path.Combine(MediaService.GetStoragePath(), uri.Host);
contentType = query["mime"];
return new FileStream(filePath, FileMode.Open, FileAccess.Read);
})
.RegisterWebMessageReceivedHandler(async (object sender, string message) =>
{
using var scope = app.Services.CreateScope();
var importService = scope.ServiceProvider.GetService<ImportService>();
var window = (PhotinoWindow)sender;
switch (message)
{
case "import":
await importService.ImportAsync();
window.SendWebMessage("importComplete");
break;
}
});
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
{
app.MainWindow.ShowMessage("Fatal exception", error.ExceptionObject.ToString());
};
#region Scaffold Required Directories
string[] requiredDirectories = new string[]
{
"Backups",
"Media"
};
foreach (var directory in requiredDirectories)
{
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, directory);
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
#endregion
#region Migrate Database
using var scope = app.Services.CreateScope();
using var db = scope.ServiceProvider.GetService<DatabaseContext>();
if (db.Database.GetPendingMigrations().Any())
{
var backupPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Backups");
var dataSource = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LANCommander.db");
var backupName = Path.Combine(backupPath, $"LANCommander.db.{DateTime.Now.ToString("dd-MM-yyyy-HH.mm.ss.bak")}");
if (File.Exists(dataSource))
{
File.Copy(dataSource, backupName);
}
db.Database.Migrate();
}
#endregion
app.Run();
}
}
}

View file

@ -0,0 +1,7 @@
{
"profiles": {
"HelloPhotinoApp": {
"commandName": "Project"
}
}
}

View file

@ -0,0 +1,17 @@
using LANCommander.Client.Data;
using LANCommander.Client.Data.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Services
{
public class CollectionService : BaseDatabaseService<Collection>
{
public CollectionService(DatabaseContext dbContext) : base(dbContext)
{
}
}
}

View file

@ -0,0 +1,17 @@
using LANCommander.Client.Data;
using LANCommander.Client.Data.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Services
{
public class CompanyService : BaseDatabaseService<Company>
{
public CompanyService(DatabaseContext dbContext) : base(dbContext)
{
}
}
}

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