/*
* 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.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using log4net;
using Mono.Addins;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Client;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
namespace OpenSim.Services.Connectors.SimianGrid
{
///
/// Avatar profile flags
///
[Flags]
public enum ProfileFlags : uint
{
AllowPublish = 1,
MaturePublish = 2,
Identified = 4,
Transacted = 8,
Online = 16
}
///
/// Connects avatar profile and classified queries to the SimianGrid
/// backend
///
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
public class SimianProfiles : INonSharedRegionModule
{
private static readonly ILog m_log =
LogManager.GetLogger(
MethodBase.GetCurrentMethod().DeclaringType);
private string m_serverUrl = String.Empty;
#region INonSharedRegionModule
public Type ReplaceableInterface { get { return null; } }
public void RegionLoaded(Scene scene) { }
public void Close() { }
public SimianProfiles() { }
public string Name { get { return "SimianProfiles"; } }
public void AddRegion(Scene scene) { if (!String.IsNullOrEmpty(m_serverUrl)) { CheckEstateManager(scene); scene.EventManager.OnClientConnect += ClientConnectHandler; } }
public void RemoveRegion(Scene scene) { if (!String.IsNullOrEmpty(m_serverUrl)) { scene.EventManager.OnClientConnect -= ClientConnectHandler; } }
#endregion INonSharedRegionModule
public SimianProfiles(IConfigSource source)
{
Initialise(source);
}
public void Initialise(IConfigSource source)
{
IConfig gridConfig = source.Configs["UserAccountService"];
if (gridConfig != null)
{
string serviceUrl = gridConfig.GetString("UserAccountServerURI");
if (!String.IsNullOrEmpty(serviceUrl))
{
if (!serviceUrl.EndsWith("/") && !serviceUrl.EndsWith("="))
serviceUrl = serviceUrl + '/';
m_serverUrl = serviceUrl;
}
}
if (String.IsNullOrEmpty(m_serverUrl))
m_log.Info("[SIMIAN PROFILES]: No UserAccountServerURI specified, disabling connector");
}
private void ClientConnectHandler(IClientCore clientCore)
{
if (clientCore is IClientAPI)
{
IClientAPI client = (IClientAPI)clientCore;
// Classifieds
client.AddGenericPacketHandler("avatarclassifiedsrequest", AvatarClassifiedsRequestHandler);
client.OnClassifiedInfoRequest += ClassifiedInfoRequestHandler;
client.OnClassifiedInfoUpdate += ClassifiedInfoUpdateHandler;
client.OnClassifiedDelete += ClassifiedDeleteHandler;
// Picks
client.AddGenericPacketHandler("avatarpicksrequest", HandleAvatarPicksRequest);
client.AddGenericPacketHandler("pickinforequest", HandlePickInfoRequest);
client.OnPickInfoUpdate += PickInfoUpdateHandler;
client.OnPickDelete += PickDeleteHandler;
// Notes
client.AddGenericPacketHandler("avatarnotesrequest", HandleAvatarNotesRequest);
client.OnAvatarNotesUpdate += AvatarNotesUpdateHandler;
// Profiles
client.OnRequestAvatarProperties += RequestAvatarPropertiesHandler;
client.OnUpdateAvatarProperties += UpdateAvatarPropertiesHandler;
client.OnAvatarInterestUpdate += AvatarInterestUpdateHandler;
client.OnUserInfoRequest += UserInfoRequestHandler;
client.OnUpdateUserInfo += UpdateUserInfoHandler;
}
}
#region Classifieds
private void AvatarClassifiedsRequestHandler(Object sender, string method, List args)
{
if (!(sender is IClientAPI))
return;
IClientAPI client = (IClientAPI)sender;
UUID targetAvatarID;
if (args.Count < 1 || !UUID.TryParse(args[0], out targetAvatarID))
{
m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method);
return;
}
// FIXME: Query the generic key/value store for classifieds
client.SendAvatarClassifiedReply(targetAvatarID, new Dictionary(0));
}
private void ClassifiedInfoRequestHandler(UUID classifiedID, IClientAPI client)
{
// FIXME: Fetch this info
client.SendClassifiedInfoReply(classifiedID, UUID.Zero, 0, Utils.DateTimeToUnixTime(DateTime.UtcNow + TimeSpan.FromDays(1)),
0, String.Empty, String.Empty, UUID.Zero, 0, UUID.Zero, String.Empty, Vector3.Zero, String.Empty, 0, 0);
}
private void ClassifiedInfoUpdateHandler(UUID classifiedID, uint category, string name, string description,
UUID parcelID, uint parentEstate, UUID snapshotID, Vector3 globalPos, byte classifiedFlags, int price,
IClientAPI client)
{
// FIXME: Save this info
}
private void ClassifiedDeleteHandler(UUID classifiedID, IClientAPI client)
{
// FIXME: Delete the specified classified ad
}
#endregion Classifieds
#region Picks
private void HandleAvatarPicksRequest(Object sender, string method, List args)
{
if (!(sender is IClientAPI))
return;
IClientAPI client = (IClientAPI)sender;
UUID targetAvatarID;
if (args.Count < 1 || !UUID.TryParse(args[0], out targetAvatarID))
{
m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method);
return;
}
// FIXME: Fetch these
client.SendAvatarPicksReply(targetAvatarID, new Dictionary(0));
}
private void HandlePickInfoRequest(Object sender, string method, List args)
{
if (!(sender is IClientAPI))
return;
IClientAPI client = (IClientAPI)sender;
UUID avatarID;
UUID pickID;
if (args.Count < 2 || !UUID.TryParse(args[0], out avatarID) || !UUID.TryParse(args[1], out pickID))
{
m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method);
return;
}
// FIXME: Fetch this
client.SendPickInfoReply(pickID, avatarID, false, UUID.Zero, String.Empty, String.Empty, UUID.Zero, String.Empty,
String.Empty, String.Empty, Vector3.Zero, 0, false);
}
private void PickInfoUpdateHandler(IClientAPI client, UUID pickID, UUID creatorID, bool topPick, string name,
string desc, UUID snapshotID, int sortOrder, bool enabled)
{
// FIXME: Save this
}
private void PickDeleteHandler(IClientAPI client, UUID pickID)
{
// FIXME: Delete
}
#endregion Picks
#region Notes
private void HandleAvatarNotesRequest(Object sender, string method, List args)
{
if (!(sender is IClientAPI))
return;
IClientAPI client = (IClientAPI)sender;
UUID targetAvatarID;
if (args.Count < 1 || !UUID.TryParse(args[0], out targetAvatarID))
{
m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method);
return;
}
// FIXME: Fetch this
client.SendAvatarNotesReply(targetAvatarID, String.Empty);
}
private void AvatarNotesUpdateHandler(IClientAPI client, UUID targetID, string notes)
{
// FIXME: Save this
}
#endregion Notes
#region Profiles
private void RequestAvatarPropertiesHandler(IClientAPI client, UUID avatarID)
{
OSDMap user = FetchUserData(avatarID);
ProfileFlags flags = ProfileFlags.AllowPublish | ProfileFlags.MaturePublish;
if (user != null)
{
OSDMap about = null;
if (user.ContainsKey("LLAbout"))
{
try { about = OSDParser.DeserializeJson(user["LLAbout"].AsString()) as OSDMap; }
catch { }
}
if (about == null)
about = new OSDMap(0);
// Check if this user is a grid operator
byte[] charterMember;
if (user["AccessLevel"].AsInteger() >= 200)
charterMember = Utils.StringToBytes("Operator");
else
charterMember = Utils.EmptyBytes;
// Check if the user is online
if (client.Scene is Scene)
{
OpenSim.Services.Interfaces.PresenceInfo[] presences = ((Scene)client.Scene).PresenceService.GetAgents(new string[] { avatarID.ToString() });
if (presences != null && presences.Length > 0)
flags |= ProfileFlags.Online;
}
// Check if the user is identified
if (user["Identified"].AsBoolean())
flags |= ProfileFlags.Identified;
client.SendAvatarProperties(avatarID, about["About"].AsString(), user["CreationDate"].AsDate().ToString("M/d/yyyy",
System.Globalization.CultureInfo.InvariantCulture), charterMember, about["FLAbout"].AsString(), (uint)flags,
about["FLImage"].AsUUID(), about["Image"].AsUUID(), about["URL"].AsString(), user["Partner"].AsUUID());
OSDMap interests = null;
if (user.ContainsKey("LLInterests"))
{
try
{
interests = OSDParser.DeserializeJson(user["LLInterests"].AsString()) as OSDMap;
client.SendAvatarInterestsReply(avatarID, interests["WantMask"].AsUInteger(), interests["WantText"].AsString(), interests["SkillsMask"].AsUInteger(), interests["SkillsText"].AsString(), interests["Languages"].AsString());
}
catch { }
}
if (about == null)
about = new OSDMap(0);
}
else
{
m_log.Warn("[SIMIAN PROFILES]: Failed to fetch profile information for " + client.Name + ", returning default values");
client.SendAvatarProperties(avatarID, String.Empty, "1/1/1970", Utils.EmptyBytes,
String.Empty, (uint)flags, UUID.Zero, UUID.Zero, String.Empty, UUID.Zero);
}
}
private void UpdateAvatarPropertiesHandler(IClientAPI client, UserProfileData profileData)
{
OSDMap map = new OSDMap
{
{ "About", OSD.FromString(profileData.AboutText) },
{ "Image", OSD.FromUUID(profileData.Image) },
{ "FLAbout", OSD.FromString(profileData.FirstLifeAboutText) },
{ "FLImage", OSD.FromUUID(profileData.FirstLifeImage) },
{ "URL", OSD.FromString(profileData.ProfileUrl) }
};
AddUserData(client.AgentId, "LLAbout", map);
}
private void AvatarInterestUpdateHandler(IClientAPI client, uint wantmask, string wanttext, uint skillsmask,
string skillstext, string languages)
{
OSDMap map = new OSDMap
{
{ "WantMask", OSD.FromInteger(wantmask) },
{ "WantText", OSD.FromString(wanttext) },
{ "SkillsMask", OSD.FromInteger(skillsmask) },
{ "SkillsText", OSD.FromString(skillstext) },
{ "Languages", OSD.FromString(languages) }
};
AddUserData(client.AgentId, "LLInterests", map);
}
private void UserInfoRequestHandler(IClientAPI client)
{
m_log.Error("[SIMIAN PROFILES]: UserInfoRequestHandler");
// Fetch this user's e-mail address
NameValueCollection requestArgs = new NameValueCollection
{
{ "RequestMethod", "GetUser" },
{ "UserID", client.AgentId.ToString() }
};
OSDMap response = WebUtil.PostToService(m_serverUrl, requestArgs);
string email = response["Email"].AsString();
if (!response["Success"].AsBoolean())
m_log.Warn("[SIMIAN PROFILES]: GetUser failed during a user info request for " + client.Name);
client.SendUserInfoReply(false, true, email);
}
private void UpdateUserInfoHandler(bool imViaEmail, bool visible, IClientAPI client)
{
m_log.Info("[SIMIAN PROFILES]: Ignoring user info update from " + client.Name);
}
#endregion Profiles
///
/// Sanity checks regions for a valid estate owner at startup
///
private void CheckEstateManager(Scene scene)
{
EstateSettings estate = scene.RegionInfo.EstateSettings;
if (estate.EstateOwner == UUID.Zero)
{
// Attempt to lookup the grid admin
UserAccount admin = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, UUID.Zero);
if (admin != null)
{
m_log.InfoFormat("[SIMIAN PROFILES]: Setting estate {0} (ID: {1}) owner to {2}", estate.EstateName,
estate.EstateID, admin.Name);
estate.EstateOwner = admin.PrincipalID;
estate.Save();
}
else
{
m_log.WarnFormat("[SIMIAN PROFILES]: Estate {0} (ID: {1}) does not have an owner", estate.EstateName, estate.EstateID);
}
}
}
private bool AddUserData(UUID userID, string key, OSDMap value)
{
NameValueCollection requestArgs = new NameValueCollection
{
{ "RequestMethod", "AddUserData" },
{ "UserID", userID.ToString() },
{ key, OSDParser.SerializeJsonString(value) }
};
OSDMap response = WebUtil.PostToService(m_serverUrl, requestArgs);
bool success = response["Success"].AsBoolean();
if (!success)
m_log.WarnFormat("[SIMIAN PROFILES]: Failed to add user data with key {0} for {1}: {2}", key, userID, response["Message"].AsString());
return success;
}
private OSDMap FetchUserData(UUID userID)
{
NameValueCollection requestArgs = new NameValueCollection
{
{ "RequestMethod", "GetUser" },
{ "UserID", userID.ToString() }
};
OSDMap response = WebUtil.PostToService(m_serverUrl, requestArgs);
if (response["Success"].AsBoolean() && response["User"] is OSDMap)
{
return (OSDMap)response["User"];
}
else
{
m_log.Error("[SIMIAN PROFILES]: Failed to fetch user data for " + userID + ": " + response["Message"].AsString());
}
return null;
}
}
}