reverted the new updates
This commit is contained in:
parent
9c04036ac0
commit
49600d1cfd
5 changed files with 532 additions and 748 deletions
|
|
@ -1,417 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* Game-agnostic Steam Workshop support for GSP
|
||||
* This library centralizes config/state handling so UI pages can stay thin.
|
||||
*/
|
||||
|
||||
if (!defined('IN_OGP')) {
|
||||
exit('Direct access not permitted');
|
||||
}
|
||||
|
||||
class WorkshopConfigStore
|
||||
{
|
||||
private $file;
|
||||
|
||||
public function __construct($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
$dir = dirname($file);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
$data = $this->readFile();
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
public function get($homeId)
|
||||
{
|
||||
$all = $this->all();
|
||||
return isset($all[$homeId]) ? $all[$homeId] : null;
|
||||
}
|
||||
|
||||
public function put($homeId, array $config)
|
||||
{
|
||||
$all = $this->all();
|
||||
$all[$homeId] = $config;
|
||||
$this->writeFile($all);
|
||||
}
|
||||
|
||||
public function delete($homeId)
|
||||
{
|
||||
$all = $this->all();
|
||||
if (isset($all[$homeId])) {
|
||||
unset($all[$homeId]);
|
||||
$this->writeFile($all);
|
||||
}
|
||||
}
|
||||
|
||||
private function readFile()
|
||||
{
|
||||
if (!is_file($this->file)) {
|
||||
return [];
|
||||
}
|
||||
$fh = fopen($this->file, 'c+');
|
||||
if ($fh === false) {
|
||||
return [];
|
||||
}
|
||||
flock($fh, LOCK_SH);
|
||||
$raw = stream_get_contents($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
$data = json_decode($raw, true);
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
private function writeFile(array $data)
|
||||
{
|
||||
$fh = fopen($this->file, 'c+');
|
||||
if ($fh === false) {
|
||||
return;
|
||||
}
|
||||
flock($fh, LOCK_EX);
|
||||
ftruncate($fh, 0);
|
||||
rewind($fh);
|
||||
fwrite($fh, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
fflush($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkshopStateStore
|
||||
{
|
||||
private $dir;
|
||||
|
||||
public function __construct($dir)
|
||||
{
|
||||
$this->dir = rtrim($dir, '/');
|
||||
if (!is_dir($this->dir)) {
|
||||
mkdir($this->dir, 0775, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function get($homeId)
|
||||
{
|
||||
$path = $this->statePath($homeId);
|
||||
if (!is_file($path)) {
|
||||
return ['items' => [], 'last_sync' => null, 'last_status' => null];
|
||||
}
|
||||
$raw = file_get_contents($path);
|
||||
$data = json_decode($raw, true);
|
||||
if (!is_array($data)) {
|
||||
return ['items' => [], 'last_sync' => null, 'last_status' => null];
|
||||
}
|
||||
if (!isset($data['items'])) {
|
||||
$data['items'] = [];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function put($homeId, array $state)
|
||||
{
|
||||
$path = $this->statePath($homeId);
|
||||
$payload = json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
file_put_contents($path, $payload);
|
||||
}
|
||||
|
||||
public function updateItem($homeId, $itemId, array $itemState)
|
||||
{
|
||||
$state = $this->get($homeId);
|
||||
$state['items'][$itemId] = $itemState;
|
||||
$this->put($homeId, $state);
|
||||
}
|
||||
|
||||
private function statePath($homeId)
|
||||
{
|
||||
return $this->dir . '/state_' . $homeId . '.json';
|
||||
}
|
||||
}
|
||||
|
||||
class WorkshopLock
|
||||
{
|
||||
private $fh;
|
||||
|
||||
public function __construct($lockPath)
|
||||
{
|
||||
$dir = dirname($lockPath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
$this->fh = fopen($lockPath, 'c');
|
||||
}
|
||||
|
||||
public function acquire()
|
||||
{
|
||||
if (!$this->fh) {
|
||||
return false;
|
||||
}
|
||||
return flock($this->fh, LOCK_EX | LOCK_NB);
|
||||
}
|
||||
|
||||
public function release()
|
||||
{
|
||||
if ($this->fh) {
|
||||
flock($this->fh, LOCK_UN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WorkshopResolver
|
||||
{
|
||||
public function resolveItems(array $config)
|
||||
{
|
||||
$items = array_map('trim', isset($config['workshop_item_ids']) ? (array)$config['workshop_item_ids'] : []);
|
||||
$collections = array_map('trim', isset($config['collection_ids']) ? (array)$config['collection_ids'] : []);
|
||||
|
||||
if (!empty($collections)) {
|
||||
foreach ($collections as $collectionId) {
|
||||
$expanded = $this->expandCollection($collectionId);
|
||||
$items = array_merge($items, $expanded);
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_values(array_unique(array_filter($items, 'strlen')));
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function expandCollection($collectionId)
|
||||
{
|
||||
$payload = http_build_query([
|
||||
'collectioncount' => 1,
|
||||
'publishedfileids[0]' => $collectionId,
|
||||
]);
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded",
|
||||
'content' => $payload,
|
||||
'timeout' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$json = @file_get_contents('https://api.steampowered.com/ISteamRemoteStorage/GetCollectionDetails/v1/', false, $context);
|
||||
if ($json === false) {
|
||||
return [];
|
||||
}
|
||||
$data = json_decode($json, true);
|
||||
if (!isset($data['response']['collectiondetails'][0]['children'])) {
|
||||
return [];
|
||||
}
|
||||
$children = $data['response']['collectiondetails'][0]['children'];
|
||||
$ids = [];
|
||||
foreach ($children as $child) {
|
||||
if (isset($child['publishedfileid'])) {
|
||||
$ids[] = $child['publishedfileid'];
|
||||
}
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public function fetchItemDetails(array $itemIds)
|
||||
{
|
||||
if (empty($itemIds)) {
|
||||
return [];
|
||||
}
|
||||
$payload = ['itemcount' => count($itemIds)];
|
||||
$index = 0;
|
||||
foreach ($itemIds as $id) {
|
||||
$payload['publishedfileids[' . $index . ']'] = $id;
|
||||
$index++;
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded",
|
||||
'content' => http_build_query($payload),
|
||||
'timeout' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$json = @file_get_contents('https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/', false, $context);
|
||||
if ($json === false) {
|
||||
return [];
|
||||
}
|
||||
$data = json_decode($json, true);
|
||||
if (!isset($data['response']['publishedfiledetails'])) {
|
||||
return [];
|
||||
}
|
||||
$details = [];
|
||||
foreach ($data['response']['publishedfiledetails'] as $item) {
|
||||
$details[$item['publishedfileid']] = $item;
|
||||
}
|
||||
return $details;
|
||||
}
|
||||
}
|
||||
|
||||
class WorkshopSyncResult
|
||||
{
|
||||
public $success;
|
||||
public $message;
|
||||
public $updatedItems;
|
||||
public $skippedItems;
|
||||
|
||||
public function __construct($success, $message, array $updatedItems = [], array $skippedItems = [])
|
||||
{
|
||||
$this->success = $success;
|
||||
$this->message = $message;
|
||||
$this->updatedItems = $updatedItems;
|
||||
$this->skippedItems = $skippedItems;
|
||||
}
|
||||
}
|
||||
|
||||
class WorkshopSyncService
|
||||
{
|
||||
private $remote;
|
||||
private $homeCfg;
|
||||
private $configStore;
|
||||
private $stateStore;
|
||||
private $resolver;
|
||||
|
||||
public function __construct($remote, array $homeCfg, WorkshopConfigStore $configStore, WorkshopStateStore $stateStore, WorkshopResolver $resolver)
|
||||
{
|
||||
$this->remote = $remote;
|
||||
$this->homeCfg = $homeCfg;
|
||||
$this->configStore = $configStore;
|
||||
$this->stateStore = $stateStore;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function sync($homeId)
|
||||
{
|
||||
$config = $this->configStore->get($homeId);
|
||||
if (!$config) {
|
||||
return new WorkshopSyncResult(false, 'No workshop configuration found for this server.');
|
||||
}
|
||||
|
||||
$itemIds = $this->resolver->resolveItems($config);
|
||||
if (empty($itemIds)) {
|
||||
return new WorkshopSyncResult(true, 'No workshop items configured.', [], []);
|
||||
}
|
||||
|
||||
$details = $this->resolver->fetchItemDetails($itemIds);
|
||||
$state = $this->stateStore->get($homeId);
|
||||
$needsUpdate = [];
|
||||
$skipped = [];
|
||||
|
||||
foreach ($itemIds as $id) {
|
||||
$remoteUpdated = isset($details[$id]['time_updated']) ? (int)$details[$id]['time_updated'] : null;
|
||||
$local = isset($state['items'][$id]) ? $state['items'][$id] : null;
|
||||
$localSeen = $local && isset($local['last_seen_manifest_id']) ? (int)$local['last_seen_manifest_id'] : null;
|
||||
if ($remoteUpdated !== null && $localSeen !== null && $remoteUpdated <= $localSeen) {
|
||||
$skipped[] = $id;
|
||||
continue;
|
||||
}
|
||||
$needsUpdate[] = $id;
|
||||
}
|
||||
|
||||
if (empty($needsUpdate)) {
|
||||
$state['last_sync'] = time();
|
||||
$state['last_status'] = 'up-to-date';
|
||||
$this->stateStore->put($homeId, $state);
|
||||
return new WorkshopSyncResult(true, 'All workshop items are up-to-date.', [], $skipped);
|
||||
}
|
||||
|
||||
$modsFullPath = $this->modsPath($config);
|
||||
$paths = $this->buildPaths($config);
|
||||
$modNamesList = implode(',', $needsUpdate);
|
||||
|
||||
$result = $this->remote->steam_workshop(
|
||||
$homeId,
|
||||
$modsFullPath,
|
||||
$config['workshop_app_id'],
|
||||
implode(',', $needsUpdate),
|
||||
$config['regex'],
|
||||
(int)$config['mods_backreference_index'],
|
||||
$config['variable'],
|
||||
$config['place_after'],
|
||||
$config['mod_string'],
|
||||
$config['string_separator'],
|
||||
$paths['config_file_path'],
|
||||
$config['post_install'],
|
||||
$modNamesList,
|
||||
$config['anonymous_login'] ? '1' : '0',
|
||||
$config['steam_username'],
|
||||
$config['steam_password'],
|
||||
$config['download_method'],
|
||||
'',
|
||||
''
|
||||
);
|
||||
|
||||
if ($result !== 1) {
|
||||
return new WorkshopSyncResult(false, 'Workshop sync failed to start (agent error code ' . $result . ').');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
foreach ($needsUpdate as $id) {
|
||||
$state['items'][$id] = [
|
||||
'last_seen_manifest_id' => isset($details[$id]['time_updated']) ? (int)$details[$id]['time_updated'] : $now,
|
||||
'last_downloaded_at' => $now,
|
||||
'local_path' => $modsFullPath,
|
||||
'deploy_path' => $paths['deploy_destination'],
|
||||
'last_error' => null,
|
||||
];
|
||||
}
|
||||
$state['last_sync'] = $now;
|
||||
$state['last_status'] = 'started';
|
||||
$this->stateStore->put($homeId, $state);
|
||||
|
||||
return new WorkshopSyncResult(true, 'Workshop sync started.', $needsUpdate, $skipped);
|
||||
}
|
||||
|
||||
private function modsPath(array $config)
|
||||
{
|
||||
if (!empty($config['staging_path'])) {
|
||||
return clean_path($config['staging_path']);
|
||||
}
|
||||
return clean_path($this->homeCfg['home_path'] . '/workshop');
|
||||
}
|
||||
|
||||
private function buildPaths(array $config)
|
||||
{
|
||||
$serverRoot = rtrim($this->homeCfg['home_path'], '/');
|
||||
$deployDest = !empty($config['deploy_destination']) ? clean_path($config['deploy_destination']) : ($serverRoot . '/mods');
|
||||
$configPath = !empty($config['filepath']) ? clean_path($config['filepath']) : ($serverRoot . '/config.cfg');
|
||||
return [
|
||||
'deploy_destination' => $deployDest,
|
||||
'config_file_path' => $configPath,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function workshop_build_default_config($homeCfg, $modXml, $settings)
|
||||
{
|
||||
$modsPath = ($modXml && isset($modXml->mods_path)) ? (string)$modXml->mods_path : 'mods';
|
||||
$configNode = $modXml && isset($modXml->config) ? $modXml->config : null;
|
||||
|
||||
return [
|
||||
'workshop_app_id' => ($modXml && isset($modXml->workshop_id)) ? (string)$modXml->workshop_id : '',
|
||||
'download_method' => ($modXml && isset($modXml->download_method)) ? (string)$modXml->download_method : 'steamcmd',
|
||||
'deploy_mode' => 'copy',
|
||||
'deploy_destination' => clean_path($homeCfg['home_path'] . '/' . $modsPath),
|
||||
'staging_path' => clean_path($homeCfg['home_path'] . '/' . $modsPath),
|
||||
'mods_path' => clean_path($homeCfg['home_path'] . '/' . $modsPath),
|
||||
'regex' => ($configNode && isset($configNode->regex)) ? (string)$configNode->regex : '',
|
||||
'mods_backreference_index' => ($configNode && isset($configNode->mods_backreference_index)) ? (int)$configNode->mods_backreference_index : 1,
|
||||
'variable' => ($configNode && isset($configNode->variable)) ? (string)$configNode->variable : '',
|
||||
'place_after' => ($configNode && isset($configNode->place_after)) ? (string)$configNode->place_after : '',
|
||||
'mod_string' => ($configNode && isset($configNode->mod_string)) ? (string)$configNode->mod_string : '%workshop_mod_id%',
|
||||
'string_separator' => ($configNode && isset($configNode->string_separator)) ? (string)$configNode->string_separator : ';',
|
||||
'filepath' => ($configNode && isset($configNode->filepath)) ? clean_path($homeCfg['home_path'] . '/' . $configNode->filepath) : '',
|
||||
'post_install' => ($modXml && isset($modXml->post_install)) ? (string)$modXml->post_install : '',
|
||||
'uninstall' => ($modXml && isset($modXml->uninstall)) ? (string)$modXml->uninstall : '',
|
||||
'anonymous_login' => ($modXml && isset($modXml->anonymous_login)) ? ((string)$modXml->anonymous_login === '1') : true,
|
||||
'steam_username' => isset($settings['steam_user']) ? $settings['steam_user'] : '',
|
||||
'steam_password' => isset($settings['steam_pass']) ? $settings['steam_pass'] : '',
|
||||
'workshop_item_ids' => [],
|
||||
'collection_ids' => [],
|
||||
'check_on_start' => true,
|
||||
'periodic_check_minutes' => null,
|
||||
'apply_updates' => 'on_start_only',
|
||||
];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue