Veteran Member
|
01-09-2014
, 19:28
[INC] Copycfg
|
#1
|
This include is meant to be used by plugin developers, both as a companion to the updater plugin, and as a replacement for AutoExecConfig.
The name doesn't really tell all it can do. It can back up existing .cfg files, and create cvars that load into memory and get dumped to a .cfg in the same operation. There's also an operation built in to allow server restarts at map end, should your plugin have changed radically between versions.
Revisions
2014.01.12.001 - Changed incorrect boolean bounds compliance FROM: "if (!cvar && cvar!= 1) cvar = 1" TO: "if (cvar > 1) cvar = 1"
2014.01.12.002 - Added support to opcode 0 to ignore existing .cfg files with the current version.
Bad news:- Slow and expensive file operations
- User comments not supported (though they can be saved in a backup and manually edited in by the user - see Good News below)
Good news:- No limitation on number of, or location(s) of your .cfg file(s)
- Ability to create backup .cfg files for each version
- Unused cvars removed from the .cfg file
- New cvars added to the .cfg file
- Existing cvars keep their current values*
- *If an existing cvar's value falls outside of any defined bounds, it is adjusted up or down to comply with the bounds
- Friendly to the developer and the user - server restarts can be requested by developer but disabled by the user
- Easily tailored to the developer's needs
copycfg.inc:
PHP Code:
#if defined _copycfg_included #endinput #endif #define _copycfg_included
//Revisions //2014.01.12.001 - Changed incorrect boolean bounds compliance FROM: "if (!cvar && cvar!= 1) cvar = 1" TO: "if (cvar > 1) cvar = 1" //2014.01.12.002 - Added support to opcode 0 to ignore existing .cfg files with the current version.
/** * Carries out copycfg operations according to the opcode. * opcode 0 backs up and creates the .cfg file * opcode 1 creates a convar and writes it to the .cfg file * opcode 2 creates a repeating warning message, and sets the cvar copycfg_restartmap to true. Your plugin can read this cvar to decide if restart is required * * @param opcode Defines the operation to carry out. 0=backup & create .cfg file 1=CreateConVar 2=set up server restart at end of map * @param pathtocfgfile path to the plugin's .cfg file relative to the game folder. Example: "/cfg/sourcemod/example.cfg" * If opcode is 2, send the value to use as a repeating warning timer as a string. Example "120.0" = 120 seconds. The default is 10.0 * @param convartype The type of the ConVar 0|1|2|3. 0=boolean, 1=integer, 2=float, 3=string. * @param name name of the ConVar. * @param defaultValue Default value of the ConVar. This does not need to match whatever is in memory. This is how your redefine the default value * @param description Description for the ConVar. This does not need to match whatever is in memory. This is how your redefine the description * @param flags Optional bitstring of flags determining how the convar should be handled. See FCVAR_* constants for more details. * @param hasMin Boolean that defines if the ConVar has a lower bounds. This does not need to match whatever is in memory. This is how the redefine the minimum value * @param min Float that defines the lower bounds of the ConVar. This does not need to match whatever is in memory. This is how the redefine the minimum value * @param hasMax Boolean that defines if the ConVar has an upper bounds. This does not need to match whatever is in memory. This is how the redefine the maximum value * @param max Float that defines the upper bounds of the ConVar. This does not need to match whatever is in memory. This is how the redefine the maximum value * @return opcode 0: INVALID_HANDLE on failure. The calling plugin's handle on success * @return opcode 1: INVALID_HANDLE on failure. The cvar's handle on success * @return opcode 2: The calling plugin's handle if server restart not allowed by user. INVALID_HANDLE if a server restart is scheduled at the end of the map */ stock Handle:Copycfg(const opcode=0, const String:pathtocfgfile[]="", const convartype=0, const String:name[]="", const String:defaultValue[]="", const String:description[]="", flags=0, bool:hasMin=false, Float:min=0.0, bool:hasMax=false, Float:max=0.0) { new String:execpath[PLATFORM_MAX_PATH] = "" new String:backupfilename[PLATFORM_MAX_PATH] = "" new String:line[256] = "" new String:search[256] = "" new String:previousversion[64] = "" new String:pluginname[128] = "" new String:pluginversion[64] = "" GetPluginInfo(INVALID_HANDLE, PlInfo_Name, pluginname, sizeof(pluginname)) GetPluginInfo(INVALID_HANDLE, PlInfo_Version, pluginversion, sizeof(pluginversion)) new Handle:f_file = INVALID_HANDLE new Handle:c_cvar = INVALID_HANDLE CreateConVar("copycfg_allow_restart", "1" , "user end cvar to allow/block server restarts at the end of the map") CreateConVar("copycfg_restart_scheduled", "0" , "cvar used to communicate a server restart", FCVAR_DONTRECORD|FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY) //Create copycfg.cfg if it does not exist. We can't use AutoExecConfig or we'll get other cvars in the file if (!FileExists("/cfg/sourcemod/copycfg.cfg")) { //Create a copycfg.cfg file f_file = OpenFile("/cfg/sourcemod/copycfg.cfg", "w") if (f_file != INVALID_HANDLE) { Format(line, sizeof(line), "// This file was auto-generated by Copycfg") WriteFileLine(f_file, line) Format(line, sizeof(line), "// ConVars used by copycfg.inc") WriteFileLine(f_file, line) //Write 2 blank lines to keep compatible with existing format line = "\r\n" WriteFileLine(f_file, line) WriteFileLine(f_file, line) Format(line, sizeof(line), "// user end cvar to allow/block server restarts at the end of the map") WriteFileLine(f_file, line) Format(line, sizeof(line), "// -") WriteFileLine(f_file, line) Format(line, sizeof(line), "// Default: %s%s%s", '"', "1", '"') WriteFileLine(f_file, line) Format(line, sizeof(line), "copycfg_allow_restart %s%s%s", '"', "1", '"') WriteFileLine(f_file, line) //Write 1 blank line to keep compatible with existing format line = "\r\n" WriteFileLine(f_file, line) CloseHandle(f_file) } else { PrintToServer("[%s %s] - Error creating /cfg/sourcemod/copycfg.cfg", pluginname, pluginversion) } } //Execute copycfg.cfg ServerCommand("exec /sourcemod/copycfg.cfg") new pos = StrContains(pathtocfgfile, "/cfg/", false) if (pos == 0) { Format(execpath, sizeof(execpath), pathtocfgfile[5]) } else { Format(execpath, sizeof(execpath), pathtocfgfile) } switch (opcode) { case 0: //Back up & create .cfg file { //Check it the file exists. If so execute it, if not create it if (FileExists(pathtocfgfile)) { PrintToServer("[%s %s] - Executing existing cfg file %s", pluginname, pluginversion, pathtocfgfile) ServerCommand("exec %s", execpath) //Open the file and get the version f_file = OpenFile(pathtocfgfile, "r") if (f_file == INVALID_HANDLE) { PrintToServer("[%s %s] - ERROR! Could open file %s for reading", pluginname, pluginversion, pathtocfgfile) return INVALID_HANDLE } Format(search, sizeof(search), "// ConVars for plugin %s version ", pluginname) while (!IsEndOfFile(f_file)) { ReadFileLine(f_file, line, sizeof(line)) TrimString(line) if (StrContains(line, search) > -1) //This is the line containing the version { Format(previousversion, sizeof(previousversion), line[strlen(search)]) break } } CloseHandle(f_file) //If this .cfg version matches the current version, we don't need to do anything else if (StrEqual(previousversion, pluginversion)) { PrintToServer("[%s %s] - %s is the current version", pluginname, pluginversion, pathtocfgfile) return GetMyHandle() } //Send a message that the .cfg will be backed up Format(backupfilename, sizeof(backupfilename), "%s.%s.bak", pathtocfgfile, previousversion) PrintToServer("[%s %s] - Copycfg is backing up %s", pluginname, pluginversion, pathtocfgfile) PrintToServer("to %s", backupfilename) //If a backup already exists, delete it if (FileExists(backupfilename)) DeleteFile(backupfilename) // Now we want to rename the .cfg file to .cfg.version to back it up if (RenameFile(backupfilename, pathtocfgfile)) { PrintToServer("[%s %s] - Your previous configuration file %s was backed up", pluginname, pluginversion, pathtocfgfile) PrintToServer("as %s", backupfilename) } else { PrintToServer("[%s %s] - ERROR! Could not rename file %s to %s", pluginname, pluginversion, pathtocfgfile, backupfilename) return INVALID_HANDLE } } //Open the specified .cfg file f_file = OpenFile(pathtocfgfile, "w") //Make sure handle to file was created if (f_file == INVALID_HANDLE) { PrintToServer("[%s %s] - ERROR! Could not create handle to the file %s", pluginname, pluginversion, pathtocfgfile) return INVALID_HANDLE } //If the file has a size of -1, it does not exist, so an error has occurred creating it if (FileSize(pathtocfgfile) == -1) { PrintToServer("[%s %s] - ERROR! Could not open the file %s for writing", pluginname, pluginversion, pathtocfgfile) CloseHandle(f_file) return INVALID_HANDLE } else //File was just created, write headers { Format(line, sizeof(line), "// This file was auto-generated by Copycfg") WriteFileLine(f_file, line) Format(line, sizeof(line), "// ConVars for plugin %s version %s", pluginname, pluginversion) WriteFileLine(f_file, line) //Write 2 blank lines to keep compatible with existing format line = "\r\n" WriteFileLine(f_file, line) WriteFileLine(f_file, line) } CloseHandle(f_file) return GetMyHandle() } case 1: //Create ConVars { c_cvar = CreateConVar(name, defaultValue, description, flags, hasMin, min, hasMax, max) if (c_cvar == INVALID_HANDLE) { PrintToServer("[%s %s] - ERROR! Could not create or open handle to the cvar %s", pluginname, pluginversion, name) return INVALID_HANDLE } //Open the specified .cfg file f_file = OpenFile(pathtocfgfile, "a") if (f_file == INVALID_HANDLE) { PrintToServer("[%s %s] - ERROR! Could not open the file %s", pluginname, pluginversion, pathtocfgfile) return INVALID_HANDLE } //If the file has a size of -1, it does not exist, so an error has occurred creating it if (FileSize(pathtocfgfile) == -1) { PrintToServer("[%s %s] - ERROR! Could not open the file %s", pluginname, pluginversion, pathtocfgfile) return INVALID_HANDLE } if (FileSize(pathtocfgfile) == 0) //If the file has a size of 0, it was just created, don't use { PrintToServer("[%s %s] - ERROR! The file %s does not have valid headers. Use opcode 0 before calling opcode 1.", pluginname, pluginversion, pathtocfgfile) return INVALID_HANDLE } //Now write the ConVar Format(line, sizeof(line), "// %s", description) WriteFileLine(f_file, line) Format(line, sizeof(line), "// -") WriteFileLine(f_file, line) Format(line, sizeof(line), "// Default: %s%s%s", '"', defaultValue, '"') WriteFileLine(f_file, line) if (hasMin) { Format(line, sizeof(line), "// Minimum: %s%f%s", '"', min, '"') WriteFileLine(f_file, line) } if (hasMax) { Format(line, sizeof(line), "// Maximum: %s%f%s", '"', max, '"') WriteFileLine(f_file, line) } switch (convartype) { case 0: { new cvar = GetConVarInt(c_cvar) if (cvar < 0) cvar = 0 if (cvar > 1) cvar = 1 Format(line, sizeof(line), "%s %s%i%s", name, '"', cvar, '"') } case 1: { new cvar = GetConVarInt(c_cvar) new Float:cvarfloat = float(cvar) if (hasMin && cvarfloat < min)cvar = RoundFloat(min) if (hasMax && cvarfloat > max)cvar = RoundFloat(max) Format(line, sizeof(line), "%s %s%i%s", name, '"', cvar, '"') } case 2: { new Float:cvar = GetConVarFloat(c_cvar) if (hasMin && cvar < min)cvar = min if (hasMax && cvar > max)cvar = max Format(line, sizeof(line), "%s %s%f%s", name, '"', cvar, '"') } case 3: { new String:cvar[256] GetConVarString(c_cvar, cvar, sizeof(cvar)) new Float:cvarfloat = StringToFloat(cvar) if (hasMin && cvarfloat < min)FloatToString(min, cvar, sizeof(cvar)) if (hasMax && cvarfloat > max)FloatToString(max, cvar, sizeof(cvar)) Format(line, sizeof(line), "%s %s%s%s", name, '"', cvar, '"') } } WriteFileLine(f_file, line) //Write 1 blank line to keep compatible with existing format line = "\r\n" WriteFileLine(f_file, line) CloseHandle(f_file) return c_cvar } case 2: //Set up server restart { if (GetConVarBool(FindConVar("copycfg_allow_restart"))) { if (!GetConVarBool(FindConVar("copycfg_restart_scheduled"))) { SetConVarBool(FindConVar("copycfg_restart_scheduled"), true) PrintToServer("[%s %s] - Server will restart at the end of the map for updates.", pluginname, pluginversion) PrintCenterTextAll("Server will restart at the end of the map for updates.") new Float:freq = StringToFloat(pathtocfgfile) if (freq <= 0.0) freq = 10.0 CreateTimer(freq, CopycfgSendWarningMsg, _, TIMER_REPEAT) return INVALID_HANDLE } else { PrintToServer("[%s %s] - Server restart already scheduled", pluginname, pluginversion) return INVALID_HANDLE } } else { PrintToServer("[%s %s] - Developer requested server restart disabled in copycfg.cfg", pluginname, pluginversion) return GetMyHandle() } } } PrintToServer("[%s %s] - ERROR! Copycfg opcode %i failed! The opcode is not defined...", pluginname, pluginversion, opcode) return INVALID_HANDLE } public Action:CopycfgSendWarningMsg(Handle:timer) { new String:pluginname[128] = "" new String:pluginversion[64] = "" GetPluginInfo(INVALID_HANDLE, PlInfo_Name, pluginname, sizeof(pluginname)) GetPluginInfo(INVALID_HANDLE, PlInfo_Version, pluginversion, sizeof(pluginversion)) PrintToServer("[%s %s] - Server will restart at the end of the map for updates.", pluginname, pluginversion) PrintCenterTextAll("Server will restart at the end of the map for updates.") }
Example plugins:
example1.sp:
PHP Code:
#include <sourcemod> #include <updater> #include <autoexecconfig> #include <copycfg>
#define UPDATE_URL "http://your_url/updater/example.txt"
new const String:PLUGIN_NAME[]= "example" new const String:PLUGIN_AUTHOR[]= "Bittersweet" new const String:PLUGIN_DESCRIPTION[]= "An example/test plugin using copycfg concept. To use Copycfg, you must include copycfg.inc and use Copycfg with the correct opcode to handle creating/loading .cfg files and cvars." new const String:PLUGIN_VERSION[]= "1.0.0"
public Plugin:myinfo = { name = PLUGIN_NAME, author = PLUGIN_AUTHOR, description = PLUGIN_DESCRIPTION, version = PLUGIN_VERSION, url = "http://www.sourcemod.net/" } public OnPluginStart() { PrintToServer("[%s %s] Loaded", PLUGIN_NAME, PLUGIN_VERSION) CreateConVar("example_version", PLUGIN_VERSION, "example plugin used for testing copycfg plugin", FCVAR_DONTRECORD|FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY) //Always call copycfg with opcode 0 before calling any other opcodes, or you get no backup file for the previous version Copycfg(0, "/cfg/sourcemod/example.cfg") Copycfg(1, "/cfg/sourcemod/example.cfg", 0, "example_cvar1", "0", "Boolean test cvar.") Copycfg(1, "/cfg/sourcemod/example.cfg", 1, "example_cvar2", "25", "Integer test cvar.", FCVAR_PLUGIN, true, 0.0, true, 100.0) Copycfg(1, "/cfg/sourcemod/example.cfg", 2, "example_cvar3", "2.0", "Float test cvar.", FCVAR_PLUGIN, true, 0.0, true, 10.0) //Copycfg(1, "/cfg/sourcemod/example.cfg", 3, "example_cvar4", "test", "String test cvar.", FCVAR_PLUGIN) if (LibraryExists("updater")) { Updater_AddPlugin(UPDATE_URL) } } public OnLibraryAdded(const String:name[]) { if (StrEqual(name, "updater")) { Updater_AddPlugin(UPDATE_URL) } } public OnConfigsExecuted() { //Forcing an update would be optional new UpdateTriggered = Updater_ForceUpdate() if (UpdateTriggered) { PrintToServer("[%s %s] - Checking for update(s)...", PLUGIN_NAME, PLUGIN_VERSION) } else { PrintToServer("[%s %s] - Updater DID NOT trigger an update...", PLUGIN_NAME, PLUGIN_VERSION) } } public Action:Updater_OnPluginChecking() { PrintToServer("[%s %s] - Contacting update server...", PLUGIN_NAME, PLUGIN_VERSION) return Plugin_Continue } public Action:Updater_OnPluginDownloading() { PrintToServer("[%s %s] - Downloading update(s)...", PLUGIN_NAME, PLUGIN_VERSION) return Plugin_Continue } public Updater_OnPluginUpdated() { //Only reload the plugin if: the user has blocked restarting the server, or a restart was already scheduled if (Copycfg(2) == GetMyHandle()) ReloadPlugin(INVALID_HANDLE) } public OnMapStart() { PrintToServer("[%s %s] - Map start", PLUGIN_NAME, PLUGIN_VERSION) if (GetConVarBool(FindConVar("copycfg_restart_scheduled"))) { PrintToServer("[%s %s] - Server should restart", PLUGIN_NAME, PLUGIN_VERSION) ServerCommand("_restart") } else { PrintToServer("[%s %s] - No restart scheduled", PLUGIN_NAME, PLUGIN_VERSION) } } //End of code
example2.sp:
PHP Code:
#include <sourcemod> #include <updater> #include <autoexecconfig> #include <copycfg>
#define UPDATE_URL "http://your_url/updater/example.txt"
new const String:PLUGIN_NAME[]= "example" new const String:PLUGIN_AUTHOR[]= "Bittersweet" new const String:PLUGIN_DESCRIPTION[]= "An example/test plugin using copycfg concept. To use Copycfg, you must include copycfg.inc and use copycfg with the correct opcode to handle creating/loading .cfg files and cvars." new const String:PLUGIN_VERSION[]= "2.0.0"
public Plugin:myinfo = { name = PLUGIN_NAME, author = PLUGIN_AUTHOR, description = PLUGIN_DESCRIPTION, version = PLUGIN_VERSION, url = "http://www.sourcemod.net/" } public OnPluginStart() { PrintToServer("[%s %s] Loaded", PLUGIN_NAME, PLUGIN_VERSION) CreateConVar("example_version", PLUGIN_VERSION, "example plugin used for testing copycfg plugin", FCVAR_DONTRECORD|FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY) //Always call copycfg with opcode 0 before calling any other opcodes, or you get no backup file for the previous version Copycfg(0, "/cfg/sourcemod/example.cfg") //Copycfg(1, "/cfg/sourcemod/example.cfg", 0, "example_cvar1", "0", "Boolean test cvar.") Copycfg(1, "/cfg/sourcemod/example.cfg", 1, "example_cvar2", "75", "Integer test cvar.", FCVAR_PLUGIN, true, 50.0, true, 100.0) Copycfg(1, "/cfg/sourcemod/example.cfg", 2, "example_cvar3", "7.0", "Float test cvar.", FCVAR_PLUGIN, true, 5.0, true, 10.0) Copycfg(1, "/cfg/sourcemod/example.cfg", 3, "example_cvar4", "test", "String test cvar.", FCVAR_PLUGIN) if (LibraryExists("updater")) { Updater_AddPlugin(UPDATE_URL) } } public OnLibraryAdded(const String:name[]) { if (StrEqual(name, "updater")) { Updater_AddPlugin(UPDATE_URL) } } public OnConfigsExecuted() { //Forcing an update would be optional new UpdateTriggered = Updater_ForceUpdate() if (UpdateTriggered) { PrintToServer("[%s %s] - Checking for update(s)...", PLUGIN_NAME, PLUGIN_VERSION) } else { PrintToServer("[%s %s] - Updater DID NOT trigger an update...", PLUGIN_NAME, PLUGIN_VERSION) } } public Action:Updater_OnPluginChecking() { PrintToServer("[%s %s] - Contacting update server...", PLUGIN_NAME, PLUGIN_VERSION) return Plugin_Continue } public Action:Updater_OnPluginDownloading() { PrintToServer("[%s %s] - Downloading update(s)...", PLUGIN_NAME, PLUGIN_VERSION) return Plugin_Continue } public Updater_OnPluginUpdated() { //Only reload the plugin if: the user has blocked restarting the server, or a restart was already scheduled if (Copycfg(2) == GetMyHandle()) ReloadPlugin(INVALID_HANDLE) } public OnMapStart() { PrintToServer("[%s %s] - Map start", PLUGIN_NAME, PLUGIN_VERSION) if (GetConVarBool(FindConVar("copycfg_restart_scheduled"))) { PrintToServer("[%s %s] - Server should restart", PLUGIN_NAME, PLUGIN_VERSION) ServerCommand("_restart") } else { PrintToServer("[%s %s] - No restart scheduled", PLUGIN_NAME, PLUGIN_VERSION) } } //End of code
How to use examples:
1. Place the copycfg.inc file in your /sourcemod/scripting/include folder
2. Copy example1.sp and insert your UPDATE_URL in the define directive.
To prevent your server from restarting, remove Copycfg(2) from Updater_OnPluginUpdated().
You can change other things later after you see how it works.
Save it in your /addons/sourcemod/scripting folder.
3. Copy example2.sp and insert your UPDATE_URL in the define directive.
To prevent your server from restarting, remove Copycfg(2) from Updater_OnPluginUpdated().
You can change other things later after you see how it works.
Save it in your /addons/sourcemod/scripting folder.
4. Compile example1.sp, and rename it to example.smx (or whatever filename.smx)
5. Place the newly created .smx into your updater's plugin folder
6. Create the necessary example.txt file for updater with version 2 being the latest
7. Compile example2.sp, and rename it to example.smx (or whatever filename.smx)
8. Copy the newly created .smx to your plugin folder and load it
9. Load the plugin and watch your server console
If you don't change the filenames being passed to Copycfg, you should see the plugin updating,
and when all is said and done you should have example version 2.0.0 in your plugins folder and
loaded into memory. You should have a backup .cfg file named example.cfg.1.0.0.bak and a new
example.cfg with only the cvars defined in example2. Cvars that were defined in example1, but
not in example2, will still exist in memory until the server is restarted, but existing cvars will keep
user defined values unless they fall outside of defined bounds, in which case they are adjusted to
fall just within the lower or higher end of the bounds.
Details:
Copycfg works with opcodes. The opcode is always the 1st parameter.
The 2nd parameter is a full file path (except for opcode 2, see below), so you aren't constrained to any certain directory or just one .cfg file. See copycfg.inc for details on all of the parameters,
but briefly:
opcode 0 creates a back up .cfg with the current version.
The version is also noted inside the file. It also creates a new empty .cfg file as specified
in the 2nd parameter. As of revision 2014.01.12.002, no file writing operations are carried out if
the current .cfg file contains the current version header.
opcode 1 creates convars, both in memory and in the file passed in the 2nd parameter.
It also ensures any pre-existing cvars are set to user-defined values, unless they fall out of
bounds. This opcode will also create the file specified in the 2nd parameter if it doesn't exist,
but it will not back up the previous version as opcode 0 does, therefore using opcode 0 first
is recommended for each .cfg file. With opcode 1, the 3rd parameter specifies the cvar type,
which allows for cvars to be set correctly without any guesswork. See copycfg.inc for details.
opcode 2 is used to schedule a server restart, if the user hasn't disabled them in
copycfg.cfg. The developer actually has to use code as shown in the examples in OnMapStart()
(it wouldn't work in OnMapEnd() on my CS:S server) to restart the server.
The opcode simply sets a cvar as a flag that a server restart has been requested.
In most cases, you probably won't even need to do this. With opcode 2, the second
parameter defines the frequency of warning messages sent to the server console, and to players
in-game that the server will restart at the end of the map.
Other notes:
AutoExecConfig is never used anywhere in Copycfg, and you shouldn't use it if using Copycfg.
If the file /cfg/sourcemod/copycfg.cfg doesn't exist, it is created by the include.
The default I have defined allows server restarts, but you can easily change that if you wish.
Running the example plugins I've provided without altering anything other than UPDATE_URL,
will result in a server restart at the end of the map!
__________________
Last edited by Bittersweet; 01-12-2014 at 10:12.
|
|