Files
opensim/OpenSim/Region/Application/OpenSim.cs
Melanie Thielker 4d1536f1ec Allow OpenSim to respond to Unix signals. This may need work to be
properly ignoed on Windows.
Windows devs, please test and check for platform flags if this causes
issues in Windows
2016-12-12 00:07:36 +00:00

1479 lines
63 KiB
C#

/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
using log4net;
using NDesk.Options;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Console;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Monitoring;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
namespace OpenSim
{
/// <summary>
/// Interactive OpenSim region server
/// </summary>
public class OpenSim : OpenSimBase
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected string m_startupCommandsFile;
protected string m_shutdownCommandsFile;
protected bool m_gui = false;
protected string m_consoleType = "local";
protected uint m_consolePort = 0;
/// <summary>
/// Prompt to use for simulator command line.
/// </summary>
private string m_consolePrompt;
/// <summary>
/// Regex for parsing out special characters in the prompt.
/// </summary>
private Regex m_consolePromptRegex = new Regex(@"([^\\])\\(\w)", RegexOptions.Compiled);
private string m_timedScript = "disabled";
private int m_timeInterval = 1200;
private System.Timers.Timer m_scriptTimer;
public OpenSim(IConfigSource configSource) : base(configSource)
{
}
protected override void ReadExtraConfigSettings()
{
base.ReadExtraConfigSettings();
IConfig startupConfig = Config.Configs["Startup"];
IConfig networkConfig = Config.Configs["Network"];
int stpMinThreads = 2;
int stpMaxThreads = 15;
if (startupConfig != null)
{
m_startupCommandsFile = startupConfig.GetString("startup_console_commands_file", "startup_commands.txt");
m_shutdownCommandsFile = startupConfig.GetString("shutdown_console_commands_file", "shutdown_commands.txt");
if (startupConfig.GetString("console", String.Empty) == String.Empty)
m_gui = startupConfig.GetBoolean("gui", false);
else
m_consoleType= startupConfig.GetString("console", String.Empty);
if (networkConfig != null)
m_consolePort = (uint)networkConfig.GetInt("console_port", 0);
m_timedScript = startupConfig.GetString("timer_Script", "disabled");
if (m_timedScript != "disabled")
{
m_timeInterval = startupConfig.GetInt("timer_Interval", 1200);
}
string asyncCallMethodStr = startupConfig.GetString("async_call_method", String.Empty);
FireAndForgetMethod asyncCallMethod;
if (!String.IsNullOrEmpty(asyncCallMethodStr) && Utils.EnumTryParse<FireAndForgetMethod>(asyncCallMethodStr, out asyncCallMethod))
Util.FireAndForgetMethod = asyncCallMethod;
stpMinThreads = startupConfig.GetInt("MinPoolThreads", 2 );
stpMaxThreads = startupConfig.GetInt("MaxPoolThreads", 25);
m_consolePrompt = startupConfig.GetString("ConsolePrompt", @"Region (\R) ");
}
if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool)
Util.InitThreadPool(stpMinThreads, stpMaxThreads);
m_log.Info("[OPENSIM MAIN]: Using async_call_method " + Util.FireAndForgetMethod);
}
private static Mono.Unix.UnixSignal[] signals = new Mono.Unix.UnixSignal[]
{
// new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGINT),
new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGTERM)
};
private Thread signal_thread = new Thread (delegate ()
{
System.Console.WriteLine("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
while (true)
{
// Wait for a signal to be delivered
int index = Mono.Unix.UnixSignal.WaitAny (signals, -1);
//Mono.Unix.Native.Signum signal = signals [index].Signum;
MainConsole.Instance.RunCommand("shutdown");
}
});
/// <summary>
/// Performs initialisation of the scene, such as loading configuration from disk.
/// </summary>
protected override void StartupSpecific()
{
m_log.Info("====================================================================");
m_log.Info("========================= STARTING OPENSIM =========================");
m_log.Info("====================================================================");
signal_thread.Start();
//m_log.InfoFormat("[OPENSIM MAIN]: GC Is Server GC: {0}", GCSettings.IsServerGC.ToString());
// http://msdn.microsoft.com/en-us/library/bb384202.aspx
//GCSettings.LatencyMode = GCLatencyMode.Batch;
//m_log.InfoFormat("[OPENSIM MAIN]: GC Latency Mode: {0}", GCSettings.LatencyMode.ToString());
if (m_gui) // Driven by external GUI
{
m_console = new CommandConsole("Region");
}
else
{
switch (m_consoleType)
{
case "basic":
m_console = new CommandConsole("Region");
break;
case "rest":
m_console = new RemoteConsole("Region");
((RemoteConsole)m_console).ReadConfig(Config);
break;
default:
m_console = new LocalConsole("Region", Config.Configs["Startup"]);
break;
}
}
MainConsole.Instance = m_console;
LogEnvironmentInformation();
RegisterCommonAppenders(Config.Configs["Startup"]);
RegisterConsoleCommands();
base.StartupSpecific();
MainServer.Instance.AddStreamHandler(new OpenSim.SimStatusHandler());
MainServer.Instance.AddStreamHandler(new OpenSim.XSimStatusHandler(this));
if (userStatsURI != String.Empty)
MainServer.Instance.AddStreamHandler(new OpenSim.UXSimStatusHandler(this));
if (managedStatsURI != String.Empty)
{
string urlBase = String.Format("/{0}/", managedStatsURI);
MainServer.Instance.AddHTTPHandler(urlBase, StatsManager.HandleStatsRequest);
m_log.InfoFormat("[OPENSIM] Enabling remote managed stats fetch. URL = {0}", urlBase);
}
if (m_console is RemoteConsole)
{
if (m_consolePort == 0)
{
((RemoteConsole)m_console).SetServer(m_httpServer);
}
else
{
((RemoteConsole)m_console).SetServer(MainServer.GetHttpServer(m_consolePort));
}
}
// Hook up to the watchdog timer
Watchdog.OnWatchdogTimeout += WatchdogTimeoutHandler;
PrintFileToConsole("startuplogo.txt");
// For now, start at the 'root' level by default
if (SceneManager.Scenes.Count == 1) // If there is only one region, select it
ChangeSelectedRegion("region",
new string[] {"change", "region", SceneManager.Scenes[0].RegionInfo.RegionName});
else
ChangeSelectedRegion("region", new string[] {"change", "region", "root"});
//Run Startup Commands
if (String.IsNullOrEmpty(m_startupCommandsFile))
{
m_log.Info("[STARTUP]: No startup command script specified. Moving on...");
}
else
{
RunCommandScript(m_startupCommandsFile);
}
// Start timer script (run a script every xx seconds)
if (m_timedScript != "disabled")
{
m_scriptTimer = new System.Timers.Timer();
m_scriptTimer.Enabled = true;
m_scriptTimer.Interval = m_timeInterval*1000;
m_scriptTimer.Elapsed += RunAutoTimerScript;
}
}
/// <summary>
/// Register standard set of region console commands
/// </summary>
private void RegisterConsoleCommands()
{
MainServer.RegisterHttpConsoleCommands(m_console);
m_console.Commands.AddCommand("Objects", false, "force update",
"force update",
"Force the update of all objects on clients",
HandleForceUpdate);
m_console.Commands.AddCommand("General", false, "change region",
"change region <region name>",
"Change current console region",
ChangeSelectedRegion);
m_console.Commands.AddCommand("Archiving", false, "save xml",
"save xml [<file name>]",
"Save a region's data in XML format",
SaveXml);
m_console.Commands.AddCommand("Archiving", false, "save xml2",
"save xml2 [<file name>]",
"Save a region's data in XML2 format",
SaveXml2);
m_console.Commands.AddCommand("Archiving", false, "load xml",
"load xml [<file name> [-newUID [<x> <y> <z>]]]",
"Load a region's data from XML format",
LoadXml);
m_console.Commands.AddCommand("Archiving", false, "load xml2",
"load xml2 [<file name>]",
"Load a region's data from XML2 format",
LoadXml2);
m_console.Commands.AddCommand("Archiving", false, "save prims xml2",
"save prims xml2 [<prim name> <file name>]",
"Save named prim to XML2",
SavePrimsXml2);
m_console.Commands.AddCommand("Archiving", false, "load oar",
"load oar [-m|--merge] [-s|--skip-assets]"
+ " [--default-user \"User Name\"]"
+ " [--force-terrain] [--force-parcels]"
+ " [--no-objects]"
+ " [--rotation degrees]"
+ " [--bounding-origin \"<x,y,z>\"]"
+ " [--bounding-size \"<x,y,z>\"]"
+ " [--displacement \"<x,y,z>\"]"
+ " [-d|--debug]"
+ " [<OAR path>]",
"Load a region's data from an OAR archive.",
"--merge will merge the OAR with the existing scene (suppresses terrain and parcel info loading).\n"
+ "--skip-assets will load the OAR but ignore the assets it contains.\n"
+ "--default-user will use this user for any objects with an owner whose UUID is not found in the grid.\n"
+ "--force-terrain forces the loading of terrain from the oar (undoes suppression done by --merge).\n"
+ "--force-parcels forces the loading of parcels from the oar (undoes suppression done by --merge).\n"
+ "--no-objects suppresses the addition of any objects (good for loading only the terrain).\n"
+ "--rotation specified rotation to be applied to the oar. Specified in degrees.\n"
+ "--bounding-origin will only place objects that after displacement and rotation fall within the bounding cube who's position starts at <x,y,z>. Defaults to <0,0,0>.\n"
+ "--bounding-size specifies the size of the bounding cube. The default is the size of the destination region and cannot be larger than this.\n"
+ "--displacement will add this value to the position of every object loaded.\n"
+ "--debug forces the archiver to display messages about where each object is being placed.\n\n"
+ "The path can be either a filesystem location or a URI.\n"
+ " If this is not given then the command looks for an OAR named region.oar in the current directory."
+ " [--rotation-center \"<x,y,z>\"] used to be an option, now it does nothing and will be removed soon."
+ "When an OAR is being loaded, operations are applied in this order:\n"
+ "1: Rotation (around the incoming OARs region center)\n"
+ "2: Cropping (a bounding cube with origin and size)\n"
+ "3: Displacement (setting offset coordinates within the destination region)",
LoadOar); ;
m_console.Commands.AddCommand("Archiving", false, "save oar",
//"save oar [-v|--version=<N>] [-p|--profile=<url>] [<OAR path>]",
"save oar [-h|--home=<url>] [--noassets] [--publish] [--perm=<permissions>] [--all] [<OAR path>]",
"Save a region's data to an OAR archive.",
// "-v|--version=<N> generates scene objects as per older versions of the serialization (e.g. -v=0)" + Environment.NewLine
"-h|--home=<url> adds the url of the profile service to the saved user information.\n"
+ "--noassets stops assets being saved to the OAR.\n"
+ "--publish saves an OAR stripped of owner and last owner information.\n"
+ " on reload, the estate owner will be the owner of all objects\n"
+ " this is useful if you're making oars generally available that might be reloaded to the same grid from which you published\n"
+ "--perm=<permissions> stops objects with insufficient permissions from being saved to the OAR.\n"
+ " <permissions> can contain one or more of these characters: \"C\" = Copy, \"T\" = Transfer\n"
+ "--all saves all the regions in the simulator, instead of just the current region.\n"
+ "The OAR path must be a filesystem path."
+ " If this is not given then the oar is saved to region.oar in the current directory.",
SaveOar);
m_console.Commands.AddCommand("Objects", false, "edit scale",
"edit scale <name> <x> <y> <z>",
"Change the scale of a named prim",
HandleEditScale);
m_console.Commands.AddCommand("Objects", false, "rotate scene",
"rotate scene <degrees> [centerX, centerY]",
"Rotates all scene objects around centerX, centerY (default 128, 128) (please back up your region before using)",
HandleRotateScene);
m_console.Commands.AddCommand("Objects", false, "scale scene",
"scale scene <factor>",
"Scales the scene objects (please back up your region before using)",
HandleScaleScene);
m_console.Commands.AddCommand("Objects", false, "translate scene",
"translate scene xOffset yOffset zOffset",
"translates the scene objects (please back up your region before using)",
HandleTranslateScene);
m_console.Commands.AddCommand("Users", false, "kick user",
"kick user <first> <last> [--force] [message]",
"Kick a user off the simulator",
"The --force option will kick the user without any checks to see whether it's already in the process of closing\n"
+ "Only use this option if you are sure the avatar is inactive and a normal kick user operation does not removed them",
KickUserCommand);
m_console.Commands.AddCommand("Users", false, "show users",
"show users [full]",
"Show user data for users currently on the region",
"Without the 'full' option, only users actually on the region are shown."
+ " With the 'full' option child agents of users in neighbouring regions are also shown.",
HandleShow);
m_console.Commands.AddCommand("Comms", false, "show connections",
"show connections",
"Show connection data",
HandleShow);
m_console.Commands.AddCommand("Comms", false, "show circuits",
"show circuits",
"Show agent circuit data",
HandleShow);
m_console.Commands.AddCommand("Comms", false, "show pending-objects",
"show pending-objects",
"Show # of objects on the pending queues of all scene viewers",
HandleShow);
m_console.Commands.AddCommand("General", false, "show modules",
"show modules",
"Show module data",
HandleShow);
m_console.Commands.AddCommand("Regions", false, "show regions",
"show regions",
"Show region data",
HandleShow);
m_console.Commands.AddCommand("Regions", false, "show ratings",
"show ratings",
"Show rating data",
HandleShow);
m_console.Commands.AddCommand("Objects", false, "backup",
"backup",
"Persist currently unsaved object changes immediately instead of waiting for the normal persistence call.",
RunCommand);
m_console.Commands.AddCommand("Regions", false, "create region",
"create region [\"region name\"] <region_file.ini>",
"Create a new region.",
"The settings for \"region name\" are read from <region_file.ini>. Paths specified with <region_file.ini> are relative to your Regions directory, unless an absolute path is given."
+ " If \"region name\" does not exist in <region_file.ini>, it will be added." + Environment.NewLine
+ "Without \"region name\", the first region found in <region_file.ini> will be created." + Environment.NewLine
+ "If <region_file.ini> does not exist, it will be created.",
HandleCreateRegion);
m_console.Commands.AddCommand("Regions", false, "restart",
"restart",
"Restart the currently selected region(s) in this instance",
RunCommand);
m_console.Commands.AddCommand("General", false, "command-script",
"command-script <script>",
"Run a command script from file",
RunCommand);
m_console.Commands.AddCommand("Regions", false, "remove-region",
"remove-region <name>",
"Remove a region from this simulator",
RunCommand);
m_console.Commands.AddCommand("Regions", false, "delete-region",
"delete-region <name>",
"Delete a region from disk",
RunCommand);
m_console.Commands.AddCommand("Estates", false, "estate create",
"estate create <owner UUID> <estate name>",
"Creates a new estate with the specified name, owned by the specified user."
+ " Estate name must be unique.",
CreateEstateCommand);
m_console.Commands.AddCommand("Estates", false, "estate set owner",
"estate set owner <estate-id>[ <UUID> | <Firstname> <Lastname> ]",
"Sets the owner of the specified estate to the specified UUID or user. ",
SetEstateOwnerCommand);
m_console.Commands.AddCommand("Estates", false, "estate set name",
"estate set name <estate-id> <new name>",
"Sets the name of the specified estate to the specified value. New name must be unique.",
SetEstateNameCommand);
m_console.Commands.AddCommand("Estates", false, "estate link region",
"estate link region <estate ID> <region ID>",
"Attaches the specified region to the specified estate.",
EstateLinkRegionCommand);
}
protected override void ShutdownSpecific()
{
if (m_shutdownCommandsFile != String.Empty)
{
RunCommandScript(m_shutdownCommandsFile);
}
base.ShutdownSpecific();
}
/// <summary>
/// Timer to run a specific text file as console commands. Configured in in the main ini file
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RunAutoTimerScript(object sender, EventArgs e)
{
if (m_timedScript != "disabled")
{
RunCommandScript(m_timedScript);
}
}
private void WatchdogTimeoutHandler(Watchdog.ThreadWatchdogInfo twi)
{
int now = Environment.TickCount & Int32.MaxValue;
m_log.ErrorFormat(
"[WATCHDOG]: Timeout detected for thread \"{0}\". ThreadState={1}. Last tick was {2}ms ago. {3}",
twi.Thread.Name,
twi.Thread.ThreadState,
now - twi.LastTick,
twi.AlarmMethod != null ? string.Format("Data: {0}", twi.AlarmMethod()) : "");
}
#region Console Commands
/// <summary>
/// Kicks users off the region
/// </summary>
/// <param name="module"></param>
/// <param name="cmdparams">name of avatar to kick</param>
private void KickUserCommand(string module, string[] cmdparams)
{
bool force = false;
OptionSet options = new OptionSet().Add("f|force", delegate (string v) { force = v != null; });
List<string> mainParams = options.Parse(cmdparams);
if (mainParams.Count < 4)
return;
string alert = null;
if (mainParams.Count > 4)
alert = String.Format("\n{0}\n", String.Join(" ", cmdparams, 4, cmdparams.Length - 4));
IList agents = SceneManager.GetCurrentSceneAvatars();
foreach (ScenePresence presence in agents)
{
RegionInfo regionInfo = presence.Scene.RegionInfo;
if (presence.Firstname.ToLower().Equals(mainParams[2].ToLower()) &&
presence.Lastname.ToLower().Equals(mainParams[3].ToLower()))
{
MainConsole.Instance.Output(
String.Format(
"Kicking user: {0,-16} {1,-16} {2,-37} in region: {3,-16}",
presence.Firstname, presence.Lastname, presence.UUID, regionInfo.RegionName));
// kick client...
if (alert != null)
presence.ControllingClient.Kick(alert);
else
presence.ControllingClient.Kick("\nYou have been logged out by an administrator.\n");
presence.Scene.CloseAgent(presence.UUID, force);
break;
}
}
MainConsole.Instance.Output("");
}
/// <summary>
/// Opens a file and uses it as input to the console command parser.
/// </summary>
/// <param name="fileName">name of file to use as input to the console</param>
private static void PrintFileToConsole(string fileName)
{
if (File.Exists(fileName))
{
StreamReader readFile = File.OpenText(fileName);
string currentLine;
while ((currentLine = readFile.ReadLine()) != null)
{
m_log.Info("[!]" + currentLine);
}
}
}
/// <summary>
/// Force resending of all updates to all clients in active region(s)
/// </summary>
/// <param name="module"></param>
/// <param name="args"></param>
private void HandleForceUpdate(string module, string[] args)
{
MainConsole.Instance.Output("Updating all clients");
SceneManager.ForceCurrentSceneClientUpdate();
}
/// <summary>
/// Edits the scale of a primative with the name specified
/// </summary>
/// <param name="module"></param>
/// <param name="args">0,1, name, x, y, z</param>
private void HandleEditScale(string module, string[] args)
{
if (args.Length == 6)
{
SceneManager.HandleEditCommandOnCurrentScene(args);
}
else
{
MainConsole.Instance.Output("Argument error: edit scale <prim name> <x> <y> <z>");
}
}
private void HandleRotateScene(string module, string[] args)
{
string usage = "Usage: rotate scene <angle in degrees> [centerX centerY] (centerX and centerY are optional and default to Constants.RegionSize / 2";
float centerX = Constants.RegionSize * 0.5f;
float centerY = Constants.RegionSize * 0.5f;
if (args.Length < 3 || args.Length == 4)
{
MainConsole.Instance.Output(usage);
return;
}
float angle = (float)(Convert.ToSingle(args[2]) / 180.0 * Math.PI);
OpenMetaverse.Quaternion rot = OpenMetaverse.Quaternion.CreateFromAxisAngle(0, 0, 1, angle);
if (args.Length > 4)
{
centerX = Convert.ToSingle(args[3]);
centerY = Convert.ToSingle(args[4]);
}
Vector3 center = new Vector3(centerX, centerY, 0.0f);
SceneManager.ForEachSelectedScene(delegate(Scene scene)
{
scene.ForEachSOG(delegate(SceneObjectGroup sog)
{
if (!sog.IsAttachment)
{
sog.RootPart.UpdateRotation(rot * sog.GroupRotation);
Vector3 offset = sog.AbsolutePosition - center;
offset *= rot;
sog.UpdateGroupPosition(center + offset);
}
});
});
}
private void HandleScaleScene(string module, string[] args)
{
string usage = "Usage: scale scene <factor>";
if (args.Length < 3)
{
MainConsole.Instance.Output(usage);
return;
}
float factor = (float)(Convert.ToSingle(args[2]));
float minZ = float.MaxValue;
SceneManager.ForEachSelectedScene(delegate(Scene scene)
{
scene.ForEachSOG(delegate(SceneObjectGroup sog)
{
if (!sog.IsAttachment)
{
if (sog.RootPart.AbsolutePosition.Z < minZ)
minZ = sog.RootPart.AbsolutePosition.Z;
}
});
});
SceneManager.ForEachSelectedScene(delegate(Scene scene)
{
scene.ForEachSOG(delegate(SceneObjectGroup sog)
{
if (!sog.IsAttachment)
{
Vector3 tmpRootPos = sog.RootPart.AbsolutePosition;
tmpRootPos.Z -= minZ;
tmpRootPos *= factor;
tmpRootPos.Z += minZ;
foreach (SceneObjectPart sop in sog.Parts)
{
if (sop.ParentID != 0)
sop.OffsetPosition *= factor;
sop.Scale *= factor;
}
sog.UpdateGroupPosition(tmpRootPos);
}
});
});
}
private void HandleTranslateScene(string module, string[] args)
{
string usage = "Usage: translate scene <xOffset, yOffset, zOffset>";
if (args.Length < 5)
{
MainConsole.Instance.Output(usage);
return;
}
float xOFfset = (float)Convert.ToSingle(args[2]);
float yOffset = (float)Convert.ToSingle(args[3]);
float zOffset = (float)Convert.ToSingle(args[4]);
Vector3 offset = new Vector3(xOFfset, yOffset, zOffset);
SceneManager.ForEachSelectedScene(delegate(Scene scene)
{
scene.ForEachSOG(delegate(SceneObjectGroup sog)
{
if (!sog.IsAttachment)
sog.UpdateGroupPosition(sog.AbsolutePosition + offset);
});
});
}
/// <summary>
/// Creates a new region based on the parameters specified. This will ask the user questions on the console
/// </summary>
/// <param name="module"></param>
/// <param name="cmd">0,1,region name, region ini or XML file</param>
private void HandleCreateRegion(string module, string[] cmd)
{
string regionName = string.Empty;
string regionFile = string.Empty;
if (cmd.Length == 3)
{
regionFile = cmd[2];
}
else if (cmd.Length > 3)
{
regionName = cmd[2];
regionFile = cmd[3];
}
string extension = Path.GetExtension(regionFile).ToLower();
bool isXml = extension.Equals(".xml");
bool isIni = extension.Equals(".ini");
if (!isXml && !isIni)
{
MainConsole.Instance.Output("Usage: create region [\"region name\"] <region_file.ini>");
return;
}
if (!Path.IsPathRooted(regionFile))
{
string regionsDir = ConfigSource.Source.Configs["Startup"].GetString("regionload_regionsdir", "Regions").Trim();
regionFile = Path.Combine(regionsDir, regionFile);
}
RegionInfo regInfo;
if (isXml)
{
regInfo = new RegionInfo(regionName, regionFile, false, ConfigSource.Source);
}
else
{
regInfo = new RegionInfo(regionName, regionFile, false, ConfigSource.Source, regionName);
}
Scene existingScene;
if (SceneManager.TryGetScene(regInfo.RegionID, out existingScene))
{
MainConsole.Instance.OutputFormat(
"ERROR: Cannot create region {0} with ID {1}, this ID is already assigned to region {2}",
regInfo.RegionName, regInfo.RegionID, existingScene.RegionInfo.RegionName);
return;
}
bool changed = PopulateRegionEstateInfo(regInfo);
IScene scene;
CreateRegion(regInfo, true, out scene);
if (changed)
m_estateDataService.StoreEstateSettings(regInfo.EstateSettings);
scene.Start();
}
/// <summary>
/// Runs commands issued by the server console from the operator
/// </summary>
/// <param name="command">The first argument of the parameter (the command)</param>
/// <param name="cmdparams">Additional arguments passed to the command</param>
public void RunCommand(string module, string[] cmdparams)
{
List<string> args = new List<string>(cmdparams);
if (args.Count < 1)
return;
string command = args[0];
args.RemoveAt(0);
cmdparams = args.ToArray();
switch (command)
{
case "backup":
MainConsole.Instance.Output("Triggering save of pending object updates to persistent store");
SceneManager.BackupCurrentScene();
break;
case "remove-region":
string regRemoveName = CombineParams(cmdparams, 0);
Scene removeScene;
if (SceneManager.TryGetScene(regRemoveName, out removeScene))
RemoveRegion(removeScene, false);
else
MainConsole.Instance.Output("No region with that name");
break;
case "delete-region":
string regDeleteName = CombineParams(cmdparams, 0);
Scene killScene;
if (SceneManager.TryGetScene(regDeleteName, out killScene))
RemoveRegion(killScene, true);
else
MainConsole.Instance.Output("no region with that name");
break;
case "restart":
SceneManager.RestartCurrentScene();
break;
}
}
/// <summary>
/// Change the currently selected region. The selected region is that operated upon by single region commands.
/// </summary>
/// <param name="cmdParams"></param>
protected void ChangeSelectedRegion(string module, string[] cmdparams)
{
if (cmdparams.Length > 2)
{
string newRegionName = CombineParams(cmdparams, 2);
if (!SceneManager.TrySetCurrentScene(newRegionName))
MainConsole.Instance.Output(String.Format("Couldn't select region {0}", newRegionName));
else
RefreshPrompt();
}
else
{
MainConsole.Instance.Output("Usage: change region <region name>");
}
}
/// <summary>
/// Refreshs prompt with the current selection details.
/// </summary>
private void RefreshPrompt()
{
string regionName = (SceneManager.CurrentScene == null ? "root" : SceneManager.CurrentScene.RegionInfo.RegionName);
MainConsole.Instance.Output(String.Format("Currently selected region is {0}", regionName));
// m_log.DebugFormat("Original prompt is {0}", m_consolePrompt);
string prompt = m_consolePrompt;
// Replace "\R" with the region name
// Replace "\\" with "\"
prompt = m_consolePromptRegex.Replace(prompt, m =>
{
// m_log.DebugFormat("Matched {0}", m.Groups[2].Value);
if (m.Groups[2].Value == "R")
return m.Groups[1].Value + regionName;
else
return m.Groups[0].Value;
});
m_console.DefaultPrompt = prompt;
m_console.ConsoleScene = SceneManager.CurrentScene;
}
protected override void HandleRestartRegion(RegionInfo whichRegion)
{
base.HandleRestartRegion(whichRegion);
// Where we are restarting multiple scenes at once, a previous call to RefreshPrompt may have set the
// m_console.ConsoleScene to null (indicating all scenes).
if (m_console.ConsoleScene != null && whichRegion.RegionName == ((Scene)m_console.ConsoleScene).Name)
SceneManager.TrySetCurrentScene(whichRegion.RegionName);
RefreshPrompt();
}
// see BaseOpenSimServer
/// <summary>
/// Many commands list objects for debugging. Some of the types are listed here
/// </summary>
/// <param name="mod"></param>
/// <param name="cmd"></param>
public override void HandleShow(string mod, string[] cmd)
{
base.HandleShow(mod, cmd);
List<string> args = new List<string>(cmd);
args.RemoveAt(0);
string[] showParams = args.ToArray();
switch (showParams[0])
{
case "users":
IList agents;
if (showParams.Length > 1 && showParams[1] == "full")
{
agents = SceneManager.GetCurrentScenePresences();
} else
{
agents = SceneManager.GetCurrentSceneAvatars();
}
MainConsole.Instance.Output(String.Format("\nAgents connected: {0}\n", agents.Count));
MainConsole.Instance.Output(
String.Format("{0,-16} {1,-16} {2,-37} {3,-11} {4,-16} {5,-30}", "Firstname", "Lastname",
"Agent ID", "Root/Child", "Region", "Position")
);
foreach (ScenePresence presence in agents)
{
RegionInfo regionInfo = presence.Scene.RegionInfo;
string regionName;
if (regionInfo == null)
{
regionName = "Unresolvable";
} else
{
regionName = regionInfo.RegionName;
}
MainConsole.Instance.Output(
String.Format(
"{0,-16} {1,-16} {2,-37} {3,-11} {4,-16} {5,-30}",
presence.Firstname,
presence.Lastname,
presence.UUID,
presence.IsChildAgent ? "Child" : "Root",
regionName,
presence.AbsolutePosition.ToString())
);
}
MainConsole.Instance.Output(String.Empty);
break;
case "connections":
HandleShowConnections();
break;
case "circuits":
HandleShowCircuits();
break;
case "modules":
SceneManager.ForEachSelectedScene(
scene =>
{
MainConsole.Instance.OutputFormat("Loaded region modules in {0} are:", scene.Name);
List<IRegionModuleBase> sharedModules = new List<IRegionModuleBase>();
List<IRegionModuleBase> nonSharedModules = new List<IRegionModuleBase>();
foreach (IRegionModuleBase module in scene.RegionModules.Values)
{
if (module.GetType().GetInterface("ISharedRegionModule") == null)
nonSharedModules.Add(module);
else
sharedModules.Add(module);
}
foreach (IRegionModuleBase module in sharedModules.OrderBy(m => m.Name))
MainConsole.Instance.OutputFormat("New Region Module (Shared): {0}", module.Name);
foreach (IRegionModuleBase module in nonSharedModules.OrderBy(m => m.Name))
MainConsole.Instance.OutputFormat("New Region Module (Non-Shared): {0}", module.Name);
}
);
MainConsole.Instance.Output("");
break;
case "regions":
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("Name", ConsoleDisplayUtil.RegionNameSize);
cdt.AddColumn("ID", ConsoleDisplayUtil.UuidSize);
cdt.AddColumn("Position", ConsoleDisplayUtil.CoordTupleSize);
cdt.AddColumn("Size", 11);
cdt.AddColumn("Port", ConsoleDisplayUtil.PortSize);
cdt.AddColumn("Ready?", 6);
cdt.AddColumn("Estate", ConsoleDisplayUtil.EstateNameSize);
SceneManager.ForEachScene(
scene =>
{
RegionInfo ri = scene.RegionInfo;
cdt.AddRow(
ri.RegionName,
ri.RegionID,
string.Format("{0},{1}", ri.RegionLocX, ri.RegionLocY),
string.Format("{0}x{1}", ri.RegionSizeX, ri.RegionSizeY),
ri.InternalEndPoint.Port,
scene.Ready ? "Yes" : "No",
ri.EstateSettings.EstateName);
}
);
MainConsole.Instance.Output(cdt.ToString());
break;
case "ratings":
SceneManager.ForEachScene(
delegate(Scene scene)
{
string rating = "";
if (scene.RegionInfo.RegionSettings.Maturity == 1)
{
rating = "MATURE";
}
else if (scene.RegionInfo.RegionSettings.Maturity == 2)
{
rating = "ADULT";
}
else
{
rating = "PG";
}
MainConsole.Instance.Output(String.Format(
"Region Name: {0}, Region Rating {1}",
scene.RegionInfo.RegionName,
rating));
});
break;
}
}
private void HandleShowCircuits()
{
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("Region", 20);
cdt.AddColumn("Avatar name", 24);
cdt.AddColumn("Type", 5);
cdt.AddColumn("Code", 10);
cdt.AddColumn("IP", 16);
cdt.AddColumn("Viewer Name", 24);
SceneManager.ForEachScene(
s =>
{
foreach (AgentCircuitData aCircuit in s.AuthenticateHandler.GetAgentCircuits().Values)
cdt.AddRow(
s.Name,
aCircuit.Name,
aCircuit.child ? "child" : "root",
aCircuit.circuitcode.ToString(),
aCircuit.IPAddress != null ? aCircuit.IPAddress.ToString() : "not set",
Util.GetViewerName(aCircuit));
});
MainConsole.Instance.Output(cdt.ToString());
}
private void HandleShowConnections()
{
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("Region", 20);
cdt.AddColumn("Avatar name", 24);
cdt.AddColumn("Circuit code", 12);
cdt.AddColumn("Endpoint", 23);
cdt.AddColumn("Active?", 7);
cdt.AddColumn("ChildAgent?", 7);
cdt.AddColumn("ping(ms)", 8);
SceneManager.ForEachScene(
s => s.ForEachClient(
c =>
{
bool child = false;
if(c.SceneAgent != null && c.SceneAgent.IsChildAgent)
child = true;
cdt.AddRow(
s.Name,
c.Name,
c.CircuitCode.ToString(),
c.RemoteEndPoint.ToString(),
c.IsActive.ToString(),
child.ToString(),
c.PingTimeMS);
}));
MainConsole.Instance.Output(cdt.ToString());
}
/// <summary>
/// Use XML2 format to serialize data to a file
/// </summary>
/// <param name="module"></param>
/// <param name="cmdparams"></param>
protected void SavePrimsXml2(string module, string[] cmdparams)
{
if (cmdparams.Length > 4)
{
SceneManager.SaveNamedPrimsToXml2(cmdparams[3], cmdparams[4]);
}
else
{
SceneManager.SaveNamedPrimsToXml2("Primitive", DEFAULT_PRIM_BACKUP_FILENAME);
}
}
/// <summary>
/// Use XML format to serialize data to a file
/// </summary>
/// <param name="module"></param>
/// <param name="cmdparams"></param>
protected void SaveXml(string module, string[] cmdparams)
{
MainConsole.Instance.Output("PLEASE NOTE, save-xml is DEPRECATED and may be REMOVED soon. If you are using this and there is some reason you can't use save-xml2, please file a mantis detailing the reason.");
if (cmdparams.Length > 2)
{
SceneManager.SaveCurrentSceneToXml(cmdparams[2]);
}
else
{
SceneManager.SaveCurrentSceneToXml(DEFAULT_PRIM_BACKUP_FILENAME);
}
}
/// <summary>
/// Loads data and region objects from XML format.
/// </summary>
/// <param name="module"></param>
/// <param name="cmdparams"></param>
protected void LoadXml(string module, string[] cmdparams)
{
MainConsole.Instance.Output("PLEASE NOTE, load-xml is DEPRECATED and may be REMOVED soon. If you are using this and there is some reason you can't use load-xml2, please file a mantis detailing the reason.");
Vector3 loadOffset = new Vector3(0, 0, 0);
if (cmdparams.Length > 2)
{
bool generateNewIDS = false;
if (cmdparams.Length > 3)
{
if (cmdparams[3] == "-newUID")
{
generateNewIDS = true;
}
if (cmdparams.Length > 4)
{
loadOffset.X = (float)Convert.ToDecimal(cmdparams[4], Culture.NumberFormatInfo);
if (cmdparams.Length > 5)
{
loadOffset.Y = (float)Convert.ToDecimal(cmdparams[5], Culture.NumberFormatInfo);
}
if (cmdparams.Length > 6)
{
loadOffset.Z = (float)Convert.ToDecimal(cmdparams[6], Culture.NumberFormatInfo);
}
MainConsole.Instance.Output(String.Format("loadOffsets <X,Y,Z> = <{0},{1},{2}>",loadOffset.X,loadOffset.Y,loadOffset.Z));
}
}
SceneManager.LoadCurrentSceneFromXml(cmdparams[2], generateNewIDS, loadOffset);
}
else
{
try
{
SceneManager.LoadCurrentSceneFromXml(DEFAULT_PRIM_BACKUP_FILENAME, false, loadOffset);
}
catch (FileNotFoundException)
{
MainConsole.Instance.Output("Default xml not found. Usage: load-xml <filename>");
}
}
}
/// <summary>
/// Serialize region data to XML2Format
/// </summary>
/// <param name="module"></param>
/// <param name="cmdparams"></param>
protected void SaveXml2(string module, string[] cmdparams)
{
if (cmdparams.Length > 2)
{
SceneManager.SaveCurrentSceneToXml2(cmdparams[2]);
}
else
{
SceneManager.SaveCurrentSceneToXml2(DEFAULT_PRIM_BACKUP_FILENAME);
}
}
/// <summary>
/// Load region data from Xml2Format
/// </summary>
/// <param name="module"></param>
/// <param name="cmdparams"></param>
protected void LoadXml2(string module, string[] cmdparams)
{
if (cmdparams.Length > 2)
{
try
{
SceneManager.LoadCurrentSceneFromXml2(cmdparams[2]);
}
catch (FileNotFoundException)
{
MainConsole.Instance.Output("Specified xml not found. Usage: load xml2 <filename>");
}
}
else
{
try
{
SceneManager.LoadCurrentSceneFromXml2(DEFAULT_PRIM_BACKUP_FILENAME);
}
catch (FileNotFoundException)
{
MainConsole.Instance.Output("Default xml not found. Usage: load xml2 <filename>");
}
}
}
/// <summary>
/// Load a whole region from an opensimulator archive.
/// </summary>
/// <param name="cmdparams"></param>
protected void LoadOar(string module, string[] cmdparams)
{
try
{
SceneManager.LoadArchiveToCurrentScene(cmdparams);
}
catch (Exception e)
{
MainConsole.Instance.Output(e.Message);
}
}
/// <summary>
/// Save a region to a file, including all the assets needed to restore it.
/// </summary>
/// <param name="cmdparams"></param>
protected void SaveOar(string module, string[] cmdparams)
{
SceneManager.SaveCurrentSceneToArchive(cmdparams);
}
protected void CreateEstateCommand(string module, string[] args)
{
string response = null;
UUID userID;
if (args.Length == 2)
{
response = "No user specified.";
}
else if (!UUID.TryParse(args[2], out userID))
{
response = String.Format("{0} is not a valid UUID", args[2]);
}
else if (args.Length == 3)
{
response = "No estate name specified.";
}
else
{
Scene scene = SceneManager.CurrentOrFirstScene;
// TODO: Is there a better choice here?
UUID scopeID = UUID.Zero;
UserAccount account = scene.UserAccountService.GetUserAccount(scopeID, userID);
if (account == null)
{
response = String.Format("Could not find user {0}", userID);
}
else
{
// concatenate it all to "name"
StringBuilder sb = new StringBuilder(args[3]);
for (int i = 4; i < args.Length; i++)
sb.Append (" " + args[i]);
string estateName = sb.ToString().Trim();
// send it off for processing.
IEstateModule estateModule = scene.RequestModuleInterface<IEstateModule>();
response = estateModule.CreateEstate(estateName, userID);
if (response == String.Empty)
{
List<int> estates = scene.EstateDataService.GetEstates(estateName);
response = String.Format("Estate {0} created as \"{1}\"", estates.ElementAt(0), estateName);
}
}
}
// give the user some feedback
if (response != null)
MainConsole.Instance.Output(response);
}
protected void SetEstateOwnerCommand(string module, string[] args)
{
string response = null;
Scene scene = SceneManager.CurrentOrFirstScene;
IEstateModule estateModule = scene.RequestModuleInterface<IEstateModule>();
if (args.Length == 3)
{
response = "No estate specified.";
}
else
{
int estateId;
if (!int.TryParse(args[3], out estateId))
{
response = String.Format("\"{0}\" is not a valid ID for an Estate", args[3]);
}
else
{
if (args.Length == 4)
{
response = "No user specified.";
}
else
{
UserAccount account = null;
// TODO: Is there a better choice here?
UUID scopeID = UUID.Zero;
string s1 = args[4];
if (args.Length == 5)
{
// attempt to get account by UUID
UUID u;
if (UUID.TryParse(s1, out u))
{
account = scene.UserAccountService.GetUserAccount(scopeID, u);
if (account == null)
response = String.Format("Could not find user {0}", s1);
}
else
{
response = String.Format("Invalid UUID {0}", s1);
}
}
else
{
// attempt to get account by Firstname, Lastname
string s2 = args[5];
account = scene.UserAccountService.GetUserAccount(scopeID, s1, s2);
if (account == null)
response = String.Format("Could not find user {0} {1}", s1, s2);
}
// If it's valid, send it off for processing.
if (account != null)
response = estateModule.SetEstateOwner(estateId, account);
if (response == String.Empty)
{
response = String.Format("Estate owner changed to {0} ({1} {2})", account.PrincipalID, account.FirstName, account.LastName);
}
}
}
}
// give the user some feedback
if (response != null)
MainConsole.Instance.Output(response);
}
protected void SetEstateNameCommand(string module, string[] args)
{
string response = null;
Scene scene = SceneManager.CurrentOrFirstScene;
IEstateModule estateModule = scene.RequestModuleInterface<IEstateModule>();
if (args.Length == 3)
{
response = "No estate specified.";
}
else
{
int estateId;
if (!int.TryParse(args[3], out estateId))
{
response = String.Format("\"{0}\" is not a valid ID for an Estate", args[3]);
}
else
{
if (args.Length == 4)
{
response = "No name specified.";
}
else
{
// everything after the estate ID is "name"
StringBuilder sb = new StringBuilder(args[4]);
for (int i = 5; i < args.Length; i++)
sb.Append (" " + args[i]);
string estateName = sb.ToString();
// send it off for processing.
response = estateModule.SetEstateName(estateId, estateName);
if (response == String.Empty)
{
response = String.Format("Estate {0} renamed to \"{1}\"", estateId, estateName);
}
}
}
}
// give the user some feedback
if (response != null)
MainConsole.Instance.Output(response);
}
private void EstateLinkRegionCommand(string module, string[] args)
{
int estateId =-1;
UUID regionId = UUID.Zero;
Scene scene = null;
string response = null;
if (args.Length == 3)
{
response = "No estate specified.";
}
else if (!int.TryParse(args [3], out estateId))
{
response = String.Format("\"{0}\" is not a valid ID for an Estate", args [3]);
}
else if (args.Length == 4)
{
response = "No region specified.";
}
else if (!UUID.TryParse(args[4], out regionId))
{
response = String.Format("\"{0}\" is not a valid UUID for a Region", args [4]);
}
else if (!SceneManager.TryGetScene(regionId, out scene))
{
// region may exist, but on a different sim.
response = String.Format("No access to Region \"{0}\"", args [4]);
}
if (response != null)
{
MainConsole.Instance.Output(response);
return;
}
// send it off for processing.
IEstateModule estateModule = scene.RequestModuleInterface<IEstateModule>();
response = estateModule.SetRegionEstate(scene.RegionInfo, estateId);
if (response == String.Empty)
{
estateModule.TriggerRegionInfoChange();
estateModule.sendRegionHandshakeToAll();
response = String.Format ("Region {0} is now attached to estate {1}", regionId, estateId);
}
// give the user some feedback
if (response != null)
MainConsole.Instance.Output (response);
}
#endregion
private static string CombineParams(string[] commandParams, int pos)
{
string result = String.Empty;
for (int i = pos; i < commandParams.Length; i++)
{
result += commandParams[i] + " ";
}
result = result.TrimEnd(' ');
return result;
}
}
}