dvander@2562: /**
dvander@2562: * vim: set ts=4 :
dvander@2562: * =============================================================================
dvander@2562: * SourceMod Updater Extension
dvander@2562: * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
dvander@2562: * =============================================================================
dvander@2562: *
dvander@2562: * This program is free software; you can redistribute it and/or modify it under
dvander@2562: * the terms of the GNU General Public License, version 3.0, as published by the
dvander@2562: * Free Software Foundation.
dvander@2562: *
dvander@2562: * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@2562: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@2562: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
dvander@2562: * details.
dvander@2562: *
dvander@2562: * You should have received a copy of the GNU General Public License along with
dvander@2562: * this program. If not, see .
dvander@2562: *
dvander@2562: * As a special exception, AlliedModders LLC gives you permission to link the
dvander@2562: * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@2562: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@2562: * by the Valve Corporation. You must obey the GNU General Public License in
dvander@2562: * all respects for all other code used. Additionally, AlliedModders LLC grants
dvander@2562: * this exception to all derivative works. AlliedModders LLC defines further
dvander@2562: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@2562: * or .
dvander@2562: *
dvander@2562: * Version: $Id$
dvander@2562: */
dvander@2562:
dvander@2783: #include
dvander@2562: #include
dvander@2562: #include
dvander@2562: #include
dvander@2562: #include
dvander@2562: #include "extension.h"
dvander@2562: #include "Updater.h"
dvander@2562: #include
dvander@2562: #include
dvander@2562:
dvander@2575: #define DEFAULT_UPDATE_URL "http://www.sourcemod.net/update/"
dvander@2575:
dvander@2562: using namespace SourceHook;
dvander@2562:
dvander@2562: SmUpdater g_Updater; /**< Global singleton for extension's main interface */
dvander@2562: SMEXT_LINK(&g_Updater);
dvander@2562:
dvander@2562: IWebternet *webternet;
dvander@2562: static List update_errors;
dvander@2562: static IThreadHandle *update_thread;
dvander@2575: static String update_url;
dvander@2562:
dvander@2562: bool SmUpdater::SDK_OnLoad(char *error, size_t maxlength, bool late)
dvander@2562: {
dvander@2562: sharesys->AddDependency(myself, "webternet.ext", true, true);
dvander@2562:
dvander@2562: SM_GET_IFACE(WEBTERNET, webternet);
dvander@2682:
dvander@2682: const char *url = smutils->GetCoreConfigValue("AutoUpdateURL");
dvander@2682: if (url == NULL)
dvander@2682: {
dvander@2682: url = DEFAULT_UPDATE_URL;
dvander@2682: }
dvander@2682: update_url.assign(url);
dvander@2562:
dvander@2562: ThreadParams params;
dvander@2562: params.flags = Thread_Default;
dvander@2562: params.prio = ThreadPrio_Low;
dvander@2562: update_thread = threader->MakeThread(this, ¶ms);
dvander@2562:
dvander@2562: if (update_thread == NULL)
dvander@2562: {
dvander@2562: smutils->Format(error, maxlength, "Could not create thread");
dvander@2562: return false;
dvander@2562: }
dvander@2562:
dvander@2562: return true;
dvander@2562: }
dvander@2562:
dvander@2562: void SmUpdater::SDK_OnUnload()
dvander@2562: {
dvander@2562: /* An interface drop might have killed this thread.
dvander@2562: * But if the handle is still there, we have to wait.
dvander@2562: */
dvander@2562: if (update_thread != NULL)
dvander@2562: {
dvander@2562: update_thread->WaitForThread();
dvander@2562: update_thread->DestroyThis();
dvander@2562: }
dvander@2562:
dvander@2562: /* Clear message tables */
dvander@2634: List::iterator iter = update_errors.begin();
dvander@2562: while (iter != update_errors.end())
dvander@2562: {
dvander@2562: iter = update_errors.erase(iter);
dvander@2562: }
dvander@2562: }
dvander@2562:
dvander@2562: bool SmUpdater::QueryInterfaceDrop(SourceMod::SMInterface *pInterface)
dvander@2562: {
dvander@2562: if (pInterface == webternet)
dvander@2562: {
dvander@2562: return false;
dvander@2562: }
dvander@2562:
dvander@2562: return true;
dvander@2562: }
dvander@2562:
dvander@2562: void SmUpdater::NotifyInterfaceDrop(SMInterface *pInterface)
dvander@2562: {
dvander@2562: if (pInterface == webternet)
dvander@2562: {
dvander@2562: /* Can't be in the thread if we're losing this extension. */
dvander@2562: update_thread->WaitForThread();
dvander@2562: update_thread->DestroyThis();
dvander@2562: update_thread = NULL;
dvander@2562: }
dvander@2562: }
dvander@2562:
dvander@2573: static void PumpUpdate(void *data)
dvander@2562: {
dvander@2562: String *str;
dvander@2628: bool new_files = false;
dvander@2562: List::iterator iter;
dvander@2562:
dvander@2573: char path[PLATFORM_MAX_PATH];
dvander@2573: UpdatePart *temp;
dvander@2573: UpdatePart *part = (UpdatePart*)data;
dvander@2573: while (part != NULL)
dvander@2573: {
dvander@2576: if (strstr(part->file, "..") != NULL)
dvander@2576: {
dvander@2576: /* Naughty naughty */
dvander@2576: AddUpdateError("Detected invalid path escape (..): %s", part->file);
dvander@2576: goto skip_create;
dvander@2576: }
dvander@2573: if (part->data == NULL)
dvander@2573: {
dvander@2573: smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file);
dvander@2573: if (libsys->IsPathDirectory(path))
dvander@2573: {
dvander@2679: goto skip_create;
dvander@2573: }
dvander@2573: if (!libsys->CreateFolder(path))
dvander@2573: {
dvander@2573: AddUpdateError("Could not create folder: %s", path);
dvander@2573: }
dvander@2573: else
dvander@2573: {
dvander@2573: smutils->LogMessage(myself, "Created folder \"%s\" from updater", path);
dvander@2573: }
dvander@2573: }
dvander@2573: else
dvander@2573: {
dvander@2573: smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file);
dvander@2630: FILE *fp = fopen(path, "wb");
dvander@2573: if (fp == NULL)
dvander@2573: {
dvander@2573: AddUpdateError("Could not open %s for writing", path);
dvander@2679: goto skip_create;
dvander@2573: }
dvander@2633: if (fwrite(part->data, 1, part->length, fp) != part->length)
dvander@2633: {
dvander@2633: AddUpdateError("Could not write file %s", path);
dvander@2633: }
dvander@2633: else
dvander@2633: {
dvander@2633: smutils->LogMessage(myself,
dvander@2633: "Successfully updated gamedata file \"%s\"",
dvander@2633: part->file);
dvander@2633: new_files = true;
dvander@2633: }
dvander@2573: fclose(fp);
dvander@2573: }
dvander@2576: skip_create:
dvander@2573: temp = part->next;
dvander@2573: free(part->data);
dvander@2573: free(part->file);
dvander@2573: delete part;
dvander@2573: part = temp;
dvander@2573: }
dvander@2573:
dvander@2562: if (update_errors.size())
dvander@2562: {
dvander@2562: smutils->LogError(myself, "--- BEGIN ERRORS FROM AUTOMATIC UPDATER ---");
dvander@2562:
dvander@2562: for (iter = update_errors.begin();
dvander@2562: iter != update_errors.end();
dvander@2562: iter++)
dvander@2562: {
dvander@2562: str = (*iter);
dvander@2562: smutils->LogError(myself, "%s", str->c_str());
dvander@2562: }
dvander@2562:
dvander@2562: smutils->LogError(myself, "--- END ERRORS FROM AUTOMATIC UPDATER ---");
dvander@2562: }
dvander@2628:
dvander@2628: if (new_files)
dvander@2628: {
dvander@2628: const char *force_restart = smutils->GetCoreConfigValue("ForceRestartAfterUpdate");
dvander@2628: if (force_restart == NULL || strcasecmp(force_restart, "yes") != 0)
dvander@2628: {
dvander@2628: smutils->LogMessage(myself,
dvander@2628: "SourceMod has been updated, please reload it or restart your server.");
dvander@2628: }
dvander@2628: else
dvander@2628: {
dvander@2628: char buffer[255];
dvander@2628: smutils->Format(buffer,
dvander@2628: sizeof(buffer),
dvander@2628: "meta unload %d\n",
dvander@2628: smutils->GetPluginId());
dvander@2628: gamehelpers->ServerCommand(buffer);
dvander@2628: smutils->Format(buffer,
dvander@2628: sizeof(buffer),
dvander@2628: "changelevel \"%s\"\n",
dvander@2628: gamehelpers->GetCurrentMap());
dvander@2628: gamehelpers->ServerCommand(buffer);
dvander@2628: gamehelpers->ServerCommand("echo SourceMod has been restarted from an automatic update.\n");
dvander@2628: }
dvander@2628: }
dvander@2562: }
dvander@2562:
dvander@2562: void SmUpdater::RunThread(IThreadHandle *pHandle)
dvander@2562: {
dvander@2562: UpdateReader ur;
dvander@2562:
dvander@2575: ur.PerformUpdate(update_url.c_str());
dvander@2562:
dvander@2573: smutils->AddFrameAction(PumpUpdate, ur.DetachParts());
dvander@2562: }
dvander@2562:
dvander@2562: void SmUpdater::OnTerminate(IThreadHandle *pHandle, bool cancel)
dvander@2562: {
dvander@2562: }
dvander@2562:
dvander@2562: void AddUpdateError(const char *fmt, ...)
dvander@2562: {
dvander@2562: va_list ap;
dvander@2562: char buffer[2048];
dvander@2562:
dvander@2562: va_start(ap, fmt);
dvander@2562: smutils->FormatArgs(buffer, sizeof(buffer), fmt, ap);
dvander@2562: va_end(ap);
dvander@2562:
dvander@2562: update_errors.push_back(new String(buffer));
dvander@2562: }
dvander@2783:
dvander@2790: const char *SmUpdater::GetExtensionVerString()
dvander@2783: {
asherkin@3630: return SM_VERSION_STRING;
dvander@2783: }
dvander@2783:
dvander@2790: const char *SmUpdater::GetExtensionDateString()
dvander@2783: {
dvander@2783: return SM_BUILD_TIMESTAMP;
dvander@2783: }
dvander@2783: