Upload mirror
This commit is contained in:
1 parent
d0038e50d7
commit
3636ecd7cd
10 files changed
+1700
No files matched your search
@@ -0,0 +1,97 @@
|
||||
#if defined _updater_included
|
||||
#endinput
|
||||
#endif
|
||||
#define _updater_included
|
||||
|
||||
/**
|
||||
* Adds your plugin to the updater. The URL will be updated if
|
||||
* your plugin was previously added.
|
||||
*
|
||||
* @param url URL to your plugin's update file.
|
||||
* @noreturn
|
||||
*/
|
||||
native void Updater_AddPlugin(const char[] url);
|
||||
|
||||
/**
|
||||
* Removes your plugin from the updater. This does not need to
|
||||
* be called during OnPluginEnd.
|
||||
*
|
||||
* @noreturn
|
||||
*/
|
||||
native void Updater_RemovePlugin();
|
||||
|
||||
/**
|
||||
* Forces your plugin to be checked for updates. The behaviour
|
||||
* of the update is dependant on the server's configuration.
|
||||
*
|
||||
* @return True if an update was triggered. False otherwise.
|
||||
* @error Plugin not found in updater.
|
||||
*/
|
||||
native bool Updater_ForceUpdate();
|
||||
|
||||
/**
|
||||
* Called when your plugin is about to be checked for updates.
|
||||
*
|
||||
* @return Plugin_Handled to prevent checking, Plugin_Continue to allow it.
|
||||
*/
|
||||
forward Action Updater_OnPluginChecking();
|
||||
|
||||
/**
|
||||
* Called when your plugin is about to begin downloading an available update.
|
||||
*
|
||||
* @return Plugin_Handled to prevent downloading, Plugin_Continue to allow it.
|
||||
*/
|
||||
forward Action Updater_OnPluginDownloading();
|
||||
|
||||
/**
|
||||
* Called when your plugin's update files have been fully downloaded
|
||||
* and are about to write to their proper location. This should be used
|
||||
* to free read-only resources that require write access for your update.
|
||||
*
|
||||
* @note OnPluginUpdated will be called later during the same frame.
|
||||
*
|
||||
* @noreturn
|
||||
*/
|
||||
forward void Updater_OnPluginUpdating();
|
||||
|
||||
/**
|
||||
* Called when your plugin's update has been completed. It is safe
|
||||
* to reload your plugin at this time.
|
||||
*
|
||||
* @noreturn
|
||||
*/
|
||||
forward void Updater_OnPluginUpdated();
|
||||
|
||||
/**
|
||||
* @brief Reloads a plugin.
|
||||
*
|
||||
* @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin).
|
||||
* @noreturn
|
||||
*/
|
||||
stock void ReloadPlugin(Handle plugin=null)
|
||||
{
|
||||
char filename[64];
|
||||
GetPluginFilename(plugin, filename, sizeof(filename));
|
||||
ServerCommand("sm plugins reload %s", filename);
|
||||
}
|
||||
|
||||
|
||||
public SharedPlugin __pl_updater =
|
||||
{
|
||||
name = "updater",
|
||||
file = "updater.smx",
|
||||
#if defined REQUIRE_PLUGIN
|
||||
required = 1,
|
||||
#else
|
||||
required = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
#if !defined REQUIRE_PLUGIN
|
||||
public void __pl_updater_SetNTVOptional()
|
||||
{
|
||||
MarkNativeAsOptional("Updater_AddPlugin");
|
||||
MarkNativeAsOptional("Updater_RemovePlugin");
|
||||
MarkNativeAsOptional("Updater_ForceUpdate");
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,316 @@
|
||||
#pragma semicolon 1
|
||||
#pragma newdecls required
|
||||
|
||||
/* SM Includes */
|
||||
#include <sourcemod>
|
||||
#undef REQUIRE_EXTENSIONS
|
||||
#include <cURL>
|
||||
#include <socket>
|
||||
#include <steamtools>
|
||||
#include <SteamWorks>
|
||||
#define REQUIRE_EXTENSIONS
|
||||
|
||||
/* Plugin Info */
|
||||
#define PLUGIN_NAME "Updater"
|
||||
#define PLUGIN_VERSION "1.2.2"
|
||||
|
||||
public Plugin myinfo =
|
||||
{
|
||||
name = PLUGIN_NAME,
|
||||
author = "GoD-Tony",
|
||||
description = "Automatically updates SourceMod plugins and files",
|
||||
version = PLUGIN_VERSION,
|
||||
url = "http://forums.alliedmods.net/showthread.php?t=169095"
|
||||
};
|
||||
|
||||
/* Globals */
|
||||
//#define DEBUG // This will enable verbose logging. Useful for developers testing their updates.
|
||||
|
||||
#define CURL_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "curl_easy_init") == FeatureStatus_Available)
|
||||
#define SOCKET_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "SocketCreate") == FeatureStatus_Available)
|
||||
#define STEAMTOOLS_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "Steam_CreateHTTPRequest") == FeatureStatus_Available)
|
||||
#define STEAMWORKS_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "SteamWorks_WriteHTTPResponseBodyToFile") == FeatureStatus_Available)
|
||||
|
||||
#define EXTENSION_ERROR "This plugin requires one of the cURL, Socket, SteamTools, or SteamWorks extensions to function."
|
||||
#define TEMP_FILE_EXT "temp" // All files are downloaded with this extension first.
|
||||
#define MAX_URL_LENGTH 256
|
||||
|
||||
#define UPDATE_URL "http://godtony.mooo.com/updater/updater.txt"
|
||||
|
||||
enum UpdateStatus {
|
||||
Status_Idle,
|
||||
Status_Checking, // Checking for updates.
|
||||
Status_Downloading, // Downloading an update.
|
||||
Status_Updated, // Update is complete.
|
||||
Status_Error, // An error occured while downloading.
|
||||
};
|
||||
|
||||
bool g_bGetDownload;
|
||||
bool g_bGetSource;
|
||||
|
||||
Handle g_hPluginPacks = INVALID_HANDLE;
|
||||
Handle g_hDownloadQueue = INVALID_HANDLE;
|
||||
Handle g_hRemoveQueue = INVALID_HANDLE;
|
||||
bool g_bDownloading = false;
|
||||
|
||||
static Handle _hUpdateTimer = INVALID_HANDLE;
|
||||
static float _fLastUpdate = 0.0;
|
||||
static char _sDataPath[PLATFORM_MAX_PATH];
|
||||
|
||||
/* Core Includes */
|
||||
#include "updater/plugins.sp"
|
||||
#include "updater/filesys.sp"
|
||||
#include "updater/download.sp"
|
||||
#include "updater/api.sp"
|
||||
|
||||
/* Plugin Functions */
|
||||
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
|
||||
{
|
||||
// cURL
|
||||
MarkNativeAsOptional("curl_OpenFile");
|
||||
MarkNativeAsOptional("curl_slist");
|
||||
MarkNativeAsOptional("curl_slist_append");
|
||||
MarkNativeAsOptional("curl_easy_init");
|
||||
MarkNativeAsOptional("curl_easy_setopt_int_array");
|
||||
MarkNativeAsOptional("curl_easy_setopt_handle");
|
||||
MarkNativeAsOptional("curl_easy_setopt_string");
|
||||
MarkNativeAsOptional("curl_easy_perform_thread");
|
||||
MarkNativeAsOptional("curl_easy_strerror");
|
||||
|
||||
// Socket
|
||||
MarkNativeAsOptional("SocketCreate");
|
||||
MarkNativeAsOptional("SocketSetArg");
|
||||
MarkNativeAsOptional("SocketSetOption");
|
||||
MarkNativeAsOptional("SocketConnect");
|
||||
MarkNativeAsOptional("SocketSend");
|
||||
|
||||
// SteamTools
|
||||
MarkNativeAsOptional("Steam_CreateHTTPRequest");
|
||||
MarkNativeAsOptional("Steam_SetHTTPRequestHeaderValue");
|
||||
MarkNativeAsOptional("Steam_SendHTTPRequest");
|
||||
MarkNativeAsOptional("Steam_WriteHTTPResponseBody");
|
||||
MarkNativeAsOptional("Steam_ReleaseHTTPRequest");
|
||||
|
||||
API_Init();
|
||||
RegPluginLibrary("updater");
|
||||
|
||||
return APLRes_Success;
|
||||
}
|
||||
|
||||
public void OnPluginStart()
|
||||
{
|
||||
if (!CURL_AVAILABLE() && !SOCKET_AVAILABLE() && !STEAMTOOLS_AVAILABLE() && !STEAMWORKS_AVAILABLE())
|
||||
{
|
||||
SetFailState(EXTENSION_ERROR);
|
||||
}
|
||||
|
||||
LoadTranslations("common.phrases");
|
||||
|
||||
// Convars.
|
||||
Handle hCvar = INVALID_HANDLE;
|
||||
|
||||
hCvar = CreateConVar("sm_updater_version", PLUGIN_VERSION, PLUGIN_NAME, 0|FCVAR_NOTIFY|FCVAR_DONTRECORD);
|
||||
OnVersionChanged(hCvar, "", "");
|
||||
HookConVarChange(hCvar, OnVersionChanged);
|
||||
|
||||
hCvar = CreateConVar("sm_updater", "2", "Determines update functionality. (1 = Notify, 2 = Download, 3 = Include source code)", 0, true, 1.0, true, 3.0);
|
||||
OnSettingsChanged(hCvar, "", "");
|
||||
HookConVarChange(hCvar, OnSettingsChanged);
|
||||
|
||||
// Commands.
|
||||
RegAdminCmd("sm_updater_check", Command_Check, ADMFLAG_RCON, "Forces Updater to check for updates.");
|
||||
RegAdminCmd("sm_updater_status", Command_Status, ADMFLAG_RCON, "View the status of Updater.");
|
||||
|
||||
// Initialize arrays.
|
||||
g_hPluginPacks = CreateArray();
|
||||
g_hDownloadQueue = CreateArray();
|
||||
g_hRemoveQueue = CreateArray();
|
||||
|
||||
// Temp path for checking update files.
|
||||
BuildPath(Path_SM, _sDataPath, sizeof(_sDataPath), "data/updater.txt");
|
||||
|
||||
#if !defined DEBUG
|
||||
// Add this plugin to the autoupdater.
|
||||
Updater_AddPlugin(GetMyHandle(), UPDATE_URL);
|
||||
#endif
|
||||
|
||||
// Check for updates every 24 hours.
|
||||
_hUpdateTimer = CreateTimer(86400.0, Timer_CheckUpdates, _, TIMER_REPEAT);
|
||||
}
|
||||
|
||||
public void OnAllPluginsLoaded()
|
||||
{
|
||||
// Check for updates on startup.
|
||||
TriggerTimer(_hUpdateTimer, true);
|
||||
}
|
||||
|
||||
public Action Timer_CheckUpdates(Handle timer)
|
||||
{
|
||||
Updater_FreeMemory();
|
||||
|
||||
// Update everything!
|
||||
int maxPlugins = GetMaxPlugins();
|
||||
for (int i = 0; i < maxPlugins; i++)
|
||||
{
|
||||
if (Updater_GetStatus(i) == Status_Idle)
|
||||
{
|
||||
Updater_Check(i);
|
||||
}
|
||||
}
|
||||
|
||||
_fLastUpdate = GetTickedTime();
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
public Action Command_Check(int client, int args)
|
||||
{
|
||||
float fNextUpdate = _fLastUpdate + 3600.0;
|
||||
|
||||
if (fNextUpdate > GetTickedTime())
|
||||
{
|
||||
ReplyToCommand(client, "[Updater] Updates can only be checked once per hour. %.1f minutes remaining.", (fNextUpdate - GetTickedTime()) / 60.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplyToCommand(client, "[Updater] Checking for updates.");
|
||||
TriggerTimer(_hUpdateTimer, true);
|
||||
}
|
||||
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Command_Status(int client, int args)
|
||||
{
|
||||
char sFilename[64];
|
||||
Handle hPlugin = INVALID_HANDLE;
|
||||
int maxPlugins = GetMaxPlugins();
|
||||
|
||||
ReplyToCommand(client, "[Updater] -- Status Begin --");
|
||||
ReplyToCommand(client, "Plugins being monitored for updates:");
|
||||
|
||||
for (int i = 0; i < maxPlugins; i++)
|
||||
{
|
||||
hPlugin = IndexToPlugin(i);
|
||||
|
||||
if (IsValidPlugin(hPlugin))
|
||||
{
|
||||
GetPluginFilename(hPlugin, sFilename, sizeof(sFilename));
|
||||
ReplyToCommand(client, " [%i] %s", i, sFilename);
|
||||
}
|
||||
}
|
||||
|
||||
ReplyToCommand(client, "Last update check was %.1f minutes ago.", (GetTickedTime() - _fLastUpdate) / 60.0);
|
||||
ReplyToCommand(client, "[Updater] --- Status End ---");
|
||||
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public void OnVersionChanged(Handle convar, const char[] oldValue, const char[] newValue)
|
||||
{
|
||||
if (!StrEqual(newValue, PLUGIN_VERSION))
|
||||
{
|
||||
SetConVarString(convar, PLUGIN_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSettingsChanged(Handle convar, const char[] oldValue, const char[] newValue)
|
||||
{
|
||||
switch (GetConVarInt(convar))
|
||||
{
|
||||
case 1: // Notify only.
|
||||
{
|
||||
g_bGetDownload = false;
|
||||
g_bGetSource = false;
|
||||
}
|
||||
|
||||
case 2: // Download updates.
|
||||
{
|
||||
g_bGetDownload = true;
|
||||
g_bGetSource = false;
|
||||
}
|
||||
|
||||
case 3: // Download with source code.
|
||||
{
|
||||
g_bGetDownload = true;
|
||||
g_bGetSource = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined DEBUG
|
||||
public void Updater_OnPluginUpdated()
|
||||
{
|
||||
Updater_Log("Reloading Updater plugin... updates will resume automatically.");
|
||||
|
||||
// Reload this plugin.
|
||||
char filename[64];
|
||||
GetPluginFilename(INVALID_HANDLE, filename, sizeof(filename));
|
||||
ServerCommand("sm plugins reload %s", filename);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Updater_Check(int index)
|
||||
{
|
||||
if (Fwd_OnPluginChecking(IndexToPlugin(index)) == Plugin_Continue)
|
||||
{
|
||||
char url[MAX_URL_LENGTH];
|
||||
Updater_GetURL(index, url, sizeof(url));
|
||||
Updater_SetStatus(index, Status_Checking);
|
||||
AddToDownloadQueue(index, url, _sDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
void Updater_FreeMemory()
|
||||
{
|
||||
// Make sure that no threads are active.
|
||||
if (g_bDownloading || GetArraySize(g_hDownloadQueue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all queued plugins.
|
||||
int index;
|
||||
int maxPlugins = GetArraySize(g_hRemoveQueue);
|
||||
for (int i = 0; i < maxPlugins; i++)
|
||||
{
|
||||
index = PluginToIndex(GetArrayCell(g_hRemoveQueue, i));
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
Updater_RemovePlugin(index);
|
||||
}
|
||||
}
|
||||
|
||||
ClearArray(g_hRemoveQueue);
|
||||
|
||||
// Remove plugins that have been unloaded.
|
||||
for (int i = 0; i < GetMaxPlugins(); i++)
|
||||
{
|
||||
if (!IsValidPlugin(IndexToPlugin(i)))
|
||||
{
|
||||
Updater_RemovePlugin(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Updater_Log(const char[] format, any:...)
|
||||
{
|
||||
char buffer[256];
|
||||
char path[PLATFORM_MAX_PATH];
|
||||
VFormat(buffer, sizeof(buffer), format, 2);
|
||||
BuildPath(Path_SM, path, sizeof(path), "logs/Updater.log");
|
||||
LogToFileEx(path, "%s", buffer);
|
||||
}
|
||||
|
||||
#if defined DEBUG
|
||||
int Updater_DebugLog(const char[] format, any:...)
|
||||
{
|
||||
char buffer[256];
|
||||
char path[PLATFORM_MAX_PATH];
|
||||
VFormat(buffer, sizeof(buffer), format, 2);
|
||||
BuildPath(Path_SM, path, sizeof(path), "logs/Updater_Debug.log");
|
||||
LogToFileEx(path, "%s", buffer);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
|
||||
/* API - Natives & Forwards */
|
||||
|
||||
static Handle fwd_OnPluginChecking = INVALID_HANDLE;
|
||||
static Handle fwd_OnPluginDownloading = INVALID_HANDLE;
|
||||
static Handle fwd_OnPluginUpdating = INVALID_HANDLE;
|
||||
static Handle fwd_OnPluginUpdated = INVALID_HANDLE;
|
||||
|
||||
void API_Init()
|
||||
{
|
||||
CreateNative("Updater_AddPlugin", Native_AddPlugin);
|
||||
CreateNative("Updater_RemovePlugin", Native_RemovePlugin);
|
||||
CreateNative("Updater_ForceUpdate", Native_ForceUpdate);
|
||||
|
||||
fwd_OnPluginChecking = CreateForward(ET_Event);
|
||||
fwd_OnPluginDownloading = CreateForward(ET_Event);
|
||||
fwd_OnPluginUpdating = CreateForward(ET_Ignore);
|
||||
fwd_OnPluginUpdated = CreateForward(ET_Ignore);
|
||||
}
|
||||
|
||||
// native Updater_AddPlugin(const char[] url);
|
||||
public int Native_AddPlugin(Handle plugin, int numParams)
|
||||
{
|
||||
char url[MAX_URL_LENGTH];
|
||||
GetNativeString(1, url, sizeof(url));
|
||||
|
||||
Updater_AddPlugin(plugin, url);
|
||||
}
|
||||
|
||||
// native Updater_RemovePlugin();
|
||||
public int Native_RemovePlugin(Handle plugin, int numParams)
|
||||
{
|
||||
int index = PluginToIndex(plugin);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
Updater_QueueRemovePlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// native bool Updater_ForceUpdate();
|
||||
public int Native_ForceUpdate(Handle plugin, int numParams)
|
||||
{
|
||||
int index = PluginToIndex(plugin);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
ThrowNativeError(SP_ERROR_NOT_FOUND, "Plugin not found in updater.");
|
||||
}
|
||||
else if (Updater_GetStatus(index) == Status_Idle)
|
||||
{
|
||||
Updater_Check(index);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// forward Action:Updater_OnPluginChecking();
|
||||
Action Fwd_OnPluginChecking(Handle plugin)
|
||||
{
|
||||
Action result = Plugin_Continue;
|
||||
Function func = GetFunctionByName(plugin, "Updater_OnPluginChecking");
|
||||
|
||||
if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginChecking, plugin, func))
|
||||
{
|
||||
Call_StartForward(fwd_OnPluginChecking);
|
||||
Call_Finish(result);
|
||||
RemoveAllFromForward(fwd_OnPluginChecking, plugin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// forward Action:Updater_OnPluginDownloading();
|
||||
Action Fwd_OnPluginDownloading(Handle plugin)
|
||||
{
|
||||
Action result = Plugin_Continue;
|
||||
Function func = GetFunctionByName(plugin, "Updater_OnPluginDownloading");
|
||||
|
||||
if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginDownloading, plugin, func))
|
||||
{
|
||||
Call_StartForward(fwd_OnPluginDownloading);
|
||||
Call_Finish(result);
|
||||
RemoveAllFromForward(fwd_OnPluginDownloading, plugin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// forward Updater_OnPluginUpdating();
|
||||
void Fwd_OnPluginUpdating(Handle plugin)
|
||||
{
|
||||
Function func = GetFunctionByName(plugin, "Updater_OnPluginUpdating");
|
||||
|
||||
if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginUpdating, plugin, func))
|
||||
{
|
||||
Call_StartForward(fwd_OnPluginUpdating);
|
||||
Call_Finish();
|
||||
RemoveAllFromForward(fwd_OnPluginUpdating, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// forward Updater_OnPluginUpdated();
|
||||
void Fwd_OnPluginUpdated(Handle plugin)
|
||||
{
|
||||
Function func = GetFunctionByName(plugin, "Updater_OnPluginUpdated");
|
||||
|
||||
if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginUpdated, plugin, func))
|
||||
{
|
||||
Call_StartForward(fwd_OnPluginUpdated);
|
||||
Call_Finish();
|
||||
RemoveAllFromForward(fwd_OnPluginUpdated, plugin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
|
||||
/* Download Manager */
|
||||
|
||||
#include "updater/download_curl.sp"
|
||||
#include "updater/download_socket.sp"
|
||||
#include "updater/download_steamtools.sp"
|
||||
#include "updater/download_steamworks.sp"
|
||||
|
||||
DataPackPos QueuePack_URL;
|
||||
|
||||
void FinalizeDownload(int index)
|
||||
{
|
||||
/* Strip the temporary file extension from downloaded files. */
|
||||
char newpath[PLATFORM_MAX_PATH];
|
||||
char oldpath[PLATFORM_MAX_PATH];
|
||||
Handle hFiles = Updater_GetFiles(index);
|
||||
|
||||
int maxFiles = GetArraySize(hFiles);
|
||||
for (int i = 0; i < maxFiles; i++)
|
||||
{
|
||||
GetArrayString(hFiles, i, newpath, sizeof(newpath));
|
||||
Format(oldpath, sizeof(oldpath), "%s.%s", newpath, TEMP_FILE_EXT);
|
||||
|
||||
// Rename doesn't overwrite on Windows. Make sure the path is clear.
|
||||
if (FileExists(newpath))
|
||||
{
|
||||
DeleteFile(newpath);
|
||||
}
|
||||
|
||||
RenameFile(newpath, oldpath);
|
||||
}
|
||||
|
||||
ClearArray(hFiles);
|
||||
}
|
||||
|
||||
void AbortDownload(int index)
|
||||
{
|
||||
/* Delete all downloaded temporary files. */
|
||||
char path[PLATFORM_MAX_PATH];
|
||||
Handle hFiles = Updater_GetFiles(index);
|
||||
|
||||
int maxFiles = GetArraySize(hFiles);
|
||||
for (int i = 0; i < maxFiles; i++)
|
||||
{
|
||||
GetArrayString(hFiles, 0, path, sizeof(path));
|
||||
Format(path, sizeof(path), "%s.%s", path, TEMP_FILE_EXT);
|
||||
|
||||
if (FileExists(path))
|
||||
{
|
||||
DeleteFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
ClearArray(hFiles);
|
||||
}
|
||||
|
||||
void ProcessDownloadQueue(bool force=false)
|
||||
{
|
||||
if (!force && (g_bDownloading || !GetArraySize(g_hDownloadQueue)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Handle hQueuePack = GetArrayCell(g_hDownloadQueue, 0);
|
||||
SetPackPosition(hQueuePack, QueuePack_URL);
|
||||
|
||||
char url[MAX_URL_LENGTH];
|
||||
char dest[PLATFORM_MAX_PATH];
|
||||
ReadPackString(hQueuePack, url, sizeof(url));
|
||||
ReadPackString(hQueuePack, dest, sizeof(dest));
|
||||
|
||||
if (!CURL_AVAILABLE() && !SOCKET_AVAILABLE() && !STEAMTOOLS_AVAILABLE() && !STEAMWORKS_AVAILABLE())
|
||||
{
|
||||
SetFailState(EXTENSION_ERROR);
|
||||
}
|
||||
|
||||
#if defined DEBUG
|
||||
Updater_DebugLog("Download started:");
|
||||
Updater_DebugLog(" [0] URL: %s", url);
|
||||
Updater_DebugLog(" [1] Destination: %s", dest);
|
||||
#endif
|
||||
|
||||
g_bDownloading = true;
|
||||
|
||||
if (STEAMWORKS_AVAILABLE())
|
||||
{
|
||||
if (SteamWorks_IsLoaded())
|
||||
{
|
||||
Download_SteamWorks(url, dest);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateTimer(10.0, Timer_RetryQueue);
|
||||
}
|
||||
}
|
||||
else if (STEAMTOOLS_AVAILABLE())
|
||||
{
|
||||
if (g_bSteamLoaded)
|
||||
{
|
||||
Download_SteamTools(url, dest);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateTimer(10.0, Timer_RetryQueue);
|
||||
}
|
||||
}
|
||||
else if (CURL_AVAILABLE())
|
||||
{
|
||||
Download_cURL(url, dest);
|
||||
}
|
||||
else if (SOCKET_AVAILABLE())
|
||||
{
|
||||
Download_Socket(url, dest);
|
||||
}
|
||||
}
|
||||
|
||||
public Action Timer_RetryQueue(Handle timer)
|
||||
{
|
||||
ProcessDownloadQueue(true);
|
||||
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
void AddToDownloadQueue(int index, const char[] url, const char[] dest)
|
||||
{
|
||||
Handle hQueuePack = CreateDataPack();
|
||||
WritePackCell(hQueuePack, index);
|
||||
|
||||
QueuePack_URL = GetPackPosition(hQueuePack);
|
||||
WritePackString(hQueuePack, url);
|
||||
WritePackString(hQueuePack, dest);
|
||||
|
||||
PushArrayCell(g_hDownloadQueue, hQueuePack);
|
||||
|
||||
ProcessDownloadQueue();
|
||||
}
|
||||
|
||||
void DownloadEnded(bool successful, const char[] error="")
|
||||
{
|
||||
Handle hQueuePack = GetArrayCell(g_hDownloadQueue, 0);
|
||||
ResetPack(hQueuePack);
|
||||
|
||||
char url[MAX_URL_LENGTH];
|
||||
char dest[PLATFORM_MAX_PATH];
|
||||
int index = ReadPackCell(hQueuePack);
|
||||
ReadPackString(hQueuePack, url, sizeof(url));
|
||||
ReadPackString(hQueuePack, dest, sizeof(dest));
|
||||
|
||||
// Remove from the queue.
|
||||
CloseHandle(hQueuePack);
|
||||
RemoveFromArray(g_hDownloadQueue, 0);
|
||||
|
||||
#if defined DEBUG
|
||||
Updater_DebugLog(" [2] Successful: %s", successful ? "Yes" : "No");
|
||||
#endif
|
||||
|
||||
switch (Updater_GetStatus(index))
|
||||
{
|
||||
case Status_Checking:
|
||||
{
|
||||
if (!successful || !ParseUpdateFile(index, dest))
|
||||
{
|
||||
Updater_SetStatus(index, Status_Idle);
|
||||
|
||||
#if defined DEBUG
|
||||
if (error[0] != '\0')
|
||||
{
|
||||
Updater_DebugLog(" [2] %s", error);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
case Status_Downloading:
|
||||
{
|
||||
if (successful)
|
||||
{
|
||||
// Check if this was the last file we needed.
|
||||
char lastfile[PLATFORM_MAX_PATH];
|
||||
Handle hFiles = Updater_GetFiles(index);
|
||||
|
||||
GetArrayString(hFiles, GetArraySize(hFiles) - 1, lastfile, sizeof(lastfile));
|
||||
Format(lastfile, sizeof(lastfile), "%s.%s", lastfile, TEMP_FILE_EXT);
|
||||
|
||||
if (StrEqual(dest, lastfile))
|
||||
{
|
||||
Handle hPlugin = IndexToPlugin(index);
|
||||
|
||||
Fwd_OnPluginUpdating(hPlugin);
|
||||
FinalizeDownload(index);
|
||||
|
||||
char sName[64];
|
||||
if (!GetPluginInfo(hPlugin, PlInfo_Name, sName, sizeof(sName)))
|
||||
{
|
||||
strcopy(sName, sizeof(sName), "Null");
|
||||
}
|
||||
|
||||
Updater_Log("Successfully updated and installed \"%s\".", sName);
|
||||
|
||||
Updater_SetStatus(index, Status_Updated);
|
||||
Fwd_OnPluginUpdated(hPlugin);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed during an update.
|
||||
AbortDownload(index);
|
||||
Updater_SetStatus(index, Status_Error);
|
||||
|
||||
char filename[64];
|
||||
GetPluginFilename(IndexToPlugin(index), filename, sizeof(filename));
|
||||
Updater_Log("Error downloading update for plugin: %s", filename);
|
||||
Updater_Log(" [0] URL: %s", url);
|
||||
Updater_Log(" [1] Destination: %s", dest);
|
||||
|
||||
if (error[0] != '\0')
|
||||
{
|
||||
Updater_Log(" [2] %s", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case Status_Error:
|
||||
{
|
||||
// Delete any additional files that this plugin had queued.
|
||||
if (successful && FileExists(dest))
|
||||
{
|
||||
DeleteFile(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_bDownloading = false;
|
||||
ProcessDownloadQueue();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
/* Extension Helper - cURL */
|
||||
|
||||
void Download_cURL(const char[] url, const char[] dest)
|
||||
{
|
||||
char sURL[MAX_URL_LENGTH];
|
||||
PrefixURL(sURL, sizeof(sURL), url);
|
||||
|
||||
Handle hFile = curl_OpenFile(dest, "wb");
|
||||
|
||||
if (hFile == INVALID_HANDLE)
|
||||
{
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "Error writing to file: %s", dest);
|
||||
DownloadEnded(false, sError);
|
||||
return;
|
||||
}
|
||||
|
||||
int CURL_Default_opt[][2] = {
|
||||
{view_as<int>(CURLOPT_NOSIGNAL), 1},
|
||||
{view_as<int>(CURLOPT_NOPROGRESS), 1},
|
||||
{view_as<int>(CURLOPT_TIMEOUT), 30},
|
||||
{view_as<int>(CURLOPT_CONNECTTIMEOUT), 60},
|
||||
{view_as<int>(CURLOPT_VERBOSE), 0}
|
||||
};
|
||||
|
||||
Handle headers = curl_slist();
|
||||
curl_slist_append(headers, "Pragma: no-cache");
|
||||
curl_slist_append(headers, "Cache-Control: no-cache");
|
||||
|
||||
Handle hDLPack = CreateDataPack();
|
||||
WritePackCell(hDLPack, view_as<int>(hFile));
|
||||
WritePackCell(hDLPack, view_as<int>(headers));
|
||||
|
||||
Handle curl = curl_easy_init();
|
||||
curl_easy_setopt_int_array(curl, CURL_Default_opt, sizeof(CURL_Default_opt));
|
||||
curl_easy_setopt_handle(curl, CURLOPT_WRITEDATA, hFile);
|
||||
curl_easy_setopt_string(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt_handle(curl, CURLOPT_HTTPHEADER, headers);
|
||||
curl_easy_perform_thread(curl, OnCurlComplete, hDLPack);
|
||||
}
|
||||
|
||||
public void OnCurlComplete(Handle curl, CURLcode code, any hDLPack)
|
||||
{
|
||||
ResetPack(hDLPack);
|
||||
CloseHandle(ReadPackCell(hDLPack)); // hFile
|
||||
CloseHandle(ReadPackCell(hDLPack)); // headers
|
||||
CloseHandle(hDLPack);
|
||||
CloseHandle(curl);
|
||||
|
||||
if(code == CURLE_OK)
|
||||
{
|
||||
DownloadEnded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
char sError[256];
|
||||
curl_easy_strerror(code, sError, sizeof(sError));
|
||||
Format(sError, sizeof(sError), "cURL error: %s", sError);
|
||||
DownloadEnded(false, sError);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
|
||||
/* Extension Helper - Socket */
|
||||
|
||||
#define MAX_REDIRECTS 5
|
||||
|
||||
static DataPackPos DLPack_Header;
|
||||
static DataPackPos DLPack_Redirects;
|
||||
static DataPackPos DLPack_File;
|
||||
static DataPackPos DLPack_Request;
|
||||
|
||||
void Download_Socket(const char[] url, const char[] dest)
|
||||
{
|
||||
char sURL[MAX_URL_LENGTH];
|
||||
PrefixURL(sURL, sizeof(sURL), url);
|
||||
|
||||
if (strncmp(sURL, "https://", 8) == 0)
|
||||
{
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "Socket does not support HTTPs (URL: %s).", sURL);
|
||||
DownloadEnded(false, sError);
|
||||
return;
|
||||
}
|
||||
|
||||
Handle hFile = OpenFile(dest, "wb");
|
||||
|
||||
if (hFile == INVALID_HANDLE)
|
||||
{
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "Error writing to file: %s", dest);
|
||||
DownloadEnded(false, sError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Format HTTP GET method.
|
||||
char hostname[64];
|
||||
char location[128];
|
||||
char filename[64];
|
||||
char sRequest[MAX_URL_LENGTH+128];
|
||||
ParseURL(sURL, hostname, sizeof(hostname), location, sizeof(location), filename, sizeof(filename));
|
||||
FormatEx(sRequest, sizeof(sRequest), "GET %s/%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", location, filename, hostname);
|
||||
|
||||
Handle hDLPack = CreateDataPack();
|
||||
|
||||
DLPack_Header = GetPackPosition(hDLPack);
|
||||
WritePackCell(hDLPack, 0);
|
||||
|
||||
DLPack_Redirects = GetPackPosition(hDLPack);
|
||||
WritePackCell(hDLPack, 0);
|
||||
|
||||
DLPack_File = GetPackPosition(hDLPack);
|
||||
WritePackCell(hDLPack, view_as<int>(hFile));
|
||||
|
||||
DLPack_Request = GetPackPosition(hDLPack);
|
||||
WritePackString(hDLPack, sRequest);
|
||||
|
||||
Handle socket = SocketCreate(SOCKET_TCP, OnSocketError);
|
||||
SocketSetArg(socket, hDLPack);
|
||||
SocketSetOption(socket, ConcatenateCallbacks, 4096);
|
||||
SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, hostname, 80);
|
||||
}
|
||||
|
||||
public void OnSocketConnected(Handle socket, any hDLPack)
|
||||
{
|
||||
char sRequest[MAX_URL_LENGTH+128];
|
||||
SetPackPosition(hDLPack, DLPack_Request);
|
||||
ReadPackString(hDLPack, sRequest, sizeof(sRequest));
|
||||
|
||||
SocketSend(socket, sRequest);
|
||||
}
|
||||
|
||||
public void OnSocketReceive(Handle socket, char[] data, const int size, any hDLPack)
|
||||
{
|
||||
int idx = 0;
|
||||
|
||||
// Check if the HTTP header has already been parsed.
|
||||
SetPackPosition(hDLPack, DLPack_Header);
|
||||
bool bParsedHeader = view_as<bool>(ReadPackCell(hDLPack));
|
||||
int iRedirects = ReadPackCell(hDLPack);
|
||||
|
||||
if (!bParsedHeader)
|
||||
{
|
||||
// Parse header data.
|
||||
if ((idx = StrContains(data, "\r\n\r\n")) == -1)
|
||||
{
|
||||
idx = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
idx += 4;
|
||||
}
|
||||
|
||||
if (strncmp(data, "HTTP/", 5) == 0)
|
||||
{
|
||||
// Check for location header.
|
||||
int idx2 = StrContains(data, "\nLocation: ", false);
|
||||
|
||||
if (idx2 > -1 && (idx2 < idx || !idx))
|
||||
{
|
||||
if (++iRedirects > MAX_REDIRECTS)
|
||||
{
|
||||
CloseSocketHandles(socket, hDLPack);
|
||||
DownloadEnded(false, "Socket error: too many redirects.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPackPosition(hDLPack, DLPack_Redirects);
|
||||
WritePackCell(hDLPack, iRedirects);
|
||||
}
|
||||
|
||||
// skip to url
|
||||
idx2 += 11;
|
||||
|
||||
char sURL[MAX_URL_LENGTH];
|
||||
strcopy(sURL, (FindCharInString(data[idx2], '\r') + 1), data[idx2]);
|
||||
|
||||
PrefixURL(sURL, sizeof(sURL), sURL);
|
||||
|
||||
#if defined DEBUG
|
||||
Updater_DebugLog(" [ ] Redirected: %s", sURL);
|
||||
#endif
|
||||
|
||||
if (strncmp(sURL, "https://", 8) == 0)
|
||||
{
|
||||
CloseSocketHandles(socket, hDLPack);
|
||||
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "Socket does not support HTTPs (URL: %s).", sURL);
|
||||
DownloadEnded(false, sError);
|
||||
return;
|
||||
}
|
||||
|
||||
char hostname[64];
|
||||
char location[128];
|
||||
char filename[64];
|
||||
char sRequest[MAX_URL_LENGTH+128];
|
||||
ParseURL(sURL, hostname, sizeof(hostname), location, sizeof(location), filename, sizeof(filename));
|
||||
FormatEx(sRequest, sizeof(sRequest), "GET %s/%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", location, filename, hostname);
|
||||
|
||||
SetPackPosition(hDLPack, DLPack_Request); // sRequest
|
||||
WritePackString(hDLPack, sRequest);
|
||||
|
||||
Handle newSocket = SocketCreate(SOCKET_TCP, OnSocketError);
|
||||
SocketSetArg(newSocket, hDLPack);
|
||||
SocketSetOption(newSocket, ConcatenateCallbacks, 4096);
|
||||
SocketConnect(newSocket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, hostname, 80);
|
||||
|
||||
CloseHandle(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check HTTP status code
|
||||
char sStatusCode[64];
|
||||
strcopy(sStatusCode, (FindCharInString(data, '\r') - 8), data[9]);
|
||||
|
||||
if (strncmp(sStatusCode, "200", 3) != 0)
|
||||
{
|
||||
CloseSocketHandles(socket, hDLPack);
|
||||
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "Socket error: %s", sStatusCode);
|
||||
DownloadEnded(false, sError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetPackPosition(hDLPack, DLPack_Header);
|
||||
WritePackCell(hDLPack, 1); // bParsedHeader
|
||||
}
|
||||
|
||||
// Write data to file.
|
||||
SetPackPosition(hDLPack, DLPack_File);
|
||||
Handle hFile = view_as<Handle>(ReadPackCell(hDLPack));
|
||||
|
||||
while (idx < size)
|
||||
{
|
||||
WriteFileCell(hFile, data[idx++], 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSocketDisconnected(Handle socket, any hDLPack)
|
||||
{
|
||||
CloseSocketHandles(socket, hDLPack);
|
||||
|
||||
DownloadEnded(true);
|
||||
}
|
||||
|
||||
public void OnSocketError(Handle socket, const int errorType, const int errorNum, any hDLPack)
|
||||
{
|
||||
CloseSocketHandles(socket, hDLPack);
|
||||
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "Socket error: %d (Error code %d)", errorType, errorNum);
|
||||
DownloadEnded(false, sError);
|
||||
}
|
||||
|
||||
void CloseSocketHandles(Handle socket, Handle hDLPack)
|
||||
{
|
||||
SetPackPosition(hDLPack, DLPack_File);
|
||||
CloseHandle(view_as<Handle>(ReadPackCell(hDLPack))); // hFile
|
||||
CloseHandle(hDLPack);
|
||||
CloseHandle(socket);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
/* Extension Helper - SteamTools */
|
||||
|
||||
void Download_SteamTools(const char[] url, const char[] dest)
|
||||
{
|
||||
char sURL[MAX_URL_LENGTH];
|
||||
PrefixURL(sURL, sizeof(sURL), url);
|
||||
|
||||
Handle hDLPack = CreateDataPack();
|
||||
WritePackString(hDLPack, dest);
|
||||
|
||||
HTTPRequestHandle hRequest = Steam_CreateHTTPRequest(HTTPMethod_GET, sURL);
|
||||
Steam_SetHTTPRequestHeaderValue(hRequest, "Pragma", "no-cache");
|
||||
Steam_SetHTTPRequestHeaderValue(hRequest, "Cache-Control", "no-cache");
|
||||
Steam_SendHTTPRequest(hRequest, OnSteamHTTPComplete, hDLPack);
|
||||
}
|
||||
|
||||
public void OnSteamHTTPComplete(HTTPRequestHandle HTTPRequest, bool requestSuccessful, HTTPStatusCode statusCode, any hDLPack)
|
||||
{
|
||||
char sDest[PLATFORM_MAX_PATH];
|
||||
ResetPack(hDLPack);
|
||||
ReadPackString(hDLPack, sDest, sizeof(sDest));
|
||||
CloseHandle(hDLPack);
|
||||
|
||||
if (requestSuccessful && statusCode == HTTPStatusCode_OK)
|
||||
{
|
||||
Steam_WriteHTTPResponseBody(HTTPRequest, sDest);
|
||||
DownloadEnded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "SteamTools error (status code %i). Request successful: %s", view_as<int>(statusCode), requestSuccessful ? "True" : "False");
|
||||
DownloadEnded(false, sError);
|
||||
}
|
||||
|
||||
Steam_ReleaseHTTPRequest(HTTPRequest);
|
||||
}
|
||||
|
||||
/* Keep track of SteamTools load state. */
|
||||
bool g_bSteamLoaded;
|
||||
|
||||
public void Steam_FullyLoaded()
|
||||
{
|
||||
g_bSteamLoaded = true;
|
||||
}
|
||||
|
||||
public void Steam_Shutdown()
|
||||
{
|
||||
g_bSteamLoaded = false;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
/* Extension Helper - SteamWorks */
|
||||
|
||||
void Download_SteamWorks(const char[] url, const char[] dest)
|
||||
{
|
||||
char sURL[MAX_URL_LENGTH];
|
||||
PrefixURL(sURL, sizeof(sURL), url);
|
||||
|
||||
Handle hDLPack = CreateDataPack();
|
||||
WritePackString(hDLPack, dest);
|
||||
|
||||
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sURL);
|
||||
SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Pragma", "no-cache");
|
||||
SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Cache-Control", "no-cache");
|
||||
SteamWorks_SetHTTPCallbacks(hRequest, OnSteamWorksHTTPComplete);
|
||||
SteamWorks_SetHTTPRequestContextValue(hRequest, hDLPack);
|
||||
SteamWorks_SendHTTPRequest(hRequest);
|
||||
}
|
||||
|
||||
public void OnSteamWorksHTTPComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any hDLPack)
|
||||
{
|
||||
char sDest[PLATFORM_MAX_PATH];
|
||||
ResetPack(hDLPack);
|
||||
ReadPackString(hDLPack, sDest, sizeof(sDest));
|
||||
CloseHandle(hDLPack);
|
||||
|
||||
if (bRequestSuccessful && eStatusCode == k_EHTTPStatusCode200OK)
|
||||
{
|
||||
SteamWorks_WriteHTTPResponseBodyToFile(hRequest, sDest);
|
||||
DownloadEnded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
char sError[256];
|
||||
FormatEx(sError, sizeof(sError), "SteamWorks error (status code %i). Request successful: %s", view_as<int>(eStatusCode), bRequestSuccessful ? "True" : "False");
|
||||
DownloadEnded(false, sError);
|
||||
}
|
||||
|
||||
CloseHandle(hRequest);
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
|
||||
/* File System Parsers */
|
||||
|
||||
// Strip filename from path.
|
||||
void StripPathFilename(char[] path)
|
||||
{
|
||||
strcopy(path, FindCharInString(path, '/', true) + 1, path);
|
||||
}
|
||||
|
||||
// Return the filename and extension from a given path.
|
||||
void GetPathBasename(char[] path, char[] buffer, int maxlength)
|
||||
{
|
||||
int check = -1;
|
||||
if ((check = FindCharInString(path, '/', true)) != -1 ||
|
||||
(check = FindCharInString(path, '\\', true)) != -1)
|
||||
{
|
||||
strcopy(buffer, maxlength, path[check+1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcopy(buffer, maxlength, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Add http protocol to url if it's missing.
|
||||
void PrefixURL(char[] buffer, int maxlength, const char[] url)
|
||||
{
|
||||
if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0)
|
||||
{
|
||||
Format(buffer, maxlength, "http://%s", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcopy(buffer, maxlength, url);
|
||||
}
|
||||
}
|
||||
|
||||
// Split URL into hostname, location, and filename. No trailing slashes.
|
||||
void ParseURL(const char[] url, char[] host, int maxHost, char[] location, int maxLoc, char[] filename, int maxName)
|
||||
{
|
||||
// Strip url prefix.
|
||||
int idx = StrContains(url, "://");
|
||||
idx = (idx != -1) ? idx + 3 : 0;
|
||||
|
||||
char dirs[16][64];
|
||||
int total = ExplodeString(url[idx], "/", dirs, sizeof(dirs), sizeof(dirs[]));
|
||||
|
||||
// host
|
||||
Format(host, maxHost, "%s", dirs[0]);
|
||||
|
||||
// location
|
||||
location[0] = '\0';
|
||||
for (int i = 1; i < total - 1; i++)
|
||||
{
|
||||
Format(location, maxLoc, "%s/%s", location, dirs[i]);
|
||||
}
|
||||
|
||||
// filename
|
||||
Format(filename, maxName, "%s", dirs[total-1]);
|
||||
}
|
||||
|
||||
// Converts Updater SMC file paths into paths relative to the game folder.
|
||||
void ParseSMCPathForLocal(const char[] path, char[] buffer, int maxlength)
|
||||
{
|
||||
char dirs[16][64];
|
||||
int total = ExplodeString(path, "/", dirs, sizeof(dirs), sizeof(dirs[]));
|
||||
|
||||
if (StrEqual(dirs[0], "Path_SM"))
|
||||
{
|
||||
BuildPath(Path_SM, buffer, maxlength, "");
|
||||
}
|
||||
else // Path_Mod
|
||||
{
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
|
||||
// Construct the path and create directories if needed.
|
||||
for (int i = 1; i < total - 1; i++)
|
||||
{
|
||||
Format(buffer, maxlength, "%s%s/", buffer, dirs[i]);
|
||||
|
||||
if(!DirExists(buffer))
|
||||
{
|
||||
CreateDirectory(buffer, 511);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the filename to the end of the path.
|
||||
Format(buffer, maxlength, "%s%s", buffer, dirs[total-1]);
|
||||
}
|
||||
|
||||
// Converts Updater SMC file paths into paths relative to the plugin's update URL.
|
||||
void ParseSMCPathForDownload(const char[] path, char[] buffer, int maxlength)
|
||||
{
|
||||
char dirs[16][64];
|
||||
int total = ExplodeString(path, "/", dirs, sizeof(dirs), sizeof(dirs[]));
|
||||
|
||||
// Construct the path.
|
||||
buffer[0] = '\0';
|
||||
for (int i = 1; i < total; i++)
|
||||
{
|
||||
Format(buffer, maxlength, "%s/%s", buffer, dirs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a plugin's update file.
|
||||
// Logs update notes and begins download if required.
|
||||
// Returns true if an update was available.
|
||||
static Handle SMC_Sections;
|
||||
static Handle SMC_DataTrie;
|
||||
static Handle SMC_DataPack;
|
||||
static int SMC_LineNum;
|
||||
|
||||
bool ParseUpdateFile(int index, const char[] path)
|
||||
{
|
||||
/* Return true if an update was available. */
|
||||
SMC_Sections = CreateArray(64);
|
||||
SMC_DataTrie = CreateTrie();
|
||||
SMC_DataPack = CreateDataPack();
|
||||
SMC_LineNum = 0;
|
||||
|
||||
Handle smc = SMC_CreateParser();
|
||||
|
||||
SMC_SetRawLine(smc, Updater_RawLine);
|
||||
SMC_SetReaders(smc, Updater_NewSection, Updater_KeyValue, Updater_EndSection);
|
||||
|
||||
char sBuffer[MAX_URL_LENGTH];
|
||||
Handle hPack;
|
||||
bool bUpdate = false;
|
||||
SMCError err = SMC_ParseFile(smc, path);
|
||||
|
||||
if (err == SMCError_Okay)
|
||||
{
|
||||
// Initialize data
|
||||
Handle hPlugin = IndexToPlugin(index);
|
||||
Handle hFiles = Updater_GetFiles(index);
|
||||
ClearArray(hFiles);
|
||||
|
||||
// current version.
|
||||
char sCurrentVersion[16];
|
||||
|
||||
if (!GetPluginInfo(hPlugin, PlInfo_Version, sCurrentVersion, sizeof(sCurrentVersion)))
|
||||
{
|
||||
strcopy(sCurrentVersion, sizeof(sCurrentVersion), "Null");
|
||||
}
|
||||
|
||||
// latest version.
|
||||
char smcLatestVersion[16];
|
||||
|
||||
if (GetTrieValue(SMC_DataTrie, "version->latest", hPack))
|
||||
{
|
||||
ResetPack(hPack);
|
||||
ReadPackString(hPack, smcLatestVersion, sizeof(smcLatestVersion));
|
||||
}
|
||||
|
||||
// Check if we have the latest version.
|
||||
if (!StrEqual(sCurrentVersion, smcLatestVersion))
|
||||
{
|
||||
char sFilename[64];
|
||||
char sName[64];
|
||||
GetPluginFilename(hPlugin, sFilename, sizeof(sFilename));
|
||||
|
||||
if (GetPluginInfo(hPlugin, PlInfo_Name, sName, sizeof(sName)))
|
||||
{
|
||||
Updater_Log("Update available for \"%s\" (%s). Current: %s - Latest: %s", sName, sFilename, sCurrentVersion, smcLatestVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
Updater_Log("Update available for \"%s\". Current: %s - Latest: %s", sFilename, sCurrentVersion, smcLatestVersion);
|
||||
}
|
||||
|
||||
if (GetTrieValue(SMC_DataTrie, "information->notes", hPack))
|
||||
{
|
||||
ResetPack(hPack);
|
||||
|
||||
int iCount = 0;
|
||||
while (IsPackReadable(hPack, 1))
|
||||
{
|
||||
ReadPackString(hPack, sBuffer, sizeof(sBuffer));
|
||||
Updater_Log(" [%i] %s", iCount++, sBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Log update notes, save file list, and begin downloading.
|
||||
if (g_bGetDownload && Fwd_OnPluginDownloading(hPlugin) == Plugin_Continue)
|
||||
{
|
||||
// Get previous version.
|
||||
char smcPrevVersion[16];
|
||||
if (GetTrieValue(SMC_DataTrie, "version->previous", hPack))
|
||||
{
|
||||
ResetPack(hPack);
|
||||
ReadPackString(hPack, smcPrevVersion, sizeof(smcPrevVersion));
|
||||
}
|
||||
|
||||
// Check if we only need the patch files.
|
||||
if (StrEqual(sCurrentVersion, smcPrevVersion) && GetTrieValue(SMC_DataTrie, "patch->plugin", hPack))
|
||||
{
|
||||
ParseSMCFilePack(index, hPack, hFiles);
|
||||
|
||||
if (g_bGetSource && GetTrieValue(SMC_DataTrie, "patch->source", hPack))
|
||||
{
|
||||
ParseSMCFilePack(index, hPack, hFiles);
|
||||
}
|
||||
}
|
||||
else if (GetTrieValue(SMC_DataTrie, "files->plugin", hPack))
|
||||
{
|
||||
ParseSMCFilePack(index, hPack, hFiles);
|
||||
|
||||
if (g_bGetSource && GetTrieValue(SMC_DataTrie, "files->source", hPack))
|
||||
{
|
||||
ParseSMCFilePack(index, hPack, hFiles);
|
||||
}
|
||||
}
|
||||
|
||||
Updater_SetStatus(index, Status_Downloading);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't want to spam the logs with the same update notification.
|
||||
Updater_SetStatus(index, Status_Updated);
|
||||
}
|
||||
|
||||
bUpdate = true;
|
||||
}
|
||||
|
||||
#if defined DEBUG
|
||||
iCount = 0;
|
||||
|
||||
Updater_DebugLog(" ");
|
||||
Updater_DebugLog("SMC DEBUG");
|
||||
ResetPack(SMC_DataPack);
|
||||
|
||||
while (IsPackReadable(SMC_DataPack, 1))
|
||||
{
|
||||
ReadPackString(SMC_DataPack, sBuffer, sizeof(sBuffer));
|
||||
Updater_DebugLog("%s", sBuffer);
|
||||
|
||||
if (GetTrieValue(SMC_DataTrie, sBuffer, hPack))
|
||||
{
|
||||
iCount = 0;
|
||||
ResetPack(hPack);
|
||||
|
||||
while (IsPackReadable(hPack, 1))
|
||||
{
|
||||
ReadPackString(hPack, sBuffer, sizeof(sBuffer));
|
||||
Updater_DebugLog(" [%i] %s", iCount++, sBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Updater_DebugLog("END SMC DEBUG");
|
||||
Updater_DebugLog(" ");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Updater_Log("SMC parsing error on line %d", SMC_LineNum);
|
||||
|
||||
Updater_GetURL(index, sBuffer, sizeof(sBuffer));
|
||||
Updater_Log(" [0] URL: %s", sBuffer);
|
||||
|
||||
if (SMC_GetErrorString(err, sBuffer, sizeof(sBuffer)))
|
||||
{
|
||||
Updater_Log(" [1] ERROR: %s", sBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up SMC data.
|
||||
ResetPack(SMC_DataPack);
|
||||
|
||||
while (IsPackReadable(SMC_DataPack, 1))
|
||||
{
|
||||
ReadPackString(SMC_DataPack, sBuffer, sizeof(sBuffer));
|
||||
|
||||
if (GetTrieValue(SMC_DataTrie, sBuffer, hPack))
|
||||
{
|
||||
CloseHandle(hPack);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(SMC_Sections);
|
||||
CloseHandle(SMC_DataTrie);
|
||||
CloseHandle(SMC_DataPack);
|
||||
CloseHandle(smc);
|
||||
|
||||
return bUpdate;
|
||||
}
|
||||
|
||||
void ParseSMCFilePack(int index, Handle hPack, Handle hFiles)
|
||||
{
|
||||
// Prepare URL
|
||||
char urlprefix[MAX_URL_LENGTH];
|
||||
char url[MAX_URL_LENGTH];
|
||||
char dest[PLATFORM_MAX_PATH];
|
||||
char sBuffer[MAX_URL_LENGTH];
|
||||
Updater_GetURL(index, urlprefix, sizeof(urlprefix));
|
||||
StripPathFilename(urlprefix);
|
||||
|
||||
ResetPack(hPack);
|
||||
|
||||
while (IsPackReadable(hPack, 1))
|
||||
{
|
||||
ReadPackString(hPack, sBuffer, sizeof(sBuffer));
|
||||
|
||||
// Merge url.
|
||||
ParseSMCPathForDownload(sBuffer, url, sizeof(url));
|
||||
Format(url, sizeof(url), "%s%s", urlprefix, url);
|
||||
|
||||
// Make sure the current plugin path matches the update.
|
||||
ParseSMCPathForLocal(sBuffer, dest, sizeof(dest));
|
||||
|
||||
char sLocalBase[64];
|
||||
char sPluginBase[64];
|
||||
char sFilename[64];
|
||||
GetPathBasename(dest, sLocalBase, sizeof(sLocalBase));
|
||||
GetPathBasename(sFilename, sPluginBase, sizeof(sPluginBase));
|
||||
|
||||
if (StrEqual(sLocalBase, sPluginBase))
|
||||
{
|
||||
StripPathFilename(dest);
|
||||
Format(dest, sizeof(dest), "%s/%s", dest, sFilename);
|
||||
}
|
||||
|
||||
// Save the file location for later.
|
||||
PushArrayString(hFiles, dest);
|
||||
|
||||
// Add temporary file extension.
|
||||
Format(dest, sizeof(dest), "%s.%s", dest, TEMP_FILE_EXT);
|
||||
|
||||
// Begin downloading file.
|
||||
AddToDownloadQueue(index, url, dest);
|
||||
}
|
||||
}
|
||||
|
||||
public SMCResult Updater_RawLine(Handle smc, const char[] line, int lineno)
|
||||
{
|
||||
SMC_LineNum = lineno;
|
||||
return SMCParse_Continue;
|
||||
}
|
||||
|
||||
public SMCResult Updater_NewSection(Handle smc, const char[] name, bool opt_quotes)
|
||||
{
|
||||
PushArrayString(SMC_Sections, name);
|
||||
return SMCParse_Continue;
|
||||
}
|
||||
|
||||
public SMCResult Updater_KeyValue(Handle smc, const char[] key, const char[] value, bool key_quotes, bool value_quotes)
|
||||
{
|
||||
char sCurSection[MAX_URL_LENGTH];
|
||||
char sKey[MAX_URL_LENGTH];
|
||||
Handle hPack;
|
||||
|
||||
GetArrayString(SMC_Sections, GetArraySize(SMC_Sections)-1, sCurSection, sizeof(sCurSection));
|
||||
FormatEx(sKey, sizeof(sKey), "%s->%s", sCurSection, key);
|
||||
StringToLower(sKey);
|
||||
|
||||
if (!GetTrieValue(SMC_DataTrie, sKey, hPack))
|
||||
{
|
||||
hPack = CreateDataPack();
|
||||
SetTrieValue(SMC_DataTrie, sKey, hPack);
|
||||
WritePackString(SMC_DataPack, sKey);
|
||||
}
|
||||
|
||||
WritePackString(hPack, value);
|
||||
return SMCParse_Continue;
|
||||
}
|
||||
|
||||
public SMCResult Updater_EndSection(Handle smc)
|
||||
{
|
||||
if (GetArraySize(SMC_Sections))
|
||||
{
|
||||
RemoveFromArray(SMC_Sections, GetArraySize(SMC_Sections)-1);
|
||||
}
|
||||
|
||||
return SMCParse_Continue;
|
||||
}
|
||||
|
||||
stock void StringToLower(char[] input)
|
||||
{
|
||||
int length = strlen(input);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
input[i] = CharToLower(input[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
|
||||
/* PluginPack Helpers */
|
||||
|
||||
static DataPackPos PluginPack_Plugin;
|
||||
static DataPackPos PluginPack_Files;
|
||||
static DataPackPos PluginPack_Status;
|
||||
static DataPackPos PluginPack_URL;
|
||||
|
||||
int GetMaxPlugins()
|
||||
{
|
||||
return GetArraySize(g_hPluginPacks);
|
||||
}
|
||||
|
||||
bool IsValidPlugin(Handle plugin)
|
||||
{
|
||||
/* Check if the plugin handle is pointing to a valid plugin. */
|
||||
Handle hIterator = GetPluginIterator();
|
||||
bool bIsValid = false;
|
||||
|
||||
while (MorePlugins(hIterator))
|
||||
{
|
||||
if (plugin == ReadPlugin(hIterator))
|
||||
{
|
||||
bIsValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hIterator);
|
||||
return bIsValid;
|
||||
}
|
||||
|
||||
int PluginToIndex(Handle plugin)
|
||||
{
|
||||
Handle hPluginPack = INVALID_HANDLE;
|
||||
|
||||
int maxPlugins = GetMaxPlugins();
|
||||
for (int i = 0; i < maxPlugins; i++)
|
||||
{
|
||||
hPluginPack = GetArrayCell(g_hPluginPacks, i);
|
||||
SetPackPosition(hPluginPack, PluginPack_Plugin);
|
||||
|
||||
if (plugin == view_as<Handle>(ReadPackCell(hPluginPack)))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Handle IndexToPlugin(int index)
|
||||
{
|
||||
Handle hPluginPack = GetArrayCell(g_hPluginPacks, index);
|
||||
SetPackPosition(hPluginPack, PluginPack_Plugin);
|
||||
return view_as<Handle>(ReadPackCell(hPluginPack));
|
||||
}
|
||||
|
||||
void Updater_AddPlugin(Handle plugin, const char[] url)
|
||||
{
|
||||
int index = PluginToIndex(plugin);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
// Remove plugin from removal queue.
|
||||
int maxPlugins = GetArraySize(g_hRemoveQueue);
|
||||
for (int i = 0; i < maxPlugins; i++)
|
||||
{
|
||||
if (plugin == GetArrayCell(g_hRemoveQueue, i))
|
||||
{
|
||||
RemoveFromArray(g_hRemoveQueue, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the url.
|
||||
Updater_SetURL(index, url);
|
||||
}
|
||||
else
|
||||
{
|
||||
Handle hPluginPack = CreateDataPack();
|
||||
Handle hFiles = CreateArray(PLATFORM_MAX_PATH);
|
||||
|
||||
PluginPack_Plugin = GetPackPosition(hPluginPack);
|
||||
WritePackCell(hPluginPack, view_as<int>(plugin));
|
||||
|
||||
PluginPack_Files = GetPackPosition(hPluginPack);
|
||||
WritePackCell(hPluginPack, view_as<int>(hFiles));
|
||||
|
||||
PluginPack_Status = GetPackPosition(hPluginPack);
|
||||
WritePackCell(hPluginPack, view_as<int>(Status_Idle));
|
||||
|
||||
PluginPack_URL = GetPackPosition(hPluginPack);
|
||||
WritePackString(hPluginPack, url);
|
||||
|
||||
PushArrayCell(g_hPluginPacks, hPluginPack);
|
||||
}
|
||||
}
|
||||
|
||||
void Updater_QueueRemovePlugin(Handle plugin)
|
||||
{
|
||||
/* Flag a plugin for removal. */
|
||||
int maxPlugins = GetArraySize(g_hRemoveQueue);
|
||||
for (int i = 0; i < maxPlugins; i++)
|
||||
{
|
||||
// Make sure it wasn't previously flagged.
|
||||
if (plugin == GetArrayCell(g_hRemoveQueue, i))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PushArrayCell(g_hRemoveQueue, plugin);
|
||||
Updater_FreeMemory();
|
||||
}
|
||||
|
||||
void Updater_RemovePlugin(int index)
|
||||
{
|
||||
/* Warning: Removing a plugin will shift indexes. */
|
||||
CloseHandle(Updater_GetFiles(index)); // hFiles
|
||||
CloseHandle(GetArrayCell(g_hPluginPacks, index)); // hPluginPack
|
||||
RemoveFromArray(g_hPluginPacks, index);
|
||||
}
|
||||
|
||||
Handle Updater_GetFiles(int index)
|
||||
{
|
||||
Handle hPluginPack = GetArrayCell(g_hPluginPacks, index);
|
||||
SetPackPosition(hPluginPack, PluginPack_Files);
|
||||
return view_as<Handle>(ReadPackCell(hPluginPack));
|
||||
}
|
||||
|
||||
UpdateStatus Updater_GetStatus(int index)
|
||||
{
|
||||
Handle hPluginPack = GetArrayCell(g_hPluginPacks, index);
|
||||
SetPackPosition(hPluginPack, PluginPack_Status);
|
||||
return view_as<UpdateStatus>(ReadPackCell(hPluginPack));
|
||||
}
|
||||
|
||||
void Updater_SetStatus(int index, UpdateStatus status)
|
||||
{
|
||||
Handle hPluginPack = GetArrayCell(g_hPluginPacks, index);
|
||||
SetPackPosition(hPluginPack, PluginPack_Status);
|
||||
WritePackCell(hPluginPack, view_as<int>(status));
|
||||
}
|
||||
|
||||
void Updater_GetURL(int index, char[] buffer, int size)
|
||||
{
|
||||
Handle hPluginPack = GetArrayCell(g_hPluginPacks, index);
|
||||
SetPackPosition(hPluginPack, PluginPack_URL);
|
||||
ReadPackString(hPluginPack, buffer, size);
|
||||
}
|
||||
|
||||
void Updater_SetURL(int index, const char[] url)
|
||||
{
|
||||
Handle hPluginPack = GetArrayCell(g_hPluginPacks, index);
|
||||
SetPackPosition(hPluginPack, PluginPack_URL);
|
||||
WritePackString(hPluginPack, url);
|
||||
}
|
||||
|
||||
/* Stocks */
|
||||
stock void ReloadPlugin(Handle plugin=INVALID_HANDLE)
|
||||
{
|
||||
char filename[64];
|
||||
GetPluginFilename(plugin, filename, sizeof(filename));
|
||||
ServerCommand("sm plugins reload %s", filename);
|
||||
}
|
||||
|
||||
stock void UnloadPlugin(Handle plugin=INVALID_HANDLE)
|
||||
{
|
||||
char filename[64];
|
||||
GetPluginFilename(plugin, filename, sizeof(filename));
|
||||
ServerCommand("sm plugins unload %s", filename);
|
||||
}
|
||||
|
||||
stock void DisablePlugin(Handle plugin=INVALID_HANDLE)
|
||||
{
|
||||
char filename[64];
|
||||
char path_disabled[PLATFORM_MAX_PATH];
|
||||
char path_plugin[PLATFORM_MAX_PATH];
|
||||
|
||||
GetPluginFilename(plugin, filename, sizeof(filename));
|
||||
BuildPath(Path_SM, path_disabled, sizeof(path_disabled), "plugins/disabled/%s", filename);
|
||||
BuildPath(Path_SM, path_plugin, sizeof(path_plugin), "plugins/%s", filename);
|
||||
|
||||
if (FileExists(path_disabled))
|
||||
{
|
||||
DeleteFile(path_disabled);
|
||||
}
|
||||
|
||||
if (!RenameFile(path_disabled, path_plugin))
|
||||
{
|
||||
DeleteFile(path_plugin);
|
||||
}
|
||||
|
||||
ServerCommand("sm plugins unload %s", filename);
|
||||
}
|
||||
Reference in new issue
Block a user