updated workshop
This commit is contained in:
parent
0885bfef92
commit
8857f441e7
1 changed files with 673 additions and 680 deletions
|
|
@ -32,9 +32,7 @@ class SteamWorkshopService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all homes visible to the given user.
|
* @return array<int,array<string,mixed>>
|
||||||
*
|
|
||||||
* @return array<int, array<string, mixed>>
|
|
||||||
*/
|
*/
|
||||||
public function listHomesForUser(int $userId, bool $isAdmin): array
|
public function listHomesForUser(int $userId, bool $isAdmin): array
|
||||||
{
|
{
|
||||||
|
|
@ -48,9 +46,6 @@ class SteamWorkshopService
|
||||||
return array_values($homes);
|
return array_values($homes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a single home, ensuring the user is allowed to see it.
|
|
||||||
*/
|
|
||||||
public function getHome(int $homeId, int $userId, bool $isAdmin): ?array
|
public function getHome(int $homeId, int $userId, bool $isAdmin): ?array
|
||||||
{
|
{
|
||||||
$home = $isAdmin
|
$home = $isAdmin
|
||||||
|
|
@ -60,24 +55,9 @@ class SteamWorkshopService
|
||||||
return is_array($home) ? $home : null;
|
return is_array($home) ? $home : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array{
|
|
||||||
* workshop_enabled: bool,
|
|
||||||
* adapter_key: string,
|
|
||||||
* update_interval_minutes: int,
|
|
||||||
* staging_dir: string,
|
|
||||||
* install_strategy: string,
|
|
||||||
* on_update_action: string,
|
|
||||||
* post_install_script: string,
|
|
||||||
* workshop_items: array<int, array<string, mixed>>,
|
|
||||||
* raw_definition: string,
|
|
||||||
* last_saved_at: int|null
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
public function loadConfig(int $homeId): array
|
public function loadConfig(int $homeId): array
|
||||||
{
|
{
|
||||||
$path = $this->getConfigPath($homeId);
|
$path = $this->getConfigPath($homeId);
|
||||||
|
|
||||||
if (!is_file($path)) {
|
if (!is_file($path)) {
|
||||||
return $this->defaultConfig();
|
return $this->defaultConfig();
|
||||||
}
|
}
|
||||||
|
|
@ -96,9 +76,7 @@ class SteamWorkshopService
|
||||||
$config['on_update_action'] = (string)($xml->onUpdateAction ?? $config['on_update_action']);
|
$config['on_update_action'] = (string)($xml->onUpdateAction ?? $config['on_update_action']);
|
||||||
$config['post_install_script'] = trim((string)($xml->postInstallScript ?? ''));
|
$config['post_install_script'] = trim((string)($xml->postInstallScript ?? ''));
|
||||||
$config['raw_definition'] = (string)($xml->rawDefinition ?? '');
|
$config['raw_definition'] = (string)($xml->rawDefinition ?? '');
|
||||||
$config['last_saved_at'] = isset($xml->timestamps->savedAt)
|
$config['last_saved_at'] = isset($xml->timestamps->savedAt) ? (int)$xml->timestamps->savedAt : null;
|
||||||
? (int)$xml->timestamps->savedAt
|
|
||||||
: null;
|
|
||||||
|
|
||||||
$mods = [];
|
$mods = [];
|
||||||
if (isset($xml->mods)) {
|
if (isset($xml->mods)) {
|
||||||
|
|
@ -121,6 +99,7 @@ class SteamWorkshopService
|
||||||
{
|
{
|
||||||
$path = $this->getConfigPath($homeId);
|
$path = $this->getConfigPath($homeId);
|
||||||
$config = $this->normalizeConfig($config);
|
$config = $this->normalizeConfig($config);
|
||||||
|
|
||||||
$doc = new DOMDocument('1.0', 'UTF-8');
|
$doc = new DOMDocument('1.0', 'UTF-8');
|
||||||
$doc->formatOutput = true;
|
$doc->formatOutput = true;
|
||||||
|
|
||||||
|
|
@ -157,62 +136,48 @@ class SteamWorkshopService
|
||||||
|
|
||||||
$doc->save($path);
|
$doc->save($path);
|
||||||
}
|
}
|
||||||
if ($gameKey === '') {
|
|
||||||
throw new RuntimeException('Game key is required.');
|
public function buildConfigFromRequest(array $payload): array
|
||||||
|
{
|
||||||
|
$input = $payload['workshop'] ?? [];
|
||||||
|
$rawMods = trim((string)($input['raw_items'] ?? ''));
|
||||||
|
$items = $this->parseWorkshopItems($rawMods);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'workshop_enabled' => isset($input['workshop_enabled']) ? (bool)$input['workshop_enabled'] : false,
|
||||||
|
'adapter_key' => $this->sanitizeAdapterKey((string)($input['adapter_key'] ?? 'dayz')),
|
||||||
|
'update_interval_minutes' => $this->sanitizeInterval(isset($input['update_interval_minutes']) ? (int)$input['update_interval_minutes'] : null),
|
||||||
|
'staging_dir' => trim((string)($input['staging_dir'] ?? '')),
|
||||||
|
'install_strategy' => $this->sanitizeInstallStrategy((string)($input['install_strategy'] ?? 'copy')),
|
||||||
|
'on_update_action' => $this->sanitizeUpdateAction((string)($input['on_update_action'] ?? 'queue_for_restart')),
|
||||||
|
'post_install_script' => trim((string)($input['post_install_script'] ?? '')),
|
||||||
|
'workshop_items' => $items,
|
||||||
|
'raw_definition' => $rawMods,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$normalized = $this->normalizeAdapterData($gameKey, $data);
|
public function parseWorkshopItems(string $raw): array
|
||||||
if ($normalized['steam_app_id'] === '') {
|
{
|
||||||
throw new RuntimeException('Steam App ID is required.');
|
if ($raw === '') {
|
||||||
}
|
return [];
|
||||||
if ($normalized['mods_dir'] === '') {
|
|
||||||
throw new RuntimeException('Mods directory is required.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$doc = new DOMDocument('1.0', 'UTF-8');
|
$items = [];
|
||||||
$doc->formatOutput = true;
|
|
||||||
|
|
||||||
$root = $doc->createElement('adapter');
|
|
||||||
$root->setAttribute('key', $gameKey);
|
|
||||||
$root->setAttribute('name', $normalized['name']);
|
|
||||||
$doc->appendChild($root);
|
|
||||||
|
|
||||||
$root->appendChild($doc->createElement('steamAppId', $normalized['steam_app_id']));
|
|
||||||
$root->appendChild($doc->createElement('modsDir', $normalized['mods_dir']));
|
|
||||||
if ($normalized['keys_dir'] !== '') {
|
|
||||||
$root->appendChild($doc->createElement('keysDir', $normalized['keys_dir']));
|
|
||||||
}
|
|
||||||
$root->appendChild($doc->createElement('supportsHotReload', $normalized['supports_hot_reload'] ? 'true' : 'false'));
|
|
||||||
|
|
||||||
$activationNode = $doc->createElement('activation');
|
|
||||||
$templateNode = $doc->createElement('template');
|
|
||||||
if ($normalized['activation_template'] !== '') {
|
|
||||||
$templateNode->appendChild($doc->createCDATASection($normalized['activation_template']));
|
|
||||||
}
|
|
||||||
$activationNode->appendChild($templateNode);
|
|
||||||
$root->appendChild($activationNode);
|
|
||||||
|
|
||||||
if ($normalized['notes'] !== '') {
|
|
||||||
$root->appendChild($doc->createElement('notes', $normalized['notes']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $this->getGameAdapterPath($gameKey);
|
|
||||||
$doc->save($path);
|
|
||||||
$lines = preg_split('/\r\n|\r|\n/', $raw);
|
$lines = preg_split('/\r\n|\r|\n/', $raw);
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
$line = trim($line);
|
$line = trim($line);
|
||||||
if ($line === '') {
|
if ($line === '') {
|
||||||
continue;
|
continue;
|
||||||
if ($gameKey === '') {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $this->getGameAdapterPath($gameKey);
|
$parts = array_map('trim', explode(',', $line, 2));
|
||||||
if (!is_file($path)) {
|
$id = preg_replace('/[^0-9]/', '', $parts[0]);
|
||||||
return false;
|
if ($id === '') {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
$label = $parts[1] ?? '';
|
||||||
return unlink($path);
|
if ($label === '') {
|
||||||
|
$label = '@' . $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$items[] = [
|
$items[] = [
|
||||||
|
|
@ -226,21 +191,13 @@ class SteamWorkshopService
|
||||||
return $items;
|
return $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a SteamCMD command array for a single workshop item.
|
|
||||||
*/
|
|
||||||
public function buildSteamCmdArgs(array $config, string $workshopId, ?string $login = null): array
|
public function buildSteamCmdArgs(array $config, string $workshopId, ?string $login = null): array
|
||||||
{
|
{
|
||||||
$loginUser = $login !== null && $login !== '' ? $login : 'anonymous';
|
$loginUser = $login !== null && $login !== '' ? $login : 'anonymous';
|
||||||
$adapter = $this->getAdapterByKey($config['adapter_key'] ?? '');
|
$adapter = $this->getAdapterByKey($config['adapter_key'] ?? '');
|
||||||
$appId = $adapter['steam_app_id'] ?? ($config['steam_app_id'] ?? '');
|
$appId = $adapter['steam_app_id'] ?? ($config['steam_app_id'] ?? '');
|
||||||
|
|
||||||
return [
|
return ['+login', $loginUser, '+workshop_download_item', $appId, $workshopId, 'validate'];
|
||||||
'+login', $loginUser,
|
|
||||||
'+workshop_download_item', $appId,
|
|
||||||
$workshopId,
|
|
||||||
'validate',
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAdapterOptions(): array
|
public function getAdapterOptions(): array
|
||||||
|
|
@ -257,17 +214,11 @@ class SteamWorkshopService
|
||||||
return $options;
|
return $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load adapter metadata for UI and validation.
|
|
||||||
*
|
|
||||||
* @return array<int, array<string, mixed>>
|
|
||||||
*/
|
|
||||||
public function loadAdapters(): array
|
public function loadAdapters(): array
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
$schema = $this->adapterDir . '/schema.xsd';
|
$schema = $this->adapterDir . '/schema.xsd';
|
||||||
$useSchema = is_file($schema);
|
$useSchema = is_file($schema);
|
||||||
$previousLibxml = libxml_use_internal_errors(true);
|
|
||||||
|
|
||||||
foreach (glob($this->adapterDir . '/*.xml') as $file) {
|
foreach (glob($this->adapterDir . '/*.xml') as $file) {
|
||||||
if (substr($file, -4) !== '.xml' || basename($file) === 'schema.xsd') {
|
if (substr($file, -4) !== '.xml' || basename($file) === 'schema.xsd') {
|
||||||
|
|
@ -294,8 +245,6 @@ class SteamWorkshopService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
libxml_use_internal_errors($previousLibxml);
|
|
||||||
|
|
||||||
return array_values(array_filter($result, static function (array $adapter): bool {
|
return array_values(array_filter($result, static function (array $adapter): bool {
|
||||||
return $adapter['key'] !== '';
|
return $adapter['key'] !== '';
|
||||||
}));
|
}));
|
||||||
|
|
@ -312,9 +261,6 @@ class SteamWorkshopService
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return adapter key chosen for the given game key, or null if unmapped.
|
|
||||||
*/
|
|
||||||
public function getAdapterKeyForGame(string $gameKey): ?string
|
public function getAdapterKeyForGame(string $gameKey): ?string
|
||||||
{
|
{
|
||||||
$gameKey = trim($gameKey);
|
$gameKey = trim($gameKey);
|
||||||
|
|
@ -334,9 +280,6 @@ class SteamWorkshopService
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Persist adapter mappings (game_key => adapter_key).
|
|
||||||
*/
|
|
||||||
public function saveAdapterMappings(array $mappings): void
|
public function saveAdapterMappings(array $mappings): void
|
||||||
{
|
{
|
||||||
$sanitized = [];
|
$sanitized = [];
|
||||||
|
|
@ -380,9 +323,6 @@ class SteamWorkshopService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string,string>
|
|
||||||
*/
|
|
||||||
public function getAdapterMappings(): array
|
public function getAdapterMappings(): array
|
||||||
{
|
{
|
||||||
if (!is_file($this->adapterMapFile)) {
|
if (!is_file($this->adapterMapFile)) {
|
||||||
|
|
@ -406,11 +346,6 @@ class SteamWorkshopService
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return metadata for every custom adapter stored on disk.
|
|
||||||
*
|
|
||||||
* @return array<int,array<string,mixed>>
|
|
||||||
*/
|
|
||||||
public function listGameAdapters(): array
|
public function listGameAdapters(): array
|
||||||
{
|
{
|
||||||
$adapters = [];
|
$adapters = [];
|
||||||
|
|
@ -501,11 +436,67 @@ class SteamWorkshopService
|
||||||
return $defaults;
|
return $defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function saveGameAdapter(string $gameKey, array $data): void
|
||||||
* Discover available game keys from server config XMLs.
|
{
|
||||||
*
|
$gameKey = $this->sanitizeGameKey($gameKey);
|
||||||
* @return array<int,string>
|
if ($gameKey === '') {
|
||||||
*/
|
throw new RuntimeException('Game key is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalized = $this->normalizeAdapterData($gameKey, $data);
|
||||||
|
if ($normalized['steam_app_id'] === '') {
|
||||||
|
throw new RuntimeException('Steam App ID is required.');
|
||||||
|
}
|
||||||
|
if ($normalized['mods_dir'] === '') {
|
||||||
|
throw new RuntimeException('Mods directory is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$doc = new DOMDocument('1.0', 'UTF-8');
|
||||||
|
$doc->formatOutput = true;
|
||||||
|
|
||||||
|
$root = $doc->createElement('adapter');
|
||||||
|
$root->setAttribute('key', $gameKey);
|
||||||
|
$root->setAttribute('name', $normalized['name']);
|
||||||
|
$doc->appendChild($root);
|
||||||
|
|
||||||
|
$root->appendChild($doc->createElement('steamAppId', $normalized['steam_app_id']));
|
||||||
|
$root->appendChild($doc->createElement('modsDir', $normalized['mods_dir']));
|
||||||
|
if ($normalized['keys_dir'] !== '') {
|
||||||
|
$root->appendChild($doc->createElement('keysDir', $normalized['keys_dir']));
|
||||||
|
}
|
||||||
|
$root->appendChild($doc->createElement('supportsHotReload', $normalized['supports_hot_reload'] ? 'true' : 'false'));
|
||||||
|
|
||||||
|
$activationNode = $doc->createElement('activation');
|
||||||
|
$templateNode = $doc->createElement('template');
|
||||||
|
if ($normalized['activation_template'] !== '') {
|
||||||
|
$templateNode->appendChild($doc->createCDATASection($normalized['activation_template']));
|
||||||
|
}
|
||||||
|
$activationNode->appendChild($templateNode);
|
||||||
|
$root->appendChild($activationNode);
|
||||||
|
|
||||||
|
if ($normalized['notes'] !== '') {
|
||||||
|
$root->appendChild($doc->createElement('notes', $normalized['notes']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $this->getGameAdapterPath($gameKey);
|
||||||
|
$doc->save($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteGameAdapter(string $gameKey): bool
|
||||||
|
{
|
||||||
|
$gameKey = $this->sanitizeGameKey($gameKey);
|
||||||
|
if ($gameKey === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $this->getGameAdapterPath($gameKey);
|
||||||
|
if (!is_file($path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
public function listAvailableGameKeys(): array
|
public function listAvailableGameKeys(): array
|
||||||
{
|
{
|
||||||
$keys = [];
|
$keys = [];
|
||||||
|
|
@ -592,7 +583,8 @@ class SteamWorkshopService
|
||||||
private function sanitizeGameKey(string $gameKey): string
|
private function sanitizeGameKey(string $gameKey): string
|
||||||
{
|
{
|
||||||
$gameKey = strtolower(trim($gameKey));
|
$gameKey = strtolower(trim($gameKey));
|
||||||
return preg_replace('/[^a-z0-9_\-.]/', '', $gameKey);
|
$sanitized = preg_replace('/[^a-z0-9_\-.]/', '', $gameKey);
|
||||||
|
return is_string($sanitized) ? $sanitized : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeAdapterData(string $gameKey, array $data): array
|
private function normalizeAdapterData(string $gameKey, array $data): array
|
||||||
|
|
@ -632,6 +624,7 @@ class SteamWorkshopService
|
||||||
|
|
||||||
$key = $forcedKey ?? (string)($adapter['key'] ?? '');
|
$key = $forcedKey ?? (string)($adapter['key'] ?? '');
|
||||||
if ($key === '') {
|
if ($key === '') {
|
||||||
|
libxml_use_internal_errors($previous);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue