diff --git a/.gitignore b/.gitignore index fae7509131..91e9be50cf 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,5 @@ OpenSim/Region/ScriptEngine/test-results/ OpenSim/Tests/Common/test-results/ OpenSim/Tests/test-results/ test-results/ +doc/html +doc/doxygen.error.log diff --git a/.nant/local.include b/.nant/local.include index 5185717cbc..c20794499f 100644 --- a/.nant/local.include +++ b/.nant/local.include @@ -43,10 +43,11 @@ + - + diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 43dea0bd3b..8ff55df3da 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -65,6 +65,7 @@ what it is today. * A_Biondi * alex_carnell * Alan Webb (IBM) +* Allen Kerensky * BigFootAg * BlueWall Slade * brianw/Sir_Ahzz @@ -154,6 +155,7 @@ what it is today. * tglion * tlaukkan/Tommil (Tommi S. E. Laukkanen, Bubble Cloud) * tyre +* Vegaslon * VikingErik * Vytek * webmage (IBM) diff --git a/OpenSim/Addons/Groups/ForeignImporter.cs b/OpenSim/Addons/Groups/ForeignImporter.cs new file mode 100644 index 0000000000..788d21dfc5 --- /dev/null +++ b/OpenSim/Addons/Groups/ForeignImporter.cs @@ -0,0 +1,77 @@ +/* + * 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 OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Groups +{ + public class ForeignImporter + { + IUserManagement m_UserManagement; + public ForeignImporter(IUserManagement uman) + { + m_UserManagement = uman; + } + + public GroupMembersData ConvertGroupMembersData(ExtendedGroupMembersData _m) + { + GroupMembersData m = new GroupMembersData(); + m.AcceptNotices = _m.AcceptNotices; + m.AgentPowers = _m.AgentPowers; + m.Contribution = _m.Contribution; + m.IsOwner = _m.IsOwner; + m.ListInProfile = _m.ListInProfile; + m.OnlineStatus = _m.OnlineStatus; + m.Title = _m.Title; + + string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty; + Util.ParseUniversalUserIdentifier(_m.AgentID, out m.AgentID, out url, out first, out last, out tmp); + if (url != string.Empty) + m_UserManagement.AddUser(m.AgentID, first, last, url); + + return m; + } + + public GroupRoleMembersData ConvertGroupRoleMembersData(ExtendedGroupRoleMembersData _rm) + { + GroupRoleMembersData rm = new GroupRoleMembersData(); + rm.RoleID = _rm.RoleID; + + string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty; + Util.ParseUniversalUserIdentifier(_rm.MemberID, out rm.MemberID, out url, out first, out last, out tmp); + if (url != string.Empty) + m_UserManagement.AddUser(rm.MemberID, first, last, url); + + return rm; + } + + } +} diff --git a/OpenSim/Addons/Groups/GroupsExtendedData.cs b/OpenSim/Addons/Groups/GroupsExtendedData.cs new file mode 100644 index 0000000000..6f4db286f4 --- /dev/null +++ b/OpenSim/Addons/Groups/GroupsExtendedData.cs @@ -0,0 +1,509 @@ +/* + * 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 OpenSim.Framework; +using OpenMetaverse; + +namespace OpenSim.Groups +{ + public class ExtendedGroupRecord : GroupRecord + { + public int MemberCount; + public int RoleCount; + public string ServiceLocation; + public string FounderUUI; + } + + public class ExtendedGroupMembershipData : GroupMembershipData + { + public string AccessToken; + } + + public class ExtendedGroupMembersData + { + // This is the only difference: this is a string + public string AgentID; + public int Contribution; + public string OnlineStatus; + public ulong AgentPowers; + public string Title; + public bool IsOwner; + public bool ListInProfile; + public bool AcceptNotices; + public string AccessToken; + } + + public class ExtendedGroupRoleMembersData + { + public UUID RoleID; + // This is the only difference: this is a string + public string MemberID; + + } + + public struct ExtendedGroupNoticeData + { + public UUID NoticeID; + public uint Timestamp; + public string FromName; + public string Subject; + public bool HasAttachment; + public byte AttachmentType; + public string AttachmentName; + public UUID AttachmentItemID; + public string AttachmentOwnerID; + + public GroupNoticeData ToGroupNoticeData() + { + GroupNoticeData n = new GroupNoticeData(); + n.FromName = this.FromName; + n.AssetType = this.AttachmentType; + n.HasAttachment = this.HasAttachment; + n.NoticeID = this.NoticeID; + n.Subject = this.Subject; + n.Timestamp = this.Timestamp; + + return n; + } + } + + public class GroupsDataUtils + { + public static string Sanitize(string s) + { + return s == null ? string.Empty : s; + } + + public static Dictionary GroupRecord(ExtendedGroupRecord grec) + { + Dictionary dict = new Dictionary(); + if (grec == null) + return dict; + + dict["AllowPublish"] = grec.AllowPublish.ToString(); + dict["Charter"] = Sanitize(grec.Charter); + dict["FounderID"] = grec.FounderID.ToString(); + dict["FounderUUI"] = Sanitize(grec.FounderUUI); + dict["GroupID"] = grec.GroupID.ToString(); + dict["GroupName"] = Sanitize(grec.GroupName); + dict["InsigniaID"] = grec.GroupPicture.ToString(); + dict["MaturePublish"] = grec.MaturePublish.ToString(); + dict["MembershipFee"] = grec.MembershipFee.ToString(); + dict["OpenEnrollment"] = grec.OpenEnrollment.ToString(); + dict["OwnerRoleID"] = grec.OwnerRoleID.ToString(); + dict["ServiceLocation"] = Sanitize(grec.ServiceLocation); + dict["ShownInList"] = grec.ShowInList.ToString(); + dict["MemberCount"] = grec.MemberCount.ToString(); + dict["RoleCount"] = grec.RoleCount.ToString(); + + return dict; + } + + public static ExtendedGroupRecord GroupRecord(Dictionary dict) + { + if (dict == null) + return null; + + ExtendedGroupRecord grec = new ExtendedGroupRecord(); + if (dict.ContainsKey("AllowPublish") && dict["AllowPublish"] != null) + grec.AllowPublish = bool.Parse(dict["AllowPublish"].ToString()); + + if (dict.ContainsKey("Charter") && dict["Charter"] != null) + grec.Charter = dict["Charter"].ToString(); + else + grec.Charter = string.Empty; + + if (dict.ContainsKey("FounderID") && dict["FounderID"] != null) + grec.FounderID = UUID.Parse(dict["FounderID"].ToString()); + + if (dict.ContainsKey("FounderUUI") && dict["FounderUUI"] != null) + grec.FounderUUI = dict["FounderUUI"].ToString(); + else + grec.FounderUUI = string.Empty; + + if (dict.ContainsKey("GroupID") && dict["GroupID"] != null) + grec.GroupID = UUID.Parse(dict["GroupID"].ToString()); + + if (dict.ContainsKey("GroupName") && dict["GroupName"] != null) + grec.GroupName = dict["GroupName"].ToString(); + else + grec.GroupName = string.Empty; + + if (dict.ContainsKey("InsigniaID") && dict["InsigniaID"] != null) + grec.GroupPicture = UUID.Parse(dict["InsigniaID"].ToString()); + + if (dict.ContainsKey("MaturePublish") && dict["MaturePublish"] != null) + grec.MaturePublish = bool.Parse(dict["MaturePublish"].ToString()); + + if (dict.ContainsKey("MembershipFee") && dict["MembershipFee"] != null) + grec.MembershipFee = Int32.Parse(dict["MembershipFee"].ToString()); + + if (dict.ContainsKey("OpenEnrollment") && dict["OpenEnrollment"] != null) + grec.OpenEnrollment = bool.Parse(dict["OpenEnrollment"].ToString()); + + if (dict.ContainsKey("OwnerRoleID") && dict["OwnerRoleID"] != null) + grec.OwnerRoleID = UUID.Parse(dict["OwnerRoleID"].ToString()); + + if (dict.ContainsKey("ServiceLocation") && dict["ServiceLocation"] != null) + grec.ServiceLocation = dict["ServiceLocation"].ToString(); + else + grec.GroupName = string.Empty; + + if (dict.ContainsKey("ShownInList") && dict["ShownInList"] != null) + grec.ShowInList = bool.Parse(dict["ShownInList"].ToString()); + + if (dict.ContainsKey("MemberCount") && dict["MemberCount"] != null) + grec.MemberCount = Int32.Parse(dict["MemberCount"].ToString()); + + if (dict.ContainsKey("RoleCount") && dict["RoleCount"] != null) + grec.RoleCount = Int32.Parse(dict["RoleCount"].ToString()); + + return grec; + } + + public static Dictionary GroupMembershipData(ExtendedGroupMembershipData membership) + { + Dictionary dict = new Dictionary(); + if (membership == null) + return dict; + + dict["AcceptNotices"] = membership.AcceptNotices.ToString(); + dict["AccessToken"] = Sanitize(membership.AccessToken); + dict["Active"] = membership.Active.ToString(); + dict["ActiveRole"] = membership.ActiveRole.ToString(); + dict["AllowPublish"] = membership.AllowPublish.ToString(); + dict["Charter"] = Sanitize(membership.Charter); + dict["Contribution"] = membership.Contribution.ToString(); + dict["FounderID"] = membership.FounderID.ToString(); + dict["GroupID"] = membership.GroupID.ToString(); + dict["GroupName"] = Sanitize(membership.GroupName); + dict["GroupPicture"] = membership.GroupPicture.ToString(); + dict["GroupPowers"] = membership.GroupPowers.ToString(); + dict["GroupTitle"] = Sanitize(membership.GroupTitle); + dict["ListInProfile"] = membership.ListInProfile.ToString(); + dict["MaturePublish"] = membership.MaturePublish.ToString(); + dict["MembershipFee"] = membership.MembershipFee.ToString(); + dict["OpenEnrollment"] = membership.OpenEnrollment.ToString(); + dict["ShowInList"] = membership.ShowInList.ToString(); + + return dict; + } + + public static ExtendedGroupMembershipData GroupMembershipData(Dictionary dict) + { + if (dict == null) + return null; + + ExtendedGroupMembershipData membership = new ExtendedGroupMembershipData(); + + if (dict.ContainsKey("AcceptNotices") && dict["AcceptNotices"] != null) + membership.AcceptNotices = bool.Parse(dict["AcceptNotices"].ToString()); + + if (dict.ContainsKey("AccessToken") && dict["AccessToken"] != null) + membership.AccessToken = dict["AccessToken"].ToString(); + else + membership.AccessToken = string.Empty; + + if (dict.ContainsKey("Active") && dict["Active"] != null) + membership.Active = bool.Parse(dict["Active"].ToString()); + + if (dict.ContainsKey("ActiveRole") && dict["ActiveRole"] != null) + membership.ActiveRole = UUID.Parse(dict["ActiveRole"].ToString()); + + if (dict.ContainsKey("AllowPublish") && dict["AllowPublish"] != null) + membership.AllowPublish = bool.Parse(dict["AllowPublish"].ToString()); + + if (dict.ContainsKey("Charter") && dict["Charter"] != null) + membership.Charter = dict["Charter"].ToString(); + else + membership.Charter = string.Empty; + + if (dict.ContainsKey("Contribution") && dict["Contribution"] != null) + membership.Contribution = Int32.Parse(dict["Contribution"].ToString()); + + if (dict.ContainsKey("FounderID") && dict["FounderID"] != null) + membership.FounderID = UUID.Parse(dict["FounderID"].ToString()); + + if (dict.ContainsKey("GroupID") && dict["GroupID"] != null) + membership.GroupID = UUID.Parse(dict["GroupID"].ToString()); + + if (dict.ContainsKey("GroupName") && dict["GroupName"] != null) + membership.GroupName = dict["GroupName"].ToString(); + else + membership.GroupName = string.Empty; + + if (dict.ContainsKey("GroupPicture") && dict["GroupPicture"] != null) + membership.GroupPicture = UUID.Parse(dict["GroupPicture"].ToString()); + + if (dict.ContainsKey("GroupPowers") && dict["GroupPowers"] != null) + membership.GroupPowers = UInt64.Parse(dict["GroupPowers"].ToString()); + + if (dict.ContainsKey("GroupTitle") && dict["GroupTitle"] != null) + membership.GroupTitle = dict["GroupTitle"].ToString(); + else + membership.GroupTitle = string.Empty; + + if (dict.ContainsKey("ListInProfile") && dict["ListInProfile"] != null) + membership.ListInProfile = bool.Parse(dict["ListInProfile"].ToString()); + + if (dict.ContainsKey("MaturePublish") && dict["MaturePublish"] != null) + membership.MaturePublish = bool.Parse(dict["MaturePublish"].ToString()); + + if (dict.ContainsKey("MembershipFee") && dict["MembershipFee"] != null) + membership.MembershipFee = Int32.Parse(dict["MembershipFee"].ToString()); + + if (dict.ContainsKey("OpenEnrollment") && dict["OpenEnrollment"] != null) + membership.OpenEnrollment = bool.Parse(dict["OpenEnrollment"].ToString()); + + if (dict.ContainsKey("ShowInList") && dict["ShowInList"] != null) + membership.ShowInList = bool.Parse(dict["ShowInList"].ToString()); + + return membership; + } + + public static Dictionary GroupMembersData(ExtendedGroupMembersData member) + { + Dictionary dict = new Dictionary(); + + dict["AcceptNotices"] = member.AcceptNotices.ToString(); + dict["AccessToken"] = Sanitize(member.AccessToken); + dict["AgentID"] = Sanitize(member.AgentID); + dict["AgentPowers"] = member.AgentPowers.ToString(); + dict["Contribution"] = member.Contribution.ToString(); + dict["IsOwner"] = member.IsOwner.ToString(); + dict["ListInProfile"] = member.ListInProfile.ToString(); + dict["OnlineStatus"] = Sanitize(member.OnlineStatus); + dict["Title"] = Sanitize(member.Title); + + return dict; + } + + public static ExtendedGroupMembersData GroupMembersData(Dictionary dict) + { + ExtendedGroupMembersData member = new ExtendedGroupMembersData(); + + if (dict == null) + return member; + + if (dict.ContainsKey("AcceptNotices") && dict["AcceptNotices"] != null) + member.AcceptNotices = bool.Parse(dict["AcceptNotices"].ToString()); + + if (dict.ContainsKey("AccessToken") && dict["AccessToken"] != null) + member.AccessToken = Sanitize(dict["AccessToken"].ToString()); + else + member.AccessToken = string.Empty; + + if (dict.ContainsKey("AgentID") && dict["AgentID"] != null) + member.AgentID = Sanitize(dict["AgentID"].ToString()); + else + member.AgentID = UUID.Zero.ToString(); + + if (dict.ContainsKey("AgentPowers") && dict["AgentPowers"] != null) + member.AgentPowers = UInt64.Parse(dict["AgentPowers"].ToString()); + + if (dict.ContainsKey("Contribution") && dict["Contribution"] != null) + member.Contribution = Int32.Parse(dict["Contribution"].ToString()); + + if (dict.ContainsKey("IsOwner") && dict["IsOwner"] != null) + member.IsOwner = bool.Parse(dict["IsOwner"].ToString()); + + if (dict.ContainsKey("ListInProfile") && dict["ListInProfile"] != null) + member.ListInProfile = bool.Parse(dict["ListInProfile"].ToString()); + + if (dict.ContainsKey("OnlineStatus") && dict["OnlineStatus"] != null) + member.OnlineStatus = Sanitize(dict["OnlineStatus"].ToString()); + else + member.OnlineStatus = string.Empty; + + if (dict.ContainsKey("Title") && dict["Title"] != null) + member.Title = Sanitize(dict["Title"].ToString()); + else + member.Title = string.Empty; + + return member; + } + + public static Dictionary GroupRolesData(GroupRolesData role) + { + Dictionary dict = new Dictionary(); + + dict["Description"] = Sanitize(role.Description); + dict["Members"] = role.Members.ToString(); + dict["Name"] = Sanitize(role.Name); + dict["Powers"] = role.Powers.ToString(); + dict["RoleID"] = role.RoleID.ToString(); + dict["Title"] = Sanitize(role.Title); + + return dict; + } + + public static GroupRolesData GroupRolesData(Dictionary dict) + { + GroupRolesData role = new GroupRolesData(); + + if (dict == null) + return role; + + if (dict.ContainsKey("Description") && dict["Description"] != null) + role.Description = Sanitize(dict["Description"].ToString()); + else + role.Description = string.Empty; + + if (dict.ContainsKey("Members") && dict["Members"] != null) + role.Members = Int32.Parse(dict["Members"].ToString()); + + if (dict.ContainsKey("Name") && dict["Name"] != null) + role.Name = Sanitize(dict["Name"].ToString()); + else + role.Name = string.Empty; + + if (dict.ContainsKey("Powers") && dict["Powers"] != null) + role.Powers = UInt64.Parse(dict["Powers"].ToString()); + + if (dict.ContainsKey("Title") && dict["Title"] != null) + role.Title = Sanitize(dict["Title"].ToString()); + else + role.Title = string.Empty; + + if (dict.ContainsKey("RoleID") && dict["RoleID"] != null) + role.RoleID = UUID.Parse(dict["RoleID"].ToString()); + + return role; + } + + public static Dictionary GroupRoleMembersData(ExtendedGroupRoleMembersData rmember) + { + Dictionary dict = new Dictionary(); + + dict["RoleID"] = rmember.RoleID.ToString(); + dict["MemberID"] = rmember.MemberID; + return dict; + } + + public static ExtendedGroupRoleMembersData GroupRoleMembersData(Dictionary dict) + { + ExtendedGroupRoleMembersData rmember = new ExtendedGroupRoleMembersData(); + + if (dict.ContainsKey("RoleID") && dict["RoleID"] != null) + rmember.RoleID = new UUID(dict["RoleID"].ToString()); + + if (dict.ContainsKey("MemberID") && dict["MemberID"] != null) + rmember.MemberID = dict["MemberID"].ToString(); + + return rmember; + } + + public static Dictionary GroupInviteInfo(GroupInviteInfo invite) + { + Dictionary dict = new Dictionary(); + + dict["InviteID"] = invite.InviteID.ToString(); + dict["GroupID"] = invite.GroupID.ToString(); + dict["RoleID"] = invite.RoleID.ToString(); + dict["AgentID"] = invite.AgentID; + + return dict; + } + + public static GroupInviteInfo GroupInviteInfo(Dictionary dict) + { + if (dict == null) + return null; + + GroupInviteInfo invite = new GroupInviteInfo(); + + invite.InviteID = new UUID(dict["InviteID"].ToString()); + invite.GroupID = new UUID(dict["GroupID"].ToString()); + invite.RoleID = new UUID(dict["RoleID"].ToString()); + invite.AgentID = Sanitize(dict["AgentID"].ToString()); + + return invite; + } + + public static Dictionary GroupNoticeData(ExtendedGroupNoticeData notice) + { + Dictionary dict = new Dictionary(); + + dict["NoticeID"] = notice.NoticeID.ToString(); + dict["Timestamp"] = notice.Timestamp.ToString(); + dict["FromName"] = Sanitize(notice.FromName); + dict["Subject"] = Sanitize(notice.Subject); + dict["HasAttachment"] = notice.HasAttachment.ToString(); + dict["AttachmentItemID"] = notice.AttachmentItemID.ToString(); + dict["AttachmentName"] = Sanitize(notice.AttachmentName); + dict["AttachmentType"] = notice.AttachmentType.ToString(); + dict["AttachmentOwnerID"] = Sanitize(notice.AttachmentOwnerID); + + return dict; + } + + public static ExtendedGroupNoticeData GroupNoticeData(Dictionary dict) + { + ExtendedGroupNoticeData notice = new ExtendedGroupNoticeData(); + + if (dict == null) + return notice; + + notice.NoticeID = new UUID(dict["NoticeID"].ToString()); + notice.Timestamp = UInt32.Parse(dict["Timestamp"].ToString()); + notice.FromName = Sanitize(dict["FromName"].ToString()); + notice.Subject = Sanitize(dict["Subject"].ToString()); + notice.HasAttachment = bool.Parse(dict["HasAttachment"].ToString()); + notice.AttachmentItemID = new UUID(dict["AttachmentItemID"].ToString()); + notice.AttachmentName = dict["AttachmentName"].ToString(); + notice.AttachmentType = byte.Parse(dict["AttachmentType"].ToString()); + notice.AttachmentOwnerID = dict["AttachmentOwnerID"].ToString(); + + return notice; + } + + public static Dictionary GroupNoticeInfo(GroupNoticeInfo notice) + { + Dictionary dict = GroupNoticeData(notice.noticeData); + + dict["GroupID"] = notice.GroupID.ToString(); + dict["Message"] = Sanitize(notice.Message); + + return dict; + } + + public static GroupNoticeInfo GroupNoticeInfo(Dictionary dict) + { + GroupNoticeInfo notice = new GroupNoticeInfo(); + + notice.noticeData = GroupNoticeData(dict); + notice.GroupID = new UUID(dict["GroupID"].ToString()); + notice.Message = Sanitize(dict["Message"].ToString()); + + return notice; + } + } + +} diff --git a/OpenSim/Addons/Groups/GroupsMessagingModule.cs b/OpenSim/Addons/Groups/GroupsMessagingModule.cs new file mode 100644 index 0000000000..d172d48c6c --- /dev/null +++ b/OpenSim/Addons/Groups/GroupsMessagingModule.cs @@ -0,0 +1,594 @@ +/* + * 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.Linq; +using System.Reflection; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo; + +namespace OpenSim.Groups +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsMessagingModule")] + public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private List m_sceneList = new List(); + private IPresenceService m_presenceService; + + private IMessageTransferModule m_msgTransferModule = null; + + private IGroupsServicesConnector m_groupData = null; + + // Config Options + private bool m_groupMessagingEnabled = false; + private bool m_debugEnabled = true; + + /// + /// If enabled, module only tries to send group IMs to online users by querying cached presence information. + /// + private bool m_messageOnlineAgentsOnly; + + /// + /// Cache for online users. + /// + /// + /// Group ID is key, presence information for online members is value. + /// Will only be non-null if m_messageOnlineAgentsOnly = true + /// We cache here so that group messages don't constantly have to re-request the online user list to avoid + /// attempted expensive sending of messages to offline users. + /// The tradeoff is that a user that comes online will not receive messages consistently from all other users + /// until caches have updated. + /// Therefore, we set the cache expiry to just 20 seconds. + /// + private ExpiringCache m_usersOnlineCache; + + private int m_usersOnlineCacheExpirySeconds = 20; + + #region Region Module interfaceBase Members + + public void Initialise(IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + + if (groupsConfig == null) + // Do not run this module by default. + return; + + // if groups aren't enabled, we're not needed. + // if we're not specified as the connector to use, then we're not wanted + if ((groupsConfig.GetBoolean("Enabled", false) == false) + || (groupsConfig.GetString("MessagingModule", "") != Name)) + { + m_groupMessagingEnabled = false; + return; + } + + m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true); + + if (!m_groupMessagingEnabled) + return; + + m_messageOnlineAgentsOnly = groupsConfig.GetBoolean("MessageOnlineUsersOnly", false); + + if (m_messageOnlineAgentsOnly) + m_usersOnlineCache = new ExpiringCache(); + + m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true); + + m_log.InfoFormat( + "[Groups.Messaging]: GroupsMessagingModule enabled with MessageOnlineOnly = {0}, DebugEnabled = {1}", + m_messageOnlineAgentsOnly, m_debugEnabled); + } + + public void AddRegion(Scene scene) + { + if (!m_groupMessagingEnabled) + return; + + scene.RegisterModuleInterface(this); + m_sceneList.Add(scene); + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; + scene.EventManager.OnClientLogin += OnClientLogin; + } + + public void RegionLoaded(Scene scene) + { + if (!m_groupMessagingEnabled) + return; + + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_groupData = scene.RequestModuleInterface(); + + // No groups module, no groups messaging + if (m_groupData == null) + { + m_log.Error("[Groups.Messaging]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled."); + RemoveRegion(scene); + return; + } + + m_msgTransferModule = scene.RequestModuleInterface(); + + // No message transfer module, no groups messaging + if (m_msgTransferModule == null) + { + m_log.Error("[Groups.Messaging]: Could not get MessageTransferModule"); + RemoveRegion(scene); + return; + } + + if (m_presenceService == null) + m_presenceService = scene.PresenceService; + + } + + public void RemoveRegion(Scene scene) + { + if (!m_groupMessagingEnabled) + return; + + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_sceneList.Remove(scene); + scene.EventManager.OnNewClient -= OnNewClient; + scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage; + scene.EventManager.OnClientLogin -= OnClientLogin; + scene.UnregisterModuleInterface(this); + } + + public void Close() + { + if (!m_groupMessagingEnabled) + return; + + if (m_debugEnabled) m_log.Debug("[Groups.Messaging]: Shutting down GroupsMessagingModule module."); + + m_sceneList.Clear(); + + m_groupData = null; + m_msgTransferModule = null; + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public string Name + { + get { return "Groups Messaging Module V2"; } + } + + public void PostInitialise() + { + // NoOp + } + + #endregion + + + /// + /// Not really needed, but does confirm that the group exists. + /// + public bool StartGroupChatSession(UUID agentID, UUID groupID) + { + if (m_debugEnabled) + m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID.ToString(), groupID, null); + + if (groupInfo != null) + { + return true; + } + else + { + return false; + } + } + + public void SendMessageToGroup(GridInstantMessage im, UUID groupID) + { + List groupMembers = m_groupData.GetGroupMembers(new UUID(im.fromAgentID).ToString(), groupID); + int groupMembersCount = groupMembers.Count; + + if (m_messageOnlineAgentsOnly) + { + string[] t1 = groupMembers.ConvertAll(gmd => gmd.AgentID.ToString()).ToArray(); + + // We cache in order not to overwhlem the presence service on large grids with many groups. This does + // mean that members coming online will not see all group members until after m_usersOnlineCacheExpirySeconds has elapsed. + // (assuming this is the same across all grid simulators). + PresenceInfo[] onlineAgents; + if (!m_usersOnlineCache.TryGetValue(groupID, out onlineAgents)) + { + onlineAgents = m_presenceService.GetAgents(t1); + m_usersOnlineCache.Add(groupID, onlineAgents, m_usersOnlineCacheExpirySeconds); + } + + HashSet onlineAgentsUuidSet = new HashSet(); + Array.ForEach(onlineAgents, pi => onlineAgentsUuidSet.Add(pi.UserID)); + + groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList(); + + // if (m_debugEnabled) +// m_log.DebugFormat( +// "[Groups.Messaging]: SendMessageToGroup called for group {0} with {1} visible members, {2} online", +// groupID, groupMembersCount, groupMembers.Count()); + } + else + { + if (m_debugEnabled) + m_log.DebugFormat( + "[Groups.Messaging]: SendMessageToGroup called for group {0} with {1} visible members", + groupID, groupMembers.Count); + } + + int requestStartTick = Environment.TickCount; + + foreach (GroupMembersData member in groupMembers) + { + if (m_groupData.hasAgentDroppedGroupChatSession(member.AgentID.ToString(), groupID)) + { + // Don't deliver messages to people who have dropped this session + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} has dropped session, not delivering to them", member.AgentID); + continue; + } + + // Copy Message + GridInstantMessage msg = new GridInstantMessage(); + msg.imSessionID = groupID.Guid; + msg.fromAgentName = im.fromAgentName; + msg.message = im.message; + msg.dialog = im.dialog; + msg.offline = im.offline; + msg.ParentEstateID = im.ParentEstateID; + msg.Position = im.Position; + msg.RegionID = im.RegionID; + msg.binaryBucket = im.binaryBucket; + msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + + msg.fromAgentID = im.fromAgentID; + msg.fromGroup = true; + + msg.toAgentID = member.AgentID.Guid; + + IClientAPI client = GetActiveClient(member.AgentID); + if (client == null) + { + // If they're not local, forward across the grid + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} via Grid", member.AgentID); + m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { }); + } + else + { + // Deliver locally, directly + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name); + ProcessMessageFromGroupSession(msg); + } + } + + // Temporary for assessing how long it still takes to send messages to large online groups. + if (m_messageOnlineAgentsOnly) + m_log.DebugFormat( + "[Groups.Messaging]: SendMessageToGroup for group {0} with {1} visible members, {2} online took {3}ms", + groupID, groupMembersCount, groupMembers.Count(), Environment.TickCount - requestStartTick); + } + + #region SimGridEventHandlers + + void OnClientLogin(IClientAPI client) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name); + } + + private void OnNewClient(IClientAPI client) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name); + + client.OnInstantMessage += OnInstantMessage; + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // The instant message module will only deliver messages of dialog types: + // MessageFromAgent, StartTyping, StopTyping, MessageFromObject + // + // Any other message type will not be delivered to a client by the + // Instant Message Module + + + if (m_debugEnabled) + { + m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + DebugGridInstantMessage(msg); + } + + // Incoming message from a group + if ((msg.fromGroup == true) && + ((msg.dialog == (byte)InstantMessageDialog.SessionSend) + || (msg.dialog == (byte)InstantMessageDialog.SessionAdd) + || (msg.dialog == (byte)InstantMessageDialog.SessionDrop))) + { + ProcessMessageFromGroupSession(msg); + } + } + + private void ProcessMessageFromGroupSession(GridInstantMessage msg) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID); + + UUID AgentID = new UUID(msg.fromAgentID); + UUID GroupID = new UUID(msg.imSessionID); + + switch (msg.dialog) + { + case (byte)InstantMessageDialog.SessionAdd: + m_groupData.AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID); + break; + + case (byte)InstantMessageDialog.SessionDrop: + m_groupData.AgentDroppedFromGroupChatSession(AgentID.ToString(), GroupID); + break; + + case (byte)InstantMessageDialog.SessionSend: + if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID.ToString(), GroupID) + && !m_groupData.hasAgentBeenInvitedToGroupChatSession(AgentID.ToString(), GroupID) + ) + { + // Agent not in session and hasn't dropped from session + // Add them to the session for now, and Invite them + m_groupData.AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID); + + UUID toAgentID = new UUID(msg.toAgentID); + IClientAPI activeClient = GetActiveClient(toAgentID); + if (activeClient != null) + { + GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null); + if (groupInfo != null) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Sending chatterbox invite instant message"); + + // Force? open the group session dialog??? + // and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg); + IEventQueue eq = activeClient.Scene.RequestModuleInterface(); + eq.ChatterboxInvitation( + GroupID + , groupInfo.GroupName + , new UUID(msg.fromAgentID) + , msg.message + , new UUID(msg.toAgentID) + , msg.fromAgentName + , msg.dialog + , msg.timestamp + , msg.offline == 1 + , (int)msg.ParentEstateID + , msg.Position + , 1 + , new UUID(msg.imSessionID) + , msg.fromGroup + , OpenMetaverse.Utils.StringToBytes(groupInfo.GroupName) + ); + + eq.ChatterBoxSessionAgentListUpdates( + new UUID(GroupID) + , new UUID(msg.fromAgentID) + , new UUID(msg.toAgentID) + , false //canVoiceChat + , false //isModerator + , false //text mute + ); + } + } + } + else if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID.ToString(), GroupID)) + { + // User hasn't dropped, so they're in the session, + // maybe we should deliver it. + IClientAPI client = GetActiveClient(new UUID(msg.toAgentID)); + if (client != null) + { + // Deliver locally, directly + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} locally", client.Name); + client.SendInstantMessage(msg); + } + else + { + m_log.WarnFormat("[Groups.Messaging]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID); + } + } + break; + + default: + m_log.WarnFormat("[Groups.Messaging]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString()); + break; + } + } + + #endregion + + + #region ClientEvents + private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) + { + if (m_debugEnabled) + { + m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + DebugGridInstantMessage(im); + } + + // Start group IM session + if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart)) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups.Messaging]: imSessionID({0}) toAgentID({1})", im.imSessionID, im.toAgentID); + + UUID GroupID = new UUID(im.imSessionID); + UUID AgentID = new UUID(im.fromAgentID); + + GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null); + + if (groupInfo != null) + { + m_groupData.AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID); + + ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, GroupID); + + IEventQueue queue = remoteClient.Scene.RequestModuleInterface(); + queue.ChatterBoxSessionAgentListUpdates( + GroupID + , AgentID + , new UUID(im.toAgentID) + , false //canVoiceChat + , false //isModerator + , false //text mute + ); + } + } + + // Send a message from locally connected client to a group + if ((im.dialog == (byte)InstantMessageDialog.SessionSend)) + { + UUID GroupID = new UUID(im.imSessionID); + UUID AgentID = new UUID(im.fromAgentID); + + if (m_debugEnabled) + m_log.DebugFormat("[Groups.Messaging]: Send message to session for group {0} with session ID {1}", GroupID, im.imSessionID.ToString()); + + //If this agent is sending a message, then they want to be in the session + m_groupData.AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID); + + SendMessageToGroup(im, GroupID); + } + } + + #endregion + + void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + OSDMap moderatedMap = new OSDMap(4); + moderatedMap.Add("voice", OSD.FromBoolean(false)); + + OSDMap sessionMap = new OSDMap(4); + sessionMap.Add("moderated_mode", moderatedMap); + sessionMap.Add("session_name", OSD.FromString(groupName)); + sessionMap.Add("type", OSD.FromInteger(0)); + sessionMap.Add("voice_enabled", OSD.FromBoolean(false)); + + OSDMap bodyMap = new OSDMap(4); + bodyMap.Add("session_id", OSD.FromUUID(groupID)); + bodyMap.Add("temp_session_id", OSD.FromUUID(groupID)); + bodyMap.Add("success", OSD.FromBoolean(true)); + bodyMap.Add("session_info", sessionMap); + + IEventQueue queue = remoteClient.Scene.RequestModuleInterface(); + + if (queue != null) + { + queue.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId); + } + } + + private void DebugGridInstantMessage(GridInstantMessage im) + { + // Don't log any normal IMs (privacy!) + if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent) + { + m_log.WarnFormat("[Groups.Messaging]: IM: fromGroup({0})", im.fromGroup ? "True" : "False"); + m_log.WarnFormat("[Groups.Messaging]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentID({0})", im.fromAgentID.ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentName({0})", im.fromAgentName.ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: imSessionID({0})", im.imSessionID.ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: message({0})", im.message.ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: offline({0})", im.offline.ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: toAgentID({0})", im.toAgentID.ToString()); + m_log.WarnFormat("[Groups.Messaging]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket")); + } + } + + #region Client Tools + + /// + /// Try to find an active IClientAPI reference for agentID giving preference to root connections + /// + private IClientAPI GetActiveClient(UUID agentID) + { + if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Looking for local client {0}", agentID); + + IClientAPI child = null; + + // Try root avatar first + foreach (Scene scene in m_sceneList) + { + ScenePresence sp = scene.GetScenePresence(agentID); + if (sp != null) + { + if (!sp.IsChildAgent) + { + if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Found root agent for client : {0}", sp.ControllingClient.Name); + return sp.ControllingClient; + } + else + { + if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Found child agent for client : {0}", sp.ControllingClient.Name); + child = sp.ControllingClient; + } + } + } + + // If we didn't find a root, then just return whichever child we found, or null if none + if (child == null) + { + if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Could not find local client for agent : {0}", agentID); + } + else + { + if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Returning child agent for client : {0}", child.Name); + } + return child; + } + + #endregion + } +} diff --git a/OpenSim/Addons/Groups/GroupsModule.cs b/OpenSim/Addons/Groups/GroupsModule.cs new file mode 100644 index 0000000000..10bfa8f852 --- /dev/null +++ b/OpenSim/Addons/Groups/GroupsModule.cs @@ -0,0 +1,1467 @@ +/* + * 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.Reflection; +using System.Timers; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags; + +namespace OpenSim.Groups +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsModule")] + public class GroupsModule : ISharedRegionModule, IGroupsModule + { + /// + /// + + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private List m_sceneList = new List(); + + private IMessageTransferModule m_msgTransferModule = null; + + private IGroupsServicesConnector m_groupData = null; + private IUserManagement m_UserManagement; + + // Configuration settings + private bool m_groupsEnabled = false; + private bool m_groupNoticesEnabled = true; + private bool m_debugEnabled = false; + private int m_levelGroupCreate = 0; + + #region Region Module interfaceBase Members + + public void Initialise(IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + + if (groupsConfig == null) + { + // Do not run this module by default. + return; + } + else + { + m_groupsEnabled = groupsConfig.GetBoolean("Enabled", false); + if (!m_groupsEnabled) + { + return; + } + + if (groupsConfig.GetString("Module", "Default") != Name) + { + m_groupsEnabled = false; + + return; + } + + m_log.InfoFormat("[Groups]: Initializing {0}", this.Name); + + m_groupNoticesEnabled = groupsConfig.GetBoolean("NoticesEnabled", true); + m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", false); + m_levelGroupCreate = groupsConfig.GetInt("LevelGroupCreate", 0); + } + } + + public void AddRegion(Scene scene) + { + if (m_groupsEnabled) + { + scene.RegisterModuleInterface(this); + scene.AddCommand( + "debug", + this, + "debug groups verbose", + "debug groups verbose ", + "This setting turns on very verbose groups debugging", + HandleDebugGroupsVerbose); + } + } + + private void HandleDebugGroupsVerbose(object modules, string[] args) + { + if (args.Length < 4) + { + MainConsole.Instance.Output("Usage: debug groups verbose "); + return; + } + + bool verbose = false; + if (!bool.TryParse(args[3], out verbose)) + { + MainConsole.Instance.Output("Usage: debug groups verbose "); + return; + } + + m_debugEnabled = verbose; + + MainConsole.Instance.OutputFormat("{0} verbose logging set to {1}", Name, m_debugEnabled); + } + + public void RegionLoaded(Scene scene) + { + if (!m_groupsEnabled) + return; + + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; + // The InstantMessageModule itself doesn't do this, + // so lets see if things explode if we don't do it + // scene.EventManager.OnClientClosed += OnClientClosed; + + if (m_groupData == null) + { + m_groupData = scene.RequestModuleInterface(); + + // No Groups Service Connector, then nothing works... + if (m_groupData == null) + { + m_groupsEnabled = false; + m_log.Error("[Groups]: Could not get IGroupsServicesConnector"); + RemoveRegion(scene); + return; + } + } + + if (m_msgTransferModule == null) + { + m_msgTransferModule = scene.RequestModuleInterface(); + + // No message transfer module, no notices, group invites, rejects, ejects, etc + if (m_msgTransferModule == null) + { + m_log.Warn("[Groups]: Could not get MessageTransferModule"); + } + } + + if (m_UserManagement == null) + { + m_UserManagement = scene.RequestModuleInterface(); + if (m_UserManagement == null) + m_log.Warn("[Groups]: Could not get UserManagementModule"); + } + + lock (m_sceneList) + { + m_sceneList.Add(scene); + } + + + } + + public void RemoveRegion(Scene scene) + { + if (!m_groupsEnabled) + return; + + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + scene.EventManager.OnNewClient -= OnNewClient; + scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage; + + lock (m_sceneList) + { + m_sceneList.Remove(scene); + } + } + + public void Close() + { + if (!m_groupsEnabled) + return; + + if (m_debugEnabled) m_log.Debug("[Groups]: Shutting down Groups module."); + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public string Name + { + get { return "Groups Module V2"; } + } + + public void PostInitialise() + { + // NoOp + } + + #endregion + + #region EventHandlers + private void OnNewClient(IClientAPI client) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + client.OnUUIDGroupNameRequest += HandleUUIDGroupNameRequest; + client.OnAgentDataUpdateRequest += OnAgentDataUpdateRequest; + client.OnDirFindQuery += OnDirFindQuery; + client.OnRequestAvatarProperties += OnRequestAvatarProperties; + + // Used for Notices and Group Invites/Accept/Reject + client.OnInstantMessage += OnInstantMessage; + + // Send client their groups information. + SendAgentGroupDataUpdate(client, client.AgentId); + } + + private void OnRequestAvatarProperties(IClientAPI remoteClient, UUID avatarID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + //GroupMembershipData[] avatarGroups = m_groupData.GetAgentGroupMemberships(GetRequestingAgentID(remoteClient), avatarID).ToArray(); + GroupMembershipData[] avatarGroups = GetProfileListedGroupMemberships(remoteClient, avatarID); + remoteClient.SendAvatarGroupsReply(avatarID, avatarGroups); + } + + /* + * This becomes very problematic in a shared module. In a shared module you may have more then one + * reference to IClientAPI's, one for 0 or 1 root connections, and 0 or more child connections. + * The OnClientClosed event does not provide anything to indicate which one of those should be closed + * nor does it provide what scene it was from so that the specific reference can be looked up. + * The InstantMessageModule.cs does not currently worry about unregistering the handles, + * and it should be an issue, since it's the client that references us not the other way around + * , so as long as we don't keep a reference to the client laying around, the client can still be GC'ed + private void OnClientClosed(UUID AgentId) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + lock (m_ActiveClients) + { + if (m_ActiveClients.ContainsKey(AgentId)) + { + IClientAPI client = m_ActiveClients[AgentId]; + client.OnUUIDGroupNameRequest -= HandleUUIDGroupNameRequest; + client.OnAgentDataUpdateRequest -= OnAgentDataUpdateRequest; + client.OnDirFindQuery -= OnDirFindQuery; + client.OnInstantMessage -= OnInstantMessage; + + m_ActiveClients.Remove(AgentId); + } + else + { + if (m_debugEnabled) m_log.WarnFormat("[Groups]: Client closed that wasn't registered here."); + } + + + } + } + */ + + void OnDirFindQuery(IClientAPI remoteClient, UUID queryID, string queryText, uint queryFlags, int queryStart) + { + if (((DirFindFlags)queryFlags & DirFindFlags.Groups) == DirFindFlags.Groups) + { + if (m_debugEnabled) + m_log.DebugFormat( + "[Groups]: {0} called with queryText({1}) queryFlags({2}) queryStart({3})", + System.Reflection.MethodBase.GetCurrentMethod().Name, queryText, (DirFindFlags)queryFlags, queryStart); + + // TODO: This currently ignores pretty much all the query flags including Mature and sort order + remoteClient.SendDirGroupsReply(queryID, m_groupData.FindGroups(GetRequestingAgentIDStr(remoteClient), queryText).ToArray()); + } + + } + + private void OnAgentDataUpdateRequest(IClientAPI remoteClient, UUID dataForAgentID, UUID sessionID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + UUID activeGroupID = UUID.Zero; + string activeGroupTitle = string.Empty; + string activeGroupName = string.Empty; + ulong activeGroupPowers = (ulong)GroupPowers.None; + + GroupMembershipData membership = m_groupData.GetAgentActiveMembership(GetRequestingAgentIDStr(remoteClient), dataForAgentID.ToString()); + if (membership != null) + { + activeGroupID = membership.GroupID; + activeGroupTitle = membership.GroupTitle; + activeGroupPowers = membership.GroupPowers; + } + + SendAgentDataUpdate(remoteClient, dataForAgentID, activeGroupID, activeGroupName, activeGroupPowers, activeGroupTitle); + + SendScenePresenceUpdate(dataForAgentID, activeGroupTitle); + } + + private void HandleUUIDGroupNameRequest(UUID GroupID, IClientAPI remoteClient) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + string GroupName; + + GroupRecord group = m_groupData.GetGroupRecord(GetRequestingAgentIDStr(remoteClient), GroupID, null); + if (group != null) + { + GroupName = group.GroupName; + } + else + { + GroupName = "Unknown"; + } + + remoteClient.SendGroupNameReply(GroupID, GroupName); + } + + private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_log.DebugFormat("[Groups]: IM From {0} to {1} msg {2} type {3}", im.fromAgentID, im.toAgentID, im.message, (InstantMessageDialog)im.dialog); + // Group invitations + if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) + { + UUID inviteID = new UUID(im.imSessionID); + GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetRequestingAgentIDStr(remoteClient), inviteID); + + if (inviteInfo == null) + { + if (m_debugEnabled) m_log.WarnFormat("[Groups]: Received an Invite IM for an invite that does not exist {0}.", inviteID); + return; + } + + //m_log.DebugFormat("[XXX]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID); + + UUID fromAgentID = new UUID(im.fromAgentID); + UUID invitee = UUID.Zero; + string tmp = string.Empty; + Util.ParseUniversalUserIdentifier(inviteInfo.AgentID, out invitee, out tmp, out tmp, out tmp, out tmp); + if ((inviteInfo != null) && (fromAgentID == invitee)) + { + // Accept + if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) + { + //m_log.DebugFormat("[XXX]: Received an accept invite notice."); + + // and the sessionid is the role + string reason = string.Empty; + if (!m_groupData.AddAgentToGroup(GetRequestingAgentIDStr(remoteClient), invitee.ToString(), inviteInfo.GroupID, inviteInfo.RoleID, string.Empty, out reason)) + remoteClient.SendAgentAlertMessage("Unable to add you to the group: " + reason, false); + else + { + GridInstantMessage msg = new GridInstantMessage(); + msg.imSessionID = UUID.Zero.Guid; + msg.fromAgentID = UUID.Zero.Guid; + msg.toAgentID = invitee.Guid; + msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + msg.fromAgentName = "Groups"; + msg.message = string.Format("You have been added to the group."); + msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox; + msg.fromGroup = false; + msg.offline = (byte)0; + msg.ParentEstateID = 0; + msg.Position = Vector3.Zero; + msg.RegionID = UUID.Zero.Guid; + msg.binaryBucket = new byte[0]; + + OutgoingInstantMessage(msg, invitee); + + UpdateAllClientsWithGroupInfo(invitee); + } + + m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentIDStr(remoteClient), inviteID); + + } + + // Reject + if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: Received a reject invite notice."); + m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentIDStr(remoteClient), inviteID); + + m_groupData.RemoveAgentFromGroup(GetRequestingAgentIDStr(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID); + } + } + } + + // Group notices + if ((im.dialog == (byte)InstantMessageDialog.GroupNotice)) + { + if (!m_groupNoticesEnabled) + { + return; + } + + UUID GroupID = new UUID(im.toAgentID); + if (m_groupData.GetGroupRecord(GetRequestingAgentIDStr(remoteClient), GroupID, null) != null) + { + UUID NoticeID = UUID.Random(); + string Subject = im.message.Substring(0, im.message.IndexOf('|')); + string Message = im.message.Substring(Subject.Length + 1); + + InventoryItemBase item = null; + bool hasAttachment = false; + + if (im.binaryBucket.Length >= 1 && im.binaryBucket[0] > 0) + { + hasAttachment = true; + string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); + binBucket = binBucket.Remove(0, 14).Trim(); + + OSD binBucketOSD = OSDParser.DeserializeLLSDXml(binBucket); + if (binBucketOSD is OSDMap) + { + OSDMap binBucketMap = (OSDMap)binBucketOSD; + + UUID itemID = binBucketMap["item_id"].AsUUID(); + UUID ownerID = binBucketMap["owner_id"].AsUUID(); + item = new InventoryItemBase(itemID, ownerID); + item = m_sceneList[0].InventoryService.GetItem(item); + } + else + m_log.DebugFormat("[Groups]: Received OSD with unexpected type: {0}", binBucketOSD.GetType()); + } + + if (m_groupData.AddGroupNotice(GetRequestingAgentIDStr(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, + hasAttachment, + (byte)(item == null ? 0 : item.AssetType), + item == null ? null : item.Name, + item == null ? UUID.Zero : item.ID, + item == null ? UUID.Zero.ToString() : item.Owner.ToString())) + { + if (OnNewGroupNotice != null) + { + OnNewGroupNotice(GroupID, NoticeID); + } + + // Send notice out to everyone that wants notices + // Build notice IIM + GridInstantMessage msg = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); + foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentIDStr(remoteClient), GroupID)) + { + if (member.AcceptNotices) + { + msg.toAgentID = member.AgentID.Guid; + OutgoingInstantMessage(msg, member.AgentID); + } + } + } + } + } + + if (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted) + { + if (im.binaryBucket.Length < 16) // Invalid + return; + + //// 16 bytes are the UUID. Maybe. + UUID folderID = new UUID(im.binaryBucket, 0); + UUID noticeID = new UUID(im.imSessionID); + + GroupNoticeInfo notice = m_groupData.GetGroupNotice(remoteClient.AgentId.ToString(), noticeID); + if (notice != null) + { + UUID giver = new UUID(im.toAgentID); + string tmp = string.Empty; + Util.ParseUniversalUserIdentifier(notice.noticeData.AttachmentOwnerID, out giver, out tmp, out tmp, out tmp, out tmp); + + m_log.DebugFormat("[Groups]: Giving inventory from {0} to {1}", giver, remoteClient.AgentId); + InventoryItemBase itemCopy = ((Scene)(remoteClient.Scene)).GiveInventoryItem(remoteClient.AgentId, + giver, notice.noticeData.AttachmentItemID); + + if (itemCopy == null) + { + remoteClient.SendAgentAlertMessage("Can't find item to give. Nothing given.", false); + return; + } + + remoteClient.SendInventoryItemCreateUpdate(itemCopy, 0); + } + + } + + // Interop, received special 210 code for ejecting a group member + // this only works within the comms servers domain, and won't work hypergrid + // TODO:FIXME: Use a presense server of some kind to find out where the + // client actually is, and try contacting that region directly to notify them, + // or provide the notification via xmlrpc update queue + if ((im.dialog == 210)) + { + // This is sent from the region that the ejectee was ejected from + // if it's being delivered here, then the ejectee is here + // so we need to send local updates to the agent. + + UUID ejecteeID = new UUID(im.toAgentID); + + im.dialog = (byte)InstantMessageDialog.MessageFromAgent; + OutgoingInstantMessage(im, ejecteeID); + + IClientAPI ejectee = GetActiveClient(ejecteeID); + if (ejectee != null) + { + UUID groupID = new UUID(im.imSessionID); + ejectee.SendAgentDropGroup(groupID); + } + } + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // Trigger the above event handler + OnInstantMessage(null, msg); + + // If a message from a group arrives here, it may need to be forwarded to a local client + if (msg.fromGroup == true) + { + switch (msg.dialog) + { + case (byte)InstantMessageDialog.GroupInvitation: + case (byte)InstantMessageDialog.GroupNotice: + UUID toAgentID = new UUID(msg.toAgentID); + IClientAPI localClient = GetActiveClient(toAgentID); + if (localClient != null) + { + localClient.SendInstantMessage(msg); + } + break; + } + } + } + + #endregion + + #region IGroupsModule Members + + public event NewGroupNotice OnNewGroupNotice; + + public GroupRecord GetGroupRecord(UUID GroupID) + { + return m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null); + } + + public GroupRecord GetGroupRecord(string name) + { + return m_groupData.GetGroupRecord(UUID.Zero.ToString(), UUID.Zero, name); + } + + public void ActivateGroup(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_groupData.SetAgentActiveGroup(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID); + + // Changing active group changes title, active powers, all kinds of things + // anyone who is in any region that can see this client, should probably be + // updated with new group info. At a minimum, they should get ScenePresence + // updated with new title. + UpdateAllClientsWithGroupInfo(remoteClient.AgentId); + } + + /// + /// Get the Role Titles for an Agent, for a specific group + /// + public List GroupTitlesRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + List agentRoles = m_groupData.GetAgentGroupRoles(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID); + GroupMembershipData agentMembership = m_groupData.GetAgentGroupMembership(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID); + + List titles = new List(); + foreach (GroupRolesData role in agentRoles) + { + GroupTitlesData title = new GroupTitlesData(); + title.Name = role.Name; + if (agentMembership != null) + { + title.Selected = agentMembership.ActiveRole == role.RoleID; + } + title.UUID = role.RoleID; + + titles.Add(title); + } + + return titles; + } + + public List GroupMembersRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) + m_log.DebugFormat( + "[Groups]: GroupMembersRequest called for {0} from client {1}", groupID, remoteClient.Name); + + List data = m_groupData.GetGroupMembers(GetRequestingAgentIDStr(remoteClient), groupID); + + if (m_debugEnabled) + { + foreach (GroupMembersData member in data) + { + m_log.DebugFormat("[Groups]: Member({0}) - IsOwner({1})", member.AgentID, member.IsOwner); + } + } + + return data; + + } + + public List GroupRoleDataRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + List data = m_groupData.GetGroupRoles(GetRequestingAgentIDStr(remoteClient), groupID); + + return data; + } + + public List GroupRoleMembersRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + List data = m_groupData.GetGroupRoleMembers(GetRequestingAgentIDStr(remoteClient), groupID); + + if (m_debugEnabled) + { + foreach (GroupRoleMembersData member in data) + { + m_log.DebugFormat("[Groups]: Member({0}) - Role({1})", member.MemberID, member.RoleID); + } + } + return data; + } + + public GroupProfileData GroupProfileRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + GroupProfileData profile = new GroupProfileData(); + + // just to get the OwnerRole... + ExtendedGroupRecord groupInfo = m_groupData.GetGroupRecord(GetRequestingAgentIDStr(remoteClient), groupID, string.Empty); + GroupMembershipData memberInfo = m_groupData.GetAgentGroupMembership(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID); + if (groupInfo != null) + { + profile.AllowPublish = groupInfo.AllowPublish; + profile.Charter = groupInfo.Charter; + profile.FounderID = groupInfo.FounderID; + profile.GroupID = groupID; + profile.GroupMembershipCount = groupInfo.MemberCount; + profile.GroupRolesCount = groupInfo.RoleCount; + profile.InsigniaID = groupInfo.GroupPicture; + profile.MaturePublish = groupInfo.MaturePublish; + profile.MembershipFee = groupInfo.MembershipFee; + profile.Money = 0; + profile.Name = groupInfo.GroupName; + profile.OpenEnrollment = groupInfo.OpenEnrollment; + profile.OwnerRole = groupInfo.OwnerRoleID; + profile.ShowInList = groupInfo.ShowInList; + } + if (memberInfo != null) + { + profile.MemberTitle = memberInfo.GroupTitle; + profile.PowersMask = memberInfo.GroupPowers; + } + + return profile; + } + + public GroupMembershipData[] GetMembershipData(UUID agentID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + return m_groupData.GetAgentGroupMemberships(UUID.Zero.ToString(), agentID.ToString()).ToArray(); + } + + public GroupMembershipData GetMembershipData(UUID groupID, UUID agentID) + { + if (m_debugEnabled) + m_log.DebugFormat( + "[Groups]: {0} called with groupID={1}, agentID={2}", + System.Reflection.MethodBase.GetCurrentMethod().Name, groupID, agentID); + + return m_groupData.GetAgentGroupMembership(UUID.Zero.ToString(), agentID.ToString(), groupID); + } + + public void UpdateGroupInfo(IClientAPI remoteClient, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // Note: Permissions checking for modification rights is handled by the Groups Server/Service + string reason = string.Empty; + if (!m_groupData.UpdateGroup(GetRequestingAgentIDStr(remoteClient), groupID, charter, showInList, insigniaID, membershipFee, + openEnrollment, allowPublish, maturePublish, out reason)) + remoteClient.SendAgentAlertMessage(reason, false); + } + + public void SetGroupAcceptNotices(IClientAPI remoteClient, UUID groupID, bool acceptNotices, bool listInProfile) + { + // Note: Permissions checking for modification rights is handled by the Groups Server/Service + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_groupData.UpdateMembership(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID, acceptNotices, listInProfile); + } + + public UUID CreateGroup(IClientAPI remoteClient, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called in {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Scene.RegionInfo.RegionName); + + if (m_groupData.GetGroupRecord(GetRequestingAgentIDStr(remoteClient), UUID.Zero, name) != null) + { + remoteClient.SendCreateGroupReply(UUID.Zero, false, "A group with the same name already exists."); + return UUID.Zero; + } + + // check user level + ScenePresence avatar = null; + Scene scene = (Scene)remoteClient.Scene; + scene.TryGetScenePresence(remoteClient.AgentId, out avatar); + + if (avatar != null) + { + if (avatar.UserLevel < m_levelGroupCreate) + { + remoteClient.SendCreateGroupReply(UUID.Zero, false, String.Format("Insufficient permissions to create a group. Requires level {0}", m_levelGroupCreate)); + return UUID.Zero; + } + } + + // check funds + // is there is a money module present ? + IMoneyModule money = scene.RequestModuleInterface(); + if (money != null) + { + // do the transaction, that is if the agent has got sufficient funds + if (!money.AmountCovered(remoteClient.AgentId, money.GroupCreationCharge)) { + remoteClient.SendCreateGroupReply(UUID.Zero, false, "Insufficient funds to create a group."); + return UUID.Zero; + } + money.ApplyCharge(remoteClient.AgentId, money.GroupCreationCharge, "Group Creation"); + } + string reason = string.Empty; + UUID groupID = m_groupData.CreateGroup(remoteClient.AgentId, name, charter, showInList, insigniaID, membershipFee, openEnrollment, + allowPublish, maturePublish, remoteClient.AgentId, out reason); + + if (groupID != UUID.Zero) + { + remoteClient.SendCreateGroupReply(groupID, true, "Group created successfullly"); + + // Update the founder with new group information. + SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient)); + } + else + remoteClient.SendCreateGroupReply(groupID, false, reason); + + return groupID; + } + + public GroupNoticeData[] GroupNoticesListRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // ToDo: check if agent is a member of group and is allowed to see notices? + + List notices = m_groupData.GetGroupNotices(GetRequestingAgentIDStr(remoteClient), groupID); + List os_notices = new List(); + foreach (ExtendedGroupNoticeData n in notices) + { + GroupNoticeData osn = n.ToGroupNoticeData(); + os_notices.Add(osn); + } + + return os_notices.ToArray(); + } + + /// + /// Get the title of the agent's current role. + /// + public string GetGroupTitle(UUID avatarID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + GroupMembershipData membership = m_groupData.GetAgentActiveMembership(UUID.Zero.ToString(), avatarID.ToString()); + if (membership != null) + { + return membership.GroupTitle; + } + return string.Empty; + } + + /// + /// Change the current Active Group Role for Agent + /// + public void GroupTitleUpdate(IClientAPI remoteClient, UUID groupID, UUID titleRoleID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_groupData.SetAgentActiveGroupRole(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID, titleRoleID); + + // TODO: Not sure what all is needed here, but if the active group role change is for the group + // the client currently has set active, then we need to do a scene presence update too + // if (m_groupData.GetAgentActiveMembership(GetRequestingAgentID(remoteClient)).GroupID == GroupID) + + UpdateAllClientsWithGroupInfo(GetRequestingAgentID(remoteClient)); + } + + + public void GroupRoleUpdate(IClientAPI remoteClient, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, byte updateType) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // Security Checks are handled in the Groups Service. + + switch ((OpenMetaverse.GroupRoleUpdate)updateType) + { + case OpenMetaverse.GroupRoleUpdate.Create: + string reason = string.Empty; + if (!m_groupData.AddGroupRole(GetRequestingAgentIDStr(remoteClient), groupID, UUID.Random(), name, description, title, powers, out reason)) + remoteClient.SendAgentAlertMessage("Unable to create role: " + reason, false); + break; + + case OpenMetaverse.GroupRoleUpdate.Delete: + m_groupData.RemoveGroupRole(GetRequestingAgentIDStr(remoteClient), groupID, roleID); + break; + + case OpenMetaverse.GroupRoleUpdate.UpdateAll: + case OpenMetaverse.GroupRoleUpdate.UpdateData: + case OpenMetaverse.GroupRoleUpdate.UpdatePowers: + if (m_debugEnabled) + { + GroupPowers gp = (GroupPowers)powers; + m_log.DebugFormat("[Groups]: Role ({0}) updated with Powers ({1}) ({2})", name, powers.ToString(), gp.ToString()); + } + m_groupData.UpdateGroupRole(GetRequestingAgentIDStr(remoteClient), groupID, roleID, name, description, title, powers); + break; + + case OpenMetaverse.GroupRoleUpdate.NoUpdate: + default: + // No Op + break; + + } + + // TODO: This update really should send out updates for everyone in the role that just got changed. + SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient)); + } + + public void GroupRoleChanges(IClientAPI remoteClient, UUID groupID, UUID roleID, UUID memberID, uint changes) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + // Todo: Security check + + switch (changes) + { + case 0: + // Add + m_groupData.AddAgentToGroupRole(GetRequestingAgentIDStr(remoteClient), memberID.ToString(), groupID, roleID); + + break; + case 1: + // Remove + m_groupData.RemoveAgentFromGroupRole(GetRequestingAgentIDStr(remoteClient), memberID.ToString(), groupID, roleID); + + break; + default: + m_log.ErrorFormat("[Groups]: {0} does not understand changes == {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, changes); + break; + } + + // TODO: This update really should send out updates for everyone in the role that just got changed. + SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient)); + } + + public void GroupNoticeRequest(IClientAPI remoteClient, UUID groupNoticeID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called for notice {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, groupNoticeID); + + //GroupRecord groupInfo = m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), data.GroupID, null); + + GridInstantMessage msg = CreateGroupNoticeIM(remoteClient.AgentId, groupNoticeID, (byte)InstantMessageDialog.GroupNoticeRequested); + //GridInstantMessage msg = new GridInstantMessage(); + //msg.imSessionID = UUID.Zero.Guid; + //msg.fromAgentID = data.GroupID.Guid; + //msg.toAgentID = GetRequestingAgentID(remoteClient).Guid; + //msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + //msg.fromAgentName = "Group Notice : " + groupInfo == null ? "Unknown" : groupInfo.GroupName; + //msg.message = data.noticeData.Subject + "|" + data.Message; + //msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupNoticeRequested; + //msg.fromGroup = true; + //msg.offline = (byte)0; + //msg.ParentEstateID = 0; + //msg.Position = Vector3.Zero; + //msg.RegionID = UUID.Zero.Guid; + //msg.binaryBucket = data.BinaryBucket; + + OutgoingInstantMessage(msg, GetRequestingAgentID(remoteClient)); + } + + public GridInstantMessage CreateGroupNoticeIM(UUID agentID, UUID groupNoticeID, byte dialog) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + GridInstantMessage msg = new GridInstantMessage(); + byte[] bucket; + + msg.imSessionID = groupNoticeID.Guid; + msg.toAgentID = agentID.Guid; + msg.dialog = dialog; + // msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupNotice; + msg.fromGroup = true; + msg.offline = (byte)0; + msg.ParentEstateID = 0; + msg.Position = Vector3.Zero; + msg.RegionID = UUID.Zero.Guid; + + GroupNoticeInfo info = m_groupData.GetGroupNotice(agentID.ToString(), groupNoticeID); + if (info != null) + { + msg.fromAgentID = info.GroupID.Guid; + msg.timestamp = info.noticeData.Timestamp; + msg.fromAgentName = info.noticeData.FromName; + msg.message = info.noticeData.Subject + "|" + info.Message; + if (info.noticeData.HasAttachment) + { + byte[] name = System.Text.Encoding.UTF8.GetBytes(info.noticeData.AttachmentName); + bucket = new byte[19 + name.Length]; + bucket[0] = 1; // has attachment? + bucket[1] = info.noticeData.AttachmentType; // attachment type + name.CopyTo(bucket, 18); + } + else + { + bucket = new byte[19]; + bucket[0] = 0; // Has att? + bucket[1] = 0; // type + bucket[18] = 0; // null terminated + } + + info.GroupID.ToBytes(bucket, 2); + msg.binaryBucket = bucket; + } + else + { + m_log.DebugFormat("[Groups]: Group Notice {0} not found, composing empty message.", groupNoticeID); + msg.fromAgentID = UUID.Zero.Guid; + msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); ; + msg.fromAgentName = string.Empty; + msg.message = string.Empty; + msg.binaryBucket = new byte[0]; + } + + return msg; + } + + public void SendAgentGroupDataUpdate(IClientAPI remoteClient) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // Send agent information about his groups + SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient)); + } + + public void JoinGroupRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + string reason = string.Empty; + // Should check to see if OpenEnrollment, or if there's an outstanding invitation + if (m_groupData.AddAgentToGroup(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID, UUID.Zero, string.Empty, out reason)) + { + + remoteClient.SendJoinGroupReply(groupID, true); + + // Should this send updates to everyone in the group? + SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient)); + } + else + remoteClient.SendJoinGroupReply(groupID, false); + } + + public void LeaveGroupRequest(IClientAPI remoteClient, UUID groupID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + m_groupData.RemoveAgentFromGroup(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID); + + remoteClient.SendLeaveGroupReply(groupID, true); + + remoteClient.SendAgentDropGroup(groupID); + + // SL sends out notifcations to the group messaging session that the person has left + // Should this also update everyone who is in the group? + SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient)); + } + + public void EjectGroupMemberRequest(IClientAPI remoteClient, UUID groupID, UUID ejecteeID) + { + EjectGroupMember(remoteClient, GetRequestingAgentID(remoteClient), groupID, ejecteeID); + } + + public void EjectGroupMember(IClientAPI remoteClient, UUID agentID, UUID groupID, UUID ejecteeID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // Todo: Security check? + m_groupData.RemoveAgentFromGroup(agentID.ToString(), ejecteeID.ToString(), groupID); + + string agentName; + RegionInfo regionInfo; + + // remoteClient provided or just agentID? + if (remoteClient != null) + { + agentName = remoteClient.Name; + regionInfo = remoteClient.Scene.RegionInfo; + remoteClient.SendEjectGroupMemberReply(agentID, groupID, true); + } + else + { + IClientAPI client = GetActiveClient(agentID); + + if (client != null) + { + agentName = client.Name; + regionInfo = client.Scene.RegionInfo; + client.SendEjectGroupMemberReply(agentID, groupID, true); + } + else + { + regionInfo = m_sceneList[0].RegionInfo; + UserAccount acc = m_sceneList[0].UserAccountService.GetUserAccount(regionInfo.ScopeID, agentID); + + if (acc != null) + { + agentName = acc.FirstName + " " + acc.LastName; + } + else + { + agentName = "Unknown member"; + } + } + } + + GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID.ToString(), groupID, null); + + UserAccount account = m_sceneList[0].UserAccountService.GetUserAccount(regionInfo.ScopeID, ejecteeID); + if ((groupInfo == null) || (account == null)) + { + return; + } + + // Send Message to Ejectee + GridInstantMessage msg = new GridInstantMessage(); + + msg.imSessionID = UUID.Zero.Guid; + msg.fromAgentID = agentID.Guid; + // msg.fromAgentID = info.GroupID; + msg.toAgentID = ejecteeID.Guid; + //msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + msg.timestamp = 0; + msg.fromAgentName = agentName; + msg.message = string.Format("You have been ejected from '{1}' by {0}.", agentName, groupInfo.GroupName); + msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageFromAgent; + msg.fromGroup = false; + msg.offline = (byte)0; + msg.ParentEstateID = 0; + msg.Position = Vector3.Zero; + msg.RegionID = regionInfo.RegionID.Guid; + msg.binaryBucket = new byte[0]; + OutgoingInstantMessage(msg, ejecteeID); + + // Message to ejector + // Interop, received special 210 code for ejecting a group member + // this only works within the comms servers domain, and won't work hypergrid + // TODO:FIXME: Use a presense server of some kind to find out where the + // client actually is, and try contacting that region directly to notify them, + // or provide the notification via xmlrpc update queue + + msg = new GridInstantMessage(); + msg.imSessionID = UUID.Zero.Guid; + msg.fromAgentID = agentID.Guid; + msg.toAgentID = agentID.Guid; + msg.timestamp = 0; + msg.fromAgentName = agentName; + if (account != null) + { + msg.message = string.Format("{2} has been ejected from '{1}' by {0}.", agentName, groupInfo.GroupName, account.FirstName + " " + account.LastName); + } + else + { + msg.message = string.Format("{2} has been ejected from '{1}' by {0}.", agentName, groupInfo.GroupName, "Unknown member"); + } + msg.dialog = (byte)210; //interop + msg.fromGroup = false; + msg.offline = (byte)0; + msg.ParentEstateID = 0; + msg.Position = Vector3.Zero; + msg.RegionID = regionInfo.RegionID.Guid; + msg.binaryBucket = new byte[0]; + OutgoingInstantMessage(msg, agentID); + + + // SL sends out messages to everyone in the group + // Who all should receive updates and what should they be updated with? + UpdateAllClientsWithGroupInfo(ejecteeID); + } + + public void InviteGroupRequest(IClientAPI remoteClient, UUID groupID, UUID invitedAgentID, UUID roleID) + { + InviteGroup(remoteClient, GetRequestingAgentID(remoteClient), groupID, invitedAgentID, roleID); + } + + public void InviteGroup(IClientAPI remoteClient, UUID agentID, UUID groupID, UUID invitedAgentID, UUID roleID) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + string agentName = m_UserManagement.GetUserName(agentID); + RegionInfo regionInfo = m_sceneList[0].RegionInfo; + + GroupRecord group = m_groupData.GetGroupRecord(agentID.ToString(), groupID, null); + if (group == null) + { + m_log.DebugFormat("[Groups]: No such group {0}", groupID); + return; + } + + // Todo: Security check, probably also want to send some kind of notification + UUID InviteID = UUID.Random(); + + if (m_groupData.AddAgentToGroupInvite(agentID.ToString(), InviteID, groupID, roleID, invitedAgentID.ToString())) + { + if (m_msgTransferModule != null) + { + Guid inviteUUID = InviteID.Guid; + + GridInstantMessage msg = new GridInstantMessage(); + + msg.imSessionID = inviteUUID; + + // msg.fromAgentID = agentID.Guid; + msg.fromAgentID = groupID.Guid; + msg.toAgentID = invitedAgentID.Guid; + //msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + msg.timestamp = 0; + msg.fromAgentName = agentName; + msg.message = string.Format("{0} has invited you to join a group called {1}. There is no cost to join this group.", agentName, group.GroupName); + msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupInvitation; + msg.fromGroup = true; + msg.offline = (byte)0; + msg.ParentEstateID = 0; + msg.Position = Vector3.Zero; + msg.RegionID = regionInfo.RegionID.Guid; + msg.binaryBucket = new byte[20]; + + OutgoingInstantMessage(msg, invitedAgentID); + } + } + } + + #endregion + + #region Client/Update Tools + + /// + /// Try to find an active IClientAPI reference for agentID giving preference to root connections + /// + private IClientAPI GetActiveClient(UUID agentID) + { + IClientAPI child = null; + + // Try root avatar first + foreach (Scene scene in m_sceneList) + { + ScenePresence sp = scene.GetScenePresence(agentID); + if (sp != null) + { + if (!sp.IsChildAgent) + { + return sp.ControllingClient; + } + else + { + child = sp.ControllingClient; + } + } + } + + // If we didn't find a root, then just return whichever child we found, or null if none + return child; + } + + /// + /// Send 'remoteClient' the group membership 'data' for agent 'dataForAgentID'. + /// + private void SendGroupMembershipInfoViaCaps(IClientAPI remoteClient, UUID dataForAgentID, GroupMembershipData[] data) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + OSDArray AgentData = new OSDArray(1); + OSDMap AgentDataMap = new OSDMap(1); + AgentDataMap.Add("AgentID", OSD.FromUUID(dataForAgentID)); + AgentData.Add(AgentDataMap); + + + OSDArray GroupData = new OSDArray(data.Length); + OSDArray NewGroupData = new OSDArray(data.Length); + + foreach (GroupMembershipData membership in data) + { + if (GetRequestingAgentID(remoteClient) != dataForAgentID) + { + if (!membership.ListInProfile) + { + // If we're sending group info to remoteclient about another agent, + // filter out groups the other agent doesn't want to share. + continue; + } + } + + OSDMap GroupDataMap = new OSDMap(6); + OSDMap NewGroupDataMap = new OSDMap(1); + + GroupDataMap.Add("GroupID", OSD.FromUUID(membership.GroupID)); + GroupDataMap.Add("GroupPowers", OSD.FromULong(membership.GroupPowers)); + GroupDataMap.Add("AcceptNotices", OSD.FromBoolean(membership.AcceptNotices)); + GroupDataMap.Add("GroupInsigniaID", OSD.FromUUID(membership.GroupPicture)); + GroupDataMap.Add("Contribution", OSD.FromInteger(membership.Contribution)); + GroupDataMap.Add("GroupName", OSD.FromString(membership.GroupName)); + NewGroupDataMap.Add("ListInProfile", OSD.FromBoolean(membership.ListInProfile)); + + GroupData.Add(GroupDataMap); + NewGroupData.Add(NewGroupDataMap); + } + + OSDMap llDataStruct = new OSDMap(3); + llDataStruct.Add("AgentData", AgentData); + llDataStruct.Add("GroupData", GroupData); + llDataStruct.Add("NewGroupData", NewGroupData); + + if (m_debugEnabled) + { + m_log.InfoFormat("[Groups]: {0}", OSDParser.SerializeJsonString(llDataStruct)); + } + + IEventQueue queue = remoteClient.Scene.RequestModuleInterface(); + + if (queue != null) + { + queue.Enqueue(queue.BuildEvent("AgentGroupDataUpdate", llDataStruct), GetRequestingAgentID(remoteClient)); + } + + } + + private void SendScenePresenceUpdate(UUID AgentID, string Title) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: Updating scene title for {0} with title: {1}", AgentID, Title); + + ScenePresence presence = null; + + foreach (Scene scene in m_sceneList) + { + presence = scene.GetScenePresence(AgentID); + if (presence != null) + { + if (presence.Grouptitle != Title) + { + presence.Grouptitle = Title; + + if (! presence.IsChildAgent) + presence.SendAvatarDataToAllAgents(); + } + } + } + } + + /// + /// Send updates to all clients who might be interested in groups data for dataForClientID + /// + private void UpdateAllClientsWithGroupInfo(UUID dataForClientID) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // TODO: Probably isn't nessesary to update every client in every scene. + // Need to examine client updates and do only what's nessesary. + lock (m_sceneList) + { + foreach (Scene scene in m_sceneList) + { + scene.ForEachClient(delegate(IClientAPI client) { SendAgentGroupDataUpdate(client, dataForClientID); }); + } + } + } + + /// + /// Update remoteClient with group information about dataForAgentID + /// + private void SendAgentGroupDataUpdate(IClientAPI remoteClient, UUID dataForAgentID) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: {0} called for {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Name); + + // TODO: All the client update functions need to be reexamined because most do too much and send too much stuff + + OnAgentDataUpdateRequest(remoteClient, dataForAgentID, UUID.Zero); + + // Need to send a group membership update to the client + // UDP version doesn't seem to behave nicely. But we're going to send it out here + // with an empty group membership to hopefully remove groups being displayed due + // to the core Groups Stub + //remoteClient.SendGroupMembership(new GroupMembershipData[0]); + + GroupMembershipData[] membershipArray = GetProfileListedGroupMemberships(remoteClient, dataForAgentID); + SendGroupMembershipInfoViaCaps(remoteClient, dataForAgentID, membershipArray); + //remoteClient.SendAvatarGroupsReply(dataForAgentID, membershipArray); + if (remoteClient.AgentId == dataForAgentID) + remoteClient.RefreshGroupMembership(); + } + + /// + /// Get a list of groups memberships for the agent that are marked "ListInProfile" + /// (unless that agent has a godLike aspect, in which case get all groups) + /// + /// + /// + private GroupMembershipData[] GetProfileListedGroupMemberships(IClientAPI requestingClient, UUID dataForAgentID) + { + List membershipData = m_groupData.GetAgentGroupMemberships(requestingClient.AgentId.ToString(), dataForAgentID.ToString()); + GroupMembershipData[] membershipArray; + + // cScene and property accessor 'isGod' are in support of the opertions to bypass 'hidden' group attributes for + // those with a GodLike aspect. + Scene cScene = (Scene)requestingClient.Scene; + bool isGod = cScene.Permissions.IsGod(requestingClient.AgentId); + + if (isGod) + { + membershipArray = membershipData.ToArray(); + } + else + { + if (requestingClient.AgentId != dataForAgentID) + { + Predicate showInProfile = delegate(GroupMembershipData membership) + { + return membership.ListInProfile; + }; + + membershipArray = membershipData.FindAll(showInProfile).ToArray(); + } + else + { + membershipArray = membershipData.ToArray(); + } + } + + if (m_debugEnabled) + { + m_log.InfoFormat("[Groups]: Get group membership information for {0} requested by {1}", dataForAgentID, requestingClient.AgentId); + foreach (GroupMembershipData membership in membershipArray) + { + m_log.InfoFormat("[Groups]: {0} :: {1} - {2} - {3}", dataForAgentID, membership.GroupName, membership.GroupTitle, membership.GroupPowers); + } + } + + return membershipArray; + } + + + private void SendAgentDataUpdate(IClientAPI remoteClient, UUID dataForAgentID, UUID activeGroupID, string activeGroupName, ulong activeGroupPowers, string activeGroupTitle) + { + if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + // TODO: All the client update functions need to be reexamined because most do too much and send too much stuff + UserAccount account = m_sceneList[0].UserAccountService.GetUserAccount(remoteClient.Scene.RegionInfo.ScopeID, dataForAgentID); + string firstname, lastname; + if (account != null) + { + firstname = account.FirstName; + lastname = account.LastName; + } + else + { + firstname = "Unknown"; + lastname = "Unknown"; + } + + remoteClient.SendAgentDataUpdate(dataForAgentID, activeGroupID, firstname, + lastname, activeGroupPowers, activeGroupName, + activeGroupTitle); + } + + #endregion + + #region IM Backed Processes + + private void OutgoingInstantMessage(GridInstantMessage msg, UUID msgTo) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + + IClientAPI localClient = GetActiveClient(msgTo); + if (localClient != null) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: MsgTo ({0}) is local, delivering directly", localClient.Name); + localClient.SendInstantMessage(msg); + } + else if (m_msgTransferModule != null) + { + if (m_debugEnabled) m_log.InfoFormat("[Groups]: MsgTo ({0}) is not local, delivering via TransferModule", msgTo); + m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { if (m_debugEnabled) m_log.DebugFormat("[Groups]: Message Sent: {0}", success?"Succeeded":"Failed"); }); + } + } + + public void NotifyChange(UUID groupID) + { + // Notify all group members of a chnge in group roles and/or + // permissions + // + } + + #endregion + + private string GetRequestingAgentIDStr(IClientAPI client) + { + return GetRequestingAgentID(client).ToString(); + } + + private UUID GetRequestingAgentID(IClientAPI client) + { + UUID requestingAgentID = UUID.Zero; + if (client != null) + { + requestingAgentID = client.AgentId; + } + return requestingAgentID; + } + + } + +} diff --git a/OpenSim/Addons/Groups/Hypergrid/GroupsServiceHGConnector.cs b/OpenSim/Addons/Groups/Hypergrid/GroupsServiceHGConnector.cs new file mode 100644 index 0000000000..59fec6fa38 --- /dev/null +++ b/OpenSim/Addons/Groups/Hypergrid/GroupsServiceHGConnector.cs @@ -0,0 +1,289 @@ +/* + * 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.Linq; +using System.Reflection; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Server.Base; + +using OpenMetaverse; +using log4net; + +namespace OpenSim.Groups +{ + public class GroupsServiceHGConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_ServerURI; + private object m_Lock = new object(); + + public GroupsServiceHGConnector(string url) + { + m_ServerURI = url; + if (!m_ServerURI.EndsWith("/")) + m_ServerURI += "/"; + + m_log.DebugFormat("[Groups.HGConnector]: Groups server at {0}", m_ServerURI); + } + + public bool CreateProxy(string RequestingAgentID, string AgentID, string accessToken, UUID groupID, string url, string name, out string reason) + { + reason = string.Empty; + + Dictionary sendData = new Dictionary(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["AgentID"] = AgentID.ToString(); + sendData["AccessToken"] = accessToken; + sendData["GroupID"] = groupID.ToString(); + sendData["Location"] = url; + sendData["Name"] = name; + Dictionary ret = MakeRequest("POSTGROUP", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + { + reason = ret["REASON"].ToString(); + return false; + } + + return true; + + } + + public void RemoveAgentFromGroup(string AgentID, UUID GroupID, string token) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID; + sendData["GroupID"] = GroupID.ToString(); + sendData["AccessToken"] = GroupsDataUtils.Sanitize(token); + MakeRequest("REMOVEAGENTFROMGROUP", sendData); + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, string token) + { + if (GroupID == UUID.Zero && (GroupName == null || (GroupName != null && GroupName == string.Empty))) + return null; + + Dictionary sendData = new Dictionary(); + if (GroupID != UUID.Zero) + sendData["GroupID"] = GroupID.ToString(); + if (GroupName != null && GroupName != string.Empty) + sendData["Name"] = GroupsDataUtils.Sanitize(GroupName); + + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["AccessToken"] = GroupsDataUtils.Sanitize(token); + + Dictionary ret = MakeRequest("GETGROUP", sendData); + + if (ret == null) + return null; + + if (!ret.ContainsKey("RESULT")) + return null; + + if (ret["RESULT"].ToString() == "NULL") + return null; + + return GroupsDataUtils.GroupRecord((Dictionary)ret["RESULT"]); + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID, string token) + { + List members = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["AccessToken"] = GroupsDataUtils.Sanitize(token); + Dictionary ret = MakeRequest("GETGROUPMEMBERS", sendData); + + if (ret == null) + return members; + + if (!ret.ContainsKey("RESULT")) + return members; + + if (ret["RESULT"].ToString() == "NULL") + return members; + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + ExtendedGroupMembersData m = GroupsDataUtils.GroupMembersData((Dictionary)v); + members.Add(m); + } + + return members; + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID, string token) + { + List roles = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["AccessToken"] = GroupsDataUtils.Sanitize(token); + Dictionary ret = MakeRequest("GETGROUPROLES", sendData); + + if (ret == null) + return roles; + + if (!ret.ContainsKey("RESULT")) + return roles; + + if (ret["RESULT"].ToString() == "NULL") + return roles; + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary)v); + roles.Add(m); + } + + return roles; + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, string token) + { + List rmembers = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["AccessToken"] = GroupsDataUtils.Sanitize(token); + Dictionary ret = MakeRequest("GETROLEMEMBERS", sendData); + + if (ret == null) + return rmembers; + + if (!ret.ContainsKey("RESULT")) + return rmembers; + + if (ret["RESULT"].ToString() == "NULL") + return rmembers; + + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + ExtendedGroupRoleMembersData m = GroupsDataUtils.GroupRoleMembersData((Dictionary)v); + rmembers.Add(m); + } + + return rmembers; + } + + public bool AddNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = groupID.ToString(); + sendData["NoticeID"] = noticeID.ToString(); + sendData["FromName"] = GroupsDataUtils.Sanitize(fromName); + sendData["Subject"] = GroupsDataUtils.Sanitize(subject); + sendData["Message"] = GroupsDataUtils.Sanitize(message); + sendData["HasAttachment"] = hasAttachment.ToString(); + if (hasAttachment) + { + sendData["AttachmentType"] = attType.ToString(); + sendData["AttachmentName"] = attName.ToString(); + sendData["AttachmentItemID"] = attItemID.ToString(); + sendData["AttachmentOwnerID"] = attOwnerID; + } + sendData["RequestingAgentID"] = RequestingAgentID; + + Dictionary ret = MakeRequest("ADDNOTICE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + return false; + + return true; + } + + public bool VerifyNotice(UUID noticeID, UUID groupID) + { + Dictionary sendData = new Dictionary(); + sendData["NoticeID"] = noticeID.ToString(); + sendData["GroupID"] = groupID.ToString(); + Dictionary ret = MakeRequest("VERIFYNOTICE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + return false; + + return true; + } + + // + // + // + // + // + + #region Make Request + + private Dictionary MakeRequest(string method, Dictionary sendData) + { + sendData["METHOD"] = method; + + string reply = string.Empty; + lock (m_Lock) + reply = SynchronousRestFormsRequester.MakeRequest("POST", + m_ServerURI + "hg-groups", + ServerUtils.BuildQueryString(sendData)); + + //m_log.DebugFormat("[XXX]: reply was {0}", reply); + + if (reply == string.Empty || reply == null) + return null; + + Dictionary replyData = ServerUtils.ParseXmlResponse( + reply); + + return replyData; + } + #endregion + + } +} diff --git a/OpenSim/Addons/Groups/Hypergrid/GroupsServiceHGConnectorModule.cs b/OpenSim/Addons/Groups/Hypergrid/GroupsServiceHGConnectorModule.cs new file mode 100644 index 0000000000..f670272662 --- /dev/null +++ b/OpenSim/Addons/Groups/Hypergrid/GroupsServiceHGConnectorModule.cs @@ -0,0 +1,717 @@ +/* + * 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.Linq; +using System.Reflection; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Services.Interfaces; + +using OpenMetaverse; +using Mono.Addins; +using log4net; +using Nini.Config; + +namespace OpenSim.Groups +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceHGConnectorModule")] + public class GroupsServiceHGConnectorModule : ISharedRegionModule, IGroupsServicesConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool m_Enabled = false; + private IGroupsServicesConnector m_LocalGroupsConnector; + private string m_LocalGroupsServiceLocation; + private IUserManagement m_UserManagement; + private IOfflineIMService m_OfflineIM; + private IMessageTransferModule m_Messaging; + private List m_Scenes; + private ForeignImporter m_ForeignImporter; + private string m_ServiceLocation; + private IConfigSource m_Config; + + private Dictionary m_NetworkConnectors = new Dictionary(); + private RemoteConnectorCacheWrapper m_CacheWrapper; // for caching info of external group services + + #region ISharedRegionModule + + public void Initialise(IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + if (groupsConfig == null) + return; + + if ((groupsConfig.GetBoolean("Enabled", false) == false) + || (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name)) + { + return; + } + + m_Config = config; + m_ServiceLocation = groupsConfig.GetString("LocalService", "local"); // local or remote + m_LocalGroupsServiceLocation = groupsConfig.GetString("GroupsExternalURI", "http://127.0.0.1"); + m_Scenes = new List(); + + m_Enabled = true; + + m_log.DebugFormat("[Groups]: Initializing {0} with LocalService {1}", this.Name, m_ServiceLocation); + } + + public string Name + { + get { return "Groups HG Service Connector"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName); + scene.RegisterModuleInterface(this); + m_Scenes.Add(scene); + + scene.EventManager.OnNewClient += OnNewClient; + } + + public void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.UnregisterModuleInterface(this); + m_Scenes.Remove(scene); + } + + public void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + + if (m_UserManagement == null) + { + m_UserManagement = scene.RequestModuleInterface(); + m_OfflineIM = scene.RequestModuleInterface(); + m_Messaging = scene.RequestModuleInterface(); + m_ForeignImporter = new ForeignImporter(m_UserManagement); + + if (m_ServiceLocation.Equals("local")) + { + m_LocalGroupsConnector = new GroupsServiceLocalConnectorModule(m_Config, m_UserManagement); + // Also, if local, create the endpoint for the HGGroupsService + new HGGroupsServiceRobustConnector(m_Config, MainServer.Instance, string.Empty, + scene.RequestModuleInterface(), scene.RequestModuleInterface()); + + } + else + m_LocalGroupsConnector = new GroupsServiceRemoteConnectorModule(m_Config, m_UserManagement); + + m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement); + } + + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + #endregion + + private void OnNewClient(IClientAPI client) + { + client.OnCompleteMovementToRegion += OnCompleteMovementToRegion; + } + + void OnCompleteMovementToRegion(IClientAPI client, bool arg2) + { + object sp = null; + if (client.Scene.TryGetScenePresence(client.AgentId, out sp)) + { + if (sp is ScenePresence && ((ScenePresence)sp).PresenceType != PresenceType.Npc) + { + AgentCircuitData aCircuit = ((ScenePresence)sp).Scene.AuthenticateHandler.GetAgentCircuitData(client.AgentId); + if (aCircuit != null && (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0 && + m_OfflineIM != null && m_Messaging != null) + { + List ims = m_OfflineIM.GetMessages(aCircuit.AgentID); + if (ims != null && ims.Count > 0) + foreach (GridInstantMessage im in ims) + m_Messaging.SendInstantMessage(im, delegate(bool success) { }); + } + } + } + } + + #region IGroupsServicesConnector + + public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, + bool allowPublish, bool maturePublish, UUID founderID, out string reason) + { + m_log.DebugFormat("[Groups]: Creating group {0}", name); + reason = string.Empty; + if (m_UserManagement.IsLocalGridUser(RequestingAgentID)) + return m_LocalGroupsConnector.CreateGroup(RequestingAgentID, name, charter, showInList, insigniaID, + membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason); + else + { + reason = "Only local grid users are allowed to create a new group"; + return UUID.Zero; + } + } + + public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, + bool openEnrollment, bool allowPublish, bool maturePublish, out string reason) + { + reason = string.Empty; + string url = string.Empty; + string name = string.Empty; + if (IsLocal(groupID, out url, out name)) + return m_LocalGroupsConnector.UpdateGroup(AgentUUI(RequestingAgentID), groupID, charter, showInList, insigniaID, membershipFee, + openEnrollment, allowPublish, maturePublish, out reason); + else + { + reason = "Changes to remote group not allowed. Please go to the group's original world."; + return false; + } + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName) + { + string url = string.Empty; + string name = string.Empty; + if (IsLocal(GroupID, out url, out name)) + return m_LocalGroupsConnector.GetGroupRecord(AgentUUI(RequestingAgentID), GroupID, GroupName); + else if (url != string.Empty) + { + ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, GroupID); + string accessToken = string.Empty; + if (membership != null) + accessToken = membership.AccessToken; + else + return null; + + GroupsServiceHGConnector c = GetConnector(url); + if (c != null) + { + ExtendedGroupRecord grec = m_CacheWrapper.GetGroupRecord(RequestingAgentID, GroupID, GroupName, delegate + { + return c.GetGroupRecord(AgentUUIForOutside(RequestingAgentID), GroupID, GroupName, accessToken); + }); + + if (grec != null) + ImportForeigner(grec.FounderUUI); + return grec; + } + } + + return null; + } + + public List FindGroups(string RequestingAgentID, string search) + { + return m_LocalGroupsConnector.FindGroups(AgentUUI(RequestingAgentID), search); + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID) + { + string url = string.Empty, gname = string.Empty; + if (IsLocal(GroupID, out url, out gname)) + return m_LocalGroupsConnector.GetGroupMembers(AgentUUI(RequestingAgentID), GroupID); + else if (!string.IsNullOrEmpty(url)) + { + ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, GroupID); + string accessToken = string.Empty; + if (membership != null) + accessToken = membership.AccessToken; + else + return null; + + GroupsServiceHGConnector c = GetConnector(url); + if (c != null) + { + return m_CacheWrapper.GetGroupMembers(RequestingAgentID, GroupID, delegate + { + return c.GetGroupMembers(AgentUUIForOutside(RequestingAgentID), GroupID, accessToken); + }); + + } + } + return new List(); + } + + public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason) + { + reason = string.Empty; + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + return m_LocalGroupsConnector.AddGroupRole(AgentUUI(RequestingAgentID), groupID, roleID, name, description, title, powers, out reason); + else + { + reason = "Operation not allowed outside this group's origin world."; + return false; + } + } + + public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + return m_LocalGroupsConnector.UpdateGroupRole(AgentUUI(RequestingAgentID), groupID, roleID, name, description, title, powers); + else + { + return false; + } + + } + + public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + m_LocalGroupsConnector.RemoveGroupRole(AgentUUI(RequestingAgentID), groupID, roleID); + else + { + return; + } + } + + public List GetGroupRoles(string RequestingAgentID, UUID groupID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + return m_LocalGroupsConnector.GetGroupRoles(AgentUUI(RequestingAgentID), groupID); + else if (!string.IsNullOrEmpty(url)) + { + ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, groupID); + string accessToken = string.Empty; + if (membership != null) + accessToken = membership.AccessToken; + else + return null; + + GroupsServiceHGConnector c = GetConnector(url); + if (c != null) + { + return m_CacheWrapper.GetGroupRoles(RequestingAgentID, groupID, delegate + { + return c.GetGroupRoles(AgentUUIForOutside(RequestingAgentID), groupID, accessToken); + }); + + } + } + + return new List(); + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID groupID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + return m_LocalGroupsConnector.GetGroupRoleMembers(AgentUUI(RequestingAgentID), groupID); + else if (!string.IsNullOrEmpty(url)) + { + ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, groupID); + string accessToken = string.Empty; + if (membership != null) + accessToken = membership.AccessToken; + else + return null; + + GroupsServiceHGConnector c = GetConnector(url); + if (c != null) + { + return m_CacheWrapper.GetGroupRoleMembers(RequestingAgentID, groupID, delegate + { + return c.GetGroupRoleMembers(AgentUUIForOutside(RequestingAgentID), groupID, accessToken); + }); + + } + } + + return new List(); + } + + public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason) + { + string url = string.Empty; + string name = string.Empty; + reason = string.Empty; + + UUID uid = new UUID(AgentID); + if (IsLocal(GroupID, out url, out name)) + { + if (m_UserManagement.IsLocalGridUser(uid)) // local user + { + // normal case: local group, local user + return m_LocalGroupsConnector.AddAgentToGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID, token, out reason); + } + else // local group, foreign user + { + // the user is accepting the invitation, or joining, where the group resides + token = UUID.Random().ToString(); + bool success = m_LocalGroupsConnector.AddAgentToGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID, token, out reason); + + if (success) + { + url = m_UserManagement.GetUserServerURL(uid, "GroupsServerURI"); + if (url == string.Empty) + { + reason = "User doesn't have a groups server"; + return false; + } + + GroupsServiceHGConnector c = GetConnector(url); + if (c != null) + return c.CreateProxy(AgentUUI(RequestingAgentID), AgentID, token, GroupID, m_LocalGroupsServiceLocation, name, out reason); + } + } + } + else if (m_UserManagement.IsLocalGridUser(uid)) // local user + { + // foreign group, local user. She's been added already by the HG service. + // Let's just check + if (m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID) != null) + return true; + } + + reason = "Operation not allowed outside this group's origin world"; + return false; + } + + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + string url = string.Empty, name = string.Empty; + if (!IsLocal(GroupID, out url, out name) && url != string.Empty) + { + ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID); + if (membership != null) + { + GroupsServiceHGConnector c = GetConnector(url); + if (c != null) + c.RemoveAgentFromGroup(AgentUUIForOutside(AgentID), GroupID, membership.AccessToken); + } + } + + // remove from local service + m_LocalGroupsConnector.RemoveAgentFromGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID); + } + + public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + return m_LocalGroupsConnector.AddAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID, groupID, roleID, AgentUUI(agentID)); + else + return false; + } + + public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + return m_LocalGroupsConnector.GetAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID); ; + } + + public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + m_LocalGroupsConnector.RemoveAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID); + } + + public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(GroupID, out url, out gname)) + m_LocalGroupsConnector.AddAgentToGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID); + + } + + public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(GroupID, out url, out gname)) + m_LocalGroupsConnector.RemoveAgentFromGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID); + } + + public List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(GroupID, out url, out gname)) + return m_LocalGroupsConnector.GetAgentGroupRoles(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID); + else + return new List(); + } + + public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(GroupID, out url, out gname)) + m_LocalGroupsConnector.SetAgentActiveGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID); + } + + public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID) + { + return m_LocalGroupsConnector.GetAgentActiveMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID)); + } + + public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(GroupID, out url, out gname)) + m_LocalGroupsConnector.SetAgentActiveGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID); + } + + public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile) + { + m_LocalGroupsConnector.UpdateMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, AcceptNotices, ListInProfile); + } + + public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(GroupID, out url, out gname)) + return m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID); + else + return null; + } + + public List GetAgentGroupMemberships(string RequestingAgentID, string AgentID) + { + return m_LocalGroupsConnector.GetAgentGroupMemberships(AgentUUI(RequestingAgentID), AgentUUI(AgentID)); + } + + public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + string url = string.Empty, gname = string.Empty; + + if (IsLocal(groupID, out url, out gname)) + { + if (m_LocalGroupsConnector.AddGroupNotice(AgentUUI(RequestingAgentID), groupID, noticeID, fromName, subject, message, + hasAttachment, attType, attName, attItemID, AgentUUI(attOwnerID))) + { + // then send the notice to every grid for which there are members in this group + List members = m_LocalGroupsConnector.GetGroupMembers(AgentUUI(RequestingAgentID), groupID); + List urls = new List(); + foreach (GroupMembersData m in members) + { + UUID userID = UUID.Zero; + if (!m_UserManagement.IsLocalGridUser(m.AgentID)) + { + string gURL = m_UserManagement.GetUserServerURL(m.AgentID, "GroupsServerURI"); + if (!urls.Contains(gURL)) + urls.Add(gURL); + } + } + + // so we have the list of urls to send the notice to + // this may take a long time... + Util.FireAndForget(delegate + { + foreach (string u in urls) + { + GroupsServiceHGConnector c = GetConnector(u); + if (c != null) + { + c.AddNotice(AgentUUIForOutside(RequestingAgentID), groupID, noticeID, fromName, subject, message, + hasAttachment, attType, attName, attItemID, AgentUUIForOutside(attOwnerID)); + } + } + }); + + return true; + } + + return false; + } + else + return false; + } + + public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID) + { + GroupNoticeInfo notice = m_LocalGroupsConnector.GetGroupNotice(AgentUUI(RequestingAgentID), noticeID); + + if (notice != null && notice.noticeData.HasAttachment && notice.noticeData.AttachmentOwnerID != null) + ImportForeigner(notice.noticeData.AttachmentOwnerID); + + return notice; + } + + public List GetGroupNotices(string RequestingAgentID, UUID GroupID) + { + return m_LocalGroupsConnector.GetGroupNotices(AgentUUI(RequestingAgentID), GroupID); + } + + public void ResetAgentGroupChatSessions(string agentID) + { + } + + public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID) + { + } + + public void AgentInvitedToGroupChatSession(string agentID, UUID groupID) + { + } + + #endregion + + #region hypergrid groups + + private string AgentUUI(string AgentIDStr) + { + UUID AgentID = UUID.Zero; + try + { + AgentID = new UUID(AgentIDStr); + } + catch (FormatException) + { + return AgentID.ToString(); + } + + if (m_UserManagement.IsLocalGridUser(AgentID)) + return AgentID.ToString(); + + AgentCircuitData agent = null; + foreach (Scene scene in m_Scenes) + { + agent = scene.AuthenticateHandler.GetAgentCircuitData(AgentID); + if (agent != null) + break; + } + if (agent == null) // oops + return AgentID.ToString(); + + return Util.ProduceUserUniversalIdentifier(agent); + } + + private string AgentUUIForOutside(string AgentIDStr) + { + UUID AgentID = UUID.Zero; + try + { + AgentID = new UUID(AgentIDStr); + } + catch (FormatException) + { + return AgentID.ToString(); + } + + AgentCircuitData agent = null; + foreach (Scene scene in m_Scenes) + { + agent = scene.AuthenticateHandler.GetAgentCircuitData(AgentID); + if (agent != null) + break; + } + if (agent == null) // oops + return AgentID.ToString(); + + return Util.ProduceUserUniversalIdentifier(agent); + } + + private UUID ImportForeigner(string uID) + { + UUID userID = UUID.Zero; + string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty; + if (Util.ParseUniversalUserIdentifier(uID, out userID, out url, out first, out last, out tmp)) + m_UserManagement.AddUser(userID, first, last, url); + + return userID; + } + + private bool IsLocal(UUID groupID, out string serviceLocation, out string name) + { + serviceLocation = string.Empty; + name = string.Empty; + ExtendedGroupRecord group = m_LocalGroupsConnector.GetGroupRecord(UUID.Zero.ToString(), groupID, string.Empty); + if (group == null) + { + //m_log.DebugFormat("[XXX]: IsLocal? group {0} not found -- no.", groupID); + return false; + } + + serviceLocation = group.ServiceLocation; + name = group.GroupName; + bool isLocal = (group.ServiceLocation == string.Empty); + //m_log.DebugFormat("[XXX]: IsLocal? {0}", isLocal); + return isLocal; + } + + private GroupsServiceHGConnector GetConnector(string url) + { + lock (m_NetworkConnectors) + { + if (m_NetworkConnectors.ContainsKey(url)) + return m_NetworkConnectors[url]; + + GroupsServiceHGConnector c = new GroupsServiceHGConnector(url); + m_NetworkConnectors[url] = c; + } + + return m_NetworkConnectors[url]; + } + #endregion + } +} diff --git a/OpenSim/Addons/Groups/Hypergrid/HGGroupsServiceRobustConnector.cs b/OpenSim/Addons/Groups/Hypergrid/HGGroupsServiceRobustConnector.cs new file mode 100644 index 0000000000..3584f78c52 --- /dev/null +++ b/OpenSim/Addons/Groups/Hypergrid/HGGroupsServiceRobustConnector.cs @@ -0,0 +1,444 @@ +/* + * 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.Reflection; +using System.Text; +using System.Xml; +using System.Collections.Generic; +using System.IO; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Server.Handlers.Base; +using log4net; +using OpenMetaverse; + +namespace OpenSim.Groups +{ + public class HGGroupsServiceRobustConnector : ServiceConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private HGGroupsService m_GroupsService; + private string m_HomeURI = string.Empty; + private string m_ConfigName = "Groups"; + + // Called by Robust shell + public HGGroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) : + this(config, server, configName, null, null) + { + } + + // Called by the sim-bound module + public HGGroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName, IOfflineIMService im, IUserAccountService users) : + base(config, server, configName) + { + if (configName != String.Empty) + m_ConfigName = configName; + + m_log.DebugFormat("[Groups.RobustHGConnector]: Starting with config name {0}", m_ConfigName); + + string homeURI = Util.GetConfigVarFromSections(config, "HomeURI", + new string[] { "Startup", "Hypergrid", m_ConfigName}, string.Empty); + if (homeURI == string.Empty) + throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide the HomeURI [Startup] or in section {0}", m_ConfigName)); + + IConfig cnf = config.Configs[m_ConfigName]; + if (cnf == null) + throw new Exception(String.Format("[Groups.RobustHGConnector]: {0} section does not exist", m_ConfigName)); + + if (im == null) + { + string imDll = cnf.GetString("OfflineIMService", string.Empty); + if (imDll == string.Empty) + throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide OfflineIMService in section {0}", m_ConfigName)); + + Object[] args = new Object[] { config }; + im = ServerUtils.LoadPlugin(imDll, args); + } + + if (users == null) + { + string usersDll = cnf.GetString("UserAccountService", string.Empty); + if (usersDll == string.Empty) + throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide UserAccountService in section {0}", m_ConfigName)); + + Object[] args = new Object[] { config }; + users = ServerUtils.LoadPlugin(usersDll, args); + } + + m_GroupsService = new HGGroupsService(config, im, users, homeURI); + + server.AddStreamHandler(new HGGroupsServicePostHandler(m_GroupsService)); + } + + } + + public class HGGroupsServicePostHandler : BaseStreamHandler + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private HGGroupsService m_GroupsService; + + public HGGroupsServicePostHandler(HGGroupsService service) : + base("POST", "/hg-groups") + { + m_GroupsService = service; + } + + public override byte[] Handle(string path, Stream requestData, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + StreamReader sr = new StreamReader(requestData); + string body = sr.ReadToEnd(); + sr.Close(); + body = body.Trim(); + + //m_log.DebugFormat("[XXX]: query String: {0}", body); + + try + { + Dictionary request = + ServerUtils.ParseQueryString(body); + + if (!request.ContainsKey("METHOD")) + return FailureResult(); + + string method = request["METHOD"].ToString(); + request.Remove("METHOD"); + + m_log.DebugFormat("[Groups.RobustHGConnector]: {0}", method); + switch (method) + { + case "POSTGROUP": + return HandleAddGroupProxy(request); + case "REMOVEAGENTFROMGROUP": + return HandleRemoveAgentFromGroup(request); + case "GETGROUP": + return HandleGetGroup(request); + case "ADDNOTICE": + return HandleAddNotice(request); + case "VERIFYNOTICE": + return HandleVerifyNotice(request); + case "GETGROUPMEMBERS": + return HandleGetGroupMembers(request); + case "GETGROUPROLES": + return HandleGetGroupRoles(request); + case "GETROLEMEMBERS": + return HandleGetRoleMembers(request); + + } + m_log.DebugFormat("[Groups.RobustHGConnector]: unknown method request: {0}", method); + } + catch (Exception e) + { + m_log.DebugFormat("[Groups.RobustHGConnector]: Exception {0}", e.StackTrace); + } + + return FailureResult(); + } + + byte[] HandleAddGroupProxy(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") + || !request.ContainsKey("AgentID") + || !request.ContainsKey("AccessToken") || !request.ContainsKey("Location")) + NullResult(result, "Bad network data"); + + else + { + string RequestingAgentID = request["RequestingAgentID"].ToString(); + string agentID = request["AgentID"].ToString(); + UUID groupID = new UUID(request["GroupID"].ToString()); + string accessToken = request["AccessToken"].ToString(); + string location = request["Location"].ToString(); + string name = string.Empty; + if (request.ContainsKey("Name")) + name = request["Name"].ToString(); + + string reason = string.Empty; + bool success = m_GroupsService.CreateGroupProxy(RequestingAgentID, agentID, accessToken, groupID, location, name, out reason); + result["REASON"] = reason; + result["RESULT"] = success.ToString(); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleRemoveAgentFromGroup(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("AccessToken") || !request.ContainsKey("AgentID") || + !request.ContainsKey("GroupID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string agentID = request["AgentID"].ToString(); + string token = request["AccessToken"].ToString(); + string reason = string.Empty; + + m_GroupsService.RemoveAgentFromGroup(agentID, agentID, groupID, token); + } + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + result["RESULT"] = "true"; + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + byte[] HandleGetGroup(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AccessToken")) + NullResult(result, "Bad network data"); + else + { + string RequestingAgentID = request["RequestingAgentID"].ToString(); + string token = request["AccessToken"].ToString(); + + UUID groupID = UUID.Zero; + string groupName = string.Empty; + + if (request.ContainsKey("GroupID")) + groupID = new UUID(request["GroupID"].ToString()); + if (request.ContainsKey("Name")) + groupName = request["Name"].ToString(); + + ExtendedGroupRecord grec = m_GroupsService.GetGroupRecord(RequestingAgentID, groupID, groupName, token); + if (grec == null) + NullResult(result, "Group not found"); + else + result["RESULT"] = GroupsDataUtils.GroupRecord(grec); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetGroupMembers(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + string token = request["AccessToken"].ToString(); + + List members = m_GroupsService.GetGroupMembers(requestingAgentID, groupID, token); + if (members == null || (members != null && members.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (ExtendedGroupMembersData m in members) + { + dict["m-" + i++] = GroupsDataUtils.GroupMembersData(m); + } + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetGroupRoles(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + string token = request["AccessToken"].ToString(); + + List roles = m_GroupsService.GetGroupRoles(requestingAgentID, groupID, token); + if (roles == null || (roles != null && roles.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (GroupRolesData r in roles) + dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r); + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetRoleMembers(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + string token = request["AccessToken"].ToString(); + + List rmembers = m_GroupsService.GetGroupRoleMembers(requestingAgentID, groupID, token); + if (rmembers == null || (rmembers != null && rmembers.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (ExtendedGroupRoleMembersData rm in rmembers) + dict["rm-" + i++] = GroupsDataUtils.GroupRoleMembersData(rm); + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleAddNotice(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("NoticeID") || + !request.ContainsKey("FromName") || !request.ContainsKey("Subject") || !request.ContainsKey("Message") || + !request.ContainsKey("HasAttachment")) + NullResult(result, "Bad network data"); + + else + { + + bool hasAtt = bool.Parse(request["HasAttachment"].ToString()); + byte attType = 0; + string attName = string.Empty; + string attOwner = string.Empty; + UUID attItem = UUID.Zero; + if (request.ContainsKey("AttachmentType")) + attType = byte.Parse(request["AttachmentType"].ToString()); + if (request.ContainsKey("AttachmentName")) + attName = request["AttachmentType"].ToString(); + if (request.ContainsKey("AttachmentItemID")) + attItem = new UUID(request["AttachmentItemID"].ToString()); + if (request.ContainsKey("AttachmentOwnerID")) + attOwner = request["AttachmentOwnerID"].ToString(); + + bool success = m_GroupsService.AddNotice(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()), + new UUID(request["NoticeID"].ToString()), request["FromName"].ToString(), request["Subject"].ToString(), + request["Message"].ToString(), hasAtt, attType, attName, attItem, attOwner); + + result["RESULT"] = success.ToString(); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleVerifyNotice(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("NoticeID") || !request.ContainsKey("GroupID")) + NullResult(result, "Bad network data"); + + else + { + UUID noticeID = new UUID(request["NoticeID"].ToString()); + UUID groupID = new UUID(request["GroupID"].ToString()); + + bool success = m_GroupsService.VerifyNotice(noticeID, groupID); + //m_log.DebugFormat("[XXX]: VerifyNotice returned {0}", success); + result["RESULT"] = success.ToString(); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + // + // + // + // + // + + #region Helpers + + private void NullResult(Dictionary result, string reason) + { + result["RESULT"] = "NULL"; + result["REASON"] = reason; + } + + private byte[] FailureResult() + { + Dictionary result = new Dictionary(); + NullResult(result, "Unknown method"); + string xmlString = ServerUtils.BuildXmlResponse(result); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + #endregion + } +} diff --git a/OpenSim/Addons/Groups/IGroupsServicesConnector.cs b/OpenSim/Addons/Groups/IGroupsServicesConnector.cs new file mode 100644 index 0000000000..73deb7a7b4 --- /dev/null +++ b/OpenSim/Addons/Groups/IGroupsServicesConnector.cs @@ -0,0 +1,118 @@ +/* + * 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 OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Groups +{ + public interface IGroupsServicesConnector + { + UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, + bool openEnrollment, bool allowPublish, bool maturePublish, UUID founderID, out string reason); + bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, + bool openEnrollment, bool allowPublish, bool maturePublish, out string reason); + ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName); + List FindGroups(string RequestingAgentID, string search); + List GetGroupMembers(string RequestingAgentID, UUID GroupID); + + bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason); + bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers); + void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID); + List GetGroupRoles(string RequestingAgentID, UUID GroupID); + List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID); + + bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason); + void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID); + + bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID); + GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID); + void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID); + + void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID); + void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID); + List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID); + + void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID); + ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID); + + void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID); + void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile); + + /// + /// Get information about a specific group to which the user belongs. + /// + /// The agent requesting the information. + /// The agent requested. + /// The group requested. + /// + /// If the user is a member of the group then the data structure is returned. If not, then null is returned. + /// + ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID); + + /// + /// Get information about the groups to which a user belongs. + /// + /// The agent requesting the information. + /// The agent requested. + /// + /// Information about the groups to which the user belongs. If the user belongs to no groups then an empty + /// list is returned. + /// + List GetAgentGroupMemberships(string RequestingAgentID, string AgentID); + + bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID); + GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID); + List GetGroupNotices(string RequestingAgentID, UUID GroupID); + + void ResetAgentGroupChatSessions(string agentID); + bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID); + bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID); + void AgentDroppedFromGroupChatSession(string agentID, UUID groupID); + void AgentInvitedToGroupChatSession(string agentID, UUID groupID); + + } + + public class GroupInviteInfo + { + public UUID GroupID = UUID.Zero; + public UUID RoleID = UUID.Zero; + public string AgentID = string.Empty; + public UUID InviteID = UUID.Zero; + } + + public class GroupNoticeInfo + { + public ExtendedGroupNoticeData noticeData = new ExtendedGroupNoticeData(); + public UUID GroupID = UUID.Zero; + public string Message = string.Empty; + } + +} diff --git a/OpenSim/Addons/Groups/Local/GroupsServiceLocalConnectorModule.cs b/OpenSim/Addons/Groups/Local/GroupsServiceLocalConnectorModule.cs new file mode 100644 index 0000000000..905bc913ff --- /dev/null +++ b/OpenSim/Addons/Groups/Local/GroupsServiceLocalConnectorModule.cs @@ -0,0 +1,347 @@ +/* + * 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.Linq; +using System.Reflection; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; + +using OpenMetaverse; +using Mono.Addins; +using log4net; +using Nini.Config; + +namespace OpenSim.Groups +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceLocalConnectorModule")] + public class GroupsServiceLocalConnectorModule : ISharedRegionModule, IGroupsServicesConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool m_Enabled = false; + private GroupsService m_GroupsService; + private IUserManagement m_UserManagement; + private List m_Scenes; + private ForeignImporter m_ForeignImporter; + + #region constructors + public GroupsServiceLocalConnectorModule() + { + } + + public GroupsServiceLocalConnectorModule(IConfigSource config, IUserManagement uman) + { + Init(config); + m_UserManagement = uman; + m_ForeignImporter = new ForeignImporter(uman); + } + #endregion + + private void Init(IConfigSource config) + { + m_GroupsService = new GroupsService(config); + m_Scenes = new List(); + } + + #region ISharedRegionModule + + public void Initialise(IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + if (groupsConfig == null) + return; + + if ((groupsConfig.GetBoolean("Enabled", false) == false) + || (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name)) + { + return; + } + + Init(config); + m_Enabled = true; + + m_log.DebugFormat("[Groups]: Initializing {0}", this.Name); + } + + public string Name + { + get { return "Groups Local Service Connector"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName); + scene.RegisterModuleInterface(this); + m_Scenes.Add(scene); + } + + public void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.UnregisterModuleInterface(this); + m_Scenes.Remove(scene); + } + + public void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + + if (m_UserManagement == null) + { + m_UserManagement = scene.RequestModuleInterface(); + m_ForeignImporter = new ForeignImporter(m_UserManagement); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + #endregion + + #region IGroupsServicesConnector + + public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, + bool allowPublish, bool maturePublish, UUID founderID, out string reason) + { + m_log.DebugFormat("[Groups]: Creating group {0}", name); + reason = string.Empty; + return m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID, + membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason); + } + + public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, + bool openEnrollment, bool allowPublish, bool maturePublish, out string reason) + { + reason = string.Empty; + m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish); + return true; + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName) + { + if (GroupID != UUID.Zero) + return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID); + else if (GroupName != null) + return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupName); + + return null; + } + + public List FindGroups(string RequestingAgentID, string search) + { + return m_GroupsService.FindGroups(RequestingAgentID, search); + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID) + { + List _members = m_GroupsService.GetGroupMembers(RequestingAgentID, GroupID); + if (_members != null && _members.Count > 0) + { + List members = _members.ConvertAll(new Converter(m_ForeignImporter.ConvertGroupMembersData)); + return members; + } + + return new List(); + } + + public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason) + { + return m_GroupsService.AddGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, out reason); + } + + public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers) + { + return m_GroupsService.UpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers); + } + + public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID) + { + m_GroupsService.RemoveGroupRole(RequestingAgentID, groupID, roleID); + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID) + { + return m_GroupsService.GetGroupRoles(RequestingAgentID, GroupID); + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID) + { + List _rm = m_GroupsService.GetGroupRoleMembers(RequestingAgentID, GroupID); + if (_rm != null && _rm.Count > 0) + { + List rm = _rm.ConvertAll(new Converter(m_ForeignImporter.ConvertGroupRoleMembersData)); + return rm; + } + + return new List(); + + } + + public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason) + { + return m_GroupsService.AddAgentToGroup(RequestingAgentID, AgentID, GroupID, RoleID, token, out reason); + } + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + m_GroupsService.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID); + } + + public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID) + { + return m_GroupsService.AddAgentToGroupInvite(RequestingAgentID, inviteID, groupID, roleID, agentID); + } + + public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID); ; + } + + public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID); + } + + public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + m_GroupsService.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + } + + public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + m_GroupsService.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + } + + public List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID) + { + return m_GroupsService.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID); + } + + public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + m_GroupsService.SetAgentActiveGroup(RequestingAgentID, AgentID, GroupID); + } + + public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID) + { + return m_GroupsService.GetAgentActiveMembership(RequestingAgentID, AgentID); + } + + public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + m_GroupsService.SetAgentActiveGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + } + + public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile) + { + m_GroupsService.UpdateMembership(RequestingAgentID, AgentID, GroupID, AcceptNotices, ListInProfile); + } + + public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID) + { + return m_GroupsService.GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID); ; + } + + public List GetAgentGroupMemberships(string RequestingAgentID, string AgentID) + { + return m_GroupsService.GetAgentGroupMemberships(RequestingAgentID, AgentID); + } + + public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message, + hasAttachment, attType, attName, attItemID, attOwnerID); + } + + public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID) + { + GroupNoticeInfo notice = m_GroupsService.GetGroupNotice(RequestingAgentID, noticeID); + + //if (notice != null && notice.noticeData.HasAttachment && notice.noticeData.AttachmentOwnerID != null) + //{ + // UUID userID = UUID.Zero; + // string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty; + // Util.ParseUniversalUserIdentifier(notice.noticeData.AttachmentOwnerID, out userID, out url, out first, out last, out tmp); + // if (url != string.Empty) + // m_UserManagement.AddUser(userID, first, last, url); + //} + + return notice; + } + + public List GetGroupNotices(string RequestingAgentID, UUID GroupID) + { + return m_GroupsService.GetGroupNotices(RequestingAgentID, GroupID); + } + + public void ResetAgentGroupChatSessions(string agentID) + { + } + + public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID) + { + } + + public void AgentInvitedToGroupChatSession(string agentID, UUID groupID) + { + } + + #endregion + } +} diff --git a/OpenSim/Addons/Groups/Properties/AssemblyInfo.cs b/OpenSim/Addons/Groups/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..5ccd7fea1b --- /dev/null +++ b/OpenSim/Addons/Groups/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Mono.Addins; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenSim.Addons.Groups")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("http://opensimulator.org")] +[assembly: AssemblyProduct("OpenSim.Addons.Groups")] +[assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("313d4865-d179-4735-9b5a-fe74885878b2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("0.7.6.*")] + +[assembly: Addin("OpenSim.Groups", "0.1")] +[assembly: AddinDependency("OpenSim", "0.5")] diff --git a/OpenSim/Addons/Groups/Remote/GroupsServiceRemoteConnector.cs b/OpenSim/Addons/Groups/Remote/GroupsServiceRemoteConnector.cs new file mode 100644 index 0000000000..04328c9c3a --- /dev/null +++ b/OpenSim/Addons/Groups/Remote/GroupsServiceRemoteConnector.cs @@ -0,0 +1,642 @@ +/* + * 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.Linq; +using System.Reflection; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Server.Base; + +using OpenMetaverse; +using log4net; + +namespace OpenSim.Groups +{ + public class GroupsServiceRemoteConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_ServerURI; + private object m_Lock = new object(); + + public GroupsServiceRemoteConnector(string url) + { + m_ServerURI = url; + if (!m_ServerURI.EndsWith("/")) + m_ServerURI += "/"; + + m_log.DebugFormat("[Groups.RemoteConnector]: Groups server at {0}", m_ServerURI); + } + + public ExtendedGroupRecord CreateGroup(string RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, + bool allowPublish, bool maturePublish, UUID founderID, out string reason) + { + reason = string.Empty; + + ExtendedGroupRecord rec = new ExtendedGroupRecord(); + rec.AllowPublish = allowPublish; + rec.Charter = charter; + rec.FounderID = founderID; + rec.GroupName = name; + rec.GroupPicture = insigniaID; + rec.MaturePublish = maturePublish; + rec.MembershipFee = membershipFee; + rec.OpenEnrollment = openEnrollment; + rec.ShowInList = showInList; + + Dictionary sendData = GroupsDataUtils.GroupRecord(rec); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "ADD"; + Dictionary ret = MakeRequest("PUTGROUP", sendData); + + if (ret == null) + return null; + + if (ret["RESULT"].ToString() == "NULL") + { + reason = ret["REASON"].ToString(); + return null; + } + + return GroupsDataUtils.GroupRecord((Dictionary)ret["RESULT"]); + + } + + public ExtendedGroupRecord UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish) + { + ExtendedGroupRecord rec = new ExtendedGroupRecord(); + rec.AllowPublish = allowPublish; + rec.Charter = charter; + rec.GroupPicture = insigniaID; + rec.MaturePublish = maturePublish; + rec.GroupID = groupID; + rec.MembershipFee = membershipFee; + rec.OpenEnrollment = openEnrollment; + rec.ShowInList = showInList; + + Dictionary sendData = GroupsDataUtils.GroupRecord(rec); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "UPDATE"; + Dictionary ret = MakeRequest("PUTGROUP", sendData); + + if (ret == null || (ret != null && ret["RESULT"].ToString() == "NULL")) + return null; + + return GroupsDataUtils.GroupRecord((Dictionary)ret["RESULT"]); + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName) + { + if (GroupID == UUID.Zero && (GroupName == null || (GroupName != null && GroupName == string.Empty))) + return null; + + Dictionary sendData = new Dictionary(); + if (GroupID != UUID.Zero) + sendData["GroupID"] = GroupID.ToString(); + if (GroupName != null && GroupName != string.Empty) + sendData["Name"] = GroupsDataUtils.Sanitize(GroupName); + + sendData["RequestingAgentID"] = RequestingAgentID; + + Dictionary ret = MakeRequest("GETGROUP", sendData); + + if (ret == null || (ret != null && ret["RESULT"].ToString() == "NULL")) + return null; + + return GroupsDataUtils.GroupRecord((Dictionary)ret["RESULT"]); + } + + public GroupMembershipData AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason) + { + reason = string.Empty; + + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID; + sendData["GroupID"] = GroupID.ToString(); + sendData["RoleID"] = RoleID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["AccessToken"] = token; + Dictionary ret = MakeRequest("ADDAGENTTOGROUP", sendData); + + if (ret == null) + return null; + + if (!ret.ContainsKey("RESULT")) + return null; + + if (ret["RESULT"].ToString() == "NULL") + { + reason = ret["REASON"].ToString(); + return null; + } + + return GroupsDataUtils.GroupMembershipData((Dictionary)ret["RESULT"]); + + } + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID; + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + MakeRequest("REMOVEAGENTFROMGROUP", sendData); + } + + public ExtendedGroupMembershipData GetMembership(string RequestingAgentID, string AgentID, UUID GroupID) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID; + if (GroupID != UUID.Zero) + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETMEMBERSHIP", sendData); + + if (ret == null) + return null; + + if (!ret.ContainsKey("RESULT")) + return null; + + if (ret["RESULT"].ToString() == "NULL") + return null; + + return GroupsDataUtils.GroupMembershipData((Dictionary)ret["RESULT"]); + } + + public List GetMemberships(string RequestingAgentID, string AgentID) + { + List memberships = new List(); + + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID; + sendData["ALL"] = "true"; + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETMEMBERSHIP", sendData); + + if (ret == null) + return memberships; + + if (!ret.ContainsKey("RESULT")) + return memberships; + + if (ret["RESULT"].ToString() == "NULL") + return memberships; + + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + GroupMembershipData m = GroupsDataUtils.GroupMembershipData((Dictionary)v); + memberships.Add(m); + } + + return memberships; + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID) + { + List members = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETGROUPMEMBERS", sendData); + + if (ret == null) + return members; + + if (!ret.ContainsKey("RESULT")) + return members; + + if (ret["RESULT"].ToString() == "NULL") + return members; + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + ExtendedGroupMembersData m = GroupsDataUtils.GroupMembersData((Dictionary)v); + members.Add(m); + } + + return members; + } + + public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason) + { + reason = string.Empty; + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = groupID.ToString(); + sendData["RoleID"] = roleID.ToString(); + sendData["Name"] = GroupsDataUtils.Sanitize(name); + sendData["Description"] = GroupsDataUtils.Sanitize(description); + sendData["Title"] = GroupsDataUtils.Sanitize(title); + sendData["Powers"] = powers.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "ADD"; + Dictionary ret = MakeRequest("PUTROLE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + { + reason = ret["REASON"].ToString(); + return false; + } + + return true; + } + + public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers) + { + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = groupID.ToString(); + sendData["RoleID"] = roleID.ToString(); + sendData["Name"] = GroupsDataUtils.Sanitize(name); + sendData["Description"] = GroupsDataUtils.Sanitize(description); + sendData["Title"] = GroupsDataUtils.Sanitize(title); + sendData["Powers"] = powers.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "UPDATE"; + Dictionary ret = MakeRequest("PUTROLE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + return false; + + return true; + } + + public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID) + { + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = groupID.ToString(); + sendData["RoleID"] = roleID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + MakeRequest("REMOVEROLE", sendData); + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID) + { + List roles = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETGROUPROLES", sendData); + + if (ret == null) + return roles; + + if (!ret.ContainsKey("RESULT")) + return roles; + + if (ret["RESULT"].ToString() == "NULL") + return roles; + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary)v); + roles.Add(m); + } + + return roles; + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID) + { + List rmembers = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETROLEMEMBERS", sendData); + + if (ret == null) + return rmembers; + + if (!ret.ContainsKey("RESULT")) + return rmembers; + + if (ret["RESULT"].ToString() == "NULL") + return rmembers; + + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + ExtendedGroupRoleMembersData m = GroupsDataUtils.GroupRoleMembersData((Dictionary)v); + rmembers.Add(m); + } + + return rmembers; + } + + public bool AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID.ToString(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RoleID"] = RoleID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "ADD"; + + Dictionary ret = MakeRequest("AGENTROLE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + return false; + + return true; + } + + public bool RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID.ToString(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RoleID"] = RoleID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "DELETE"; + + Dictionary ret = MakeRequest("AGENTROLE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + return false; + + return true; + } + + public List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID) + { + List roles = new List(); + + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID.ToString(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETAGENTROLES", sendData); + + if (ret == null) + return roles; + + if (!ret.ContainsKey("RESULT")) + return roles; + + if (ret["RESULT"].ToString() == "NULL") + return roles; + + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary)v); + roles.Add(m); + } + + return roles; + } + + public GroupMembershipData SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID.ToString(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "GROUP"; + + Dictionary ret = MakeRequest("SETACTIVE", sendData); + + if (ret == null) + return null; + + if (!ret.ContainsKey("RESULT")) + return null; + + if (ret["RESULT"].ToString() == "NULL") + return null; + + return GroupsDataUtils.GroupMembershipData((Dictionary)ret["RESULT"]); + } + + public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID.ToString(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RoleID"] = RoleID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "ROLE"; + + MakeRequest("SETACTIVE", sendData); + } + + public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile) + { + Dictionary sendData = new Dictionary(); + sendData["AgentID"] = AgentID.ToString(); + sendData["GroupID"] = GroupID.ToString(); + sendData["AcceptNotices"] = AcceptNotices.ToString(); + sendData["ListInProfile"] = ListInProfile.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + MakeRequest("UPDATEMEMBERSHIP", sendData); + } + + public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID) + { + Dictionary sendData = new Dictionary(); + sendData["InviteID"] = inviteID.ToString(); + sendData["GroupID"] = groupID.ToString(); + sendData["RoleID"] = roleID.ToString(); + sendData["AgentID"] = agentID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "ADD"; + + Dictionary ret = MakeRequest("INVITE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") // it may return "NULL" + return false; + + return true; + } + + public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + Dictionary sendData = new Dictionary(); + sendData["InviteID"] = inviteID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "GET"; + + Dictionary ret = MakeRequest("INVITE", sendData); + + if (ret == null) + return null; + + if (!ret.ContainsKey("RESULT")) + return null; + + if (ret["RESULT"].ToString() == "NULL") + return null; + + return GroupsDataUtils.GroupInviteInfo((Dictionary)ret["RESULT"]); + } + + public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + Dictionary sendData = new Dictionary(); + sendData["InviteID"] = inviteID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + sendData["OP"] = "DELETE"; + + MakeRequest("INVITE", sendData); + } + + public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = groupID.ToString(); + sendData["NoticeID"] = noticeID.ToString(); + sendData["FromName"] = GroupsDataUtils.Sanitize(fromName); + sendData["Subject"] = GroupsDataUtils.Sanitize(subject); + sendData["Message"] = GroupsDataUtils.Sanitize(message); + sendData["HasAttachment"] = hasAttachment.ToString(); + if (hasAttachment) + { + sendData["AttachmentType"] = attType.ToString(); + sendData["AttachmentName"] = attName.ToString(); + sendData["AttachmentItemID"] = attItemID.ToString(); + sendData["AttachmentOwnerID"] = attOwnerID; + } + sendData["RequestingAgentID"] = RequestingAgentID; + + Dictionary ret = MakeRequest("ADDNOTICE", sendData); + + if (ret == null) + return false; + + if (!ret.ContainsKey("RESULT")) + return false; + + if (ret["RESULT"].ToString().ToLower() != "true") + return false; + + return true; + } + + public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID) + { + Dictionary sendData = new Dictionary(); + sendData["NoticeID"] = noticeID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + + Dictionary ret = MakeRequest("GETNOTICES", sendData); + + if (ret == null) + return null; + + if (!ret.ContainsKey("RESULT")) + return null; + + if (ret["RESULT"].ToString() == "NULL") + return null; + + return GroupsDataUtils.GroupNoticeInfo((Dictionary)ret["RESULT"]); + } + + public List GetGroupNotices(string RequestingAgentID, UUID GroupID) + { + List notices = new List(); + + Dictionary sendData = new Dictionary(); + sendData["GroupID"] = GroupID.ToString(); + sendData["RequestingAgentID"] = RequestingAgentID; + Dictionary ret = MakeRequest("GETNOTICES", sendData); + + if (ret == null) + return notices; + + if (!ret.ContainsKey("RESULT")) + return notices; + + if (ret["RESULT"].ToString() == "NULL") + return notices; + + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + ExtendedGroupNoticeData m = GroupsDataUtils.GroupNoticeData((Dictionary)v); + notices.Add(m); + } + + return notices; + } + + #region Make Request + + private Dictionary MakeRequest(string method, Dictionary sendData) + { + sendData["METHOD"] = method; + + string reply = string.Empty; + lock (m_Lock) + reply = SynchronousRestFormsRequester.MakeRequest("POST", + m_ServerURI + "groups", + ServerUtils.BuildQueryString(sendData)); + + if (reply == string.Empty) + return null; + + Dictionary replyData = ServerUtils.ParseXmlResponse( + reply); + + return replyData; + } + #endregion + + } +} diff --git a/OpenSim/Addons/Groups/Remote/GroupsServiceRemoteConnectorModule.cs b/OpenSim/Addons/Groups/Remote/GroupsServiceRemoteConnectorModule.cs new file mode 100644 index 0000000000..f1cf66c5ba --- /dev/null +++ b/OpenSim/Addons/Groups/Remote/GroupsServiceRemoteConnectorModule.cs @@ -0,0 +1,434 @@ +/* + * 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.Linq; +using System.Reflection; +using System.Threading; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Server.Base; + +using OpenMetaverse; +using Mono.Addins; +using log4net; +using Nini.Config; + +namespace OpenSim.Groups +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceRemoteConnectorModule")] + public class GroupsServiceRemoteConnectorModule : ISharedRegionModule, IGroupsServicesConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool m_Enabled = false; + private GroupsServiceRemoteConnector m_GroupsService; + private IUserManagement m_UserManagement; + private List m_Scenes; + + private RemoteConnectorCacheWrapper m_CacheWrapper; + + #region constructors + public GroupsServiceRemoteConnectorModule() + { + } + + public GroupsServiceRemoteConnectorModule(IConfigSource config, IUserManagement uman) + { + Init(config); + m_UserManagement = uman; + m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement); + + } + #endregion + + private void Init(IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + string url = groupsConfig.GetString("GroupsServerURI", string.Empty); + if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) + throw new Exception(string.Format("[Groups.RemoteConnector]: Malformed groups server URL {0}. Fix it or disable the Groups feature.", url)); + + m_GroupsService = new GroupsServiceRemoteConnector(url); + m_Scenes = new List(); + + } + + #region ISharedRegionModule + + public void Initialise(IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + if (groupsConfig == null) + return; + + if ((groupsConfig.GetBoolean("Enabled", false) == false) + || (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name)) + { + return; + } + + Init(config); + + m_Enabled = true; + m_log.DebugFormat("[Groups.RemoteConnector]: Initializing {0}", this.Name); + } + + public string Name + { + get { return "Groups Remote Service Connector"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + m_log.DebugFormat("[Groups.RemoteConnector]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName); + scene.RegisterModuleInterface(this); + m_Scenes.Add(scene); + } + + public void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.UnregisterModuleInterface(this); + m_Scenes.Remove(scene); + } + + public void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + + if (m_UserManagement == null) + { + m_UserManagement = scene.RequestModuleInterface(); + m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + #endregion + + #region IGroupsServicesConnector + + public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, + bool allowPublish, bool maturePublish, UUID founderID, out string reason) + { + m_log.DebugFormat("[Groups.RemoteConnector]: Creating group {0}", name); + string r = string.Empty; + + UUID groupID = m_CacheWrapper.CreateGroup(RequestingAgentID, delegate + { + return m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID, + membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out r); + }); + + reason = r; + return groupID; + } + + public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, + bool openEnrollment, bool allowPublish, bool maturePublish, out string reason) + { + string r = string.Empty; + + bool success = m_CacheWrapper.UpdateGroup(groupID, delegate + { + return m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish); + }); + + reason = r; + return success; + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName) + { + if (GroupID == UUID.Zero && (GroupName == null || GroupName != null && GroupName == string.Empty)) + return null; + + return m_CacheWrapper.GetGroupRecord(RequestingAgentID,GroupID,GroupName, delegate + { + return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName); + }); + } + + public List FindGroups(string RequestingAgentID, string search) + { + // TODO! + return new List(); + } + + public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason) + { + string agentFullID = AgentID; + m_log.DebugFormat("[Groups.RemoteConnector]: Add agent {0} to group {1}", agentFullID, GroupID); + string r = string.Empty; + + bool success = m_CacheWrapper.AddAgentToGroup(RequestingAgentID, AgentID, GroupID, delegate + { + return m_GroupsService.AddAgentToGroup(RequestingAgentID, agentFullID, GroupID, RoleID, token, out r); + }); + + reason = r; + return success; + } + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + m_CacheWrapper.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID, delegate + { + m_GroupsService.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID); + }); + + } + + public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + m_CacheWrapper.SetAgentActiveGroup(AgentID, delegate + { + return m_GroupsService.SetAgentActiveGroup(RequestingAgentID, AgentID, GroupID); + }); + } + + public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID) + { + return m_CacheWrapper.GetAgentActiveMembership(AgentID, delegate + { + return m_GroupsService.GetMembership(RequestingAgentID, AgentID, UUID.Zero); + }); + } + + public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID) + { + return m_CacheWrapper.GetAgentGroupMembership(AgentID, GroupID, delegate + { + return m_GroupsService.GetMembership(RequestingAgentID, AgentID, GroupID); + }); + } + + public List GetAgentGroupMemberships(string RequestingAgentID, string AgentID) + { + return m_CacheWrapper.GetAgentGroupMemberships(AgentID, delegate + { + return m_GroupsService.GetMemberships(RequestingAgentID, AgentID); + }); + } + + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID) + { + return m_CacheWrapper.GetGroupMembers(RequestingAgentID, GroupID, delegate + { + return m_GroupsService.GetGroupMembers(RequestingAgentID, GroupID); + }); + } + + public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason) + { + string r = string.Empty; + bool success = m_CacheWrapper.AddGroupRole(groupID, roleID, description, name, powers, title, delegate + { + return m_GroupsService.AddGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, out r); + }); + + reason = r; + return success; + } + + public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers) + { + return m_CacheWrapper.UpdateGroupRole(groupID, roleID, name, description, title, powers, delegate + { + return m_GroupsService.UpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers); + }); + } + + public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID) + { + m_CacheWrapper.RemoveGroupRole(RequestingAgentID, groupID, roleID, delegate + { + m_GroupsService.RemoveGroupRole(RequestingAgentID, groupID, roleID); + }); + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID) + { + return m_CacheWrapper.GetGroupRoles(RequestingAgentID, GroupID, delegate + { + return m_GroupsService.GetGroupRoles(RequestingAgentID, GroupID); + }); + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID) + { + return m_CacheWrapper.GetGroupRoleMembers(RequestingAgentID, GroupID, delegate + { + return m_GroupsService.GetGroupRoleMembers(RequestingAgentID, GroupID); + }); + } + + public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + m_CacheWrapper.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID, delegate + { + return m_GroupsService.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + }); + } + + public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + m_CacheWrapper.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID, delegate + { + return m_GroupsService.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + }); + } + + public List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID) + { + return m_CacheWrapper.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID, delegate + { + return m_GroupsService.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID); ; + }); + } + + public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + m_CacheWrapper.SetAgentActiveGroupRole(AgentID, GroupID, delegate + { + m_GroupsService.SetAgentActiveGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + }); + } + + public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile) + { + m_CacheWrapper.UpdateMembership(AgentID, GroupID, AcceptNotices, ListInProfile, delegate + { + m_GroupsService.UpdateMembership(RequestingAgentID, AgentID, GroupID, AcceptNotices, ListInProfile); + }); + } + + public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID) + { + return m_GroupsService.AddAgentToGroupInvite(RequestingAgentID, inviteID, groupID, roleID, agentID); + } + + public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID); + } + + public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID); + } + + public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + GroupNoticeInfo notice = new GroupNoticeInfo(); + notice.GroupID = groupID; + notice.Message = message; + notice.noticeData = new ExtendedGroupNoticeData(); + notice.noticeData.AttachmentItemID = attItemID; + notice.noticeData.AttachmentName = attName; + notice.noticeData.AttachmentOwnerID = attOwnerID.ToString(); + notice.noticeData.AttachmentType = attType; + notice.noticeData.FromName = fromName; + notice.noticeData.HasAttachment = hasAttachment; + notice.noticeData.NoticeID = noticeID; + notice.noticeData.Subject = subject; + notice.noticeData.Timestamp = (uint)Util.UnixTimeSinceEpoch(); + + return m_CacheWrapper.AddGroupNotice(groupID, noticeID, notice, delegate + { + return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message, + hasAttachment, attType, attName, attItemID, attOwnerID); + }); + } + + public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID) + { + return m_CacheWrapper.GetGroupNotice(noticeID, delegate + { + return m_GroupsService.GetGroupNotice(RequestingAgentID, noticeID); + }); + } + + public List GetGroupNotices(string RequestingAgentID, UUID GroupID) + { + return m_CacheWrapper.GetGroupNotices(GroupID, delegate + { + return m_GroupsService.GetGroupNotices(RequestingAgentID, GroupID); + }); + } + + public void ResetAgentGroupChatSessions(string agentID) + { + } + + public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID) + { + } + + public void AgentInvitedToGroupChatSession(string agentID, UUID groupID) + { + } + + #endregion + } + +} diff --git a/OpenSim/Addons/Groups/Remote/GroupsServiceRobustConnector.cs b/OpenSim/Addons/Groups/Remote/GroupsServiceRobustConnector.cs new file mode 100644 index 0000000000..f991d016f0 --- /dev/null +++ b/OpenSim/Addons/Groups/Remote/GroupsServiceRobustConnector.cs @@ -0,0 +1,760 @@ +/* + * 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.Reflection; +using System.Text; +using System.Xml; +using System.Collections.Generic; +using System.IO; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Server.Handlers.Base; +using log4net; +using OpenMetaverse; + +namespace OpenSim.Groups +{ + public class GroupsServiceRobustConnector : ServiceConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private GroupsService m_GroupsService; + private string m_ConfigName = "Groups"; + + public GroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) : + base(config, server, configName) + { + if (configName != String.Empty) + m_ConfigName = configName; + + m_log.DebugFormat("[Groups.RobustConnector]: Starting with config name {0}", m_ConfigName); + + m_GroupsService = new GroupsService(config); + + server.AddStreamHandler(new GroupsServicePostHandler(m_GroupsService)); + } + } + + public class GroupsServicePostHandler : BaseStreamHandler + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private GroupsService m_GroupsService; + + public GroupsServicePostHandler(GroupsService service) : + base("POST", "/groups") + { + m_GroupsService = service; + } + + public override byte[] Handle(string path, Stream requestData, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + StreamReader sr = new StreamReader(requestData); + string body = sr.ReadToEnd(); + sr.Close(); + body = body.Trim(); + + //m_log.DebugFormat("[XXX]: query String: {0}", body); + + try + { + Dictionary request = + ServerUtils.ParseQueryString(body); + + if (!request.ContainsKey("METHOD")) + return FailureResult(); + + string method = request["METHOD"].ToString(); + request.Remove("METHOD"); + + m_log.DebugFormat("[Groups.Handler]: {0}", method); + switch (method) + { + case "PUTGROUP": + return HandleAddOrUpdateGroup(request); + case "GETGROUP": + return HandleGetGroup(request); + case "ADDAGENTTOGROUP": + return HandleAddAgentToGroup(request); + case "REMOVEAGENTFROMGROUP": + return HandleRemoveAgentFromGroup(request); + case "GETMEMBERSHIP": + return HandleGetMembership(request); + case "GETGROUPMEMBERS": + return HandleGetGroupMembers(request); + case "PUTROLE": + return HandlePutRole(request); + case "REMOVEROLE": + return HandleRemoveRole(request); + case "GETGROUPROLES": + return HandleGetGroupRoles(request); + case "GETROLEMEMBERS": + return HandleGetRoleMembers(request); + case "AGENTROLE": + return HandleAgentRole(request); + case "GETAGENTROLES": + return HandleGetAgentRoles(request); + case "SETACTIVE": + return HandleSetActive(request); + case "UPDATEMEMBERSHIP": + return HandleUpdateMembership(request); + case "INVITE": + return HandleInvite(request); + case "ADDNOTICE": + return HandleAddNotice(request); + case "GETNOTICES": + return HandleGetNotices(request); + } + m_log.DebugFormat("[GROUPS HANDLER]: unknown method request: {0}", method); + } + catch (Exception e) + { + m_log.DebugFormat("[GROUPS HANDLER]: Exception {0}", e.StackTrace); + } + + return FailureResult(); + } + + byte[] HandleAddOrUpdateGroup(Dictionary request) + { + Dictionary result = new Dictionary(); + + ExtendedGroupRecord grec = GroupsDataUtils.GroupRecord(request); + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("OP")) + NullResult(result, "Bad network data"); + + else + { + string RequestingAgentID = request["RequestingAgentID"].ToString(); + string reason = string.Empty; + string op = request["OP"].ToString(); + if (op == "ADD") + { + grec.GroupID = m_GroupsService.CreateGroup(RequestingAgentID, grec.GroupName, grec.Charter, grec.ShowInList, grec.GroupPicture, grec.MembershipFee, + grec.OpenEnrollment, grec.AllowPublish, grec.MaturePublish, grec.FounderID, out reason); + + } + else if (op == "UPDATE") + { + m_GroupsService.UpdateGroup(RequestingAgentID, grec.GroupID, grec.Charter, grec.ShowInList, grec.GroupPicture, grec.MembershipFee, + grec.OpenEnrollment, grec.AllowPublish, grec.MaturePublish); + + } + + grec = m_GroupsService.GetGroupRecord(RequestingAgentID, grec.GroupID); + if (grec == null) + NullResult(result, "Internal Error"); + else + result["RESULT"] = GroupsDataUtils.GroupRecord(grec); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetGroup(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID")) + NullResult(result, "Bad network data"); + else + { + string RequestingAgentID = request["RequestingAgentID"].ToString(); + ExtendedGroupRecord grec = null; + if (request.ContainsKey("GroupID")) + { + UUID groupID = new UUID(request["GroupID"].ToString()); + grec = m_GroupsService.GetGroupRecord(RequestingAgentID, groupID); + } + else if (request.ContainsKey("Name")) + { + string name = request["Name"].ToString(); + grec = m_GroupsService.GetGroupRecord(RequestingAgentID, name); + } + + if (grec == null) + NullResult(result, "Group not found"); + else + result["RESULT"] = GroupsDataUtils.GroupRecord(grec); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleAddAgentToGroup(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || + !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + UUID roleID = new UUID(request["RoleID"].ToString()); + string agentID = request["AgentID"].ToString(); + string requestingAgentID = request["RequestingAgentID"].ToString(); + string token = string.Empty; + string reason = string.Empty; + + if (request.ContainsKey("AccessToken")) + token = request["AccessToken"].ToString(); + + if (!m_GroupsService.AddAgentToGroup(requestingAgentID, agentID, groupID, roleID, token, out reason)) + NullResult(result, reason); + else + { + GroupMembershipData membership = m_GroupsService.GetAgentGroupMembership(requestingAgentID, agentID, groupID); + if (membership == null) + NullResult(result, "Internal error"); + else + result["RESULT"] = GroupsDataUtils.GroupMembershipData((ExtendedGroupMembershipData)membership); + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleRemoveAgentFromGroup(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || !request.ContainsKey("GroupID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string agentID = request["AgentID"].ToString(); + string requestingAgentID = request["RequestingAgentID"].ToString(); + string reason = string.Empty; + + m_GroupsService.RemoveAgentFromGroup(requestingAgentID, agentID, groupID); + } + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + result["RESULT"] = "true"; + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + byte[] HandleGetMembership(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID")) + NullResult(result, "Bad network data"); + else + { + string agentID = request["AgentID"].ToString(); + UUID groupID = UUID.Zero; + if (request.ContainsKey("GroupID")) + groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + bool all = request.ContainsKey("ALL"); + + if (!all) + { + ExtendedGroupMembershipData membership = null; + if (groupID == UUID.Zero) + { + membership = m_GroupsService.GetAgentActiveMembership(requestingAgentID, agentID); + } + else + { + membership = m_GroupsService.GetAgentGroupMembership(requestingAgentID, agentID, groupID); + } + + if (membership == null) + NullResult(result, "No such membership"); + else + result["RESULT"] = GroupsDataUtils.GroupMembershipData(membership); + } + else + { + List memberships = m_GroupsService.GetAgentGroupMemberships(requestingAgentID, agentID); + if (memberships == null || (memberships != null && memberships.Count == 0)) + { + NullResult(result, "No memberships"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (GroupMembershipData m in memberships) + dict["m-" + i++] = GroupsDataUtils.GroupMembershipData((ExtendedGroupMembershipData)m); + + result["RESULT"] = dict; + } + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetGroupMembers(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + + List members = m_GroupsService.GetGroupMembers(requestingAgentID, groupID); + if (members == null || (members != null && members.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (ExtendedGroupMembersData m in members) + { + dict["m-" + i++] = GroupsDataUtils.GroupMembersData(m); + } + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandlePutRole(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID") || + !request.ContainsKey("Name") || !request.ContainsKey("Description") || !request.ContainsKey("Title") || + !request.ContainsKey("Powers") || !request.ContainsKey("OP")) + NullResult(result, "Bad network data"); + + else + { + string op = request["OP"].ToString(); + string reason = string.Empty; + + bool success = false; + if (op == "ADD") + success = m_GroupsService.AddGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()), + new UUID(request["RoleID"].ToString()), request["Name"].ToString(), request["Description"].ToString(), + request["Title"].ToString(), UInt64.Parse(request["Powers"].ToString()), out reason); + + else if (op == "UPDATE") + success = m_GroupsService.UpdateGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()), + new UUID(request["RoleID"].ToString()), request["Name"].ToString(), request["Description"].ToString(), + request["Title"].ToString(), UInt64.Parse(request["Powers"].ToString())); + + result["RESULT"] = success.ToString(); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleRemoveRole(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID")) + NullResult(result, "Bad network data"); + + else + { + m_GroupsService.RemoveGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()), + new UUID(request["RoleID"].ToString())); + result["RESULT"] = "true"; + } + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + byte[] HandleGetGroupRoles(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + + List roles = m_GroupsService.GetGroupRoles(requestingAgentID, groupID); + if (roles == null || (roles != null && roles.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (GroupRolesData r in roles) + dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r); + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetRoleMembers(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string requestingAgentID = request["RequestingAgentID"].ToString(); + + List rmembers = m_GroupsService.GetGroupRoleMembers(requestingAgentID, groupID); + if (rmembers == null || (rmembers != null && rmembers.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (ExtendedGroupRoleMembersData rm in rmembers) + dict["rm-" + i++] = GroupsDataUtils.GroupRoleMembersData(rm); + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleAgentRole(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID") || + !request.ContainsKey("AgentID") || !request.ContainsKey("OP")) + NullResult(result, "Bad network data"); + + else + { + string op = request["OP"].ToString(); + string reason = string.Empty; + + bool success = false; + if (op == "ADD") + success = m_GroupsService.AddAgentToGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(), + new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString())); + + else if (op == "DELETE") + success = m_GroupsService.RemoveAgentFromGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(), + new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString())); + + result["RESULT"] = success.ToString(); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetAgentRoles(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AgentID")) + NullResult(result, "Bad network data"); + else + { + UUID groupID = new UUID(request["GroupID"].ToString()); + string agentID = request["AgentID"].ToString(); + string requestingAgentID = request["RequestingAgentID"].ToString(); + + List roles = m_GroupsService.GetAgentGroupRoles(requestingAgentID, agentID, groupID); + if (roles == null || (roles != null && roles.Count == 0)) + { + NullResult(result, "No members"); + } + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (GroupRolesData r in roles) + dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r); + + result["RESULT"] = dict; + } + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleSetActive(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || + !request.ContainsKey("AgentID") || !request.ContainsKey("OP")) + { + NullResult(result, "Bad network data"); + string xmlString = ServerUtils.BuildXmlResponse(result); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + else + { + string op = request["OP"].ToString(); + string reason = string.Empty; + + if (op == "GROUP") + { + ExtendedGroupMembershipData group = m_GroupsService.SetAgentActiveGroup(request["RequestingAgentID"].ToString(), + request["AgentID"].ToString(), new UUID(request["GroupID"].ToString())); + + if (group == null) + NullResult(result, "Internal error"); + else + result["RESULT"] = GroupsDataUtils.GroupMembershipData(group); + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + + } + else if (op == "ROLE" && request.ContainsKey("RoleID")) + { + m_GroupsService.SetAgentActiveGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(), + new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString())); + result["RESULT"] = "true"; + } + + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + } + + byte[] HandleUpdateMembership(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || !request.ContainsKey("GroupID") || + !request.ContainsKey("AcceptNotices") || !request.ContainsKey("ListInProfile")) + NullResult(result, "Bad network data"); + + else + { + m_GroupsService.UpdateMembership(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(), new UUID(request["GroupID"].ToString()), + bool.Parse(request["AcceptNotices"].ToString()), bool.Parse(request["ListInProfile"].ToString())); + + result["RESULT"] = "true"; + } + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + byte[] HandleInvite(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("InviteID")) + { + NullResult(result, "Bad network data"); + string xmlString = ServerUtils.BuildXmlResponse(result); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + else + { + string op = request["OP"].ToString(); + string reason = string.Empty; + + if (op == "ADD" && request.ContainsKey("GroupID") && request.ContainsKey("RoleID") && request.ContainsKey("AgentID")) + { + bool success = m_GroupsService.AddAgentToGroupInvite(request["RequestingAgentID"].ToString(), + new UUID(request["InviteID"].ToString()), new UUID(request["GroupID"].ToString()), + new UUID(request["RoleID"].ToString()), request["AgentID"].ToString()); + + result["RESULT"] = success.ToString(); + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + + } + else if (op == "DELETE") + { + m_GroupsService.RemoveAgentToGroupInvite(request["RequestingAgentID"].ToString(), new UUID(request["InviteID"].ToString())); + result["RESULT"] = "true"; + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + else if (op == "GET") + { + GroupInviteInfo invite = m_GroupsService.GetAgentToGroupInvite(request["RequestingAgentID"].ToString(), + new UUID(request["InviteID"].ToString())); + + result["RESULT"] = GroupsDataUtils.GroupInviteInfo(invite); + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + NullResult(result, "Bad OP in request"); + return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result)); + } + + } + + byte[] HandleAddNotice(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("NoticeID") || + !request.ContainsKey("FromName") || !request.ContainsKey("Subject") || !request.ContainsKey("Message") || + !request.ContainsKey("HasAttachment")) + NullResult(result, "Bad network data"); + + else + { + + bool hasAtt = bool.Parse(request["HasAttachment"].ToString()); + byte attType = 0; + string attName = string.Empty; + string attOwner = string.Empty; + UUID attItem = UUID.Zero; + if (request.ContainsKey("AttachmentType")) + attType = byte.Parse(request["AttachmentType"].ToString()); + if (request.ContainsKey("AttachmentName")) + attName = request["AttachmentName"].ToString(); + if (request.ContainsKey("AttachmentItemID")) + attItem = new UUID(request["AttachmentItemID"].ToString()); + if (request.ContainsKey("AttachmentOwnerID")) + attOwner = request["AttachmentOwnerID"].ToString(); + + bool success = m_GroupsService.AddGroupNotice(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()), + new UUID(request["NoticeID"].ToString()), request["FromName"].ToString(), request["Subject"].ToString(), + request["Message"].ToString(), hasAtt, attType, attName, attItem, attOwner); + + result["RESULT"] = success.ToString(); + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGetNotices(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("RequestingAgentID")) + NullResult(result, "Bad network data"); + + else if (request.ContainsKey("NoticeID")) // just one + { + GroupNoticeInfo notice = m_GroupsService.GetGroupNotice(request["RequestingAgentID"].ToString(), new UUID(request["NoticeID"].ToString())); + + if (notice == null) + NullResult(result, "NO such notice"); + else + result["RESULT"] = GroupsDataUtils.GroupNoticeInfo(notice); + + } + else if (request.ContainsKey("GroupID")) // all notices for group + { + List notices = m_GroupsService.GetGroupNotices(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString())); + + if (notices == null || (notices != null && notices.Count == 0)) + NullResult(result, "No notices"); + else + { + Dictionary dict = new Dictionary(); + int i = 0; + foreach (ExtendedGroupNoticeData n in notices) + dict["n-" + i++] = GroupsDataUtils.GroupNoticeData(n); + + result["RESULT"] = dict; + } + + } + else + NullResult(result, "Bad OP in request"); + + string xmlString = ServerUtils.BuildXmlResponse(result); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + + #region Helpers + + private void NullResult(Dictionary result, string reason) + { + result["RESULT"] = "NULL"; + result["REASON"] = reason; + } + + private byte[] FailureResult() + { + Dictionary result = new Dictionary(); + NullResult(result, "Unknown method"); + string xmlString = ServerUtils.BuildXmlResponse(result); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + #endregion + } +} diff --git a/OpenSim/Addons/Groups/RemoteConnectorCacheWrapper.cs b/OpenSim/Addons/Groups/RemoteConnectorCacheWrapper.cs new file mode 100644 index 0000000000..e7d38c2020 --- /dev/null +++ b/OpenSim/Addons/Groups/RemoteConnectorCacheWrapper.cs @@ -0,0 +1,831 @@ +/* + * 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.Reflection; +using System.Threading; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +using OpenMetaverse; + +namespace OpenSim.Groups +{ + public delegate ExtendedGroupRecord GroupRecordDelegate(); + public delegate GroupMembershipData GroupMembershipDelegate(); + public delegate List GroupMembershipListDelegate(); + public delegate List GroupMembersListDelegate(); + public delegate List GroupRolesListDelegate(); + public delegate List RoleMembersListDelegate(); + public delegate GroupNoticeInfo NoticeDelegate(); + public delegate List NoticeListDelegate(); + public delegate void VoidDelegate(); + public delegate bool BooleanDelegate(); + + public class RemoteConnectorCacheWrapper + { + private ForeignImporter m_ForeignImporter; + + private Dictionary m_ActiveRequests = new Dictionary(); + private const int GROUPS_CACHE_TIMEOUT = 5 * 60; // 5 minutes + + // This all important cache cahces objects of different types: + // group- or group- => ExtendedGroupRecord + // active- => GroupMembershipData + // membership-- => GroupMembershipData + // memberships- => List + // members-- => List + // role- => GroupRolesData + // roles- => List ; all roles in the group + // roles-- => List ; roles that the agent has + // rolemembers-- => List + // notice- => GroupNoticeInfo + // notices- => List + private ExpiringCache m_Cache = new ExpiringCache(); + + public RemoteConnectorCacheWrapper(IUserManagement uman) + { + m_ForeignImporter = new ForeignImporter(uman); + } + + public UUID CreateGroup(UUID RequestingAgentID, GroupRecordDelegate d) + { + //m_log.DebugFormat("[Groups.RemoteConnector]: Creating group {0}", name); + //reason = string.Empty; + + //ExtendedGroupRecord group = m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID, + // membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason); + ExtendedGroupRecord group = d(); + + if (group == null) + return UUID.Zero; + + if (group.GroupID != UUID.Zero) + lock (m_Cache) + { + m_Cache.Add("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT); + if (m_Cache.Contains("memberships-" + RequestingAgentID.ToString())) + m_Cache.Remove("memberships-" + RequestingAgentID.ToString()); + } + + return group.GroupID; + } + + public bool UpdateGroup(UUID groupID, GroupRecordDelegate d) + { + //reason = string.Empty; + //ExtendedGroupRecord group = m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish); + ExtendedGroupRecord group = d(); + + if (group != null && group.GroupID != UUID.Zero) + lock (m_Cache) + m_Cache.AddOrUpdate("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT); + return true; + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, GroupRecordDelegate d) + { + //if (GroupID == UUID.Zero && (GroupName == null || GroupName != null && GroupName == string.Empty)) + // return null; + + object group = null; + bool firstCall = false; + string cacheKey = "group-"; + if (GroupID != UUID.Zero) + cacheKey += GroupID.ToString(); + else + cacheKey += GroupName; + + //m_log.DebugFormat("[XXX]: GetGroupRecord {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out group)) + { + //m_log.DebugFormat("[XXX]: GetGroupRecord {0} cached!", cacheKey); + return (ExtendedGroupRecord)group; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + //group = m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName); + group = d(); + + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, group, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (ExtendedGroupRecord)group; + } + } + else + Thread.Sleep(50); + } + } + + public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, GroupMembershipDelegate d) + { + GroupMembershipData membership = d(); + if (membership == null) + return false; + + lock (m_Cache) + { + // first, remove everything! add a user is a heavy-duty op + m_Cache.Clear(); + + m_Cache.AddOrUpdate("active-" + AgentID.ToString(), membership, GROUPS_CACHE_TIMEOUT); + m_Cache.AddOrUpdate("membership-" + AgentID.ToString() + "-" + GroupID.ToString(), membership, GROUPS_CACHE_TIMEOUT); + } + + + return true; + } + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, VoidDelegate d) + { + d(); + + lock (m_Cache) + { + string cacheKey = "active-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "memberships-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "roles-" + "-" + GroupID.ToString() + "-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + } + } + + public void SetAgentActiveGroup(string AgentID, GroupMembershipDelegate d) + { + GroupMembershipData activeGroup = d(); + if (activeGroup != null) + { + string cacheKey = "active-" + AgentID.ToString(); + lock (m_Cache) + if (m_Cache.Contains(cacheKey)) + m_Cache.AddOrUpdate(cacheKey, activeGroup, GROUPS_CACHE_TIMEOUT); + } + } + + public ExtendedGroupMembershipData GetAgentActiveMembership(string AgentID, GroupMembershipDelegate d) + { + object membership = null; + bool firstCall = false; + string cacheKey = "active-" + AgentID.ToString(); + + //m_log.DebugFormat("[XXX]: GetAgentActiveMembership {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out membership)) + { + //m_log.DebugFormat("[XXX]: GetAgentActiveMembership {0} cached!", cacheKey); + return (ExtendedGroupMembershipData)membership; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + membership = d(); + + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (ExtendedGroupMembershipData)membership; + } + } + else + Thread.Sleep(50); + } + + } + + public ExtendedGroupMembershipData GetAgentGroupMembership(string AgentID, UUID GroupID, GroupMembershipDelegate d) + { + object membership = null; + bool firstCall = false; + string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString(); + + //m_log.DebugFormat("[XXX]: GetAgentGroupMembership {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out membership)) + { + //m_log.DebugFormat("[XXX]: GetAgentGroupMembership {0}", cacheKey); + return (ExtendedGroupMembershipData)membership; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + membership = d(); + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (ExtendedGroupMembershipData)membership; + } + } + else + Thread.Sleep(50); + } + } + + public List GetAgentGroupMemberships(string AgentID, GroupMembershipListDelegate d) + { + object memberships = null; + bool firstCall = false; + string cacheKey = "memberships-" + AgentID.ToString(); + + //m_log.DebugFormat("[XXX]: GetAgentGroupMemberships {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out memberships)) + { + //m_log.DebugFormat("[XXX]: GetAgentGroupMemberships {0} cached!", cacheKey); + return (List)memberships; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + memberships = d(); + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, memberships, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (List)memberships; + } + } + else + Thread.Sleep(50); + } + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID, GroupMembersListDelegate d) + { + object members = null; + bool firstCall = false; + // we need to key in also on the requester, because different ppl have different view privileges + string cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupID.ToString(); + + //m_log.DebugFormat("[XXX]: GetGroupMembers {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out members)) + { + List xx = (List)members; + return xx.ConvertAll(new Converter(m_ForeignImporter.ConvertGroupMembersData)); + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + List _members = d(); + + if (_members != null && _members.Count > 0) + members = _members.ConvertAll(new Converter(m_ForeignImporter.ConvertGroupMembersData)); + else + members = new List(); + + lock (m_Cache) + { + //m_Cache.AddOrUpdate(cacheKey, members, GROUPS_CACHE_TIMEOUT); + m_Cache.AddOrUpdate(cacheKey, _members, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + + return (List)members; + } + } + else + Thread.Sleep(50); + } + } + + public bool AddGroupRole(UUID groupID, UUID roleID, string description, string name, ulong powers, string title, BooleanDelegate d) + { + if (d()) + { + GroupRolesData role = new GroupRolesData(); + role.Description = description; + role.Members = 0; + role.Name = name; + role.Powers = powers; + role.RoleID = roleID; + role.Title = title; + + lock (m_Cache) + { + m_Cache.AddOrUpdate("role-" + roleID.ToString(), role, GROUPS_CACHE_TIMEOUT); + + // also remove this list + if (m_Cache.Contains("roles-" + groupID.ToString())) + m_Cache.Remove("roles-" + groupID.ToString()); + + } + + return true; + } + + return false; + } + + public bool UpdateGroupRole(UUID groupID, UUID roleID, string name, string description, string title, ulong powers, BooleanDelegate d) + { + if (d()) + { + object role; + lock (m_Cache) + if (m_Cache.TryGetValue("role-" + roleID.ToString(), out role)) + { + GroupRolesData r = (GroupRolesData)role; + r.Description = description; + r.Name = name; + r.Powers = powers; + r.Title = title; + + m_Cache.Update("role-" + roleID.ToString(), r, GROUPS_CACHE_TIMEOUT); + } + return true; + } + else + { + lock (m_Cache) + { + if (m_Cache.Contains("role-" + roleID.ToString())) + m_Cache.Remove("role-" + roleID.ToString()); + + // also remove these lists, because they will have an outdated role + if (m_Cache.Contains("roles-" + groupID.ToString())) + m_Cache.Remove("roles-" + groupID.ToString()); + + } + + return false; + } + } + + public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, VoidDelegate d) + { + d(); + + lock (m_Cache) + { + if (m_Cache.Contains("role-" + roleID.ToString())) + m_Cache.Remove("role-" + roleID.ToString()); + + // also remove the list, because it will have an removed role + if (m_Cache.Contains("roles-" + groupID.ToString())) + m_Cache.Remove("roles-" + groupID.ToString()); + + if (m_Cache.Contains("roles-" + groupID.ToString() + "-" + RequestingAgentID.ToString())) + m_Cache.Remove("roles-" + groupID.ToString() + "-" + RequestingAgentID.ToString()); + + if (m_Cache.Contains("rolemembers-" + RequestingAgentID.ToString() + "-" + groupID.ToString())) + m_Cache.Remove("rolemembers-" + RequestingAgentID.ToString() + "-" + groupID.ToString()); + } + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID, GroupRolesListDelegate d) + { + object roles = null; + bool firstCall = false; + string cacheKey = "roles-" + GroupID.ToString(); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out roles)) + return (List)roles; + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + roles = d(); + if (roles != null) + { + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (List)roles; + } + } + } + else + Thread.Sleep(50); + } + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, RoleMembersListDelegate d) + { + object rmembers = null; + bool firstCall = false; + // we need to key in also on the requester, because different ppl have different view privileges + string cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString(); + + //m_log.DebugFormat("[XXX]: GetGroupRoleMembers {0}", cacheKey); + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out rmembers)) + { + List xx = (List)rmembers; + return xx.ConvertAll(m_ForeignImporter.ConvertGroupRoleMembersData); + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + List _rmembers = d(); + + if (_rmembers != null && _rmembers.Count > 0) + rmembers = _rmembers.ConvertAll(new Converter(m_ForeignImporter.ConvertGroupRoleMembersData)); + else + rmembers = new List(); + + lock (m_Cache) + { + // For some strange reason, when I cache the list of GroupRoleMembersData, + // it gets emptied out. The TryGet gets an empty list... + //m_Cache.AddOrUpdate(cacheKey, rmembers, GROUPS_CACHE_TIMEOUT); + // Caching the list of ExtendedGroupRoleMembersData doesn't show that issue + // I don't get it. + m_Cache.AddOrUpdate(cacheKey, _rmembers, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (List)rmembers; + } + } + else + Thread.Sleep(50); + } + } + + public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, BooleanDelegate d) + { + if (d()) + { + lock (m_Cache) + { + // update the cached role + string cacheKey = "role-" + RoleID.ToString(); + object obj; + if (m_Cache.TryGetValue(cacheKey, out obj)) + { + GroupRolesData r = (GroupRolesData)obj; + r.Members++; + } + + // add this agent to the list of role members + cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString(); + if (m_Cache.TryGetValue(cacheKey, out obj)) + { + try + { + // This may throw an exception, in which case the agentID is not a UUID but a full ID + // In that case, let's just remove the whoe things from the cache + UUID id = new UUID(AgentID); + List xx = (List)obj; + List rmlist = xx.ConvertAll(m_ForeignImporter.ConvertGroupRoleMembersData); + GroupRoleMembersData rm = new GroupRoleMembersData(); + rm.MemberID = id; + rm.RoleID = RoleID; + rmlist.Add(rm); + } + catch + { + m_Cache.Remove(cacheKey); + } + } + + // Remove the cached info about this agent's roles + // because we don't have enough local info about the new role + cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + } + } + } + + public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, BooleanDelegate d) + { + if (d()) + { + lock (m_Cache) + { + // update the cached role + string cacheKey = "role-" + RoleID.ToString(); + object obj; + if (m_Cache.TryGetValue(cacheKey, out obj)) + { + GroupRolesData r = (GroupRolesData)obj; + r.Members--; + } + + cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + } + } + } + + public List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID, GroupRolesListDelegate d) + { + object roles = null; + bool firstCall = false; + string cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString(); + + //m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out roles)) + { + //m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0} cached!", cacheKey); + return (List)roles; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + roles = d(); + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (List)roles; + } + } + else + Thread.Sleep(50); + } + } + + public void SetAgentActiveGroupRole(string AgentID, UUID GroupID, VoidDelegate d) + { + d(); + + lock (m_Cache) + { + // Invalidate cached info, because it has ActiveRoleID and Powers + string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "memberships-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + } + } + + public void UpdateMembership(string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile, VoidDelegate d) + { + d(); + + lock (m_Cache) + { + string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "memberships-" + AgentID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + cacheKey = "active-" + AgentID.ToString(); + object m = null; + if (m_Cache.TryGetValue(cacheKey, out m)) + { + GroupMembershipData membership = (GroupMembershipData)m; + membership.ListInProfile = ListInProfile; + membership.AcceptNotices = AcceptNotices; + } + } + } + + public bool AddGroupNotice(UUID groupID, UUID noticeID, GroupNoticeInfo notice, BooleanDelegate d) + { + if (d()) + { + lock (m_Cache) + { + m_Cache.AddOrUpdate("notice-" + noticeID.ToString(), notice, GROUPS_CACHE_TIMEOUT); + string cacheKey = "notices-" + groupID.ToString(); + if (m_Cache.Contains(cacheKey)) + m_Cache.Remove(cacheKey); + + } + + return true; + } + + return false; + } + + public GroupNoticeInfo GetGroupNotice(UUID noticeID, NoticeDelegate d) + { + object notice = null; + bool firstCall = false; + string cacheKey = "notice-" + noticeID.ToString(); + + //m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out notice)) + { + return (GroupNoticeInfo)notice; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + GroupNoticeInfo _notice = d(); + + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, _notice, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return _notice; + } + } + else + Thread.Sleep(50); + } + } + + public List GetGroupNotices(UUID GroupID, NoticeListDelegate d) + { + object notices = null; + bool firstCall = false; + string cacheKey = "notices-" + GroupID.ToString(); + + //m_log.DebugFormat("[XXX]: GetGroupNotices {0}", cacheKey); + + while (true) + { + lock (m_Cache) + { + if (m_Cache.TryGetValue(cacheKey, out notices)) + { + //m_log.DebugFormat("[XXX]: GetGroupNotices {0} cached!", cacheKey); + return (List)notices; + } + + // not cached + if (!m_ActiveRequests.ContainsKey(cacheKey)) + { + m_ActiveRequests.Add(cacheKey, true); + firstCall = true; + } + } + + if (firstCall) + { + notices = d(); + + lock (m_Cache) + { + m_Cache.AddOrUpdate(cacheKey, notices, GROUPS_CACHE_TIMEOUT); + m_ActiveRequests.Remove(cacheKey); + return (List)notices; + } + } + else + Thread.Sleep(50); + } + } + + + } +} diff --git a/OpenSim/Addons/Groups/Service/GroupsService.cs b/OpenSim/Addons/Groups/Service/GroupsService.cs new file mode 100644 index 0000000000..0668870ef1 --- /dev/null +++ b/OpenSim/Addons/Groups/Service/GroupsService.cs @@ -0,0 +1,1020 @@ +/* + * 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.Reflection; +using System.Timers; +using log4net; +using Nini.Config; + +using OpenMetaverse; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Groups +{ + public class GroupsService : GroupsServiceBase + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public const GroupPowers DefaultEveryonePowers = GroupPowers.AllowSetHome | + GroupPowers.Accountable | + GroupPowers.JoinChat | + GroupPowers.AllowVoiceChat | + GroupPowers.ReceiveNotices | + GroupPowers.StartProposal | + GroupPowers.VoteOnProposal; + + public const GroupPowers OwnerPowers = GroupPowers.Accountable | + GroupPowers.AllowEditLand | + GroupPowers.AllowFly | + GroupPowers.AllowLandmark | + GroupPowers.AllowRez | + GroupPowers.AllowSetHome | + GroupPowers.AllowVoiceChat | + GroupPowers.AssignMember | + GroupPowers.AssignMemberLimited | + GroupPowers.ChangeActions | + GroupPowers.ChangeIdentity | + GroupPowers.ChangeMedia | + GroupPowers.ChangeOptions | + GroupPowers.CreateRole | + GroupPowers.DeedObject | + GroupPowers.DeleteRole | + GroupPowers.Eject | + GroupPowers.FindPlaces | + GroupPowers.Invite | + GroupPowers.JoinChat | + GroupPowers.LandChangeIdentity | + GroupPowers.LandDeed | + GroupPowers.LandDivideJoin | + GroupPowers.LandEdit | + GroupPowers.LandEjectAndFreeze | + GroupPowers.LandGardening | + GroupPowers.LandManageAllowed | + GroupPowers.LandManageBanned | + GroupPowers.LandManagePasses | + GroupPowers.LandOptions | + GroupPowers.LandRelease | + GroupPowers.LandSetSale | + GroupPowers.ModerateChat | + GroupPowers.ObjectManipulate | + GroupPowers.ObjectSetForSale | + GroupPowers.ReceiveNotices | + GroupPowers.RemoveMember | + GroupPowers.ReturnGroupOwned | + GroupPowers.ReturnGroupSet | + GroupPowers.ReturnNonGroup | + GroupPowers.RoleProperties | + GroupPowers.SendNotices | + GroupPowers.SetLandingPoint | + GroupPowers.StartProposal | + GroupPowers.VoteOnProposal; + + #region Daily Cleanup + + private Timer m_CleanupTimer; + + public GroupsService(IConfigSource config, string configName) + : base(config, configName) + { + } + + public GroupsService(IConfigSource config) + : this(config, string.Empty) + { + // Once a day + m_CleanupTimer = new Timer(24 * 60 * 60 * 1000); + m_CleanupTimer.AutoReset = true; + m_CleanupTimer.Elapsed += new ElapsedEventHandler(m_CleanupTimer_Elapsed); + m_CleanupTimer.Enabled = true; + m_CleanupTimer.Start(); + } + + private void m_CleanupTimer_Elapsed(object sender, ElapsedEventArgs e) + { + m_Database.DeleteOldNotices(); + m_Database.DeleteOldInvites(); + } + + #endregion + + public UUID CreateGroup(string RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, + bool allowPublish, bool maturePublish, UUID founderID, out string reason) + { + reason = string.Empty; + + // Create the group + GroupData data = new GroupData(); + data.GroupID = UUID.Random(); + data.Data = new Dictionary(); + data.Data["Name"] = name; + data.Data["Charter"] = charter; + data.Data["InsigniaID"] = insigniaID.ToString(); + data.Data["FounderID"] = founderID.ToString(); + data.Data["MembershipFee"] = membershipFee.ToString(); + data.Data["OpenEnrollment"] = openEnrollment ? "1" : "0"; + data.Data["ShowInList"] = showInList ? "1" : "0"; + data.Data["AllowPublish"] = allowPublish ? "1" : "0"; + data.Data["MaturePublish"] = maturePublish ? "1" : "0"; + data.Data["OwnerRoleID"] = UUID.Random().ToString(); + + if (!m_Database.StoreGroup(data)) + return UUID.Zero; + + // Create Everyone role + _AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, UUID.Zero, "Everyone", "Everyone in the group", "Member of " + name, (ulong)DefaultEveryonePowers, true); + + // Create Owner role + UUID roleID = UUID.Random(); + _AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, roleID, "Owners", "Owners of the group", "Owner of " + name, (ulong)OwnerPowers, true); + + // Add founder to group + _AddAgentToGroup(RequestingAgentID, founderID.ToString(), data.GroupID, roleID); + + return data.GroupID; + } + + public void UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish) + { + GroupData data = m_Database.RetrieveGroup(groupID); + if (data == null) + return; + + // Check perms + if (!HasPower(RequestingAgentID, groupID, GroupPowers.ChangeActions)) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at updating group {1} denied because of lack of permission", RequestingAgentID, groupID); + return; + } + + data.GroupID = groupID; + data.Data["Charter"] = charter; + data.Data["ShowInList"] = showInList ? "1" : "0"; + data.Data["InsigniaID"] = insigniaID.ToString(); + data.Data["MembershipFee"] = membershipFee.ToString(); + data.Data["OpenEnrollment"] = openEnrollment ? "1" : "0"; + data.Data["AllowPublish"] = allowPublish ? "1" : "0"; + data.Data["MaturePublish"] = maturePublish ? "1" : "0"; + + m_Database.StoreGroup(data); + + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID) + { + GroupData data = m_Database.RetrieveGroup(GroupID); + + return _GroupDataToRecord(data); + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, string GroupName) + { + GroupData data = m_Database.RetrieveGroup(GroupName); + + return _GroupDataToRecord(data); + } + + public List FindGroups(string RequestingAgentID, string search) + { + List groups = new List(); + + GroupData[] data = m_Database.RetrieveGroups(search); + + if (data != null && data.Length > 0) + { + foreach (GroupData d in data) + { + // Don't list group proxies + if (d.Data.ContainsKey("Location") && d.Data["Location"] != string.Empty) + continue; + + DirGroupsReplyData g = new DirGroupsReplyData(); + g.groupID = d.GroupID; + + if (d.Data.ContainsKey("Name")) + g.groupName = d.Data["Name"]; + else + m_log.DebugFormat("[Groups]: Key Name not found"); + + g.members = m_Database.MemberCount(d.GroupID); + + groups.Add(g); + } + } + + return groups; + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID) + { + List members = new List(); + + GroupData group = m_Database.RetrieveGroup(GroupID); + if (group == null) + return members; + + UUID ownerRoleID = new UUID(group.Data["OwnerRoleID"]); + + RoleData[] roles = m_Database.RetrieveRoles(GroupID); + if (roles == null) + // something wrong with this group + return members; + List rolesList = new List(roles); + + // Is the requester a member of the group? + bool isInGroup = false; + if (m_Database.RetrieveMember(GroupID, RequestingAgentID) != null) + isInGroup = true; + + if (!isInGroup) // reduce the roles to the visible ones + rolesList = rolesList.FindAll(r => (UInt64.Parse(r.Data["Powers"]) & (ulong)GroupPowers.MemberVisible) != 0); + + MembershipData[] datas = m_Database.RetrieveMembers(GroupID); + if (datas == null || (datas != null && datas.Length == 0)) + return members; + + // OK, we have everything we need + + foreach (MembershipData d in datas) + { + RoleMembershipData[] rolememberships = m_Database.RetrieveMemberRoles(GroupID, d.PrincipalID); + List rolemembershipsList = new List(rolememberships); + + ExtendedGroupMembersData m = new ExtendedGroupMembersData(); + + // What's this person's current role in the group? + UUID selectedRole = new UUID(d.Data["SelectedRoleID"]); + RoleData selected = rolesList.Find(r => r.RoleID == selectedRole); + + if (selected != null) + { + m.Title = selected.Data["Title"]; + m.AgentPowers = UInt64.Parse(selected.Data["Powers"]); + + m.AgentID = d.PrincipalID; + m.AcceptNotices = d.Data["AcceptNotices"] == "1" ? true : false; + m.Contribution = Int32.Parse(d.Data["Contribution"]); + m.ListInProfile = d.Data["ListInProfile"] == "1" ? true : false; + + // Is this person an owner of the group? + m.IsOwner = (rolemembershipsList.Find(r => r.RoleID == ownerRoleID) != null) ? true : false; + + members.Add(m); + } + } + + return members; + } + + public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason) + { + reason = string.Empty; + // check that the requesting agent has permissions to add role + if (!HasPower(RequestingAgentID, groupID, GroupPowers.CreateRole)) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at creating role in group {1} denied because of lack of permission", RequestingAgentID, groupID); + reason = "Insufficient permission to create role"; + return false; + } + + return _AddOrUpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, true); + + } + + public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers) + { + // check perms + if (!HasPower(RequestingAgentID, groupID, GroupPowers.ChangeActions)) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at changing role in group {1} denied because of lack of permission", RequestingAgentID, groupID); + return false; + } + + return _AddOrUpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, false); + } + + public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID) + { + // check perms + if (!HasPower(RequestingAgentID, groupID, GroupPowers.DeleteRole)) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at deleting role from group {1} denied because of lack of permission", RequestingAgentID, groupID); + return; + } + + // Can't delete Everyone and Owners roles + if (roleID == UUID.Zero) + { + m_log.DebugFormat("[Groups]: Attempt at deleting Everyone role from group {0} denied", groupID); + return; + } + + GroupData group = m_Database.RetrieveGroup(groupID); + if (group == null) + { + m_log.DebugFormat("[Groups]: Attempt at deleting role from non-existing group {0}", groupID); + return; + } + + if (roleID == new UUID(group.Data["OwnerRoleID"])) + { + m_log.DebugFormat("[Groups]: Attempt at deleting Owners role from group {0} denied", groupID); + return; + } + + _RemoveGroupRole(groupID, roleID); + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID) + { + // TODO: check perms + return _GetGroupRoles(GroupID); + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID) + { + // TODO: check perms + + // Is the requester a member of the group? + bool isInGroup = false; + if (m_Database.RetrieveMember(GroupID, RequestingAgentID) != null) + isInGroup = true; + + return _GetGroupRoleMembers(GroupID, isInGroup); + } + + public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason) + { + reason = string.Empty; + + _AddAgentToGroup(RequestingAgentID, AgentID, GroupID, RoleID, token); + + return true; + } + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + // check perms + if (RequestingAgentID != AgentID && !HasPower(RequestingAgentID, GroupID, GroupPowers.Eject)) + return; + + _RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID); + } + + public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID) + { + // Check whether the invitee is already a member of the group + MembershipData m = m_Database.RetrieveMember(groupID, agentID); + if (m != null) + return false; + + // Check permission to invite + if (!HasPower(RequestingAgentID, groupID, GroupPowers.Invite)) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at inviting to group {1} denied because of lack of permission", RequestingAgentID, groupID); + return false; + } + + // Check whether there are pending invitations and delete them + InvitationData invite = m_Database.RetrieveInvitation(groupID, agentID); + if (invite != null) + m_Database.DeleteInvite(invite.InviteID); + + invite = new InvitationData(); + invite.InviteID = inviteID; + invite.PrincipalID = agentID; + invite.GroupID = groupID; + invite.RoleID = roleID; + invite.Data = new Dictionary(); + + return m_Database.StoreInvitation(invite); + } + + public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + InvitationData data = m_Database.RetrieveInvitation(inviteID); + + if (data == null) + return null; + + GroupInviteInfo inviteInfo = new GroupInviteInfo(); + inviteInfo.AgentID = data.PrincipalID; + inviteInfo.GroupID = data.GroupID; + inviteInfo.InviteID = data.InviteID; + inviteInfo.RoleID = data.RoleID; + + return inviteInfo; + } + + public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID) + { + m_Database.DeleteInvite(inviteID); + } + + public bool AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + //if (!m_Database.CheckOwnerRole(RequestingAgentID, GroupID, RoleID)) + // return; + + // check permissions + bool limited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMemberLimited); + bool unlimited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMember) | IsOwner(RequestingAgentID, GroupID); + if (!limited || !unlimited) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at assigning {1} to role {2} denied because of lack of permission", RequestingAgentID, AgentID, RoleID); + return false; + } + + // AssignMemberLimited means that the person can assign another person to the same roles that she has in the group + if (!unlimited && limited) + { + // check whether person's has this role + RoleMembershipData rolemembership = m_Database.RetrieveRoleMember(GroupID, RoleID, AgentID); + if (rolemembership == null) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at assigning {1} to role {2} denied because of limited permission", RequestingAgentID, AgentID, RoleID); + return false; + } + } + + _AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + + return true; + } + + public bool RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + // Don't remove from Everyone role! + if (RoleID == UUID.Zero) + return false; + + // check permissions + bool unlimited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMember) || IsOwner(RequestingAgentID, GroupID); + if (!unlimited) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at removing {1} from role {2} denied because of lack of permission", RequestingAgentID, AgentID, RoleID); + return false; + } + + RoleMembershipData rolemember = m_Database.RetrieveRoleMember(GroupID, RoleID, AgentID); + + if (rolemember == null) + return false; + + m_Database.DeleteRoleMember(rolemember); + + // Find another role for this person + UUID newRoleID = UUID.Zero; // Everyone + RoleMembershipData[] rdata = m_Database.RetrieveMemberRoles(GroupID, AgentID); + if (rdata != null) + foreach (RoleMembershipData r in rdata) + { + if (r.RoleID != UUID.Zero) + { + newRoleID = r.RoleID; + break; + } + } + + MembershipData member = m_Database.RetrieveMember(GroupID, AgentID); + if (member != null) + { + member.Data["SelectedRoleID"] = newRoleID.ToString(); + m_Database.StoreMember(member); + } + + return true; + } + + public List GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID) + { + List roles = new List(); + // TODO: check permissions + + RoleMembershipData[] data = m_Database.RetrieveMemberRoles(GroupID, AgentID); + if (data == null || (data != null && data.Length ==0)) + return roles; + + foreach (RoleMembershipData d in data) + { + RoleData rdata = m_Database.RetrieveRole(GroupID, d.RoleID); + if (rdata == null) // hippos + continue; + + GroupRolesData r = new GroupRolesData(); + r.Name = rdata.Data["Name"]; + r.Powers = UInt64.Parse(rdata.Data["Powers"]); + r.RoleID = rdata.RoleID; + r.Title = rdata.Data["Title"]; + + roles.Add(r); + } + + return roles; + } + + public ExtendedGroupMembershipData SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + // TODO: check perms + PrincipalData principal = new PrincipalData(); + principal.PrincipalID = AgentID; + principal.ActiveGroupID = GroupID; + m_Database.StorePrincipal(principal); + + return GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID); + } + + public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID) + { + // 1. get the principal data for the active group + PrincipalData principal = m_Database.RetrievePrincipal(AgentID); + if (principal == null) + return null; + + return GetAgentGroupMembership(RequestingAgentID, AgentID, principal.ActiveGroupID); + } + + public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID) + { + return GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID, null); + } + + private ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID, MembershipData membership) + { + // 2. get the active group + GroupData group = m_Database.RetrieveGroup(GroupID); + if (group == null) + return null; + + // 3. get the membership info if we don't have it already + if (membership == null) + { + membership = m_Database.RetrieveMember(group.GroupID, AgentID); + if (membership == null) + return null; + } + + // 4. get the active role + UUID activeRoleID = new UUID(membership.Data["SelectedRoleID"]); + RoleData role = m_Database.RetrieveRole(group.GroupID, activeRoleID); + + ExtendedGroupMembershipData data = new ExtendedGroupMembershipData(); + data.AcceptNotices = membership.Data["AcceptNotices"] == "1" ? true : false; + data.AccessToken = membership.Data["AccessToken"]; + data.Active = true; + data.ActiveRole = activeRoleID; + data.AllowPublish = group.Data["AllowPublish"] == "1" ? true : false; + data.Charter = group.Data["Charter"]; + data.Contribution = Int32.Parse(membership.Data["Contribution"]); + data.FounderID = new UUID(group.Data["FounderID"]); + data.GroupID = new UUID(group.GroupID); + data.GroupName = group.Data["Name"]; + data.GroupPicture = new UUID(group.Data["InsigniaID"]); + if (role != null) + { + data.GroupPowers = UInt64.Parse(role.Data["Powers"]); + data.GroupTitle = role.Data["Title"]; + } + data.ListInProfile = membership.Data["ListInProfile"] == "1" ? true : false; + data.MaturePublish = group.Data["MaturePublish"] == "1" ? true : false; + data.MembershipFee = Int32.Parse(group.Data["MembershipFee"]); + data.OpenEnrollment = group.Data["OpenEnrollment"] == "1" ? true : false; + data.ShowInList = group.Data["ShowInList"] == "1" ? true : false; + + return data; + } + + public List GetAgentGroupMemberships(string RequestingAgentID, string AgentID) + { + List memberships = new List(); + + // 1. Get all the groups that this person is a member of + MembershipData[] mdata = m_Database.RetrieveMemberships(AgentID); + + if (mdata == null || (mdata != null && mdata.Length == 0)) + return memberships; + + foreach (MembershipData d in mdata) + { + GroupMembershipData gmember = GetAgentGroupMembership(RequestingAgentID, AgentID, d.GroupID, d); + if (gmember != null) + { + memberships.Add(gmember); + //m_log.DebugFormat("[XXX]: Member of {0} as {1}", gmember.GroupName, gmember.GroupTitle); + //Util.PrintCallStack(); + } + } + + return memberships; + } + + public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + MembershipData data = m_Database.RetrieveMember(GroupID, AgentID); + if (data == null) + return; + + data.Data["SelectedRoleID"] = RoleID.ToString(); + m_Database.StoreMember(data); + } + + public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile) + { + // TODO: check perms + + MembershipData membership = m_Database.RetrieveMember(GroupID, AgentID); + if (membership == null) + return; + + membership.Data["AcceptNotices"] = AcceptNotices ? "1" : "0"; + membership.Data["ListInProfile"] = ListInProfile ? "1" : "0"; + + m_Database.StoreMember(membership); + } + + public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + // Check perms + if (!HasPower(RequestingAgentID, groupID, GroupPowers.SendNotices)) + { + m_log.DebugFormat("[Groups]: ({0}) Attempt at sending notice to group {1} denied because of lack of permission", RequestingAgentID, groupID); + return false; + } + + return _AddNotice(groupID, noticeID, fromName, subject, message, hasAttachment, attType, attName, attItemID, attOwnerID); + } + + public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID) + { + NoticeData data = m_Database.RetrieveNotice(noticeID); + + if (data == null) + return null; + + return _NoticeDataToInfo(data); + } + + public List GetGroupNotices(string RequestingAgentID, UUID groupID) + { + NoticeData[] data = m_Database.RetrieveNotices(groupID); + List infos = new List(); + + if (data == null || (data != null && data.Length == 0)) + return infos; + + foreach (NoticeData d in data) + { + ExtendedGroupNoticeData info = _NoticeDataToData(d); + infos.Add(info); + } + + return infos; + } + + public void ResetAgentGroupChatSessions(string agentID) + { + } + + public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID) + { + return false; + } + + public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID) + { + } + + public void AgentInvitedToGroupChatSession(string agentID, UUID groupID) + { + } + + #region Actions without permission checks + + private void _AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + _AddAgentToGroup(RequestingAgentID, AgentID, GroupID, RoleID, string.Empty); + } + + public void _RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID) + { + // 1. Delete membership + m_Database.DeleteMember(GroupID, AgentID); + + // 2. Remove from rolememberships + m_Database.DeleteMemberAllRoles(GroupID, AgentID); + + // 3. if it was active group, inactivate it + PrincipalData principal = m_Database.RetrievePrincipal(AgentID); + if (principal != null && principal.ActiveGroupID == GroupID) + { + principal.ActiveGroupID = UUID.Zero; + m_Database.StorePrincipal(principal); + } + } + + protected void _AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string accessToken) + { + // Check if it's already there + MembershipData data = m_Database.RetrieveMember(GroupID, AgentID); + if (data != null) + return; + + // Add the membership + data = new MembershipData(); + data.PrincipalID = AgentID; + data.GroupID = GroupID; + data.Data = new Dictionary(); + data.Data["SelectedRoleID"] = RoleID.ToString(); + data.Data["Contribution"] = "0"; + data.Data["ListInProfile"] = "1"; + data.Data["AcceptNotices"] = "1"; + data.Data["AccessToken"] = accessToken; + + m_Database.StoreMember(data); + + // Add principal to everyone role + _AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, UUID.Zero); + + // Add principal to role, if different from everyone role + if (RoleID != UUID.Zero) + _AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID); + + // Make thit this active group + PrincipalData pdata = new PrincipalData(); + pdata.PrincipalID = AgentID; + pdata.ActiveGroupID = GroupID; + m_Database.StorePrincipal(pdata); + + } + + private bool _AddOrUpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, bool add) + { + RoleData data = m_Database.RetrieveRole(groupID, roleID); + + if (add && data != null) // it already exists, can't create + { + m_log.DebugFormat("[Groups]: Group {0} already exists. Can't create it again", groupID); + return false; + } + + if (!add && data == null) // it deosn't exist, can't update + { + m_log.DebugFormat("[Groups]: Group {0} doesn't exist. Can't update it", groupID); + return false; + } + + if (add) + data = new RoleData(); + + data.GroupID = groupID; + data.RoleID = roleID; + data.Data = new Dictionary(); + data.Data["Name"] = name; + data.Data["Description"] = description; + data.Data["Title"] = title; + data.Data["Powers"] = powers.ToString(); + + return m_Database.StoreRole(data); + } + + private void _RemoveGroupRole(UUID groupID, UUID roleID) + { + m_Database.DeleteRole(groupID, roleID); + } + + private void _AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID) + { + RoleMembershipData data = m_Database.RetrieveRoleMember(GroupID, RoleID, AgentID); + if (data != null) + return; + + data = new RoleMembershipData(); + data.GroupID = GroupID; + data.PrincipalID = AgentID; + data.RoleID = RoleID; + m_Database.StoreRoleMember(data); + + // Make it the SelectedRoleID + MembershipData membership = m_Database.RetrieveMember(GroupID, AgentID); + if (membership == null) + { + m_log.DebugFormat("[Groups]: ({0}) No such member {0} in group {1}", AgentID, GroupID); + return; + } + + membership.Data["SelectedRoleID"] = RoleID.ToString(); + m_Database.StoreMember(membership); + + } + + private List _GetGroupRoles(UUID groupID) + { + List roles = new List(); + + RoleData[] data = m_Database.RetrieveRoles(groupID); + + if (data == null || (data != null && data.Length == 0)) + return roles; + + foreach (RoleData d in data) + { + GroupRolesData r = new GroupRolesData(); + r.Description = d.Data["Description"]; + r.Members = m_Database.RoleMemberCount(groupID, d.RoleID); + r.Name = d.Data["Name"]; + r.Powers = UInt64.Parse(d.Data["Powers"]); + r.RoleID = d.RoleID; + r.Title = d.Data["Title"]; + + roles.Add(r); + } + + return roles; + } + + private List _GetGroupRoleMembers(UUID GroupID, bool isInGroup) + { + List rmembers = new List(); + + RoleData[] rdata = new RoleData[0]; + if (!isInGroup) + { + rdata = m_Database.RetrieveRoles(GroupID); + if (rdata == null || (rdata != null && rdata.Length == 0)) + return rmembers; + } + List rlist = new List(rdata); + if (!isInGroup) + rlist = rlist.FindAll(r => (UInt64.Parse(r.Data["Powers"]) & (ulong)GroupPowers.MemberVisible) != 0); + + RoleMembershipData[] data = m_Database.RetrieveRolesMembers(GroupID); + + if (data == null || (data != null && data.Length == 0)) + return rmembers; + + foreach (RoleMembershipData d in data) + { + if (!isInGroup) + { + RoleData rd = rlist.Find(_r => _r.RoleID == d.RoleID); // visible role + if (rd == null) + continue; + } + + ExtendedGroupRoleMembersData r = new ExtendedGroupRoleMembersData(); + r.MemberID = d.PrincipalID; + r.RoleID = d.RoleID; + + rmembers.Add(r); + } + + return rmembers; + } + + protected bool _AddNotice(UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + NoticeData data = new NoticeData(); + data.GroupID = groupID; + data.NoticeID = noticeID; + data.Data = new Dictionary(); + data.Data["FromName"] = fromName; + data.Data["Subject"] = subject; + data.Data["Message"] = message; + data.Data["HasAttachment"] = hasAttachment ? "1" : "0"; + if (hasAttachment) + { + data.Data["AttachmentType"] = attType.ToString(); + data.Data["AttachmentName"] = attName; + data.Data["AttachmentItemID"] = attItemID.ToString(); + data.Data["AttachmentOwnerID"] = attOwnerID; + } + data.Data["TMStamp"] = ((uint)Util.UnixTimeSinceEpoch()).ToString(); + + return m_Database.StoreNotice(data); + } + + #endregion + + #region structure translations + ExtendedGroupRecord _GroupDataToRecord(GroupData data) + { + if (data == null) + return null; + + ExtendedGroupRecord rec = new ExtendedGroupRecord(); + rec.AllowPublish = data.Data["AllowPublish"] == "1" ? true : false; + rec.Charter = data.Data["Charter"]; + rec.FounderID = new UUID(data.Data["FounderID"]); + rec.GroupID = data.GroupID; + rec.GroupName = data.Data["Name"]; + rec.GroupPicture = new UUID(data.Data["InsigniaID"]); + rec.MaturePublish = data.Data["MaturePublish"] == "1" ? true : false; + rec.MembershipFee = Int32.Parse(data.Data["MembershipFee"]); + rec.OpenEnrollment = data.Data["OpenEnrollment"] == "1" ? true : false; + rec.OwnerRoleID = new UUID(data.Data["OwnerRoleID"]); + rec.ShowInList = data.Data["ShowInList"] == "1" ? true : false; + rec.ServiceLocation = data.Data["Location"]; + rec.MemberCount = m_Database.MemberCount(data.GroupID); + rec.RoleCount = m_Database.RoleCount(data.GroupID); + + return rec; + } + + GroupNoticeInfo _NoticeDataToInfo(NoticeData data) + { + GroupNoticeInfo notice = new GroupNoticeInfo(); + notice.GroupID = data.GroupID; + notice.Message = data.Data["Message"]; + notice.noticeData = _NoticeDataToData(data); + + return notice; + } + + ExtendedGroupNoticeData _NoticeDataToData(NoticeData data) + { + ExtendedGroupNoticeData notice = new ExtendedGroupNoticeData(); + notice.FromName = data.Data["FromName"]; + notice.NoticeID = data.NoticeID; + notice.Subject = data.Data["Subject"]; + notice.Timestamp = uint.Parse((string)data.Data["TMStamp"]); + notice.HasAttachment = data.Data["HasAttachment"] == "1" ? true : false; + if (notice.HasAttachment) + { + notice.AttachmentName = data.Data["AttachmentName"]; + notice.AttachmentItemID = new UUID(data.Data["AttachmentItemID"].ToString()); + notice.AttachmentType = byte.Parse(data.Data["AttachmentType"].ToString()); + notice.AttachmentOwnerID = data.Data["AttachmentOwnerID"].ToString(); + } + + + return notice; + } + + #endregion + + #region permissions + private bool HasPower(string agentID, UUID groupID, GroupPowers power) + { + RoleMembershipData[] rmembership = m_Database.RetrieveMemberRoles(groupID, agentID); + if (rmembership == null || (rmembership != null && rmembership.Length == 0)) + return false; + + foreach (RoleMembershipData rdata in rmembership) + { + RoleData role = m_Database.RetrieveRole(groupID, rdata.RoleID); + if ( (UInt64.Parse(role.Data["Powers"]) & (ulong)power) != 0 ) + return true; + } + return false; + } + + private bool IsOwner(string agentID, UUID groupID) + { + GroupData group = m_Database.RetrieveGroup(groupID); + if (group == null) + return false; + + RoleMembershipData rmembership = m_Database.RetrieveRoleMember(groupID, new UUID(group.Data["OwnerRoleID"]), agentID); + if (rmembership == null) + return false; + + return true; + } + #endregion + + } +} diff --git a/OpenSim/Addons/Groups/Service/GroupsServiceBase.cs b/OpenSim/Addons/Groups/Service/GroupsServiceBase.cs new file mode 100644 index 0000000000..2611a3d922 --- /dev/null +++ b/OpenSim/Addons/Groups/Service/GroupsServiceBase.cs @@ -0,0 +1,84 @@ +/* + * 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.Reflection; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Data; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Base; + +namespace OpenSim.Groups +{ + public class GroupsServiceBase : ServiceBase + { + protected IGroupsData m_Database = null; + + public GroupsServiceBase(IConfigSource config, string cName) + : base(config) + { + string dllName = String.Empty; + string connString = String.Empty; + string realm = "os_groups"; + string configName = (cName == string.Empty) ? "Groups" : cName; + + // + // Try reading the [DatabaseService] section, if it exists + // + IConfig dbConfig = config.Configs["DatabaseService"]; + if (dbConfig != null) + { + if (dllName == String.Empty) + dllName = dbConfig.GetString("StorageProvider", String.Empty); + if (connString == String.Empty) + connString = dbConfig.GetString("ConnectionString", String.Empty); + } + + // + // [Groups] section overrides [DatabaseService], if it exists + // + IConfig groupsConfig = config.Configs[configName]; + if (groupsConfig != null) + { + dllName = groupsConfig.GetString("StorageProvider", dllName); + connString = groupsConfig.GetString("ConnectionString", connString); + realm = groupsConfig.GetString("Realm", realm); + } + + // + // We tried, but this doesn't exist. We can't proceed. + // + if (dllName.Equals(String.Empty)) + throw new Exception("No StorageProvider configured"); + + m_Database = LoadPlugin(dllName, new Object[] { connString, realm }); + if (m_Database == null) + throw new Exception("Could not find a storage interface in the given module " + dllName); + } + } +} \ No newline at end of file diff --git a/OpenSim/Addons/Groups/Service/HGGroupsService.cs b/OpenSim/Addons/Groups/Service/HGGroupsService.cs new file mode 100644 index 0000000000..9d7961cd20 --- /dev/null +++ b/OpenSim/Addons/Groups/Service/HGGroupsService.cs @@ -0,0 +1,353 @@ +/* + * 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.Reflection; +using System.Timers; +using log4net; +using Nini.Config; + +using OpenMetaverse; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Groups +{ + public class HGGroupsService : GroupsService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IOfflineIMService m_OfflineIM; + private IUserAccountService m_UserAccounts; + private string m_HomeURI; + + public HGGroupsService(IConfigSource config, IOfflineIMService im, IUserAccountService users, string homeURI) + : base(config, string.Empty) + { + m_OfflineIM = im; + m_UserAccounts = users; + m_HomeURI = homeURI; + if (!m_HomeURI.EndsWith("/")) + m_HomeURI += "/"; + } + + + #region HG specific operations + + public bool CreateGroupProxy(string RequestingAgentID, string agentID, string accessToken, UUID groupID, string serviceLocation, string name, out string reason) + { + reason = string.Empty; + Uri uri = null; + try + { + uri = new Uri(serviceLocation); + } + catch (UriFormatException) + { + reason = "Bad location for group proxy"; + return false; + } + + // Check if it already exists + GroupData grec = m_Database.RetrieveGroup(groupID); + if (grec == null || + (grec != null && grec.Data["Location"] != string.Empty && grec.Data["Location"].ToLower() != serviceLocation.ToLower())) + { + // Create the group + grec = new GroupData(); + grec.GroupID = groupID; + grec.Data = new Dictionary(); + grec.Data["Name"] = name + " @ " + uri.Authority; + grec.Data["Location"] = serviceLocation; + grec.Data["Charter"] = string.Empty; + grec.Data["InsigniaID"] = UUID.Zero.ToString(); + grec.Data["FounderID"] = UUID.Zero.ToString(); + grec.Data["MembershipFee"] = "0"; + grec.Data["OpenEnrollment"] = "0"; + grec.Data["ShowInList"] = "0"; + grec.Data["AllowPublish"] = "0"; + grec.Data["MaturePublish"] = "0"; + grec.Data["OwnerRoleID"] = UUID.Zero.ToString(); + + + if (!m_Database.StoreGroup(grec)) + return false; + } + + if (grec.Data["Location"] == string.Empty) + { + reason = "Cannot add proxy membership to non-proxy group"; + return false; + } + + UUID uid = UUID.Zero; + string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty; + Util.ParseUniversalUserIdentifier(RequestingAgentID, out uid, out url, out first, out last, out tmp); + string fromName = first + "." + last + "@" + url; + + // Invite to group again + InviteToGroup(fromName, groupID, new UUID(agentID), grec.Data["Name"]); + + // Stick the proxy membership in the DB already + // we'll delete it if the agent declines the invitation + MembershipData membership = new MembershipData(); + membership.PrincipalID = agentID; + membership.GroupID = groupID; + membership.Data = new Dictionary(); + membership.Data["SelectedRoleID"] = UUID.Zero.ToString(); + membership.Data["Contribution"] = "0"; + membership.Data["ListInProfile"] = "1"; + membership.Data["AcceptNotices"] = "1"; + membership.Data["AccessToken"] = accessToken; + + m_Database.StoreMember(membership); + + return true; + } + + public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, string token) + { + // check the token + MembershipData membership = m_Database.RetrieveMember(GroupID, AgentID); + if (membership != null) + { + if (token != string.Empty && token.Equals(membership.Data["AccessToken"])) + RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID); + else + m_log.DebugFormat("[Groups.HGGroupsService]: access token {0} did not match stored one {1}", token, membership.Data["AccessToken"]); + } + else + m_log.DebugFormat("[Groups.HGGroupsService]: membership not found for {0}", AgentID); + } + + public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string groupName, string token) + { + // check the token + if (!VerifyToken(GroupID, RequestingAgentID, token)) + return null; + + ExtendedGroupRecord grec; + if (GroupID == UUID.Zero) + grec = GetGroupRecord(RequestingAgentID, groupName); + else + grec = GetGroupRecord(RequestingAgentID, GroupID); + + if (grec != null) + FillFounderUUI(grec); + + return grec; + } + + public List GetGroupMembers(string RequestingAgentID, UUID GroupID, string token) + { + if (!VerifyToken(GroupID, RequestingAgentID, token)) + return new List(); + + List members = GetGroupMembers(RequestingAgentID, GroupID); + + // convert UUIDs to UUIs + members.ForEach(delegate (ExtendedGroupMembersData m) + { + if (m.AgentID.ToString().Length == 36) // UUID + { + UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, new UUID(m.AgentID)); + if (account != null) + m.AgentID = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI); + } + }); + + return members; + } + + public List GetGroupRoles(string RequestingAgentID, UUID GroupID, string token) + { + if (!VerifyToken(GroupID, RequestingAgentID, token)) + return new List(); + + return GetGroupRoles(RequestingAgentID, GroupID); + } + + public List GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, string token) + { + if (!VerifyToken(GroupID, RequestingAgentID, token)) + return new List(); + + List rolemembers = GetGroupRoleMembers(RequestingAgentID, GroupID); + + // convert UUIDs to UUIs + rolemembers.ForEach(delegate(ExtendedGroupRoleMembersData m) + { + if (m.MemberID.ToString().Length == 36) // UUID + { + UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, new UUID(m.MemberID)); + if (account != null) + m.MemberID = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI); + } + }); + + return rolemembers; + } + + public bool AddNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, + bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID) + { + // check that the group proxy exists + ExtendedGroupRecord grec = GetGroupRecord(RequestingAgentID, groupID); + if (grec == null) + { + m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to non-existent group proxy"); + return false; + } + + // check that the group is remote + if (grec.ServiceLocation == string.Empty) + { + m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to local (non-proxy) group"); + return false; + } + + // check that there isn't already a notice with the same ID + if (GetGroupNotice(RequestingAgentID, noticeID) != null) + { + m_log.DebugFormat("[Groups.HGGroupsService]: a notice with the same ID already exists", grec.ServiceLocation); + return false; + } + + // This has good intentions (security) but it will potentially DDS the origin... + // We'll need to send a proof along with the message. Maybe encrypt the message + // using key pairs + // + //// check that the notice actually exists in the origin + //GroupsServiceHGConnector c = new GroupsServiceHGConnector(grec.ServiceLocation); + //if (!c.VerifyNotice(noticeID, groupID)) + //{ + // m_log.DebugFormat("[Groups.HGGroupsService]: notice does not exist at origin {0}", grec.ServiceLocation); + // return false; + //} + + // ok, we're good! + return _AddNotice(groupID, noticeID, fromName, subject, message, hasAttachment, attType, attName, attItemID, attOwnerID); + } + + public bool VerifyNotice(UUID noticeID, UUID groupID) + { + GroupNoticeInfo notice = GetGroupNotice(string.Empty, noticeID); + + if (notice == null) + return false; + + if (notice.GroupID != groupID) + return false; + + return true; + } + + #endregion + + private void InviteToGroup(string fromName, UUID groupID, UUID invitedAgentID, string groupName) + { + // Todo: Security check, probably also want to send some kind of notification + UUID InviteID = UUID.Random(); + + if (AddAgentToGroupInvite(InviteID, groupID, invitedAgentID.ToString())) + { + Guid inviteUUID = InviteID.Guid; + + GridInstantMessage msg = new GridInstantMessage(); + + msg.imSessionID = inviteUUID; + + // msg.fromAgentID = agentID.Guid; + msg.fromAgentID = groupID.Guid; + msg.toAgentID = invitedAgentID.Guid; + //msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + msg.timestamp = 0; + msg.fromAgentName = fromName; + msg.message = string.Format("Please confirm your acceptance to join group {0}.", groupName); + msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupInvitation; + msg.fromGroup = true; + msg.offline = (byte)0; + msg.ParentEstateID = 0; + msg.Position = Vector3.Zero; + msg.RegionID = UUID.Zero.Guid; + msg.binaryBucket = new byte[20]; + + string reason = string.Empty; + m_OfflineIM.StoreMessage(msg, out reason); + + } + } + + private bool AddAgentToGroupInvite(UUID inviteID, UUID groupID, string agentID) + { + // Check whether the invitee is already a member of the group + MembershipData m = m_Database.RetrieveMember(groupID, agentID); + if (m != null) + return false; + + // Check whether there are pending invitations and delete them + InvitationData invite = m_Database.RetrieveInvitation(groupID, agentID); + if (invite != null) + m_Database.DeleteInvite(invite.InviteID); + + invite = new InvitationData(); + invite.InviteID = inviteID; + invite.PrincipalID = agentID; + invite.GroupID = groupID; + invite.RoleID = UUID.Zero; + invite.Data = new Dictionary(); + + return m_Database.StoreInvitation(invite); + } + + private void FillFounderUUI(ExtendedGroupRecord grec) + { + UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, grec.FounderID); + if (account != null) + grec.FounderUUI = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI); + } + + private bool VerifyToken(UUID groupID, string agentID, string token) + { + // check the token + MembershipData membership = m_Database.RetrieveMember(groupID, agentID); + if (membership != null) + { + if (token != string.Empty && token.Equals(membership.Data["AccessToken"])) + return true; + else + m_log.DebugFormat("[Groups.HGGroupsService]: access token {0} did not match stored one {1}", token, membership.Data["AccessToken"]); + } + else + m_log.DebugFormat("[Groups.HGGroupsService]: membership not found for {0}", agentID); + + return false; + } + } +} diff --git a/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs b/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs new file mode 100644 index 0000000000..050ebd20f0 --- /dev/null +++ b/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs @@ -0,0 +1,267 @@ +/* + * 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.Reflection; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.OfflineIM +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "OfflineIMConnectorModule")] + public class OfflineIMRegionModule : ISharedRegionModule, IOfflineIMService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool m_Enabled = false; + private List m_SceneList = new List(); + IMessageTransferModule m_TransferModule = null; + private bool m_ForwardOfflineGroupMessages = true; + + private IOfflineIMService m_OfflineIMService; + + public void Initialise(IConfigSource config) + { + IConfig cnf = config.Configs["Messaging"]; + if (cnf == null) + return; + if (cnf != null && cnf.GetString("OfflineMessageModule", string.Empty) != Name) + return; + + m_Enabled = true; + + string serviceLocation = cnf.GetString("OfflineMessageURL", string.Empty); + if (serviceLocation == string.Empty) + m_OfflineIMService = new OfflineIMService(config); + else + m_OfflineIMService = new OfflineIMServiceRemoteConnector(serviceLocation); + + m_ForwardOfflineGroupMessages = cnf.GetBoolean("ForwardOfflineGroupMessages", m_ForwardOfflineGroupMessages); + m_log.DebugFormat("[OfflineIM.V2]: Offline messages enabled by {0}", Name); + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.RegisterModuleInterface(this); + m_SceneList.Add(scene); + scene.EventManager.OnNewClient += OnNewClient; + } + + public void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + + if (m_TransferModule == null) + { + m_TransferModule = scene.RequestModuleInterface(); + if (m_TransferModule == null) + { + scene.EventManager.OnNewClient -= OnNewClient; + + m_SceneList.Clear(); + + m_log.Error("[OfflineIM.V2]: No message transfer module is enabled. Disabling offline messages"); + } + m_TransferModule.OnUndeliveredMessage += UndeliveredMessage; + } + } + + public void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + m_SceneList.Remove(scene); + scene.EventManager.OnNewClient -= OnNewClient; + m_TransferModule.OnUndeliveredMessage -= UndeliveredMessage; + + scene.ForEachClient(delegate(IClientAPI client) + { + client.OnRetrieveInstantMessages -= RetrieveInstantMessages; + client.OnMuteListRequest -= OnMuteListRequest; + }); + } + + public void PostInitialise() + { + } + + public string Name + { + get { return "Offline Message Module V2"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Close() + { + m_SceneList.Clear(); + } + + private Scene FindScene(UUID agentID) + { + foreach (Scene s in m_SceneList) + { + ScenePresence presence = s.GetScenePresence(agentID); + if (presence != null && !presence.IsChildAgent) + return s; + } + return null; + } + + private IClientAPI FindClient(UUID agentID) + { + foreach (Scene s in m_SceneList) + { + ScenePresence presence = s.GetScenePresence(agentID); + if (presence != null && !presence.IsChildAgent) + return presence.ControllingClient; + } + return null; + } + + private void OnNewClient(IClientAPI client) + { + client.OnRetrieveInstantMessages += RetrieveInstantMessages; + client.OnMuteListRequest += OnMuteListRequest; + } + + private void RetrieveInstantMessages(IClientAPI client) + { + m_log.DebugFormat("[OfflineIM.V2]: Retrieving stored messages for {0}", client.AgentId); + + List msglist = m_OfflineIMService.GetMessages(client.AgentId); + + if (msglist == null) + m_log.DebugFormat("[OfflineIM.V2]: WARNING null message list."); + + foreach (GridInstantMessage im in msglist) + { + if (im.dialog == (byte)InstantMessageDialog.InventoryOffered) + // send it directly or else the item will be given twice + client.SendInstantMessage(im); + else + { + // Send through scene event manager so all modules get a chance + // to look at this message before it gets delivered. + // + // Needed for proper state management for stored group + // invitations + // + Scene s = FindScene(client.AgentId); + if (s != null) + s.EventManager.TriggerIncomingInstantMessage(im); + } + } + } + + // Apparently this is needed in order for the viewer to request the IMs. + private void OnMuteListRequest(IClientAPI client, uint crc) + { + m_log.DebugFormat("[OfflineIM.V2] Got mute list request for crc {0}", crc); + string filename = "mutes" + client.AgentId.ToString(); + + IXfer xfer = client.Scene.RequestModuleInterface(); + if (xfer != null) + { + xfer.AddNewFile(filename, new Byte[0]); + client.SendMuteListUpdate(filename); + } + } + + private void UndeliveredMessage(GridInstantMessage im) + { + if (im.dialog != (byte)InstantMessageDialog.MessageFromObject && + im.dialog != (byte)InstantMessageDialog.MessageFromAgent && + im.dialog != (byte)InstantMessageDialog.GroupNotice && + im.dialog != (byte)InstantMessageDialog.GroupInvitation && + im.dialog != (byte)InstantMessageDialog.InventoryOffered) + { + return; + } + + if (!m_ForwardOfflineGroupMessages) + { + if (im.dialog == (byte)InstantMessageDialog.GroupNotice || + im.dialog == (byte)InstantMessageDialog.GroupInvitation) + return; + } + + Scene scene = FindScene(new UUID(im.fromAgentID)); + if (scene == null) + scene = m_SceneList[0]; + + string reason = string.Empty; + bool success = m_OfflineIMService.StoreMessage(im, out reason); + + if (im.dialog == (byte)InstantMessageDialog.MessageFromAgent) + { + IClientAPI client = FindClient(new UUID(im.fromAgentID)); + if (client == null) + return; + + client.SendInstantMessage(new GridInstantMessage( + null, new UUID(im.toAgentID), + "System", new UUID(im.fromAgentID), + (byte)InstantMessageDialog.MessageFromAgent, + "User is not logged in. " + + (success ? "Message saved." : "Message not saved: " + reason), + false, new Vector3())); + } + } + + #region IOfflineIM + + public List GetMessages(UUID principalID) + { + return m_OfflineIMService.GetMessages(principalID); + } + + public bool StoreMessage(GridInstantMessage im, out string reason) + { + return m_OfflineIMService.StoreMessage(im, out reason); + } + + #endregion + } +} + diff --git a/OpenSim/Addons/OfflineIM/Properties/AssemblyInfo.cs b/OpenSim/Addons/OfflineIM/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ffe8a3ec21 --- /dev/null +++ b/OpenSim/Addons/OfflineIM/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Mono.Addins; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenSim.Addons.OfflineIM")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("http://opensimulator.org")] +[assembly: AssemblyProduct("OpenSim.Addons.OfflineIM")] +[assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a16a9905-4393-4872-9fca-4c81bedbd9f2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("0.7.6.*")] + +[assembly: Addin("OpenSim.OfflineIM", "0.1")] +[assembly: AddinDependency("OpenSim", "0.5")] diff --git a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs new file mode 100644 index 0000000000..69feb762f9 --- /dev/null +++ b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs @@ -0,0 +1,143 @@ +/* + * 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.Linq; +using System.Reflection; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; + +using OpenMetaverse; +using log4net; +using Nini.Config; + +namespace OpenSim.OfflineIM +{ + public class OfflineIMServiceRemoteConnector : IOfflineIMService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_ServerURI = string.Empty; + private object m_Lock = new object(); + + public OfflineIMServiceRemoteConnector(string url) + { + m_ServerURI = url; + m_log.DebugFormat("[OfflineIM.V2.RemoteConnector]: Offline IM server at {0}", m_ServerURI); + } + + public OfflineIMServiceRemoteConnector(IConfigSource config) + { + IConfig cnf = config.Configs["Messaging"]; + if (cnf == null) + { + m_log.WarnFormat("[OfflineIM.V2.RemoteConnector]: Missing Messaging configuration"); + return; + } + + m_ServerURI = cnf.GetString("OfflineMessageURL", string.Empty); + + } + + #region IOfflineIMService + public List GetMessages(UUID principalID) + { + List ims = new List(); + + Dictionary sendData = new Dictionary(); + sendData["PrincipalID"] = principalID; + Dictionary ret = MakeRequest("GET", sendData); + + if (ret == null) + return ims; + + if (!ret.ContainsKey("RESULT")) + return ims; + + if (ret["RESULT"].ToString() == "NULL") + return ims; + + foreach (object v in ((Dictionary)ret["RESULT"]).Values) + { + GridInstantMessage m = OfflineIMDataUtils.GridInstantMessage((Dictionary)v); + ims.Add(m); + } + + return ims; + } + + public bool StoreMessage(GridInstantMessage im, out string reason) + { + reason = string.Empty; + Dictionary sendData = OfflineIMDataUtils.GridInstantMessage(im); + + Dictionary ret = MakeRequest("STORE", sendData); + + if (ret == null) + { + reason = "Bad response from server"; + return false; + } + + string result = ret["RESULT"].ToString(); + if (result == "NULL" || result.ToLower() == "false") + { + reason = ret["REASON"].ToString(); + return false; + } + + return true; + } + + #endregion + + + #region Make Request + + private Dictionary MakeRequest(string method, Dictionary sendData) + { + sendData["METHOD"] = method; + + string reply = string.Empty; + lock (m_Lock) + reply = SynchronousRestFormsRequester.MakeRequest("POST", + m_ServerURI + "/offlineim", + ServerUtils.BuildQueryString(sendData)); + + Dictionary replyData = ServerUtils.ParseXmlResponse( + reply); + + return replyData; + } + #endregion + + } +} diff --git a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs new file mode 100644 index 0000000000..2b3a01d32d --- /dev/null +++ b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs @@ -0,0 +1,215 @@ +/* + * 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.Reflection; +using System.Text; +using System.Xml; +using System.Collections.Generic; +using System.IO; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Server.Handlers.Base; +using log4net; +using OpenMetaverse; + +namespace OpenSim.OfflineIM +{ + public class OfflineIMServiceRobustConnector : ServiceConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IOfflineIMService m_OfflineIMService; + private string m_ConfigName = "Messaging"; + + public OfflineIMServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) : + base(config, server, configName) + { + if (configName != String.Empty) + m_ConfigName = configName; + + m_log.DebugFormat("[OfflineIM.V2.RobustConnector]: Starting with config name {0}", m_ConfigName); + + m_OfflineIMService = new OfflineIMService(config); + + server.AddStreamHandler(new OfflineIMServicePostHandler(m_OfflineIMService)); + } + } + + public class OfflineIMServicePostHandler : BaseStreamHandler + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IOfflineIMService m_OfflineIMService; + + public OfflineIMServicePostHandler(IOfflineIMService service) : + base("POST", "/offlineim") + { + m_OfflineIMService = service; + } + + public override byte[] Handle(string path, Stream requestData, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + StreamReader sr = new StreamReader(requestData); + string body = sr.ReadToEnd(); + sr.Close(); + body = body.Trim(); + + //m_log.DebugFormat("[XXX]: query String: {0}", body); + + try + { + Dictionary request = + ServerUtils.ParseQueryString(body); + + if (!request.ContainsKey("METHOD")) + return FailureResult(); + + string method = request["METHOD"].ToString(); + request.Remove("METHOD"); + + m_log.DebugFormat("[OfflineIM.V2.Handler]: {0}", method); + switch (method) + { + case "GET": + return HandleGet(request); + case "STORE": + return HandleStore(request); + } + m_log.DebugFormat("[OFFLINE IM HANDLER]: unknown method request: {0}", method); + } + catch (Exception e) + { + m_log.DebugFormat("[OFFLINE IM HANDLER]: Exception {0}", e.StackTrace); + } + + return FailureResult(); + } + + byte[] HandleStore(Dictionary request) + { + Dictionary result = new Dictionary(); + + GridInstantMessage im = OfflineIMDataUtils.GridInstantMessage(request); + + string reason = string.Empty; + + bool success = m_OfflineIMService.StoreMessage(im, out reason); + + result["RESULT"] = success.ToString(); + if (!success) + result["REASON"] = reason; + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + byte[] HandleGet(Dictionary request) + { + Dictionary result = new Dictionary(); + + if (!request.ContainsKey("PrincipalID")) + NullResult(result, "Bad network data"); + else + { + UUID principalID = new UUID(request["PrincipalID"].ToString()); + List ims = m_OfflineIMService.GetMessages(principalID); + + Dictionary dict = new Dictionary(); + int i = 0; + foreach (GridInstantMessage m in ims) + dict["im-" + i++] = OfflineIMDataUtils.GridInstantMessage(m); + + result["RESULT"] = dict; + } + + string xmlString = ServerUtils.BuildXmlResponse(result); + + //m_log.DebugFormat("[XXX]: resp string: {0}", xmlString); + return Util.UTF8NoBomEncoding.GetBytes(xmlString); + } + + #region Helpers + + private void NullResult(Dictionary result, string reason) + { + result["RESULT"] = "NULL"; + result["REASON"] = reason; + } + + private byte[] FailureResult() + { + return BoolResult(false); + } + + private byte[] SuccessResult() + { + return BoolResult(true); + } + + private byte[] BoolResult(bool value) + { + XmlDocument doc = new XmlDocument(); + + XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration, + "", ""); + + doc.AppendChild(xmlnode); + + XmlElement rootElement = doc.CreateElement("", "ServerResponse", + ""); + + doc.AppendChild(rootElement); + + XmlElement result = doc.CreateElement("", "RESULT", ""); + result.AppendChild(doc.CreateTextNode(value.ToString())); + + rootElement.AppendChild(result); + + return DocToBytes(doc); + } + + private byte[] DocToBytes(XmlDocument doc) + { + MemoryStream ms = new MemoryStream(); + XmlTextWriter xw = new XmlTextWriter(ms, null); + xw.Formatting = Formatting.Indented; + doc.WriteTo(xw); + xw.Flush(); + + return ms.ToArray(); + } + + #endregion + } +} diff --git a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs new file mode 100644 index 0000000000..6ba022cd93 --- /dev/null +++ b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs @@ -0,0 +1,131 @@ +/* + * 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.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Timers; +using System.Xml; +using System.Xml.Serialization; +using log4net; +using Nini.Config; + +using OpenMetaverse; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Services.Interfaces; + +namespace OpenSim.OfflineIM +{ + public class OfflineIMService : OfflineIMServiceBase, IOfflineIMService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private const int MAX_IM = 25; + + private XmlSerializer m_serializer; + private static bool m_Initialized = false; + + public OfflineIMService(IConfigSource config) + : base(config) + { + m_serializer = new XmlSerializer(typeof(GridInstantMessage)); + if (!m_Initialized) + { + m_Database.DeleteOld(); + m_Initialized = true; + } + } + + public List GetMessages(UUID principalID) + { + List ims = new List(); + + OfflineIMData[] messages = m_Database.Get("PrincipalID", principalID.ToString()); + + if (messages == null || (messages != null && messages.Length == 0)) + return ims; + + foreach (OfflineIMData m in messages) + { + using (MemoryStream mstream = new MemoryStream(Encoding.UTF8.GetBytes(m.Data["Message"]))) + { + GridInstantMessage im = (GridInstantMessage)m_serializer.Deserialize(mstream); + ims.Add(im); + } + } + + // Then, delete them + m_Database.Delete("PrincipalID", principalID.ToString()); + + return ims; + } + + public bool StoreMessage(GridInstantMessage im, out string reason) + { + reason = string.Empty; + + // TODO Check limits + UUID principalID = new UUID(im.toAgentID); + long count = m_Database.GetCount("PrincipalID", principalID.ToString()); + if (count >= MAX_IM) + { + reason = "Number of offline IMs has maxed out"; + return false; + } + + string imXml = string.Empty; + using (MemoryStream mstream = new MemoryStream()) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Encoding = Encoding.UTF8; + + using (XmlWriter writer = XmlWriter.Create(mstream, settings)) + { + m_serializer.Serialize(writer, im); + writer.Flush(); + + mstream.Position = 0; + using (StreamReader sreader = new StreamReader(mstream)) + { + imXml = sreader.ReadToEnd(); + } + } + } + + OfflineIMData data = new OfflineIMData(); + data.PrincipalID = principalID; + data.Data = new Dictionary(); + data.Data["Message"] = imXml; + + return m_Database.Store(data); + + } + } +} diff --git a/OpenSim/Addons/OfflineIM/Service/OfflineIMServiceBase.cs b/OpenSim/Addons/OfflineIM/Service/OfflineIMServiceBase.cs new file mode 100644 index 0000000000..3376be4737 --- /dev/null +++ b/OpenSim/Addons/OfflineIM/Service/OfflineIMServiceBase.cs @@ -0,0 +1,83 @@ +/* + * 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.Reflection; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Data; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Base; + +namespace OpenSim.OfflineIM +{ + public class OfflineIMServiceBase : ServiceBase + { + protected IOfflineIMData m_Database = null; + + public OfflineIMServiceBase(IConfigSource config) + : base(config) + { + string dllName = String.Empty; + string connString = String.Empty; + string realm = "im_offline"; + + // + // Try reading the [DatabaseService] section, if it exists + // + IConfig dbConfig = config.Configs["DatabaseService"]; + if (dbConfig != null) + { + if (dllName == String.Empty) + dllName = dbConfig.GetString("StorageProvider", String.Empty); + if (connString == String.Empty) + connString = dbConfig.GetString("ConnectionString", String.Empty); + } + + // + // [Messaging] section overrides [DatabaseService], if it exists + // + IConfig imConfig = config.Configs["Messaging"]; + if (imConfig != null) + { + dllName = imConfig.GetString("StorageProvider", dllName); + connString = imConfig.GetString("ConnectionString", connString); + realm = imConfig.GetString("Realm", realm); + } + + // + // We tried, but this doesn't exist. We can't proceed. + // + if (dllName.Equals(String.Empty)) + throw new Exception("No StorageProvider configured"); + + m_Database = LoadPlugin(dllName, new Object[] { connString, realm }); + if (m_Database == null) + throw new Exception("Could not find a storage interface in the given module " + dllName); + } + } +} diff --git a/OpenSim/ApplicationPlugins/LoadRegions/Properties/AssemblyInfo.cs b/OpenSim/ApplicationPlugins/LoadRegions/Properties/AssemblyInfo.cs index 57615ea2d3..1b6a3e1a8e 100644 --- a/OpenSim/ApplicationPlugins/LoadRegions/Properties/AssemblyInfo.cs +++ b/OpenSim/ApplicationPlugins/LoadRegions/Properties/AssemblyInfo.cs @@ -60,7 +60,6 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -// [assembly: AssemblyVersion("0.7.5.*")] +// [assembly: AssemblyVersion("0.7.6.*")] -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] \ No newline at end of file +[assembly : AssemblyVersion("0.7.6.*")] diff --git a/OpenSim/ApplicationPlugins/RegionModulesController/Properties/AssemblyInfo.cs b/OpenSim/ApplicationPlugins/RegionModulesController/Properties/AssemblyInfo.cs index 14527d9ea9..5683a72313 100644 --- a/OpenSim/ApplicationPlugins/RegionModulesController/Properties/AssemblyInfo.cs +++ b/OpenSim/ApplicationPlugins/RegionModulesController/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/ApplicationPlugins/RegionModulesController/RegionModulesControllerPlugin.cs b/OpenSim/ApplicationPlugins/RegionModulesController/RegionModulesControllerPlugin.cs index 633d0055fd..510be371d9 100644 --- a/OpenSim/ApplicationPlugins/RegionModulesController/RegionModulesControllerPlugin.cs +++ b/OpenSim/ApplicationPlugins/RegionModulesController/RegionModulesControllerPlugin.cs @@ -85,16 +85,26 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController if (modulesConfig == null) modulesConfig = m_openSim.ConfigSource.Source.AddConfig("Modules"); + Dictionary> loadedModules = new Dictionary>(); + // Scan modules and load all that aren't disabled foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes("/OpenSim/RegionModules")) { + IList loadedModuleData; + + if (!loadedModules.ContainsKey(node.Addin)) + loadedModules.Add(node.Addin, new List { 0, 0, 0 }); + + loadedModuleData = loadedModules[node.Addin]; + if (node.Type.GetInterface(typeof(ISharedRegionModule).ToString()) != null) { if (CheckModuleEnabled(node, modulesConfig)) { m_log.DebugFormat("[REGIONMODULES]: Found shared region module {0}, class {1}", node.Id, node.Type); m_sharedModules.Add(node); + loadedModuleData[0]++; } } else if (node.Type.GetInterface(typeof(INonSharedRegionModule).ToString()) != null) @@ -103,14 +113,26 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController { m_log.DebugFormat("[REGIONMODULES]: Found non-shared region module {0}, class {1}", node.Id, node.Type); m_nonSharedModules.Add(node); + loadedModuleData[1]++; } } else { - m_log.DebugFormat("[REGIONMODULES]: Found unknown type of module {0}, class {1}", node.Id, node.Type); + m_log.WarnFormat("[REGIONMODULES]: Found unknown type of module {0}, class {1}", node.Id, node.Type); + loadedModuleData[2]++; } } + foreach (KeyValuePair> loadedModuleData in loadedModules) + { + m_log.InfoFormat( + "[REGIONMODULES]: From plugin {0}, (version {1}), loaded {2} modules, {3} shared, {4} non-shared {5} unknown", + loadedModuleData.Key.Id, + loadedModuleData.Key.Version, + loadedModuleData.Value[0] + loadedModuleData.Value[1] + loadedModuleData.Value[2], + loadedModuleData.Value[0], loadedModuleData.Value[1], loadedModuleData.Value[2]); + } + // Load and init the module. We try a constructor with a port // if a port was given, fall back to one without if there is // no port or the more specific constructor fails. diff --git a/OpenSim/ApplicationPlugins/RemoteController/Properties/AssemblyInfo.cs b/OpenSim/ApplicationPlugins/RemoteController/Properties/AssemblyInfo.cs index 8ad948c9bd..a9d3f74ecd 100644 --- a/OpenSim/ApplicationPlugins/RemoteController/Properties/AssemblyInfo.cs +++ b/OpenSim/ApplicationPlugins/RemoteController/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs b/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs index 49fc5663a5..e50dac611e 100644 --- a/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs +++ b/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs @@ -50,6 +50,7 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo; using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.ApplicationPlugins.RemoteController { @@ -140,6 +141,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController availableMethods["admin_save_heightmap"] = (req, ep) => InvokeXmlRpcMethod(req, ep, XmlRpcSaveHeightmapMethod); // Agent management + availableMethods["admin_get_agents"] = (req, ep) => InvokeXmlRpcMethod(req, ep, XmlRpcGetAgentsMethod); availableMethods["admin_teleport_agent"] = (req, ep) => InvokeXmlRpcMethod(req, ep, XmlRpcTeleportAgentMethod); // User management @@ -1900,6 +1902,71 @@ namespace OpenSim.ApplicationPlugins.RemoteController m_log.Info("[RADMIN]: Access List List Request complete"); } + private void XmlRpcGetAgentsMethod(XmlRpcRequest request, XmlRpcResponse response, IPEndPoint remoteClient) + { + Hashtable responseData = (Hashtable)response.Value; + Hashtable requestData = (Hashtable)request.Params[0]; + + bool includeChildren = false; + + if (requestData.Contains("include_children")) + bool.TryParse((string)requestData["include_children"], out includeChildren); + + Scene scene; + GetSceneFromRegionParams(requestData, responseData, out scene); + + ArrayList xmlRpcRegions = new ArrayList(); + responseData["regions"] = xmlRpcRegions; + + Hashtable xmlRpcRegion = new Hashtable(); + xmlRpcRegions.Add(xmlRpcRegion); + + xmlRpcRegion["name"] = scene.Name; + xmlRpcRegion["id"] = scene.RegionInfo.RegionID.ToString(); + + List agents = scene.GetScenePresences(); + ArrayList xmlrpcAgents = new ArrayList(); + + foreach (ScenePresence agent in agents) + { + if (agent.IsChildAgent && !includeChildren) + continue; + + Hashtable xmlRpcAgent = new Hashtable(); + xmlRpcAgent.Add("name", agent.Name); + xmlRpcAgent.Add("id", agent.UUID.ToString()); + xmlRpcAgent.Add("type", agent.PresenceType.ToString()); + xmlRpcAgent.Add("current_parcel_id", agent.currentParcelUUID.ToString()); + + Vector3 pos = agent.AbsolutePosition; + xmlRpcAgent.Add("pos_x", pos.X.ToString()); + xmlRpcAgent.Add("pos_y", pos.Y.ToString()); + xmlRpcAgent.Add("pos_z", pos.Z.ToString()); + + Vector3 lookAt = agent.Lookat; + xmlRpcAgent.Add("lookat_x", lookAt.X.ToString()); + xmlRpcAgent.Add("lookat_y", lookAt.Y.ToString()); + xmlRpcAgent.Add("lookat_z", lookAt.Z.ToString()); + + Vector3 vel = agent.Velocity; + xmlRpcAgent.Add("vel_x", vel.X.ToString()); + xmlRpcAgent.Add("vel_y", vel.Y.ToString()); + xmlRpcAgent.Add("vel_z", vel.Z.ToString()); + + xmlRpcAgent.Add("is_flying", agent.Flying.ToString()); + xmlRpcAgent.Add("is_sat_on_ground", agent.SitGround.ToString()); + xmlRpcAgent.Add("is_sat_on_object", agent.IsSatOnObject.ToString()); + + xmlrpcAgents.Add(xmlRpcAgent); + } + + m_log.DebugFormat( + "[REMOTE ADMIN]: XmlRpcGetAgents found {0} agents in {1}", xmlrpcAgents.Count, scene.Name); + + xmlRpcRegion["agents"] = xmlrpcAgents; + responseData["success"] = true; + } + private void XmlRpcTeleportAgentMethod(XmlRpcRequest request, XmlRpcResponse response, IPEndPoint remoteClient) { Hashtable responseData = (Hashtable)response.Value; diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs deleted file mode 100644 index 10f1a6ecf3..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs +++ /dev/null @@ -1,1465 +0,0 @@ -/* - * 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.IO; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Services.Interfaces; - -using OpenMetaverse; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - /// - /// This class represents the current REST request. It - /// encapsulates the request/response state and takes care - /// of response generation without exposing the REST handler - /// to the actual mechanisms involved. - /// - /// This structure is created on entry to the Handler - /// method and is disposed of upon return. It is part of - /// the plug-in infrastructure, rather than the functionally - /// specific REST handler, and fundamental changes to - /// this should be reflected in the Rest HandlerVersion. The - /// object is instantiated, and may be extended by, any - /// given handler. See the inventory handler for an example - /// of this. - /// - /// If possible, the underlying request/response state is not - /// changed until the handler explicitly issues a Respond call. - /// This ensures that the request/response pair can be safely - /// processed by subsequent, unrelated, handlers even id the - /// agent handler had completed much of its processing. Think - /// of it as a transactional req/resp capability. - /// - - public class RequestData - { - - // HTTP Server interface data (Received values) - - internal OSHttpRequest request = null; - internal OSHttpResponse response = null; - internal string qprefix = null; - - // Request lifetime values - // buffer is global because it is referenced by the handler - // in supported of streamed requests. - // If a service provider wants to construct the message - // body explicitly it can use body to do this. The value - // in body is used if the buffer is still null when a response - // is generated. - // Storing information in body will suppress the return of - // statusBody which is only intended to report status on - // requests which do not themselves ordinarily generate - // an informational response. All of this is handled in - // Respond(). - - internal byte[] buffer = null; - internal string body = null; - internal string bodyType = "text/html"; - - // The encoding in effect is set to a server default. It may - // subsequently be overridden by a Content header. This - // value is established during construction and is used - // wherever encoding services are needed. - - internal Encoding encoding = Rest.Encoding; - - // These values are derived from the supplied URL. They - // are initialized during construction. - - internal string path = null; - internal string method = null; - internal Uri uri = null; - internal string query = null; - internal string hostname = "localhost"; - internal int port = 80; - - // The path part of the URI is decomposed. pathNodes - // is an array of every element in the URI. Parameters - // is an array that contains only those nodes that - // are not a part of the authority prefix - - private string[] pathNodes = null; - private string[] parameters = null; - private static readonly string[] EmptyPath = { String.Empty }; - - // The status code gets set during the course of processing - // and is the HTTP completion code. The status body is - // initialized during construction, is appended to during the - // course of execution, and is finalized during Respond - // processing. - // - // Fail processing marks the request as failed and this is - // then used to inhibit processing during Response processing. - - internal int statusCode = 0; - internal string statusBody = String.Empty; - internal bool fail = false; - - // This carries the URL to which the client should be redirected. - // It is set by the service provider using the Redirect call. - - internal string redirectLocation = null; - - // These values influence response processing. They can be set by - // service providers according to need. The defaults are generally - // good. - - internal bool keepAlive = false; - internal bool chunked = false; - - // XML related state - - internal XmlWriter writer = null; - internal XmlReader reader = null; - - // Internal working state - - private StringBuilder sbuilder = new StringBuilder(1024); - private MemoryStream xmldata = null; - - // This is used to make the response mechanism idempotent. - - internal bool handled = false; - - // Authentication related state - // - // Two supported authentication mechanisms are: - // scheme = Rest.AS_BASIC; - // scheme = Rest.AS_DIGEST; - // Presented in that order (as required by spec) - // A service provider can set the scheme variable to - // force selection of a particular authentication model - // (choosing from amongst those supported of course) - // - - internal bool authenticated = false; - internal string scheme = Rest.Scheme; - internal string realm = Rest.Realm; - internal string domain = null; - internal string nonce = null; - internal string cnonce = null; - internal string qop = Rest.Qop_Auth; - internal string opaque = null; - internal string stale = null; - internal string algorithm = Rest.Digest_MD5; - internal string authParms = null; - internal string authPrefix = null; - internal string userName = String.Empty; - internal string userPass = String.Empty; - - // Session related tables. These are only needed if QOP is set to "auth-sess" - // and for now at least, it is not. Session related authentication is of - // questionable merit in the context of REST anyway, but it is, arguably, more - // secure. - - private static Dictionary cntable = new Dictionary(); - private static Dictionary sktable = new Dictionary(); - - // This dictionary is used to keep track fo all of the parameters discovered - // when the authorisation header is anaylsed. - - private Dictionary authparms = new Dictionary(); - - // These regular expressions are used to decipher the various header entries. - - private static Regex schema = new Regex("^\\s*(?\\w+)\\s*.*", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?\\S+)\\s*", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex digestParm1 = new Regex("\\s*(?\\w+)\\s*=\\s*\"(?[^\"]+)\"", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex digestParm2 = new Regex("\\s*(?\\w+)\\s*=\\s*(?[^\\p{P}\\s]+)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex reuserPass = new Regex("(?[^:]+):(?[\\S\\s]*)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - // For efficiency, we create static instances of these objects - - private static MD5 md5hash = MD5.Create(); - - private static StringComparer sc = StringComparer.OrdinalIgnoreCase; - -#region properties - - // Just for convenience... - - internal string MsgId - { - get { return Rest.MsgId; } - } - - /// - /// Return a boolean indication of whether or no an authenticated user is - /// associated with this request. This could be wholly integrated, but - /// that would make authentication mandatory. - /// - - internal bool IsAuthenticated - { - get - { - if (Rest.Authenticate) - { - if (!authenticated) - { - authenticate(); - } - - return authenticated; - } - else return true; - } - } - - /// - /// Access to all 'nodes' in the supplied URI as an - /// array of strings. - /// - - internal string[] PathNodes - { - get - { - return pathNodes; - } - } - - /// - /// Access to all non-prefix 'nodes' in the supplied URI as an - /// array of strings. These identify a specific resource that - /// is managed by the authority (the prefix). - /// - - internal string[] Parameters - { - get - { - return parameters; - } - } - -#endregion properties - -#region constructors - - // Constructor - - internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix) - { - - request = p_request; - response = p_response; - qprefix = p_qprefix; - - sbuilder.Length = 0; - - encoding = request.ContentEncoding; - - if (encoding == null) - { - encoding = Rest.Encoding; - } - - method = request.HttpMethod.ToLower(); - initUrl(); - - initParameters(p_qprefix.Length); - - } - -#endregion constructors - -#region authentication_common - - /// - /// The REST handler has requested authentication. Authentication - /// is considered to be with respect to the current values for - /// Realm, domain, etc. - /// - /// This method checks to see if the current request is already - /// authenticated for this domain. If it is, then it returns - /// true. If it is not, then it issues a challenge to the client - /// and responds negatively to the request. - /// - /// As soon as authentication failure is detected the method calls - /// DoChallenge() which terminates the request with REST exception - /// for unauthroized access. - /// - - private void authenticate() - { - - string authdata = request.Headers.Get("Authorization"); - string reqscheme = String.Empty; - - // If we don't have an authorization header, then this - // user is certainly not authorized. This is the typical - // pivot for the 1st request by a client. - - if (authdata == null) - { - Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); - DoChallenge(); - } - - // So, we have authentication data, now we have to check to - // see what we got and whether or not it is valid for the - // current domain. To do this we need to interpret the data - // provided in the Authorization header. First we need to - // identify the scheme being used and route accordingly. - - MatchCollection matches = schema.Matches(authdata); - - foreach (Match m in matches) - { - Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value); - reqscheme = m.Groups["scheme"].Value.ToLower(); - } - - // If we want a specific authentication mechanism, make sure - // we get it. null indicates we don't care. non-null indicates - // a specific scheme requirement. - - if (scheme != null && scheme.ToLower() != reqscheme) - { - Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId); - DoChallenge(); - } - - // In the future, these could be made into plug-ins... - // But for now at least we have no reason to use anything other - // then MD5. TLS/SSL are taken care of elsewhere. - - switch (reqscheme) - { - case "digest" : - Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId); - DoDigest(authdata); - break; - - case "basic" : - Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId); - DoBasic(authdata); - break; - } - - // If the current header is invalid, then a challenge is still needed. - - if (!authenticated) - { - Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); - DoChallenge(); - } - - } - - /// - /// Construct the necessary WWW-Authenticate headers and fail the request - /// with a NOT AUTHORIZED response. The parameters are the union of values - /// required by the supported schemes. - /// - - private void DoChallenge() - { - Flush(); - nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) - Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); - Fail(Rest.HttpStatusCodeNotAuthorized); - } - - /// - /// The Flush() call is here to support a problem encountered with the - /// client where an authentication rejection was lost because the rejection - /// may flow before the clienthas finished sending us the inbound data stream, - /// in which case the client responds to the socket error on out put, and - /// never sees the authentication challenge. The client should be fixed, - /// because this solution leaves the server prone to DOS attacks. A message - /// will be issued whenever flushing occurs. It can be enabled/disabled from - /// the configuration file. - /// - - private void Flush() - { - if (Rest.FlushEnabled) - { - byte[] dbuffer = new byte[8192]; - Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId); - while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0); - } - return; - } - - // Indicate that authentication is required - - private void Challenge(string scheme, string realm, string domain, string nonce, - string opaque, string stale, string alg, - string qop, string auth) - { - - sbuilder.Length = 0; - - // The service provider can force a particular scheme by - // assigning a value to scheme. - - // Basic authentication is pretty simple. - // Just specify the realm in question. - - if (scheme == null || scheme == Rest.AS_BASIC) - { - - sbuilder.Append(Rest.AS_BASIC); - - if (realm != null) - { - sbuilder.Append(" realm="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(realm); - sbuilder.Append(Rest.CS_DQUOTE); - } - AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); - } - - sbuilder.Length = 0; - - // Digest authentication takes somewhat more - // to express. - - if (scheme == null || scheme == Rest.AS_DIGEST) - { - - sbuilder.Append(Rest.AS_DIGEST); - sbuilder.Append(" "); - - // Specify the effective realm. This should - // never be null if we are uthenticating, as it is required for all - // authentication schemes. It defines, in conjunction with the - // absolute URI information, the domain to which the authentication - // applies. It is an arbitrary string. I *believe* this allows an - // authentication to apply to disjoint resources within the same - // server. - - if (realm != null) - { - sbuilder.Append("realm="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(realm); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // Share our nonce. This is *uniquely* generated each time a 401 is - // returned. We do not generate a very sophisticated nonce at the - // moment (it's simply a base64 encoded UUID). - - if (nonce != null) - { - sbuilder.Append("nonce="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(nonce); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // The opaque string should be returned by the client unchanged in all - // subsequent requests. - - if (opaque != null) - { - sbuilder.Append("opaque="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(opaque); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // This flag indicates that the authentication was rejected because the - // included nonce was stale. The server might use timestamp information - // in the nonce to determine this. We do not. - - if (stale != null) - { - sbuilder.Append("stale="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(stale); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // Identifies the algorithm used to produce the digest and checksum. - // The default is MD5. - - if (alg != null) - { - sbuilder.Append("algorithm="); - sbuilder.Append(alg); - sbuilder.Append(Rest.CS_COMMA); - } - - // Theoretically QOP is optional, but it is required by a compliant - // with current versions of the scheme. In fact IE requires that QOP - // be specified and will refuse to authenticate otherwise. - - if (qop != String.Empty) - { - sbuilder.Append("qop="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(qop); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // This parameter allows for arbitrary extensions to the protocol. - // Unrecognized values should be simply ignored. - - if (auth != null) - { - sbuilder.Append(auth); - sbuilder.Append(Rest.CS_COMMA); - } - - // We don't know the userid that will be used - // so we cannot make any authentication domain - // assumptions. So the prefix will determine - // this. - - sbuilder.Append("domain="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(qprefix); - sbuilder.Append(Rest.CS_DQUOTE); - - // Generate the authenticate header and we're basically - // done. - - AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); - - } - - } - -#endregion authentication_common - -#region authentication_basic - - /// - /// Interpret a BASIC authorization claim. Some clients can only - /// understand this and also expect it to be the first one - /// offered. So we do. - /// OpenSim also needs this, as it is the only scheme that allows - /// authentication using the hashed passwords stored in the - /// user database. - /// - - private void DoBasic(string authdata) - { - - string response = null; - - MatchCollection matches = basicParms.Matches(authdata); - - // In the case of basic authentication there is - // only expected to be a single argument. - - foreach (Match m in matches) - { - authparms.Add("response",m.Groups["pval"].Value); - Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", - MsgId, "response", m.Groups["pval"].Value); - } - - // Did we get a valid response? - - if (authparms.TryGetValue("response", out response)) - { - // Decode - response = Rest.Base64ToString(response); - Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response); - - // Extract user & password - Match m = reuserPass.Match(response); - userName = m.Groups["user"].Value; - userPass = m.Groups["pass"].Value; - - // Validate against user database - authenticated = Validate(userName,userPass); - } - - } - - /// - /// This method provides validation in support of the BASIC - /// authentication method. This is not normaly expected to be - /// used, but is included for completeness (and because I tried - /// it first). - /// - - private bool Validate(string user, string pass) - { - - Rest.Log.DebugFormat("{0} Simple User Validation", MsgId); - - // Both values are required - - if (user == null || pass == null) - return false; - - // Eliminate any leading or trailing spaces - user = user.Trim(); - - return vetPassword(user, pass); - - } - - /// - /// This is used by the BASIC authentication scheme to calculate - /// the double hash used by OpenSim to encode user's passwords. - /// It returns true, if the supplied password is actually correct. - /// If the specified user-id is not recognized, but the password - /// matches the God password, then this is accepted as an admin - /// session. - /// - - private bool vetPassword(string user, string pass) - { - - int x; - string first; - string last; - - // Distinguish the parts, if necessary - - if ((x=user.IndexOf(Rest.C_SPACE)) != -1) - { - first = user.Substring(0,x); - last = user.Substring(x+1); - } - else - { - first = user; - last = String.Empty; - } - - UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last); - - // If we don't recognize the user id, perhaps it is god? - if (account == null) - return pass == Rest.GodKey; - - return (Rest.AuthServices.Authenticate(account.PrincipalID, pass, 1) != string.Empty); - - } - -#endregion authentication_basic - -#region authentication_digest - - /// - /// This is an RFC2617 compliant HTTP MD5 Digest authentication - /// implementation. It has been tested with Firefox, Java HTTP client, - /// and Microsoft's Internet Explorer V7. - /// - - private void DoDigest(string authdata) - { - - string response = null; - - // Find all of the values of the for x = "y" - - MatchCollection matches = digestParm1.Matches(authdata); - - foreach (Match m in matches) - { - authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); - Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", - MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); - } - - // Find all of the values of the for x = y - - matches = digestParm2.Matches(authdata); - - foreach (Match m in matches) - { - authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); - Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", - MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); - } - - // A response string MUST be returned, otherwise we are - // NOT authenticated. - - Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId); - - if (authparms.TryGetValue("response", out response)) - { - - string temp = null; - - do - { - - string nck = null; - string ncl = null; - - // The userid is sent in clear text. Needed for the - // verification. - - authparms.TryGetValue("username", out userName); - - // All URI's of which this is a prefix are - // optimistically considered to be authenticated by the - // client. This is also needed to verify the response. - - authparms.TryGetValue("uri", out authPrefix); - - // There MUST be a nonce string present. We're not preserving any server - // side state and we can't validate the MD5 unless the client returns it - // to us, as it should. - - if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) - { - Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); - break; - } - - // If there is an opaque string present, it had better - // match what we sent. - - if (authparms.TryGetValue("opaque", out temp)) - { - if (temp != opaque) - { - Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); - break; - } - } - - // If an algorithm string is present, it had better - // match what we sent. - - if (authparms.TryGetValue("algorithm", out temp)) - { - if (temp != algorithm) - { - Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); - break; - } - } - - // Quality of protection considerations... - - if (authparms.TryGetValue("qop", out temp)) - { - - qop = temp.ToLower(); // replace with actual value used - - // if QOP was specified then - // these MUST be present. - - if (!authparms.ContainsKey("cnonce")) - { - Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - - cnonce = authparms["cnonce"]; - - if (!authparms.TryGetValue("nc", out nck) || nck == null) - { - Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - - Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId); - - if (cntable.TryGetValue(nonce, out ncl)) - { - Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl); - - if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) - { - Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - cntable[nonce] = nck; - } - else - { - lock (cntable) cntable.Add(nonce, nck); - } - - } - else - { - - qop = String.Empty; - - // if QOP was not specified then - // these MUST NOT be present. - if (authparms.ContainsKey("cnonce")) - { - Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - if (authparms.ContainsKey("nc")) - { - Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - } - - // Validate the supplied userid/password info - - authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); - - } - while (false); - - } - else - Fail(Rest.HttpStatusCodeBadRequest); - - } - - /// - /// This mechanism is used by the digest authentication mechanism - /// to return the user's password. In fact, because the OpenSim - /// user's passwords are already hashed, and the HTTP mechanism - /// does not supply an open password, the hashed passwords cannot - /// be used unless the client has used the same salting mechanism - /// to has the password before using it in the authentication - /// algorithn. This is not inconceivable... - /// - - private string getPassword(string user) - { - - int x; - string first; - string last; - - // Distinguish the parts, if necessary - - if ((x=user.IndexOf(Rest.C_SPACE)) != -1) - { - first = user.Substring(0,x); - last = user.Substring(x+1); - } - else - { - first = user; - last = String.Empty; - } - - UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last); - // If we don;t recognize the user id, perhaps it is god? - - if (account == null) - { - Rest.Log.DebugFormat("{0} Administrator", MsgId); - return Rest.GodKey; - } - else - { - Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); - - // !!! REFACTORING PROBLEM - // This is what it was. It doesn't work in 0.7 - // Nothing retrieves the password from the authentication service, there's only authentication. - //return udata.PasswordHash; - return string.Empty; - } - - } - - // Validate the request-digest - - private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) - { - - string patt = null; - string payl = String.Empty; - string KDS = null; - string HA1 = null; - string HA2 = null; - string pass = getPassword(user); - - // Generate H(A1) - - if (algorithm == Rest.Digest_MD5Sess) - { - if (!sktable.ContainsKey(cnonce)) - { - patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce); - HA1 = HashToString(patt); - sktable.Add(cnonce, HA1); - } - else - { - HA1 = sktable[cnonce]; - } - } - else - { - patt = String.Format("{0}:{1}:{2}", user, realm, pass); - HA1 = HashToString(patt); - } - - // Generate H(A2) - - if (qop == "auth-int") - { - patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl)); - } - else - { - patt = String.Format("{0}:{1}", request.HttpMethod, uri); - } - - HA2 = HashToString(patt); - - // Generate Digest - - if (qop != String.Empty) - { - patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); - } - else - { - patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2); - } - - KDS = HashToString(patt); - - // Compare the generated sequence with the original - - return (0 == sc.Compare(KDS, response)); - - } - - private string HashToString(string pattern) - { - - Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); - - byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); - - sbuilder.Length = 0; - - for (int i = 0; i < hash.Length; i++) - { - sbuilder.Append(hash[i].ToString("x2")); - } - - Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); - - return sbuilder.ToString(); - - } - -#endregion authentication_digest - -#region service_interface - - /// - /// Conditionally set a normal completion code. This allows a normal - /// execution path to default. - /// - - internal void Complete() - { - if (statusCode == 0) - { - statusCode = Rest.HttpStatusCodeOK; - } - } - - /// - /// Indicate a functionally-dependent conclusion to the - /// request. See Rest.cs for a list of possible values. - /// - - internal void Complete(int code) - { - statusCode = code; - } - - /// - /// Indicate that a request should be redirected, using - /// the HTTP completion codes. Permanent and temporary - /// redirections may be indicated. The supplied URL is - /// the new location of the resource. - /// - - internal void Redirect(string Url, bool temp) - { - - redirectLocation = Url; - - if (temp) - { - statusCode = Rest.HttpStatusCodeTemporaryRedirect; - } - else - { - statusCode = Rest.HttpStatusCodePermanentRedirect; - } - - Fail(statusCode, String.Empty, true); - - } - - /// - /// Fail for an arbitrary reason. Just a failure with - /// headers. The supplied message will be returned in the - /// message body. - /// - - internal void Fail(int code) - { - Fail(code, String.Empty, false); - } - - /// - /// For the more adventurous. This failure also includes a - /// specified entity to be appended to the code-related - /// status string. - /// - - internal void Fail(int code, string addendum) - { - Fail(code, addendum, false); - } - - internal void Fail(int code, string addendum, bool reset) - { - - statusCode = code; - appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code])); - - // Add any final addendum to the status information - - if (addendum != String.Empty) - { - appendStatus(String.Format(addendum)); - } - - // Help us understand why the request is being rejected - - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); - Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme); - Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm); - Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain); - Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce); - Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce); - Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque); - Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale); - Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm); - Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop); - Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix); - Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName); - Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass); - } - - fail = true; - - // Respond to the client's request, tag the response (for the - // benefit of trace) to indicate the reason. - - Respond(String.Format("Failure response: ({0}) : {1} ", - code, Rest.HttpStatusDesc[code])); - - // Finally initialize and the throw a RestException. All of the - // handler's infrastructure knows that this is a "normal" - // completion from a code point-of-view. - - RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">"); - - re.statusCode = code; - re.statusDesc = Rest.HttpStatusDesc[code]; - re.httpmethod = method; - re.httppath = path; - - throw re; - - } - - // Reject this request - - internal void Reject() - { - Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)"); - } - - // This MUST be called by an agent handler before it returns - // control to Handle, otherwise the request will be ignored. - // This is called implciitly for the REST stream handlers and - // is harmless if it is called twice. - - internal virtual bool Respond(string reason) - { - - - Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); - - // We do this to try and make multiple Respond requests harmless, - // as it is sometimes convenient to isse a response without - // certain knowledge that it has not previously been done. - - if (!handled) - { - - Rest.Log.DebugFormat("{0} Generating Response", MsgId); - Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); - - // A Head request can NOT have a body! So don't waste time on - // formatting if we're going to reject it anyway! - - if (method != Rest.HEAD) - { - - Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); - - // If the writer is non-null then we know that an XML - // data component exists. Flush and close the writer and - // then convert the result to the expected buffer format - // unless the request has already been failed for some - // reason. - - if (writer != null) - { - Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId); - Rest.Log.DebugFormat("{0} XML Response exists", MsgId); - writer.Flush(); - writer.Close(); - if (!fail) - { - buffer = xmldata.ToArray(); - AddHeader("Content-Type","application/xml"); - } - xmldata.Close(); - Rest.Log.DebugFormat("{0} XML Response encoded", MsgId); - Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); - } - - if (buffer == null && body != null) - { - buffer = encoding.GetBytes(body); - AddHeader("Content-Type",bodyType); - } - - // OK, if the buffer contains something, regardless of how - // it got there, set various response headers accordingly. - - if (buffer != null) - { - Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); - } - else - { - if (statusBody != String.Empty) - { - statusBody += Rest.statusTail; - buffer = encoding.GetBytes(statusBody); - AddHeader("Content-Type","text/html"); - } - else - { - statusBody = Rest.statusHead; - appendStatus(String.Format(": ({0}) {1}", - statusCode, Rest.HttpStatusDesc[statusCode])); - statusBody += Rest.statusTail; - buffer = encoding.GetBytes(statusBody); - AddHeader("Content-Type","text/html"); - } - } - - response.ContentLength64 = buffer.Length; - - if (response.ContentEncoding == null) - response.ContentEncoding = encoding; - - response.SendChunked = chunked; - response.KeepAlive = keepAlive; - - } - - // Set the status code & description. If nothing has been stored, - // we consider that a success. - - if (statusCode == 0) - { - Complete(); - } - - // Set the response code in the actual carrier - - response.StatusCode = statusCode; - - // For a redirect we need to set the relocation header accordingly - - if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || - response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) - { - Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); - response.RedirectLocation = redirectLocation; - } - - // And include the status description if provided. - - response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode]; - - // Finally we send back our response. - - // We've left the setting of handled' until the - // last minute because the header settings included - // above are pretty harmless. But everything from - // here on down probably leaves the response - // element unusable by anyone else. - - handled = true; - - // DumpHeaders(); - - // if (request.InputStream != null) - // { - // Rest.Log.DebugFormat("{0} Closing input stream", MsgId); - // request.InputStream.Close(); - // } - - if (buffer != null && buffer.Length != 0) - { - Rest.Log.DebugFormat("{0} Entity buffer, length = {1}", MsgId, buffer.Length); - // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", - // MsgId, buffer.Length, encoding.GetString(buffer)); - response.OutputStream.Write(buffer, 0, buffer.Length); - } - - // Closing the outputstream should complete the transmission process - - Rest.Log.DebugFormat("{0} Sending response", MsgId); - // response.OutputStream.Close(); - response.Send(); - - } - - Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); - - return handled; - - } - - /// - /// These methods allow a service provider to manipulate the - /// request/response headers. The DumpHeaders method is intended - /// for problem diagnosis. - /// - - internal void AddHeader(string hdr, string data) - { - if (Rest.DEBUG) Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", MsgId, hdr, data); - response.AddHeader(hdr, data); - } - - // internal void RemoveHeader(string hdr) - // { - // if (Rest.DEBUG) - // { - // Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); - // if (response.Headers.Get(hdr) == null) - // { - // Rest.Log.DebugFormat("{0} No such header existed", - // MsgId, hdr); - // } - // } - // response.Headers.Remove(hdr); - // } - - // internal void DumpHeaders() - // { - // if (Rest.DEBUG) - // { - // for (int i=0;i - /// Helper methods for deconstructing and reconstructing - /// URI path data. - /// - - private void initUrl() - { - - uri = request.Url; - - if (query == null) - { - query = uri.Query; - } - - // If the path has not been previously initialized, - // do so now. - - if (path == null) - { - path = uri.AbsolutePath; - if (path.EndsWith(Rest.UrlPathSeparator)) - path = path.Substring(0,path.Length-1); - } - - // If we succeeded in getting a path, perform any - // additional pre-processing required. - - if (path != null) - { - if (Rest.ExtendedEscape) - { - // Handle "+". Not a standard substitution, but - // common enough... - path = path.Replace(Rest.C_PLUS,Rest.C_SPACE); - } - pathNodes = path.Split(Rest.CA_PATHSEP); - } - else - { - pathNodes = EmptyPath; - } - - // Elimiate any %-escaped values. This is left until here - // so that escaped "+' are not mistakenly replaced. - - path = Uri.UnescapeDataString(path); - - // Request server context info - - hostname = uri.Host; - port = uri.Port; - - } - - private int initParameters(int prfxlen) - { - - if (prfxlen < path.Length-1) - { - parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); - } - else - { - parameters = new string[0]; - } - - // Generate a debug list of the decoded parameters - - if (Rest.DEBUG && prfxlen < path.Length-1) - { - Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); - for (int i = 0; i < parameters.Length; i++) - { - Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); - } - } - - return parameters.Length; - - } - -#endregion internal_methods - - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml b/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml deleted file mode 100644 index 777a2dc27f..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs deleted file mode 100644 index 9755e73ac3..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs +++ /dev/null @@ -1,551 +0,0 @@ -/* - * 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.Reflection; -using System.Text; -using log4net; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Framework.Communications; -using OpenSim.Services.Interfaces; -using IAvatarService = OpenSim.Services.Interfaces.IAvatarService; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class Rest - { - internal static readonly ILog Log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - internal static bool DEBUG = Log.IsDebugEnabled; - - /// - /// Supported authentication schemes - /// - - public const string AS_BASIC = "Basic"; // simple user/password verification - public const string AS_DIGEST = "Digest"; // password safe authentication - - /// Supported Digest algorithms - - public const string Digest_MD5 = "MD5"; // assumed default if omitted - public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST? - - public const string Qop_Auth = "auth"; // authentication only - public const string Qop_Int = "auth-int"; // TODO - - /// - /// These values have a single value for the whole - /// domain and lifetime of the plugin handler. We - /// make them static for ease of reference within - /// the assembly. These are initialized by the - /// RestHandler class during start-up. - /// - - internal static IRestHandler Plugin = null; - internal static OpenSimBase main = null; - internal static string Prefix = null; - internal static IConfig Config = null; - internal static string GodKey = null; - internal static bool Authenticate = true; - internal static bool Secure = true; - internal static bool ExtendedEscape = true; - internal static bool DumpAsset = false; - internal static bool Fill = true; - internal static bool FlushEnabled = true; - internal static string Realm = "OpenSim REST"; - internal static string Scheme = AS_BASIC; - internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 - - /// - /// These are all dependent upon the Comms manager - /// being initialized. So they have to be properties - /// because the comms manager is now a module and is - /// not guaranteed to be there when the rest handler - /// initializes. - /// - - internal static IInventoryService InventoryServices - { - get { return main.SceneManager.CurrentOrFirstScene.InventoryService; } - } - - internal static IUserAccountService UserServices - { - get { return main.SceneManager.CurrentOrFirstScene.UserAccountService; } - } - - internal static IAuthenticationService AuthServices - { - get { return main.SceneManager.CurrentOrFirstScene.AuthenticationService; } - } - - internal static IAvatarService AvatarServices - { - get { return main.SceneManager.CurrentOrFirstScene.AvatarService; } - } - - internal static IAssetService AssetServices - { - get { return main.SceneManager.CurrentOrFirstScene.AssetService; } - } - - /// - /// HTTP requires that status information be generated for PUT - /// and POST opertaions. This is in support of that. The - /// operation verb gets substituted into the first string, - /// and the completion code is inserted into the tail. The - /// strings are put here to encourage consistency. - /// - - internal static string statusHead = "{0} status"; - internal static string statusTail = ""; - - internal static Dictionary HttpStatusDesc; - - static Rest() - { - HttpStatusDesc = new Dictionary(); - if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length) - { - Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match"); - throw new Exception("HTTP Status array discrepancy"); - } - - // Repackage the data into something more tractable. The sparse - // nature of HTTP return codes makes an array a bad choice. - - for (int i=0; i - /// Version control for REST implementation. This - /// refers to the overall infrastructure represented - /// by the following classes - /// RequestData - /// RequestInventoryPlugin - /// Rest - /// It does no describe implementation classes such as - /// RestInventoryServices, which may morph much more - /// often. Such classes ARE dependent upon this however - /// and should check it in their Initialize method. - /// - - public static readonly float Version = 1.0F; - public const string Name = "REST 1.0"; - - /// - /// Currently defined HTTP methods. - /// Only GET and HEAD are required to be - /// supported by all servers. See Respond - /// to see how these are handled. - /// - - // REST AGENT 1.0 interpretations - public const string GET = "get"; // information retrieval - server state unchanged - public const string HEAD = "head"; // same as get except only the headers are returned. - public const string POST = "post"; // Replace the URI designated resource with the entity. - public const string PUT = "put"; // Add the entity to the context represented by the URI - public const string DELETE = "delete"; // Remove the URI designated resource from the server. - - public const string OPTIONS = "options"; // - public const string TRACE = "trace"; // - public const string CONNECT = "connect"; // - - // Define this in one place... - - public const string UrlPathSeparator = "/"; - public const string UrlMethodSeparator = ":"; - - // Redirection qualifications - - public const bool PERMANENT = false; - public const bool TEMPORARY = true; - - // Constant arrays used by String.Split - - public static readonly char C_SPACE = ' '; - public static readonly char C_SLASH = '/'; - public static readonly char C_PATHSEP = '/'; - public static readonly char C_COLON = ':'; - public static readonly char C_PLUS = '+'; - public static readonly char C_PERIOD = '.'; - public static readonly char C_COMMA = ','; - public static readonly char C_DQUOTE = '"'; - - public static readonly string CS_SPACE = " "; - public static readonly string CS_SLASH = "/"; - public static readonly string CS_PATHSEP = "/"; - public static readonly string CS_COLON = ":"; - public static readonly string CS_PLUS = "+"; - public static readonly string CS_PERIOD = "."; - public static readonly string CS_COMMA = ","; - public static readonly string CS_DQUOTE = "\""; - - public static readonly char[] CA_SPACE = { C_SPACE }; - public static readonly char[] CA_SLASH = { C_SLASH }; - public static readonly char[] CA_PATHSEP = { C_PATHSEP }; - public static readonly char[] CA_COLON = { C_COLON }; - public static readonly char[] CA_PERIOD = { C_PERIOD }; - public static readonly char[] CA_PLUS = { C_PLUS }; - public static readonly char[] CA_COMMA = { C_COMMA }; - public static readonly char[] CA_DQUOTE = { C_DQUOTE }; - - // HTTP Code Values (in value order) - - public const int HttpStatusCodeContinue = 100; - public const int HttpStatusCodeSwitchingProtocols = 101; - - public const int HttpStatusCodeOK = 200; - public const int HttpStatusCodeCreated = 201; - public const int HttpStatusCodeAccepted = 202; - public const int HttpStatusCodeNonAuthoritative = 203; - public const int HttpStatusCodeNoContent = 204; - public const int HttpStatusCodeResetContent = 205; - public const int HttpStatusCodePartialContent = 206; - - public const int HttpStatusCodeMultipleChoices = 300; - public const int HttpStatusCodePermanentRedirect = 301; - public const int HttpStatusCodeFound = 302; - public const int HttpStatusCodeSeeOther = 303; - public const int HttpStatusCodeNotModified = 304; - public const int HttpStatusCodeUseProxy = 305; - public const int HttpStatusCodeReserved306 = 306; - public const int HttpStatusCodeTemporaryRedirect = 307; - - public const int HttpStatusCodeBadRequest = 400; - public const int HttpStatusCodeNotAuthorized = 401; - public const int HttpStatusCodePaymentRequired = 402; - public const int HttpStatusCodeForbidden = 403; - public const int HttpStatusCodeNotFound = 404; - public const int HttpStatusCodeMethodNotAllowed = 405; - public const int HttpStatusCodeNotAcceptable = 406; - public const int HttpStatusCodeProxyAuthenticate = 407; - public const int HttpStatusCodeTimeOut = 408; - public const int HttpStatusCodeConflict = 409; - public const int HttpStatusCodeGone = 410; - public const int HttpStatusCodeLengthRequired = 411; - public const int HttpStatusCodePreconditionFailed = 412; - public const int HttpStatusCodeEntityTooLarge = 413; - public const int HttpStatusCodeUriTooLarge = 414; - public const int HttpStatusCodeUnsupportedMedia = 415; - public const int HttpStatusCodeRangeNotSatsified = 416; - public const int HttpStatusCodeExpectationFailed = 417; - - public const int HttpStatusCodeServerError = 500; - public const int HttpStatusCodeNotImplemented = 501; - public const int HttpStatusCodeBadGateway = 502; - public const int HttpStatusCodeServiceUnavailable = 503; - public const int HttpStatusCodeGatewayTimeout = 504; - public const int HttpStatusCodeHttpVersionError = 505; - - public static readonly int[] HttpStatusCodeArray = { - HttpStatusCodeContinue, - HttpStatusCodeSwitchingProtocols, - HttpStatusCodeOK, - HttpStatusCodeCreated, - HttpStatusCodeAccepted, - HttpStatusCodeNonAuthoritative, - HttpStatusCodeNoContent, - HttpStatusCodeResetContent, - HttpStatusCodePartialContent, - HttpStatusCodeMultipleChoices, - HttpStatusCodePermanentRedirect, - HttpStatusCodeFound, - HttpStatusCodeSeeOther, - HttpStatusCodeNotModified, - HttpStatusCodeUseProxy, - HttpStatusCodeReserved306, - HttpStatusCodeTemporaryRedirect, - HttpStatusCodeBadRequest, - HttpStatusCodeNotAuthorized, - HttpStatusCodePaymentRequired, - HttpStatusCodeForbidden, - HttpStatusCodeNotFound, - HttpStatusCodeMethodNotAllowed, - HttpStatusCodeNotAcceptable, - HttpStatusCodeProxyAuthenticate, - HttpStatusCodeTimeOut, - HttpStatusCodeConflict, - HttpStatusCodeGone, - HttpStatusCodeLengthRequired, - HttpStatusCodePreconditionFailed, - HttpStatusCodeEntityTooLarge, - HttpStatusCodeUriTooLarge, - HttpStatusCodeUnsupportedMedia, - HttpStatusCodeRangeNotSatsified, - HttpStatusCodeExpectationFailed, - HttpStatusCodeServerError, - HttpStatusCodeNotImplemented, - HttpStatusCodeBadGateway, - HttpStatusCodeServiceUnavailable, - HttpStatusCodeGatewayTimeout, - HttpStatusCodeHttpVersionError - }; - - // HTTP Status Descriptions (in status code order) - // This array must be kept strictly consistent with respect - // to the status code array above. - - public static readonly string[] HttpStatusDescArray = { - "Continue Request", - "Switching Protocols", - "OK", - "CREATED", - "ACCEPTED", - "NON-AUTHORITATIVE INFORMATION", - "NO CONTENT", - "RESET CONTENT", - "PARTIAL CONTENT", - "MULTIPLE CHOICES", - "PERMANENT REDIRECT", - "FOUND", - "SEE OTHER", - "NOT MODIFIED", - "USE PROXY", - "RESERVED CODE 306", - "TEMPORARY REDIRECT", - "BAD REQUEST", - "NOT AUTHORIZED", - "PAYMENT REQUIRED", - "FORBIDDEN", - "NOT FOUND", - "METHOD NOT ALLOWED", - "NOT ACCEPTABLE", - "PROXY AUTHENTICATION REQUIRED", - "TIMEOUT", - "CONFLICT", - "GONE", - "LENGTH REQUIRED", - "PRECONDITION FAILED", - "ENTITY TOO LARGE", - "URI TOO LARGE", - "UNSUPPORTED MEDIA", - "RANGE NOT SATISFIED", - "EXPECTATION FAILED", - "SERVER ERROR", - "NOT IMPLEMENTED", - "BAD GATEWAY", - "SERVICE UNAVAILABLE", - "GATEWAY TIMEOUT", - "HTTP VERSION NOT SUPPORTED" - }; - - // HTTP Headers - - public const string HttpHeaderAccept = "Accept"; - public const string HttpHeaderAcceptCharset = "Accept-Charset"; - public const string HttpHeaderAcceptEncoding = "Accept-Encoding"; - public const string HttpHeaderAcceptLanguage = "Accept-Language"; - public const string HttpHeaderAcceptRanges = "Accept-Ranges"; - public const string HttpHeaderAge = "Age"; - public const string HttpHeaderAllow = "Allow"; - public const string HttpHeaderAuthorization = "Authorization"; - public const string HttpHeaderCacheControl = "Cache-Control"; - public const string HttpHeaderConnection = "Connection"; - public const string HttpHeaderContentEncoding = "Content-Encoding"; - public const string HttpHeaderContentLanguage = "Content-Language"; - public const string HttpHeaderContentLength = "Content-Length"; - public const string HttpHeaderContentLocation = "Content-Location"; - public const string HttpHeaderContentMD5 = "Content-MD5"; - public const string HttpHeaderContentRange = "Content-Range"; - public const string HttpHeaderContentType = "Content-Type"; - public const string HttpHeaderDate = "Date"; - public const string HttpHeaderETag = "ETag"; - public const string HttpHeaderExpect = "Expect"; - public const string HttpHeaderExpires = "Expires"; - public const string HttpHeaderFrom = "From"; - public const string HttpHeaderHost = "Host"; - public const string HttpHeaderIfMatch = "If-Match"; - public const string HttpHeaderIfModifiedSince = "If-Modified-Since"; - public const string HttpHeaderIfNoneMatch = "If-None-Match"; - public const string HttpHeaderIfRange = "If-Range"; - public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since"; - public const string HttpHeaderLastModified = "Last-Modified"; - public const string HttpHeaderLocation = "Location"; - public const string HttpHeaderMaxForwards = "Max-Forwards"; - public const string HttpHeaderPragma = "Pragma"; - public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate"; - public const string HttpHeaderProxyAuthorization = "Proxy-Authorization"; - public const string HttpHeaderRange = "Range"; - public const string HttpHeaderReferer = "Referer"; - public const string HttpHeaderRetryAfter = "Retry-After"; - public const string HttpHeaderServer = "Server"; - public const string HttpHeaderTE = "TE"; - public const string HttpHeaderTrailer = "Trailer"; - public const string HttpHeaderTransferEncoding = "Transfer-Encoding"; - public const string HttpHeaderUpgrade = "Upgrade"; - public const string HttpHeaderUserAgent = "User-Agent"; - public const string HttpHeaderVary = "Vary"; - public const string HttpHeaderVia = "Via"; - public const string HttpHeaderWarning = "Warning"; - public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate"; - - /// Utility routines - - public static string StringToBase64(string str) - { - try - { - byte[] encData_byte = new byte[str.Length]; - encData_byte = Util.UTF8.GetBytes(str); - return Convert.ToBase64String(encData_byte); - } - catch - { - return String.Empty; - } - } - - public static string Base64ToString(string str) - { - try - { - return Util.Base64ToString(str); - } - catch - { - return String.Empty; - } - } - - private const string hvals = "0123456789abcdef"; - - public static int Hex2Int(string hex) - { - int val = 0; - int sum = 0; - string tmp = null; - - if (hex != null) - { - tmp = hex.ToLower(); - for (int i = 0; i < tmp.Length; i++) - { - val = hvals.IndexOf(tmp[i]); - if (val == -1) - break; - sum *= 16; - sum += val; - } - } - - return sum; - } - - // Nonce management - - public static string NonceGenerator() - { - return StringToBase64(CreationDate + Guid.NewGuid().ToString()); - } - - // Dump the specified data stream - - public static void Dump(byte[] data) - { - char[] buffer = new char[DumpLineSize]; - int cc = 0; - - for (int i = 0; i < data.Length; i++) - { - if (i % DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8")); - - if (i % 4 == 0) Console.Write(" "); - - Console.Write("{0}",data[i].ToString("x2")); - - if (data[i] < 127 && data[i] > 31) - buffer[i % DumpLineSize] = (char) data[i]; - else - buffer[i % DumpLineSize] = '.'; - - cc++; - - if (i != 0 && (i + 1) % DumpLineSize == 0) - { - Console.Write(" |"+(new String(buffer))+"|"); - cc = 0; - } - } - - // Finish off any incomplete line - - if (cc != 0) - { - for (int i = cc ; i < DumpLineSize; i++) - { - if (i % 4 == 0) Console.Write(" "); - Console.Write(" "); - buffer[i % DumpLineSize] = ' '; - } - Console.WriteLine(" |"+(new String(buffer))+"|"); - } - else - { - Console.Write("\n"); - } - } - } - - // Local exception type - - public class RestException : Exception - { - internal int statusCode; - internal string statusDesc; - internal string httpmethod; - internal string httppath; - - public RestException(string msg) : base(msg) - { - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs deleted file mode 100644 index 3cda98442e..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs +++ /dev/null @@ -1,860 +0,0 @@ -/* - * 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; -using System.Collections.Generic; -using System.Xml; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Services.Interfaces; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - public class RestAppearanceServices : IRest - { -// private static readonly int PARM_USERID = 0; - - // private static readonly int PARM_PATH = 1; - -// private bool enabled = false; - private string qPrefix = "appearance"; - - /// - /// The constructor makes sure that the service prefix is absolute - /// and the registers the service handler and the allocator. - /// - - public RestAppearanceServices() - { - Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // If a relative path was specified for the handler's domain, - // add the standard prefix to make it absolute, e.g. /admin - - if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) - { - Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); - } - - // Register interface using the absolute URI. - - Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate); - - // Activate if everything went OK - -// enabled = true; - - Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId); - } - - /// - /// Post-construction, pre-enabled initialization opportunity - /// Not currently exploited. - /// - - public void Initialize() - { - } - - /// - /// Called by the plug-in to halt service processing. Local processing is - /// disabled. - /// - - public void Close() - { -// enabled = false; - Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId); - } - - /// - /// This property is declared locally because it is used a lot and - /// brevity is nice. - /// - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - /// - /// The plugin (RestHandler) calls this method to allocate the request - /// state carrier for a new request. It is destroyed when the request - /// completes. All request-instance specific state is kept here. This - /// is registered when this service provider is registered. - /// - /// Inbound HTTP request information - /// Outbound HTTP request information - /// REST service domain prefix - /// A RequestData instance suitable for this service - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new AppearanceRequestData(request, response, prefix); - } - - /// - /// This method is registered with the handler when this service provider - /// is initialized. It is called whenever the plug-in identifies this service - /// provider as the best match for a given request. - /// It handles all aspects of inventory REST processing, i.e. /admin/inventory - /// - /// A consolidated HTTP request work area - - private void DoAppearance(RequestData hdata) - { - // !!! REFACTORIMG PROBLEM. This needs rewriting for 0.7 - - //AppearanceRequestData rdata = (AppearanceRequestData) hdata; - - //Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId); - - //// If we're disabled, do nothing. - - //if (!enabled) - //{ - // return; - //} - - //// Now that we know this is a serious attempt to - //// access inventory data, we should find out who - //// is asking, and make sure they are authorized - //// to do so. We need to validate the caller's - //// identity before revealing anything about the - //// status quo. Authenticate throws an exception - //// via Fail if no identity information is present. - //// - //// With the present HTTP server we can't use the - //// builtin authentication mechanisms because they - //// would be enforced for all in-bound requests. - //// Instead we look at the headers ourselves and - //// handle authentication directly. - - //try - //{ - // if (!rdata.IsAuthenticated) - // { - // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); - // } - //} - //catch (RestException e) - //{ - // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - // { - // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // else - // { - // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // throw (e); - //} - - //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); - - //// We can only get here if we are authorized - //// - //// The requestor may have specified an UUID or - //// a conjoined FirstName LastName string. We'll - //// try both. If we fail with the first, UUID, - //// attempt, we try the other. As an example, the - //// URI for a valid inventory request might be: - //// - //// http://:/admin/inventory/Arthur Dent - //// - //// Indicating that this is an inventory request for - //// an avatar named Arthur Dent. This is ALL that is - //// required to designate a GET for an entire - //// inventory. - //// - - //// Do we have at least a user agent name? - - //if (rdata.Parameters.Length < 1) - //{ - // Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); - //} - - //// The first parameter MUST be the agent identification, either an UUID - //// or a space-separated First-name Last-Name specification. We check for - //// an UUID first, if anyone names their character using a valid UUID - //// that identifies another existing avatar will cause this a problem... - - //try - //{ - // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); - // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); - //} - //catch - //{ - // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); - // if (names.Length == 2) - // { - // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); - // } - // else - // { - // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); - // } - //} - - //// If the user profile is null then either the server is broken, or the - //// user is not known. We always assume the latter case. - - //if (rdata.userProfile != null) - //{ - // Rest.Log.DebugFormat("{0} User profile obtained for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - //} - //else - //{ - // Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); - //} - - //// If we get to here, then we have effectively validated the user's - - //switch (rdata.method) - //{ - // case Rest.HEAD : // Do the processing, set the status code, suppress entity - // DoGet(rdata); - // rdata.buffer = null; - // break; - - // case Rest.GET : // Do the processing, set the status code, return entity - // DoGet(rdata); - // break; - - // case Rest.PUT : // Update named element - // DoUpdate(rdata); - // break; - - // case Rest.POST : // Add new information to identified context. - // DoExtend(rdata); - // break; - - // case Rest.DELETE : // Delete information - // DoDelete(rdata); - // break; - - // default : - // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", - // MsgId, rdata.method, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, - // String.Format("{0} not supported", rdata.method)); - // break; - //} - } - - #endregion Interface - - #region method-specific processing - - /// - /// This method implements GET processing for user's appearance. - /// - /// HTTP service request work area - -// private void DoGet(AppearanceRequestData rdata) -// { -// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID); -// -// if (adata == null) -// { -// rdata.Fail(Rest.HttpStatusCodeNoContent, -// String.Format("appearance data not found for user {0} {1}", -// rdata.userProfile.FirstName, rdata.userProfile.SurName)); -// } -// rdata.userAppearance = adata.ToAvatarAppearance(rdata.userProfile.ID); -// -// rdata.initXmlWriter(); -// -// FormatUserAppearance(rdata); -// -// // Indicate a successful request -// -// rdata.Complete(); -// -// // Send the response to the user. The body will be implicitly -// // constructed from the result of the XML writer. -// -// rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method)); -// } - - /// - /// POST adds NEW information to the user profile database. - /// This effectively resets the appearance before applying those - /// characteristics supplied in the request. - /// - -// private void DoExtend(AppearanceRequestData rdata) -// { -// -// bool created = false; -// bool modified = false; -// string newnode = String.Empty; -// -// Rest.Log.DebugFormat("{0} POST ENTRY", MsgId); -// -// //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID); -// -// rdata.userAppearance = new AvatarAppearance(); -// -// // Although the following behavior is admitted by HTTP I am becoming -// // increasingly doubtful that it is appropriate for REST. If I attempt to -// // add a new record, and it already exists, then it seems to me that the -// // attempt should fail, rather than update the existing record. -// AvatarData adata = null; -// if (GetUserAppearance(rdata)) -// { -// modified = rdata.userAppearance != null; -// created = !modified; -// adata = new AvatarData(rdata.userAppearance); -// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); -// // Rest.UserServices.UpdateUserProfile(rdata.userProfile); -// } -// else -// { -// created = true; -// adata = new AvatarData(rdata.userAppearance); -// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); -// // Rest.UserServices.UpdateUserProfile(rdata.userProfile); -// } -// -// if (created) -// { -// newnode = String.Format("{0} {1}", rdata.userProfile.FirstName, -// rdata.userProfile.SurName); -// // Must include a location header with a URI that identifies the new resource. -// -// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}", -// rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode)); -// rdata.Complete(Rest.HttpStatusCodeCreated); -// -// } -// else -// { -// if (modified) -// { -// rdata.Complete(Rest.HttpStatusCodeOK); -// } -// else -// { -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// } -// -// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); -// -// } - - /// - /// This updates the user's appearance. not all aspects need to be provided, - /// only those supplied will be changed. - /// - -// private void DoUpdate(AppearanceRequestData rdata) -// { -// -// // REFACTORING PROBLEM This was commented out. It doesn't work for 0.7 -// -// //bool created = false; -// //bool modified = false; -// -// -// //rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID); -// -// //// If the user exists then this is considered a modification regardless -// //// of what may, or may not be, specified in the payload. -// -// //if (rdata.userAppearance != null) -// //{ -// // modified = true; -// // Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance); -// // Rest.UserServices.UpdateUserProfile(rdata.userProfile); -// //} -// -// //if (created) -// //{ -// // rdata.Complete(Rest.HttpStatusCodeCreated); -// //} -// //else -// //{ -// // if (modified) -// // { -// // rdata.Complete(Rest.HttpStatusCodeOK); -// // } -// // else -// // { -// // rdata.Complete(Rest.HttpStatusCodeNoContent); -// // } -// //} -// -// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); -// -// } - - /// - /// Delete the specified user's appearance. This actually performs a reset - /// to the default avatar appearance, if the info is already there. - /// Existing ownership is preserved. All prior updates are lost and can not - /// be recovered. - /// -// private void DoDelete(AppearanceRequestData rdata) -// { -// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID); -// -// if (adata != null) -// { -// AvatarAppearance old = adata.ToAvatarAppearance(rdata.userProfile.ID); -// rdata.userAppearance = new AvatarAppearance(); -// rdata.userAppearance.Owner = old.Owner; -// adata = new AvatarData(rdata.userAppearance); -// -// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); -// -// rdata.Complete(); -// } -// else -// { -// -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// -// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); -// } - -#endregion method-specific processing - - private bool GetUserAppearance(AppearanceRequestData rdata) - { - - XmlReader xml; - bool indata = false; - - rdata.initXmlReader(); - xml = rdata.reader; - - while (xml.Read()) - { - switch (xml.NodeType) - { - case XmlNodeType.Element : - switch (xml.Name) - { - case "Appearance" : - if (xml.MoveToAttribute("Height")) - { - rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value); - indata = true; - } -// if (xml.MoveToAttribute("Owner")) -// { -// rdata.userAppearance.Owner = (UUID)xml.Value; -// indata = true; -// } - if (xml.MoveToAttribute("Serial")) - { - rdata.userAppearance.Serial = Convert.ToInt32(xml.Value); - indata = true; - } - break; -/* - case "Body" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.BodyItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.BodyAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Skin" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.SkinItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.SkinAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Hair" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.HairItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.HairAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Eyes" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.EyesItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.EyesAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Shirt" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.ShirtItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.ShirtAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Pants" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.PantsItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.PantsAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Shoes" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.ShoesItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.ShoesAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Socks" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.SocksItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.SocksAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Jacket" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.JacketItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.JacketAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Gloves" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.GlovesItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.GlovesAsset = (UUID)xml.Value; - indata = true; - } - break; - case "UnderShirt" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.UnderShirtItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value; - indata = true; - } - break; - case "UnderPants" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.UnderPantsItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Skirt" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.SkirtItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.SkirtAsset = (UUID)xml.Value; - indata = true; - } - break; -*/ - case "Attachment" : - { - - int ap; - UUID asset; - UUID item; - - if (xml.MoveToAttribute("AtPoint")) - { - ap = Convert.ToInt32(xml.Value); - if (xml.MoveToAttribute("Asset")) - { - asset = new UUID(xml.Value); - if (xml.MoveToAttribute("Asset")) - { - item = new UUID(xml.Value); - rdata.userAppearance.SetAttachment(ap, item, asset); - indata = true; - } - } - } - } - break; - case "Texture" : - if (xml.MoveToAttribute("Default")) - { - rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value)); - indata = true; - } - break; - case "Face" : - { - uint index; - if (xml.MoveToAttribute("Index")) - { - index = Convert.ToUInt32(xml.Value); - if (xml.MoveToAttribute("Id")) - { - rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value); - indata = true; - } - } - } - break; - case "VisualParameters" : - { - xml.ReadContentAsBase64(rdata.userAppearance.VisualParams, - 0, rdata.userAppearance.VisualParams.Length); - indata = true; - } - break; - } - break; - } - } - - return indata; - - } - - private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset) - { - if (item != UUID.Zero || asset != UUID.Zero) - { - rdata.writer.WriteStartElement(part); - if (item != UUID.Zero) - { - rdata.writer.WriteAttributeString("Item",item.ToString()); - } - - if (asset != UUID.Zero) - { - rdata.writer.WriteAttributeString("Asset",asset.ToString()); - } - rdata.writer.WriteEndElement(); - } - } - - private void FormatUserAppearance(AppearanceRequestData rdata) - { - - Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId); - - if (rdata.userAppearance != null) - { - - Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId); - rdata.writer.WriteStartElement("Appearance"); - - rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString()); -// if (rdata.userAppearance.Owner != UUID.Zero) -// rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString()); - rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString()); - -/* - FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset); - FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset); - FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset); - FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset); - - FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset); - FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset); - FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset); - FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset); - FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset); - - FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset); - FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset); - - FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset); - FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset); -*/ - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId); - - rdata.writer.WriteStartElement("Attachments"); - List attachments = rdata.userAppearance.GetAttachments(); - foreach (AvatarAttachment attach in attachments) - { - rdata.writer.WriteStartElement("Attachment"); - rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString()); - rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString()); - rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString()); - rdata.writer.WriteEndElement(); - } - rdata.writer.WriteEndElement(); - - Primitive.TextureEntry texture = rdata.userAppearance.Texture; - - if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null)) - { - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId); - - rdata.writer.WriteStartElement("Texture"); - - if (texture.DefaultTexture != null) - { - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId); - rdata.writer.WriteAttributeString("Default", - texture.DefaultTexture.TextureID.ToString()); - } - - if (texture.FaceTextures != null) - { - - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId); - - for (int i=0; i - /// These are the inventory specific request/response state - /// extensions. - /// - - internal UUID uuid = UUID.Zero; - internal UserProfileData userProfile = null; - internal AvatarAppearance userAppearance = null; - - internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - - } - - #endregion Appearance RequestData extension - - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs deleted file mode 100644 index 4ba3d77a5b..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs +++ /dev/null @@ -1,383 +0,0 @@ -/* - * 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.Xml; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestAssetServices : IRest - { - private bool enabled = false; - private string qPrefix = "assets"; - - // A simple constructor is used to handle any once-only - // initialization of working classes. - - public RestAssetServices() - { - Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // If the handler specifies a relative path for its domain - // then we must add the standard absolute prefix, e.g. /admin - - if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) - { - Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix); - } - - // Register interface using the fully-qualified prefix - - Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate); - - // Activate if all went OK - - enabled = true; - - Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - Rest.Log.InfoFormat("{0} Asset services ({1}) closing down", MsgId, qPrefix); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new AssetRequestData(request, response, prefix); - } - - // Asset Handler - - private void DoAsset(RequestData rparm) - { - if (!enabled) return; - - AssetRequestData rdata = (AssetRequestData) rparm; - - Rest.Log.DebugFormat("{0} REST Asset handler ({1}) ENTRY", MsgId, qPrefix); - - // Now that we know this is a serious attempt to - // access inventory data, we should find out who - // is asking, and make sure they are authorized - // to do so. We need to validate the caller's - // identity before revealing anything about the - // status quo. Authenticate throws an exception - // via Fail if no identity information is present. - // - // With the present HTTP server we can't use the - // builtin authentication mechanisms because they - // would be enforced for all in-bound requests. - // Instead we look at the headers ourselves and - // handle authentication directly. - - try - { - if (!rdata.IsAuthenticated) - { - rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); - } - } - catch (RestException e) - { - if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - { - Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - else - { - Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - throw (e); - } - - // Remove the prefix and what's left are the parameters. If we don't have - // the parameters we need, fail the request. Parameters do NOT include - // any supplied query values. - - if (rdata.Parameters.Length > 0) - { - switch (rdata.method) - { - case "get" : - DoGet(rdata); - break; - case "put" : - DoPut(rdata); - break; - case "post" : - DoPost(rdata); - break; - case "delete" : - default : - Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}", - MsgId, rdata.method); - rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); - break; - } - } - else - { - Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); - } - - Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId); - } - - #endregion Interface - - /// - /// The only parameter we recognize is a UUID.If an asset with this identification is - /// found, it's content, base-64 encoded, is returned to the client. - /// - - private void DoGet(AssetRequestData rdata) - { - Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length == 1) - { - UUID uuid = new UUID(rdata.Parameters[0]); - AssetBase asset = Rest.AssetServices.Get(uuid.ToString()); - - if (asset != null) - { - Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]); - - rdata.initXmlWriter(); - - rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty); - - rdata.writer.WriteAttributeString("id", asset.ID); - rdata.writer.WriteAttributeString("name", asset.Name); - rdata.writer.WriteAttributeString("desc", asset.Description); - rdata.writer.WriteAttributeString("type", asset.Type.ToString()); - rdata.writer.WriteAttributeString("local", asset.Local.ToString()); - rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString()); - - rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length); - - rdata.writer.WriteFullEndElement(); - - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - } - - rdata.Complete(); - rdata.Respond(String.Format("Asset <{0}> : Normal completion", rdata.method)); - - } - - /// - /// UPDATE existing item, if it exists. URI identifies the item in question. - /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded) - /// is decoded and stored in the database, identified by the supplied UUID. - /// - private void DoPut(AssetRequestData rdata) - { - bool modified = false; - bool created = false; - - AssetBase asset = null; - - Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length == 1) - { - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("Asset")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - UUID uuid = new UUID(rdata.Parameters[0]); - asset = Rest.AssetServices.Get(uuid.ToString()); - - modified = (asset != null); - created = !modified; - - asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString()); - asset.Description = xml.GetAttribute("desc"); - asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0; - asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0; - asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", "")); - - if (asset.ID != rdata.Parameters[0]) - { - Rest.Log.WarnFormat("{0} URI and payload disagree on UUID U:{1} vs P:{2}", - MsgId, rdata.Parameters[0], asset.ID); - } - - Rest.AssetServices.Store(asset); - - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method)); - - } - - ///

- /// CREATE new item, replace if it exists. URI identifies the context for the item in question. - /// No parameters are required for POST, just thepayload. - /// - - private void DoPost(AssetRequestData rdata) - { - - bool modified = false; - bool created = false; - - Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length != 0) - { - Rest.Log.WarnFormat("{0} Parameters ignored <{1}>", MsgId, rdata.path); - Rest.Log.InfoFormat("{0} POST of an asset has no parameters", MsgId, rdata.path); - } - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("Asset")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - UUID uuid = new UUID(xml.GetAttribute("id")); - AssetBase asset = Rest.AssetServices.Get(uuid.ToString()); - - modified = (asset != null); - created = !modified; - - asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString()); - asset.Description = xml.GetAttribute("desc"); - asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0; - asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0; - asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", "")); - - Rest.AssetServices.Store(asset); - - if (created) - { - rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method)); - - } - - ///

- /// Asset processing has no special data area requirements. - /// - - internal class AssetRequestData : RequestData - { - internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs deleted file mode 100644 index e79d2bd644..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs +++ /dev/null @@ -1,448 +0,0 @@ -/* - * 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.Xml; -using System.IO; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestFileServices : IRest - { - private bool enabled = false; - private string qPrefix = "files"; - - // A simple constructor is used to handle any once-only - // initialization of working classes. - - public RestFileServices() - { - Rest.Log.InfoFormat("{0} File services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // If the handler specifies a relative path for its domain - // then we must add the standard absolute prefix, e.g. /admin - - if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) - { - Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix); - } - - // Register interface using the fully-qualified prefix - - Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate); - - // Activate if all went OK - - enabled = true; - - Rest.Log.InfoFormat("{0} File services initialization complete", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new FileRequestData(request, response, prefix); - } - - // Asset Handler - - private void DoFile(RequestData rparm) - { - if (!enabled) return; - - FileRequestData rdata = (FileRequestData) rparm; - - Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix); - - // Now that we know this is a serious attempt to - // access file data, we should find out who - // is asking, and make sure they are authorized - // to do so. We need to validate the caller's - // identity before revealing anything about the - // status quo. Authenticate throws an exception - // via Fail if no identity information is present. - // - // With the present HTTP server we can't use the - // builtin authentication mechanisms because they - // would be enforced for all in-bound requests. - // Instead we look at the headers ourselves and - // handle authentication directly. - - try - { - if (!rdata.IsAuthenticated) - { - rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); - } - } - catch (RestException e) - { - if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - { - Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - else - { - Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - throw (e); - } - - // Remove the prefix and what's left are the parameters. If we don't have - // the parameters we need, fail the request. Parameters do NOT include - // any supplied query values. - - if (rdata.Parameters.Length > 0) - { - switch (rdata.method) - { - case "get" : - DoGet(rdata); - break; - case "put" : - DoPut(rdata); - break; - case "post" : - DoPost(rdata); - break; - case "delete" : - DoDelete(rdata); - break; - default : - Rest.Log.WarnFormat("{0} File: Method not supported: {1}", - MsgId, rdata.method); - rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); - break; - } - } - else - { - Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); - } - - Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId); - - } - - #endregion Interface - - /// - /// The only parameter we recognize is a UUID.If an asset with this identification is - /// found, it's content, base-64 encoded, is returned to the client. - /// - - private void DoGet(FileRequestData rdata) - { - - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - if (File.Exists(path)) - { - Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path); - Byte[] data = File.ReadAllBytes(path); - rdata.initXmlWriter(); - rdata.writer.WriteStartElement(String.Empty,"File",String.Empty); - rdata.writer.WriteAttributeString("name", path); - rdata.writer.WriteBase64(data,0,data.Length); - rdata.writer.WriteFullEndElement(); - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path); - rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path)); - } - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message); - rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}", - path, e.Message)); - } - } - - rdata.Complete(); - rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method)); - - } - - /// - /// UPDATE existing item, if it exists. URI identifies the item in question. - /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded) - /// is decoded and stored in the database, identified by the supplied UUID. - /// - private void DoPut(FileRequestData rdata) - { - bool modified = false; - bool created = false; - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - bool maymod = File.Exists(path); - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("File")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", "")); - - File.WriteAllBytes(path,data); - modified = maymod; - created = ! maymod; - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, - e.Message); - } - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); - - } - - ///

- /// CREATE new item, replace if it exists. URI identifies the context for the item in question. - /// No parameters are required for POST, just thepayload. - /// - - private void DoPost(FileRequestData rdata) - { - - bool modified = false; - bool created = false; - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - bool maymod = File.Exists(path); - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("File")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", "")); - - File.WriteAllBytes(path,data); - modified = maymod; - created = ! maymod; - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, - e.Message); - } - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); - - } - - ///

- /// CREATE new item, replace if it exists. URI identifies the context for the item in question. - /// No parameters are required for POST, just thepayload. - /// - - private void DoDelete(FileRequestData rdata) - { - - bool modified = false; - bool created = false; - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - - if (File.Exists(path)) - { - File.Delete(path); - } - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, - e.Message); - rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}", - path, e.Message)); - } - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); - - } - - ///

- /// File processing has no special data area requirements. - /// - - internal class FileRequestData : RequestData - { - internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs deleted file mode 100644 index 072bd6f010..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs +++ /dev/null @@ -1,662 +0,0 @@ -/* - * 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.Reflection; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - /// - /// The class signature reveals the roles that RestHandler plays. - /// - /// [1] It is a sub-class of RestPlugin. It inherits and extends - /// the functionality of this class, constraining it to the - /// specific needs of this REST implementation. This relates - /// to the plug-in mechanism supported by OpenSim, the specifics - /// of which are mostly hidden by RestPlugin. - /// [2] IRestHandler describes the interface that this class - /// exports to service implementations. This is the services - /// management interface. - /// [3] IHttpAgentHandler describes the interface that is exported - /// to the BaseHttpServer in support of this particular HTTP - /// processing model. This is the request interface of the - /// handler. - /// - - public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler - { - // Handler tables: both stream and REST are supported. The path handlers and their - // respective allocators are stored in separate tables. - - internal Dictionary pathHandlers = new Dictionary(); - internal Dictionary pathAllocators = new Dictionary(); - internal Dictionary streamHandlers = new Dictionary(); - - #region local static state - - private static bool handlersLoaded = false; - private static List classes = new List(); - private static List handlers = new List(); - private static Type[] parms = new Type[0]; - private static Object[] args = new Object[0]; - - /// - /// This static initializer scans the ASSEMBLY for classes that - /// export the IRest interface and builds a list of them. These - /// are later activated by the handler. To add a new handler it - /// is only necessary to create a new services class that implements - /// the IRest interface, and recompile the handler. This gives - /// all of the build-time flexibility of a modular approach - /// while not introducing yet-another module loader. Note that - /// multiple assembles can still be built, each with its own set - /// of handlers. Examples of services classes are RestInventoryServices - /// and RestSkeleton. - /// - - static RestHandler() - { - Module[] mods = Assembly.GetExecutingAssembly().GetModules(); - - foreach (Module m in mods) - { - Type[] types = m.GetTypes(); - foreach (Type t in types) - { - try - { - if (t.GetInterface("IRest") != null) - { - classes.Add(t); - } - } - catch (Exception) - { - Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t); - Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t); - } - } - } - } - - #endregion local static state - - #region local instance state - - /// - /// This routine loads all of the handlers discovered during - /// instance initialization. - /// A table of all loaded and successfully constructed handlers - /// is built, and this table is then used by the constructor to - /// initialize each of the handlers in turn. - /// NOTE: The loading process does not automatically imply that - /// the handler has registered any kind of an interface, that - /// may be (optionally) done by the handler either during - /// construction, or during initialization. - /// - /// I was not able to make this code work within a constructor - /// so it is isolated within this method. - /// - - private void LoadHandlers() - { - lock (handlers) - { - if (!handlersLoaded) - { - ConstructorInfo ci; - Object ht; - - foreach (Type t in classes) - { - try - { - ci = t.GetConstructor(parms); - ht = ci.Invoke(args); - handlers.Add((IRest)ht); - } - catch (Exception e) - { - Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message); - } - } - handlersLoaded = true; - } - } - } - - #endregion local instance state - - #region overriding properties - - // These properties override definitions - // in the base class. - - // Name is used to differentiate the message header. - - public override string Name - { - get { return "HANDLER"; } - } - - // Used to partition the .ini configuration space. - - public override string ConfigName - { - get { return "RestHandler"; } - } - - // We have to rename these because we want - // to be able to share the values with other - // classes in our assembly and the base - // names are protected. - - public string MsgId - { - get { return base.MsgID; } - } - - public string RequestId - { - get { return base.RequestID; } - } - - #endregion overriding properties - - #region overriding methods - - /// - /// This method is called by OpenSimMain immediately after loading the - /// plugin and after basic server setup, but before running any server commands. - /// - /// - /// Note that entries MUST be added to the active configuration files before - /// the plugin can be enabled. - /// - - public override void Initialise(OpenSimBase openSim) - { - try - { - // This plugin will only be enabled if the broader - // REST plugin mechanism is enabled. - - //Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId); - - base.Initialise(openSim); - - // IsEnabled is implemented by the base class and - // reflects an overall RestPlugin status - - if (!IsEnabled) - { - //Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); - return; - } - - Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name); - Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName); - - // These are stored in static variables to make - // them easy to reach from anywhere in the assembly. - - Rest.main = openSim; - if (Rest.main == null) - throw new Exception("OpenSim base pointer is null"); - - Rest.Plugin = this; - Rest.Config = Config; - Rest.Prefix = Prefix; - Rest.GodKey = GodKey; - Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate); - Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme); - Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure); - Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape); - Rest.Realm = Rest.Config.GetString("realm", Rest.Realm); - Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset); - Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill); - Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize); - Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled); - - // Note: Odd spacing is required in the following strings - - Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, - (Rest.Authenticate ? "" : "not ")); - - Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId, - (Rest.Secure ? "" : "not ")); - - Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId, - (Rest.ExtendedEscape ? "" : "not ")); - - Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, - (Rest.DumpAsset ? "" : "not ")); - - // The supplied prefix MUST be absolute - - if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator) - { - Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix); - Rest.Log.InfoFormat("{0} Prefix changed to ", MsgId, Rest.Prefix); - Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix); - } - - // If data dumping is requested, report on the chosen line - // length. - - if (Rest.DumpAsset) - { - Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize); - } - - // Load all of the handlers present in the - // assembly - - // In principle, as we're an application plug-in, - // most of what needs to be done could be done using - // static resources, however the Open Sim plug-in - // model makes this an instance, so that's what we - // need to be. - // There is only one Communications manager per - // server, and by inference, only one each of the - // user, asset, and inventory servers. So we can cache - // those using a static initializer. - // We move all of this processing off to another - // services class to minimize overlap between function - // and infrastructure. - - LoadHandlers(); - - // The intention of a post construction initializer - // is to allow for setup that is dependent upon other - // activities outside of the agency. - - foreach (IRest handler in handlers) - { - try - { - handler.Initialize(); - } - catch (Exception e) - { - Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message); - } - } - - // Now that everything is setup we can proceed to - // add THIS agent to the HTTP server's handler list - - // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will - // have to be handled through the AddHttpHandler interface. -// if (!AddAgentHandler(Rest.Name,this)) -// { -// Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId); -// foreach (IRest handler in handlers) -// { -// handler.Close(); -// } -// } - - } - catch (Exception e) - { - Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); - } - } - - /// - /// In the interests of efficiency, and because we cannot determine whether - /// or not this instance will actually be harvested, we clobber the only - /// anchoring reference to the working state for this plug-in. What the - /// call to close does is irrelevant to this class beyond knowing that it - /// can nullify the reference when it returns. - /// To make sure everything is copacetic we make sure the primary interface - /// is disabled by deleting the handler from the HTTP server tables. - /// - - public override void Close() - { - Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); - - // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will - // have to be handled through the AddHttpHandler interface. -// try -// { -// RemoveAgentHandler(Rest.Name, this); -// } -// catch (KeyNotFoundException){} - - foreach (IRest handler in handlers) - { - handler.Close(); - } - } - - #endregion overriding methods - - #region interface methods - - /// - /// This method is called by the HTTP server to match an incoming - /// request. It scans all of the strings registered by the - /// underlying handlers and looks for the best match. It returns - /// true if a match is found. - /// The matching process could be made arbitrarily complex. - /// Note: The match is case-insensitive. - /// - - public bool Match(OSHttpRequest request, OSHttpResponse response) - { - - string path = request.RawUrl.ToLower(); - - // Rest.Log.DebugFormat("{0} Match ENTRY", MsgId); - - try - { - foreach (string key in pathHandlers.Keys) - { - // Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key); - - // Note that Match will not necessarily find the handler that will - // actually be used - it does no test for the "closest" fit. It - // simply reflects that at least one possible handler exists. - - if (path.StartsWith(key)) - { - // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); - - // This apparently odd evaluation is needed to prevent a match - // on anything other than a URI token boundary. Otherwise we - // may match on URL's that were not intended for this handler. - - return (path.Length == key.Length || - path.Substring(key.Length, 1) == Rest.UrlPathSeparator); - } - } - - path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); - - foreach (string key in streamHandlers.Keys) - { - // Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key); - - // Note that Match will not necessarily find the handler that will - // actually be used - it does no test for the "closest" fit. It - // simply reflects that at least one possible handler exists. - - if (path.StartsWith(key)) - { - // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); - - // This apparently odd evaluation is needed to prevent a match - // on anything other than a URI token boundary. Otherwise we - // may match on URL's that were not intended for this handler. - - return (path.Length == key.Length || - path.Substring(key.Length, 1) == Rest.UrlPathSeparator); - } - } - } - catch (Exception e) - { - Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message); - } - - return false; - } - - /// - /// This is called by the HTTP server once the handler has indicated - /// that it is able to handle the request. - /// Preconditions: - /// [1] request != null and is a valid request object - /// [2] response != null and is a valid response object - /// Behavior is undefined if preconditions are not satisfied. - /// - - public bool Handle(OSHttpRequest request, OSHttpResponse response) - { - bool handled; - base.MsgID = base.RequestID; - - // Debug only - - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} ENTRY", MsgId); - Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent); - Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod); - - for (int i = 0; i < request.Headers.Count; i++) - { - Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", - MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); - } - Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); - } - - // If a path handler worked we're done, otherwise try any - // available stream handlers too. - - try - { - handled = (FindPathHandler(request, response) || - FindStreamHandler(request, response)); - } - catch (Exception e) - { - // A raw exception indicates that something we weren't expecting has - // happened. This should always reflect a shortcoming in the plugin, - // or a failure to satisfy the preconditions. It should not reflect - // an error in the request itself. Under such circumstances the state - // of the request cannot be determined and we are obliged to mark it - // as 'handled'. - - Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message); - handled = true; - } - - Rest.Log.DebugFormat("{0} EXIT", MsgId); - - return handled; - } - - #endregion interface methods - - /// - /// If there is a stream handler registered that can handle the - /// request, then fine. If the request is not matched, do - /// nothing. - /// Note: The selection is case-insensitive - /// - - private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response) - { - RequestData rdata = new RequestData(request, response, String.Empty); - - string bestMatch = String.Empty; - string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower(); - - Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path); - - if (!IsEnabled) - { - return false; - } - - foreach (string pattern in streamHandlers.Keys) - { - if (path.StartsWith(pattern)) - { - if (pattern.Length > bestMatch.Length) - { - bestMatch = pattern; - } - } - } - - // Handle using the best match available - - if (bestMatch.Length > 0) - { - Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch); - RestStreamHandler handler = streamHandlers[bestMatch]; - rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response); - rdata.AddHeader(rdata.response.ContentType,handler.ContentType); - rdata.Respond("FindStreamHandler Completion"); - } - - return rdata.handled; - } - - /// - /// Add a stream handler for the designated HTTP method and path prefix. - /// If the handler is not enabled, the request is ignored. If the path - /// does not start with the REST prefix, it is added. If method-qualified - /// path has not already been registered, the method is added to the active - /// handler table. - /// - public void AddStreamHandler(string httpMethod, string path, RestMethod method) - { - if (!IsEnabled) - { - return; - } - - if (!path.StartsWith(Rest.Prefix)) - { - path = String.Format("{0}{1}", Rest.Prefix, path); - } - - path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path); - - // Conditionally add to the list - - if (!streamHandlers.ContainsKey(path)) - { - streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method)); - Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path); - } - else - { - Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); - } - } - - /// - /// Given the supplied request/response, if the handler is enabled, the inbound - /// information is used to match an entry in the active path handler tables, using - /// the method-qualified path information. If a match is found, then the handler is - /// invoked. The result is the boolean result of the handler, or false if no - /// handler was located. The boolean indicates whether or not the request has been - /// handled, not whether or not the request was successful - that information is in - /// the response. - /// Note: The selection process is case-insensitive - /// - - internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) - { - RequestData rdata = null; - string bestMatch = null; - - if (!IsEnabled) - { - return false; - } - - // Conditionally add to the list - - Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl); - - foreach (string pattern in pathHandlers.Keys) - { - if (request.RawUrl.ToLower().StartsWith(pattern)) - { - if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) - { - bestMatch = pattern; - } - } - } - - if (!String.IsNullOrEmpty(bestMatch)) - { - rdata = pathAllocators[bestMatch](request, response, bestMatch); - - Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); - - try - { - pathHandlers[bestMatch](rdata); - } - - // A plugin generated error indicates a request-related error - // that has been handled by the plugin. - - catch (RestException r) - { - Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); - } - } - - return (rdata == null) ? false : rdata.handled; - } - - /// - /// A method handler and a request allocator are stored using the designated - /// path as a key. If an entry already exists, it is replaced by the new one. - /// - - public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) - { - if (!IsEnabled) - { - return; - } - - if (pathHandlers.ContainsKey(path)) - { - Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path); - pathHandlers.Remove(path); - } - - if (pathAllocators.ContainsKey(path)) - { - Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path); - pathAllocators.Remove(path); - } - - Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path); - - pathHandlers.Add(path, mh); - pathAllocators.Add(path, ra); - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs deleted file mode 100644 index 536f167793..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ /dev/null @@ -1,2343 +0,0 @@ -/* - * 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.Drawing; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Timers; -using System.Xml; -using OpenMetaverse; -using OpenMetaverse.Imaging; -using OpenSim.Framework; - -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using Timer=System.Timers.Timer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestInventoryServices : IRest - { -// private static readonly int PARM_USERID = 0; -// private static readonly int PARM_PATH = 1; - -// private bool enabled = false; - private string qPrefix = "inventory"; - -// private static readonly string PRIVATE_ROOT_NAME = "My Inventory"; - - /// - /// The constructor makes sure that the service prefix is absolute - /// and the registers the service handler and the allocator. - /// - - public RestInventoryServices() - { - Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // If a relative path was specified for the handler's domain, - // add the standard prefix to make it absolute, e.g. /admin - - if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) - { - Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); - } - - // Register interface using the absolute URI. - - Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); - - // Activate if everything went OK - -// enabled = true; - - Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); - } - - /// - /// Post-construction, pre-enabled initialization opportunity - /// Not currently exploited. - /// - - public void Initialize() - { - } - - /// - /// Called by the plug-in to halt service processing. Local processing is - /// disabled. - /// - - public void Close() - { -// enabled = false; - Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); - } - - /// - /// This property is declared locally because it is used a lot and - /// brevity is nice. - /// - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - /// - /// The plugin (RestHandler) calls this method to allocate the request - /// state carrier for a new request. It is destroyed when the request - /// completes. All request-instance specific state is kept here. This - /// is registered when this service provider is registered. - /// - /// Inbound HTTP request information - /// Outbound HTTP request information - /// REST service domain prefix - /// A RequestData instance suitable for this service - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new InventoryRequestData(request, response, prefix); - } - - /// - /// This method is registered with the handler when this service provider - /// is initialized. It is called whenever the plug-in identifies this service - /// provider as the best match for a given request. - /// It handles all aspects of inventory REST processing, i.e. /admin/inventory - /// - /// A consolidated HTTP request work area - private void DoInventory(RequestData hdata) - { -// InventoryRequestData rdata = (InventoryRequestData) hdata; - - Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); - - // !!! REFACTORING PROBLEM - - //// If we're disabled, do nothing. - - //if (!enabled) - //{ - // return; - //} - - //// Now that we know this is a serious attempt to - //// access inventory data, we should find out who - //// is asking, and make sure they are authorized - //// to do so. We need to validate the caller's - //// identity before revealing anything about the - //// status quo. Authenticate throws an exception - //// via Fail if no identity information is present. - //// - //// With the present HTTP server we can't use the - //// builtin authentication mechanisms because they - //// would be enforced for all in-bound requests. - //// Instead we look at the headers ourselves and - //// handle authentication directly. - - //try - //{ - // if (!rdata.IsAuthenticated) - // { - // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); - // } - //} - //catch (RestException e) - //{ - // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - // { - // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // else - // { - // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // throw (e); - //} - - //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); - - //// We can only get here if we are authorized - //// - //// The requestor may have specified an UUID or - //// a conjoined FirstName LastName string. We'll - //// try both. If we fail with the first, UUID, - //// attempt, we try the other. As an example, the - //// URI for a valid inventory request might be: - //// - //// http://:/admin/inventory/Arthur Dent - //// - //// Indicating that this is an inventory request for - //// an avatar named Arthur Dent. This is ALL that is - //// required to designate a GET for an entire - //// inventory. - //// - - - //// Do we have at least a user agent name? - - //if (rdata.Parameters.Length < 1) - //{ - // Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); - //} - - //// The first parameter MUST be the agent identification, either an UUID - //// or a space-separated First-name Last-Name specification. We check for - //// an UUID first, if anyone names their character using a valid UUID - //// that identifies another existing avatar will cause this a problem... - - //try - //{ - // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); - // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); - //} - //catch - //{ - // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); - // if (names.Length == 2) - // { - // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); - // } - // else - // { - // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); - // } - //} - - //// If the user profile is null then either the server is broken, or the - //// user is not known. We always assume the latter case. - - //if (rdata.userProfile != null) - //{ - // Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - //} - //else - //{ - // Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); - //} - - //// If we get to here, then we have effectively validated the user's - //// identity. Now we need to get the inventory. If the server does not - //// have the inventory, we reject the request with an appropriate explanation. - //// - //// Note that inventory retrieval is an asynchronous event, we use the rdata - //// class instance as the basis for our synchronization. - //// - - //rdata.uuid = rdata.userProfile.ID; - - //if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) - //{ - // rdata.root = Rest.InventoryServices.GetRootFolder(rdata.uuid); - - // Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - - // Rest.InventoryServices.GetUserInventory(rdata.uuid, rdata.GetUserInventory); - - // Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - - // lock (rdata) - // { - // if (!rdata.HaveInventory) - // { - // rdata.startWD(1000); - // rdata.timeout = false; - // Monitor.Wait(rdata); - // } - // } - - // if (rdata.timeout) - // { - // Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding"); - // } - - // if (rdata.root == null) - // { - // Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed"); - // } - - //} - //else - //{ - // Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - // rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user"); - //} - - //// If we get here, then we have successfully retrieved the user's information - //// and inventory information is now available locally. - - //switch (rdata.method) - //{ - // case Rest.HEAD : // Do the processing, set the status code, suppress entity - // DoGet(rdata); - // rdata.buffer = null; - // break; - - // case Rest.GET : // Do the processing, set the status code, return entity - // DoGet(rdata); - // break; - - // case Rest.PUT : // Update named element - // DoUpdate(rdata); - // break; - - // case Rest.POST : // Add new information to identified context. - // DoExtend(rdata); - // break; - - // case Rest.DELETE : // Delete information - // DoDelete(rdata); - // break; - - // default : - // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", - // MsgId, rdata.method, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, - // String.Format("{0} not supported", rdata.method)); - // break; - //} - } - - #endregion Interface - - #region method-specific processing - - /// - /// This method implements GET processing for inventory. - /// Any remaining parameters are used to locate the - /// corresponding subtree based upon node name. - /// - /// HTTP service request work area -// private void DoGet(InventoryRequestData rdata) -// { -// rdata.initXmlWriter(); -// -// rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); -// -// // If there are additional parameters, then these represent -// // a path relative to the root of the inventory. This path -// // must be traversed before we format the sub-tree thus -// // identified. -// -// traverse(rdata, rdata.root, PARM_PATH); -// -// // Close all open elements -// -// rdata.writer.WriteFullEndElement(); -// -// // Indicate a successful request -// -// rdata.Complete(); -// -// // Send the response to the user. The body will be implicitly -// // constructed from the result of the XML writer. -// -// rdata.Respond(String.Format("Inventory {0} Normal completion", rdata.method)); -// } - - /// - /// In the case of the inventory, and probably in general, - /// the distinction between PUT and POST is not always - /// easy to discern. The standard is badly worded in places, - /// and adding a node to a hierarchy can be viewed as - /// an addition, or as a modification to the inventory as - /// a whole. This is exacerbated by an unjustified lack of - /// consistency across different implementations. - /// - /// For OpenSim PUT is an update and POST is an addition. This - /// is the behavior required by the HTTP specification and - /// therefore as required by REST. - /// - /// The best way to explain the distinction is to - /// consider the relationship between the URI and the - /// enclosed entity. For PUT, the URI identifies the - /// actual entity to be modified or replaced, i.e. the - /// enclosed entity. - /// - /// If the operation is POST,then the URI describes the - /// context into which the new entity will be added. - /// - /// As an example, suppose the URI contains: - /// /admin/inventory/Clothing - /// - /// A PUT request will normally result in some modification of - /// the folder or item named "Clothing". Whereas a POST - /// request will normally add some new information into the - /// content identified by Clothing. It follows from this - /// that for POST, the element identified by the URI MUST - /// be a folder. - /// - - /// - /// POST adds new information to the inventory in the - /// context identified by the URI. - /// - /// HTTP service request work area -// private void DoExtend(InventoryRequestData rdata) -// { -// bool created = false; -// bool modified = false; -// string newnode = String.Empty; -// -// // Resolve the context node specified in the URI. Entity -// // data will be ADDED beneath this node. rdata already contains -// // information about the current content of the user's -// // inventory. -// -// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); -// -// // Processing depends upon the type of inventory node -// // identified in the URI. This is the CONTEXT for the -// // change. We either got a context or we threw an -// // exception. -// -// // It follows that we can only add information if the URI -// // has identified a folder. So only a type of folder is supported -// // in this case. -// -// if (typeof(InventoryFolderBase) == InventoryNode.GetType() || -// typeof(InventoryFolderImpl) == InventoryNode.GetType()) -// { -// // Cast the context node appropriately. -// -// InventoryFolderBase context = (InventoryFolderBase) InventoryNode; -// -// Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", -// MsgId, rdata.method, rdata.path); -// -// // Reconstitute the inventory sub-tree from the XML supplied in the entity. -// // The result is a stand-alone inventory subtree, not yet integrated into the -// // existing tree. An inventory collection consists of three components: -// // [1] A (possibly empty) set of folders. -// // [2] A (possibly empty) set of items. -// // [3] A (possibly empty) set of assets. -// // If all of these are empty, then the POST is a harmless no-operation. -// -// XmlInventoryCollection entity = ReconstituteEntity(rdata); -// -// // Inlined assets can be included in entity. These must be incorporated into -// // the asset database before we attempt to update the inventory. If anything -// // fails, return a failure to requestor. -// -// if (entity.Assets.Count > 0) -// { -// Rest.Log.DebugFormat("{0} Adding {1} assets to server", -// MsgId, entity.Assets.Count); -// -// foreach (AssetBase asset in entity.Assets) -// { -// Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", -// MsgId, asset.ID, asset.Type, asset.Name); -// Rest.AssetServices.Store(asset); -// -// created = true; -// rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", -// asset.Name, asset.ID)); -// -// if (Rest.DEBUG && Rest.DumpAsset) -// { -// Rest.Dump(asset.Data); -// } -// } -// } -// -// // Modify the context using the collection of folders and items -// // returned in the XmlInventoryCollection. -// -// foreach (InventoryFolderBase folder in entity.Folders) -// { -// InventoryFolderBase found; -// -// // If the parentID is zero, then this folder is going -// // into the root folder identified by the URI. The requestor -// // may have already set the parent ID explicitly, in which -// // case we don't have to do it here. -// -// if (folder.ParentID == UUID.Zero || folder.ParentID == context.ID) -// { -// if (newnode != String.Empty) -// { -// Rest.Log.DebugFormat("{0} Too many resources", MsgId); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed"); -// } -// folder.ParentID = context.ID; -// newnode = folder.Name; -// } -// -// // Search the existing inventory for an existing entry. If -// // we have one, we need to decide if it has really changed. -// // It could just be present as (unnecessary) context, and we -// // don't want to waste time updating the database in that -// // case, OR, it could be being moved from another location -// // in which case an update is most certainly necessary. -// -// found = null; -// -// foreach (InventoryFolderBase xf in rdata.folders) -// { -// // Compare identifying attribute -// if (xf.ID == folder.ID) -// { -// found = xf; -// break; -// } -// } -// -// if (found != null && FolderHasChanged(folder,found)) -// { -// Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); -// Rest.InventoryServices.MoveFolder(folder); -// -// modified = true; -// rdata.appendStatus(String.Format("

Created folder {0}, UUID {1}

", -// folder.Name, folder.ID)); -// } -// else -// { -// Rest.Log.DebugFormat("{0} Adding new folder", MsgId); -// Rest.InventoryServices.AddFolder(folder); -// -// created = true; -// rdata.appendStatus(String.Format("

Modified folder {0}, UUID {1}

", -// folder.Name, folder.ID)); -// } -// } -// -// // Now we repeat a similar process for the items included -// // in the entity. -// -// foreach (InventoryItemBase item in entity.Items) -// { -// InventoryItemBase found = null; -// -// // If the parentID is zero, then this is going -// // directly into the root identified by the URI. -// -// if (item.Folder == UUID.Zero) -// { -// item.Folder = context.ID; -// } -// -// // Determine whether this is a new item or a -// // replacement definition. -// -// foreach (InventoryItemBase xi in rdata.items) -// { -// // Compare identifying attribute -// if (xi.ID == item.ID) -// { -// found = xi; -// break; -// } -// } -// -// if (found != null && ItemHasChanged(item, found)) -// { -// Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", -// MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); -// Rest.InventoryServices.UpdateItem(item); -// modified = true; -// rdata.appendStatus(String.Format("

Modified item {0}, UUID {1}

", item.Name, item.ID)); -// } -// else -// { -// Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", -// MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); -// Rest.InventoryServices.AddItem(item); -// created = true; -// rdata.appendStatus(String.Format("

Created item {0}, UUID {1}

", item.Name, item.ID)); -// } -// } -// -// if (created) -// { -// // Must include a location header with a URI that identifies the new resource. -// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}", -// rdata.hostname, rdata.port,rdata.path,newnode)); -// rdata.Complete(Rest.HttpStatusCodeCreated); -// } -// else -// { -// if (modified) -// { -// rdata.Complete(Rest.HttpStatusCodeOK); -// } -// else -// { -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// } -// -// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); -// } -// else -// { -// Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", -// MsgId, rdata.method, rdata.path, InventoryNode.GetType()); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context"); -// } -// } - - ///

- /// PUT updates the URI-identified element in the inventory. This - /// is actually far more flexible than it might at first sound. For - /// PUT the URI serves two purposes: - /// [1] It identifies the user whose inventory is to be - /// processed. - /// [2] It optionally specifies a subtree of the inventory - /// that is to be used to resolve any relative subtree - /// specifications in the entity. If nothing is specified - /// then the whole of the private inventory is implied. - /// Please note that the subtree specified by the URI is only relevant - /// to an entity containing a URI relative specification, i.e. one or - /// more elements do not specify parent folder information. These - /// elements will be implicitly referenced within the context identified - /// by the URI. - /// If an element in the entity specifies an explicit parent folder, then - /// that parent is effective, regardless of any value specified in the - /// URI. If the parent does not exist, then the element, and any dependent - /// elements, are ignored. This case is actually detected and handled - /// during the reconstitution process. - /// - /// HTTP service request work area -// private void DoUpdate(InventoryRequestData rdata) -// { -// int count = 0; -// bool created = false; -// bool modified = false; -// -// // Resolve the inventory node that is to be modified. -// // rdata already contains information about the current -// // content of the user's inventory. -// -// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); -// -// // As long as we have a node, then we have something -// // meaningful to do, unlike POST. So we reconstitute the -// // subtree before doing anything else. Note that we -// // etiher got a valid node or we threw an exception. -// -// XmlInventoryCollection entity = ReconstituteEntity(rdata); -// -// // Incorporate any inlined assets first. Any failures -// // will terminate the request. -// -// if (entity.Assets.Count > 0) -// { -// Rest.Log.DebugFormat("{0} Adding {1} assets to server", -// MsgId, entity.Assets.Count); -// -// foreach (AssetBase asset in entity.Assets) -// { -// Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", -// MsgId, asset.ID, asset.Type, asset.Name); -// -// // The asset was validated during the collection process -// -// Rest.AssetServices.Store(asset); -// -// created = true; -// rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", asset.Name, asset.ID)); -// -// if (Rest.DEBUG && Rest.DumpAsset) -// { -// Rest.Dump(asset.Data); -// } -// } -// } -// -// // The URI specifies either a folder or an item to be updated. -// // -// // The root node in the entity will replace the node identified -// // by the URI. This means the parent will remain the same, but -// // any or all attributes associated with the named element -// // will change. -// // -// // If the inventory collection contains an element with a zero -// // parent ID, then this is taken to be the replacement for the -// // named node. The collection MAY also specify an explicit -// // parent ID, in this case it MAY identify the same parent as -// // the current node, or it MAY specify a different parent, -// // indicating that the folder is being moved in addition to any -// // other modifications being made. -// -// if (typeof(InventoryFolderBase) == InventoryNode.GetType() || -// typeof(InventoryFolderImpl) == InventoryNode.GetType()) -// { -// bool rfound = false; -// InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; -// InventoryFolderBase xml = null; -// -// // If the entity to be replaced resolved to be the root -// // directory itself (My Inventory), then make sure that -// // the supplied data include as appropriately typed and -// // named folder. Note that we can;t rule out the possibility -// // of a sub-directory being called "My Inventory", so that -// // is anticipated. -// -// if (uri == rdata.root) -// { -// foreach (InventoryFolderBase folder in entity.Folders) -// { -// if ((rfound = (folder.Name == PRIVATE_ROOT_NAME))) -// { -// if ((rfound = (folder.ParentID == UUID.Zero))) -// break; -// } -// } -// -// if (!rfound) -// { -// Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure"); -// } -// } -// -// // Scan the set of folders in the entity collection for an -// // entry that matches the context folder. It is assumed that -// // the only reliable indicator of this is a zero UUID (using -// // implicit context), or the parent's UUID matches that of the -// // URI designated node (explicit context). We don't allow -// // ambiguity in this case because this is POST and we are -// // supposed to be modifying a specific node. -// // We assign any element IDs required as an economy; we don't -// // want to iterate over the fodler set again if it can be -// // helped. -// -// foreach (InventoryFolderBase folder in entity.Folders) -// { -// if (folder.ParentID == uri.ParentID || -// folder.ParentID == UUID.Zero) -// { -// folder.ParentID = uri.ParentID; -// xml = folder; -// count++; -// } -// } -// -// // More than one entry is ambiguous. Other folders should be -// // added using the POST verb. -// -// if (count > 1) -// { -// Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous"); -// } -// -// // Exactly one entry means we ARE replacing the node -// // identified by the URI. So we delete the old folder -// // by moving it to the trash and then purging it. -// // We then add all of the folders and items we -// // included in the entity. The subtree has been -// // modified. -// -// if (count == 1) -// { -// InventoryFolderBase TrashCan = GetTrashCan(rdata); -// -// // All went well, so we generate a UUID is one is -// // needed. -// -// if (xml.ID == UUID.Zero) -// { -// xml.ID = UUID.Random(); -// } -// -// uri.ParentID = TrashCan.ID; -// Rest.InventoryServices.MoveFolder(uri); -// Rest.InventoryServices.PurgeFolder(TrashCan); -// modified = true; -// } -// -// // Now, regardelss of what they represent, we -// // integrate all of the elements in the entity. -// -// foreach (InventoryFolderBase f in entity.Folders) -// { -// rdata.appendStatus(String.Format("

Moving folder {0} UUID {1}

", f.Name, f.ID)); -// Rest.InventoryServices.MoveFolder(f); -// } -// -// foreach (InventoryItemBase it in entity.Items) -// { -// rdata.appendStatus(String.Format("

Storing item {0} UUID {1}

", it.Name, it.ID)); -// Rest.InventoryServices.AddItem(it); -// } -// } -// -// ///

-// /// URI specifies an item to be updated -// /// -// /// -// /// The entity must contain a single item node to be -// /// updated. ID and Folder ID must be correct. -// /// -// -// else -// { -// InventoryItemBase uri = (InventoryItemBase) InventoryNode; -// InventoryItemBase xml = null; -// -// if (entity.Folders.Count != 0) -// { -// Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed"); -// } -// -// if (entity.Items.Count > 1) -// { -// Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items"); -// } -// -// xml = entity.Items[0]; -// -// if (xml.ID == UUID.Zero) -// { -// xml.ID = UUID.Random(); -// } -// -// // If the folder reference has changed, then this item is -// // being moved. Otherwise we'll just delete the old, and -// // add in the new. -// -// // Delete the old item -// -// List uuids = new List(); -// uuids.Add(uri.ID); -// Rest.InventoryServices.DeleteItems(uri.Owner, uuids); -// -// // Add the new item to the inventory -// -// Rest.InventoryServices.AddItem(xml); -// -// rdata.appendStatus(String.Format("

Storing item {0} UUID {1}

", xml.Name, xml.ID)); -// } -// -// if (created) -// { -// rdata.Complete(Rest.HttpStatusCodeCreated); -// } -// else -// { -// if (modified) -// { -// rdata.Complete(Rest.HttpStatusCodeOK); -// } -// else -// { -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// } -// -// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); -// } - - ///

- /// Arguably the most damaging REST interface. It deletes the inventory - /// item or folder identified by the URI. - /// - /// We only process if the URI identified node appears to exist - /// We do not test for success because we either get a context, - /// or an exception is thrown. - /// - /// Folders are deleted by moving them to another folder and then - /// purging that folder. We'll do that by creating a temporary - /// sub-folder in the TrashCan and purging that folder's - /// contents. If we can't can it, we don't delete it... - /// So, if no trashcan is available, the request does nothing. - /// Items are summarily deleted. - /// - /// In the interests of safety, a delete request should normally - /// be performed using UUID, as a name might identify several - /// elements. - /// - /// HTTP service request work area -// private void DoDelete(InventoryRequestData rdata) -// { -// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false); -// -// if (typeof(InventoryFolderBase) == InventoryNode.GetType() || -// typeof(InventoryFolderImpl) == InventoryNode.GetType()) -// { -// InventoryFolderBase TrashCan = GetTrashCan(rdata); -// -// InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; -// Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted", -// MsgId, rdata.method, rdata.path); -// folder.ParentID = TrashCan.ID; -// Rest.InventoryServices.MoveFolder(folder); -// Rest.InventoryServices.PurgeFolder(TrashCan); -// -// rdata.appendStatus(String.Format("

Deleted folder {0} UUID {1}

", folder.Name, folder.ID)); -// } -// -// // Deleting items is much more straight forward. -// -// else -// { -// InventoryItemBase item = (InventoryItemBase) InventoryNode; -// Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", -// MsgId, rdata.method, rdata.path); -// List uuids = new List(); -// uuids.Add(item.ID); -// Rest.InventoryServices.DeleteItems(item.Owner, uuids); -// rdata.appendStatus(String.Format("

Deleted item {0} UUID {1}

", item.Name, item.ID)); -// } -// -// rdata.Complete(); -// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); -// } - -#endregion method-specific processing - - ///

- /// This method is called to obtain the OpenSim inventory object identified - /// by the supplied URI. This may be either an Item or a Folder, so a suitably - /// ambiguous return type is employed (Object). This method recurses as - /// necessary to process the designated hierarchy. - /// - /// If we reach the end of the URI then we return the contextual folder to - /// our caller. - /// - /// If we are not yet at the end of the URI we attempt to find a child folder - /// and if we succeed we recurse. - /// - /// If this is the last node, then we look to see if this is an item. If it is, - /// we return that item. - /// - /// If we reach the end of an inventory path and the URI si not yet exhausted, - /// then if 'fill' is specified, we create the intermediate nodes. - /// - /// Otherwise we fail the request on the ground of an invalid URI. - /// - /// An ambiguous request causes the request to fail. - /// - /// - /// HTTP service request work area - /// The folder to be searched (parent) - /// URI parameter index - /// Should missing path members be created? - - private Object getInventoryNode(InventoryRequestData rdata, - InventoryFolderBase folder, - int pi, bool fill) - { - InventoryFolderBase foundf = null; - int fk = 0; - - Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); - - // We have just run off the end of the parameter sequence - - if (pi >= rdata.Parameters.Length) - { - return folder; - } - - // There are more names in the parameter sequence, - // look for the folder named by param[pi] as a - // child of the folder supplied as an argument. - // Note that a UUID may have been supplied as the - // identifier (it is the ONLY guaranteed unambiguous - // option. - - if (rdata.folders != null) - { - foreach (InventoryFolderBase f in rdata.folders) - { - // Look for the present node in the directory list - if (f.ParentID == folder.ID && - (f.Name == rdata.Parameters[pi] || - f.ID.ToString() == rdata.Parameters[pi])) - { - foundf = f; - fk++; - } - } - } - - // If more than one node matched, then the path, as specified - // is ambiguous. - - if (fk > 1) - { - Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", - MsgId, rdata.method, rdata.path); - rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); - } - - // If we find a match, then the method - // increment the parameter index, and calls itself - // passing the found folder as the new context. - - if (foundf != null) - { - return getInventoryNode(rdata, foundf, pi+1, fill); - } - - // No folders that match. Perhaps this parameter identifies an item? If - // it does, then it MUST also be the last name in the sequence. - - if (pi == rdata.Parameters.Length-1) - { - if (rdata.items != null) - { - int k = 0; - InventoryItemBase li = null; - foreach (InventoryItemBase i in rdata.items) - { - if (i.Folder == folder.ID && - (i.Name == rdata.Parameters[pi] || - i.ID.ToString() == rdata.Parameters[pi])) - { - li = i; - k++; - } - } - if (k == 1) - { - return li; - } - else if (k > 1) - { - Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", - MsgId, rdata.method, rdata.path); - rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); - } - } - } - - // If fill is enabled, then we must create the missing intermediate nodes. - // And of course, even this is not straightforward. All intermediate nodes - // are obviously folders, but the last node may be a folder or an item. - - if (fill) - { - } - - // No fill, so abandon the request - - Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", - MsgId, rdata.method, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, - String.Format("resource {0}:{1} not found", rdata.method, rdata.path)); - - return null; /* Never reached */ - } - - /// - /// This routine traverse the inventory's structure until the end-point identified - /// in the URI is reached, the remainder of the inventory (if any) is then formatted - /// and returned to the requestor. - /// - /// Note that this method is only interested in those folder that match elements of - /// the URI supplied by the requestor, so once a match is fund, the processing does - /// not need to consider any further elements. - /// - /// Only the last element in the URI should identify an item. - /// - /// HTTP service request work area - /// The folder to be searched (parent) - /// URI parameter index - - private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi) - { - Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); - - if (rdata.folders != null) - { - // If there was only one parameter (avatar name), then the entire - // inventory is being requested. - - if (rdata.Parameters.Length == 1) - { - formatInventory(rdata, rdata.root, String.Empty); - } - - // Has the client specified the root directory name explicitly? - // if yes, then we just absorb the reference, because the folder - // we start looking in for a match *is* the root directory. If there - // are more parameters remaining we tarverse, otehrwise it's time - // to format. Otherwise,we consider the "My Inventory" to be implied - // and we just traverse normally. - - else if (folder.ID.ToString() == rdata.Parameters[pi] || - folder.Name == rdata.Parameters[pi]) - { - // Length is -1 because the avatar name is a parameter - if (pi<(rdata.Parameters.Length-1)) - { - traverseInventory(rdata, folder, pi+1); - } - else - { - formatInventory(rdata, folder, String.Empty); - } - } - else - { - traverseInventory(rdata, folder, pi); - } - - return; - } - } - - /// - /// This is the recursive method. I've separated them in this way so that - /// we do not have to waste cycles on any first-case-only processing. - /// - - private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) - { - int fk = 0; - InventoryFolderBase ffound = null; - InventoryItemBase ifound = null; - - Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); - - foreach (InventoryFolderBase f in rdata.folders) - { - if (f.ParentID == folder.ID && - (f.Name == rdata.Parameters[pi] || - f.ID.ToString() == rdata.Parameters[pi])) - { - fk++; - ffound = f; - } - } - - // If this is the last element in the parameter sequence, then - // it is reasonable to check for an item. All intermediate nodes - // MUST be folders. - - if (pi == rdata.Parameters.Length-1) - { - // Only if there are any items, and there pretty much always are. - - if (rdata.items != null) - { - foreach (InventoryItemBase i in rdata.items) - { - if (i.Folder == folder.ID && - (i.Name == rdata.Parameters[pi] || - i.ID.ToString() == rdata.Parameters[pi])) - { - fk++; - ifound = i; - } - } - } - } - - if (fk == 1) - { - if (ffound != null) - { - if (pi < rdata.Parameters.Length-1) - { - traverseInventory(rdata, ffound, pi+1); - } - else - { - formatInventory(rdata, ffound, String.Empty); - } - return; - } - else - { - // Fetching an Item has a special significance. In this - // case we also want to fetch the associated asset. - // To make it interesting, we'll do this via redirection. - string asseturl = String.Format("http://{0}:{1}/{2}{3}{4}", rdata.hostname, rdata.port, - "admin/assets",Rest.UrlPathSeparator,ifound.AssetID.ToString()); - rdata.Redirect(asseturl,Rest.PERMANENT); - Rest.Log.DebugFormat("{0} Never Reached", MsgId); - } - } - else if (fk > 1) - { - rdata.Fail(Rest.HttpStatusCodeConflict, - String.Format("ambiguous element ({0}) in path specified: <{1}>", - pi, rdata.path)); - } - - Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", - MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}", - rdata.Parameters[pi])); - - } - - /// - /// This method generates XML that describes an instance of InventoryFolderBase. - /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem - /// to generate XML for any items encountered along the way. - /// The indentation parameter is solely for the benefit of trace record - /// formatting. - /// - /// HTTP service request work area - /// The folder to be searched (parent) - /// pretty print indentation - private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) - { - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}", - MsgId, folder.ID, indent, folder.Name, folder.Type); - indent += "\t"; - } - - // Start folder item - - rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); - rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); - rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); - rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString()); - rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); - rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); - rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); - - if (rdata.folders != null) - { - foreach (InventoryFolderBase f in rdata.folders) - { - if (f.ParentID == folder.ID) - { - formatInventory(rdata, f, indent); - } - } - } - - if (rdata.items != null) - { - foreach (InventoryItemBase i in rdata.items) - { - if (i.Folder == folder.ID) - { - formatItem(rdata, i, indent); - } - } - } - - // End folder item - - rdata.writer.WriteEndElement(); - } - - /// - /// This method generates XML that describes an instance of InventoryItemBase. - /// - /// HTTP service request work area - /// The item to be formatted - /// Pretty print indentation - private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) - { - Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}", - MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType); - - rdata.writer.WriteStartElement(String.Empty, "Item", String.Empty); - - rdata.writer.WriteAttributeString("name", String.Empty, i.Name); - rdata.writer.WriteAttributeString("desc", String.Empty, i.Description); - rdata.writer.WriteAttributeString("uuid", String.Empty, i.ID.ToString()); - rdata.writer.WriteAttributeString("folder", String.Empty, i.Folder.ToString()); - rdata.writer.WriteAttributeString("owner", String.Empty, i.Owner.ToString()); - rdata.writer.WriteAttributeString("creator", String.Empty, i.CreatorId); - rdata.writer.WriteAttributeString("creatordata", String.Empty, i.CreatorData); - rdata.writer.WriteAttributeString("creationdate", String.Empty, i.CreationDate.ToString()); - rdata.writer.WriteAttributeString("invtype", String.Empty, i.InvType.ToString()); - rdata.writer.WriteAttributeString("assettype", String.Empty, i.AssetType.ToString()); - rdata.writer.WriteAttributeString("groupowned", String.Empty, i.GroupOwned.ToString()); - rdata.writer.WriteAttributeString("groupid", String.Empty, i.GroupID.ToString()); - rdata.writer.WriteAttributeString("saletype", String.Empty, i.SaleType.ToString()); - rdata.writer.WriteAttributeString("saleprice", String.Empty, i.SalePrice.ToString()); - rdata.writer.WriteAttributeString("flags", String.Empty, i.Flags.ToString()); - - rdata.writer.WriteStartElement(String.Empty, "Permissions", String.Empty); - rdata.writer.WriteAttributeString("current", String.Empty, i.CurrentPermissions.ToString("X")); - rdata.writer.WriteAttributeString("next", String.Empty, i.NextPermissions.ToString("X")); - rdata.writer.WriteAttributeString("group", String.Empty, i.GroupPermissions.ToString("X")); - rdata.writer.WriteAttributeString("everyone", String.Empty, i.EveryOnePermissions.ToString("X")); - rdata.writer.WriteAttributeString("base", String.Empty, i.BasePermissions.ToString("X")); - rdata.writer.WriteEndElement(); - - rdata.writer.WriteElementString("Asset", i.AssetID.ToString()); - - rdata.writer.WriteEndElement(); - } - - /// - /// This method creates a "trashcan" folder to support folder and item - /// deletions by this interface. The xisting trash folder is found and - /// this folder is created within it. It is called "tmp" to indicate to - /// the client that it is OK to delete this folder. The REST interface - /// will recreate the folder on an as-required basis. - /// If the trash can cannot be created, then by implication the request - /// that required it cannot be completed, and it fails accordingly. - /// - /// HTTP service request work area - private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) - { - InventoryFolderBase TrashCan = null; - - foreach (InventoryFolderBase f in rdata.folders) - { - if (f.Name == "Trash") - { - foreach (InventoryFolderBase t in rdata.folders) - { - if (t.Name == "tmp") - { - TrashCan = t; - } - } - if (TrashCan == null) - { - TrashCan = new InventoryFolderBase(); - TrashCan.Name = "tmp"; - TrashCan.ID = UUID.Random(); - TrashCan.Version = 1; - TrashCan.Type = (short) AssetType.TrashFolder; - TrashCan.ParentID = f.ID; - TrashCan.Owner = f.Owner; - Rest.InventoryServices.AddFolder(TrashCan); - } - } - } - - if (TrashCan == null) - { - Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); - rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can"); - } - - return TrashCan; - } - - /// - /// Make sure that an unchanged folder is not unnecessarily - /// processed. - /// - /// Folder obtained from enclosed entity - /// Folder obtained from the user's inventory - private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) - { - return (newf.Name != oldf.Name - || newf.ParentID != oldf.ParentID - || newf.Owner != oldf.Owner - || newf.Type != oldf.Type - || newf.Version != oldf.Version - ); - } - - /// - /// Make sure that an unchanged item is not unnecessarily - /// processed. - /// - /// Item obtained from enclosed entity - /// Item obtained from the user's inventory - private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) - { - return (newf.Name != oldf.Name - || newf.Folder != oldf.Folder - || newf.Description != oldf.Description - || newf.Owner != oldf.Owner - || newf.CreatorId != oldf.CreatorId - || newf.AssetID != oldf.AssetID - || newf.GroupID != oldf.GroupID - || newf.GroupOwned != oldf.GroupOwned - || newf.InvType != oldf.InvType - || newf.AssetType != oldf.AssetType - ); - } - - /// - /// This method is called by PUT and POST to create an XmlInventoryCollection - /// instance that reflects the content of the entity supplied on the request. - /// Any elements in the completed collection whose UUID is zero, are - /// considered to be located relative to the end-point identified int he - /// URI. In this way, an entire sub-tree can be conveyed in a single REST - /// PUT or POST request. - /// - /// A new instance of XmlInventoryCollection is created and, if the request - /// has an entity, it is more completely initialized. thus, if no entity was - /// provided the collection is valid, but empty. - /// - /// The entity is then scanned and each tag is processed to produce the - /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection - /// will reflect the subtree described by the entity. - /// - /// This is a very flexible mechanism, the entity may contain arbitrary, - /// discontiguous tree fragments, or may contain single element. The caller is - /// responsible for integrating this collection (and ensuring that any - /// missing parent IDs are resolved). - /// - /// HTTP service request work area - internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) - { - Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); - - XmlInventoryCollection ic = new XmlInventoryCollection(); - - if (rdata.request.HasEntityBody) - { - Rest.Log.DebugFormat("{0} Entity present", MsgId); - - ic.init(rdata); - - try - { - while (ic.xml.Read()) - { - switch (ic.xml.NodeType) - { - case XmlNodeType.Element: - Rest.Log.DebugFormat("{0} StartElement: <{1}>", - MsgId, ic.xml.Name); - - switch (ic.xml.Name) - { - case "Folder": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectFolder(ic); - break; - case "Item": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectItem(ic); - break; - case "Asset": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectAsset(ic); - break; - case "Permissions": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectPermissions(ic); - break; - default: - Rest.Log.DebugFormat("{0} Ignoring {1} element", - MsgId, ic.xml.Name); - break; - } - - // This stinks, but the ReadElement call above not only reads - // the imbedded data, but also consumes the end tag for Asset - // and moves the element pointer on to the containing Item's - // element-end, however, if there was a permissions element - // following, it would get us to the start of that.. - if (ic.xml.NodeType == XmlNodeType.EndElement && - ic.xml.Name == "Item") - { - Validate(ic); - } - break; - - case XmlNodeType.EndElement : - switch (ic.xml.Name) - { - case "Folder": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - ic.Pop(); - break; - case "Item": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - Validate(ic); - break; - case "Asset": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - break; - case "Permissions": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - break; - default: - Rest.Log.DebugFormat("{0} Ignoring {1} element", - MsgId, ic.xml.Name); - break; - } - break; - - default: - Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>", - MsgId, ic.xml.NodeType, ic.xml.Value); - break; - } - } - } - catch (XmlException e) - { - Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message); - throw e; - } - catch (Exception e) - { - Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); - throw e; - } - } - else - { - Rest.Log.DebugFormat("{0} Entity absent", MsgId); - } - - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId); - Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count); - Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count); - Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count); - } - - return ic; - } - - /// - /// This method creates an inventory Folder from the - /// information supplied in the request's entity. - /// A folder instance is created and initialized to reflect - /// default values. These values are then overridden - /// by information supplied in the entity. - /// If context was not explicitly provided, then the - /// appropriate ID values are determined. - /// - - private void CollectFolder(XmlInventoryCollection ic) - { - Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); - - InventoryFolderBase result = new InventoryFolderBase(); - - // Default values - - result.Name = String.Empty; - result.ID = UUID.Zero; - result.Owner = ic.UserID; - result.ParentID = UUID.Zero; // Context - result.Type = (short) AssetType.Folder; - result.Version = 1; - - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - switch (ic.xml.Name) - { - case "name": - result.Name = ic.xml.Value; - break; - case "uuid": - result.ID = new UUID(ic.xml.Value); - break; - case "parent": - result.ParentID = new UUID(ic.xml.Value); - break; - case "owner": - result.Owner = new UUID(ic.xml.Value); - break; - case "type": - result.Type = Int16.Parse(ic.xml.Value); - break; - case "version": - result.Version = UInt16.Parse(ic.xml.Value); - break; - default: - Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", - MsgId, ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", - ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - - // The client is relying upon the reconstitution process - // to determine the parent's UUID based upon context. This - // is necessary where a new folder may have been - // introduced. - - if (result.ParentID == UUID.Zero) - { - result.ParentID = ic.Parent(); - } - else - { - bool found = false; - - foreach (InventoryFolderBase parent in ic.rdata.folders) - { - if (parent.ID == result.ParentID) - { - found = true; - break; - } - } - - if (!found) - { - Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", - MsgId, ic.Item.Folder, result.ID); - ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent"); - } - } - - // This is a new folder, so no existing UUID is available - // or appropriate - - if (result.ID == UUID.Zero) - { - result.ID = UUID.Random(); - } - - // Treat this as a new context. Any other information is - // obsolete as a consequence. - - ic.Push(result); - } - - /// - /// This method is called to handle the construction of an Item - /// instance from the supplied request entity. It is called - /// whenever an Item start tag is detected. - /// An instance of an Item is created and initialized to default - /// values. These values are then overridden from values supplied - /// as attributes to the Item element. - /// This item is then stored in the XmlInventoryCollection and - /// will be verified by Validate. - /// All context is reset whenever the effective folder changes - /// or an item is successfully validated. - /// - private void CollectItem(XmlInventoryCollection ic) - { - Rest.Log.DebugFormat("{0} Interpret item element", MsgId); - - InventoryItemBase result = new InventoryItemBase(); - - result.Name = String.Empty; - result.Description = String.Empty; - result.ID = UUID.Zero; - result.Folder = UUID.Zero; - result.Owner = ic.UserID; - result.CreatorId = ic.UserID.ToString(); - result.AssetID = UUID.Zero; - result.GroupID = UUID.Zero; - result.GroupOwned = false; - result.InvType = (int) InventoryType.Unknown; - result.AssetType = (int) AssetType.Unknown; - - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - - switch (ic.xml.Name) - { - case "name": - result.Name = ic.xml.Value; - break; - case "desc": - result.Description = ic.xml.Value; - break; - case "uuid": - result.ID = new UUID(ic.xml.Value); - break; - case "folder": - result.Folder = new UUID(ic.xml.Value); - break; - case "owner": - result.Owner = new UUID(ic.xml.Value); - break; - case "invtype": - result.InvType = Int32.Parse(ic.xml.Value); - break; - case "creator": - result.CreatorId = ic.xml.Value; - break; - case "assettype": - result.AssetType = Int32.Parse(ic.xml.Value); - break; - case "groupowned": - result.GroupOwned = Boolean.Parse(ic.xml.Value); - break; - case "groupid": - result.GroupID = new UUID(ic.xml.Value); - break; - case "flags": - result.Flags = UInt32.Parse(ic.xml.Value); - break; - case "creationdate": - result.CreationDate = Int32.Parse(ic.xml.Value); - break; - case "saletype": - result.SaleType = Byte.Parse(ic.xml.Value); - break; - case "saleprice": - result.SalePrice = Int32.Parse(ic.xml.Value); - break; - - default: - Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", - MsgId, ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute", - ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - - ic.Push(result); - } - - /// - /// This method assembles an asset instance from the - /// information supplied in the request's entity. It is - /// called as a result of detecting a start tag for a - /// type of Asset. - /// The information is collected locally, and an asset - /// instance is created only if the basic XML parsing - /// completes successfully. - /// Default values for all parts of the asset are - /// established before overriding them from the supplied - /// XML. - /// If an asset has inline=true as an attribute, then - /// the element contains the data representing the - /// asset. This is saved as the data component. - /// inline=false means that the element's payload is - /// simply the UUID of the asset referenced by the - /// item being constructed. - /// An asset, if created is stored in the - /// XmlInventoryCollection - /// - private void CollectAsset(XmlInventoryCollection ic) - { - Rest.Log.DebugFormat("{0} Interpret asset element", MsgId); - - string name = String.Empty; - string desc = String.Empty; - sbyte type = (sbyte) AssetType.Unknown; - bool temp = false; - bool local = false; - - // This is not a persistent attribute - bool inline = false; - - UUID uuid = UUID.Zero; - - // Attribute is optional - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - switch (ic.xml.Name) - { - case "name" : - name = ic.xml.Value; - break; - - case "type" : - type = SByte.Parse(ic.xml.Value); - break; - - case "description" : - desc = ic.xml.Value; - break; - - case "temporary" : - temp = Boolean.Parse(ic.xml.Value); - break; - - case "uuid" : - uuid = new UUID(ic.xml.Value); - break; - - case "inline" : - inline = Boolean.Parse(ic.xml.Value); - break; - - case "local" : - local = Boolean.Parse(ic.xml.Value); - break; - - default : - Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", - MsgId, ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("unrecognized attribute <{0}>", ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - - // If this is a reference to an existing asset, just store the - // asset ID into the item. - - if (!inline) - { - if (ic.Item != null) - { - ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString()); - Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID); - } - else - { - Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset"); - } - } - - // Otherwise, generate an asset ID, store that into the item, and - // create an entry in the asset list for the inlined asset. But - // only if the size is non-zero. - - else - { - AssetBase asset = null; - string b64string = null; - - // Generate a UUID if none were given, and generally none should - // be. Ever. - - if (uuid == UUID.Zero) - { - uuid = UUID.Random(); - } - - // Create AssetBase entity to hold the inlined asset - - asset = new AssetBase(uuid, name, type, UUID.Zero.ToString()); - - asset.Description = desc; - asset.Local = local; - asset.Temporary = temp; - - b64string = ic.xml.ReadElementContentAsString(); - - Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); - Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, - b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); - - asset.Data = Convert.FromBase64String(b64string); - - // Ensure the asset always has some kind of data component - - if (asset.Data == null) - { - asset.Data = new byte[1]; - } - - // If this is in the context of an item, establish - // a link with the item in context. - - if (ic.Item != null && ic.Item.AssetID == UUID.Zero) - { - ic.Item.AssetID = uuid; - } - - ic.Push(asset); - } - } - - /// - /// Store any permissions information provided by the request. - /// This overrides the default permissions set when the - /// XmlInventoryCollection object was created. - /// - private void CollectPermissions(XmlInventoryCollection ic) - { - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - switch (ic.xml.Name) - { - case "current": - ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "next": - ic.NextPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "group": - ic.GroupPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "everyone": - ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "base": - ic.BasePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - default: - Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", - MsgId,ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("invalid attribute <{0}>", ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - } - - /// - /// This method is called whenever an Item has been successfully - /// reconstituted from the request's entity. - /// It uses the information curren tin the XmlInventoryCollection - /// to complete the item's specification, including any implied - /// context and asset associations. - /// It fails the request if any necessary item or asset information - /// is missing. - /// - - private void Validate(XmlInventoryCollection ic) - { - // There really should be an item present if we've - // called validate. So fail if there is not. - - if (ic.Item == null) - { - Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error"); - } - - // Every item is required to have a name (via REST anyway) - - if (ic.Item.Name == String.Empty) - { - Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required"); - } - - // An item MUST have an asset ID. AssetID should never be zero - // here. It should always get set from the information stored - // when the Asset element was processed. - - if (ic.Item.AssetID == UUID.Zero) - { - Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); - Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required"); - } - - // If the item is new, then assign it an ID - - if (ic.Item.ID == UUID.Zero) - { - ic.Item.ID = UUID.Random(); - } - - // If the context is being implied, obtain the current - // folder item's ID. If it was specified explicitly, make - // sure that theparent folder exists. - - if (ic.Item.Folder == UUID.Zero) - { - ic.Item.Folder = ic.Parent(); - } - else - { - bool found = false; - - foreach (InventoryFolderBase parent in ic.rdata.folders) - { - if (parent.ID == ic.Item.Folder) - { - found = true; - break; - } - } - - if (!found) - { - Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", - MsgId, ic.Item.Folder, ic.Item.ID); - ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required"); - } - } - - // If this is an inline asset being constructed in the context - // of a new Item, then use the itm's name here too. - - if (ic.Asset != null) - { - if (ic.Asset.Name == String.Empty) - ic.Asset.Name = ic.Item.Name; - if (ic.Asset.Description == String.Empty) - ic.Asset.Description = ic.Item.Description; - } - - // Assign permissions - - ic.Item.CurrentPermissions = ic.CurrentPermissions; - ic.Item.EveryOnePermissions = ic.EveryOnePermissions; - ic.Item.BasePermissions = ic.BasePermissions; - ic.Item.GroupPermissions = ic.GroupPermissions; - ic.Item.NextPermissions = ic.NextPermissions; - - // If no type was specified for this item, we can attempt to - // infer something from the file type maybe. This is NOT as - // good as having type be specified in the XML. - - if (ic.Item.AssetType == (int) AssetType.Unknown || - ic.Item.InvType == (int) InventoryType.Unknown) - { - Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); - - string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); - - if (Rest.DEBUG) - { - for (int i = 0; i < parts.Length; i++) - { - Rest.Log.DebugFormat("{0} Name part {1} : {2}", - MsgId, i, parts[i]); - } - } - - // If the associated item name is multi-part, then maybe - // the last part will indicate the item type - if we're - // lucky. - - if (parts.Length > 1) - { - Rest.Log.DebugFormat("{0} File type is {1}", - MsgId, parts[parts.Length - 1]); - switch (parts[parts.Length - 1]) - { - case "jpeg2000" : - case "jpeg-2000" : - case "jpg2000" : - case "jpg-2000" : - Rest.Log.DebugFormat("{0} Type {1} inferred", - MsgId, parts[parts.Length-1]); - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.ImageJPEG; - if (ic.Item.InvType == (int) InventoryType.Unknown) - ic.Item.InvType = (int) InventoryType.Texture; - break; - case "jpg" : - case "jpeg" : - Rest.Log.DebugFormat("{0} Type {1} inferred", - MsgId, parts[parts.Length - 1]); - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.ImageJPEG; - if (ic.Item.InvType == (int) InventoryType.Unknown) - ic.Item.InvType = (int) InventoryType.Texture; - break; - case "tga" : - if (parts[parts.Length - 2].IndexOf("_texture") != -1) - { - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.TextureTGA; - if (ic.Item.InvType == (int) AssetType.Unknown) - ic.Item.InvType = (int) InventoryType.Texture; - } - else - { - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.ImageTGA; - if (ic.Item.InvType == (int) InventoryType.Unknown) - ic.Item.InvType = (int) InventoryType.Snapshot; - } - break; - default : - Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}", - MsgId,ic.Item.Name); - break; - } - } - } - - /// If this is a TGA remember the fact - - if (ic.Item.AssetType == (int) AssetType.TextureTGA || - ic.Item.AssetType == (int) AssetType.ImageTGA) - { - Bitmap temp; - Stream tgadata = new MemoryStream(ic.Asset.Data); - - temp = LoadTGAClass.LoadTGA(tgadata); - try - { - ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true); - } - catch (DllNotFoundException) - { - Rest.Log.ErrorFormat("OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", ic.Item.Name); - ic.Asset.Data = new Byte[0]; - } - catch (IndexOutOfRangeException) - { - Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); - ic.Asset.Data = new Byte[0]; - } - catch (Exception) - { - Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); - ic.Asset.Data = new Byte[0]; - } - } - - ic.reset(); - } - - #region Inventory RequestData extension - - internal class InventoryRequestData : RequestData - { - /// - /// These are the inventory specific request/response state - /// extensions. - /// - - internal UUID uuid = UUID.Zero; - internal bool HaveInventory = false; - internal ICollection folders = null; - internal ICollection items = null; - internal UserProfileData userProfile = null; - internal InventoryFolderBase root = null; - internal bool timeout = false; - internal Timer watchDog = new Timer(); - - internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - - internal void startWD(double interval) - { - Rest.Log.DebugFormat("{0} Setting watchdog", MsgId); - watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut); - watchDog.Interval = interval; - watchDog.AutoReset = false; - watchDog.Enabled = true; - lock (watchDog) - watchDog.Start(); - - } - - internal void stopWD() - { - Rest.Log.DebugFormat("{0} Reset watchdog", MsgId); - lock (watchDog) - watchDog.Stop(); - } - - /// - /// This is the callback method required by the inventory watchdog. The - /// requestor issues an inventory request and then blocks until the - /// request completes, or this method signals the monitor. - /// - - private void OnTimeOut(object sender, ElapsedEventArgs args) - { - Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId); - // InventoryRequestData rdata = (InventoryRequestData) sender; - lock (this) - { - this.folders = null; - this.items = null; - this.HaveInventory = false; - this.timeout = true; - Monitor.Pulse(this); - } - } - - /// - /// This is the callback method required by inventory services. The - /// requestor issues an inventory request and then blocks until this - /// method signals the monitor. - /// - - internal void GetUserInventory(ICollection folders, ICollection items) - { - Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); - lock (this) - { - if (watchDog.Enabled) - { - this.stopWD(); - } - this.folders = folders; - this.items = items; - this.HaveInventory = true; - this.timeout = false; - Monitor.Pulse(this); - } - } - } - - #endregion Inventory RequestData extension - - /// - /// This class is used to record and manage the hierarchy - /// constructed from the entity supplied in the request for - /// PUT and POST. - /// - - internal class XmlInventoryCollection : InventoryCollection - { - internal InventoryRequestData rdata; - private Stack stk; - - internal List Assets; - - internal InventoryItemBase Item; - internal AssetBase Asset; - internal XmlReader xml; - - internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF; - internal /*static*/ const uint DefaultNext = 0x82000; - internal /*static*/ const uint DefaultBase = 0x7FFFFFFF; - internal /*static*/ const uint DefaultEveryOne = 0x0; - internal /*static*/ const uint DefaultGroup = 0x0; - - internal uint CurrentPermissions = 0x00; - internal uint NextPermissions = 0x00; - internal uint BasePermissions = 0x00; - internal uint EveryOnePermissions = 0x00; - internal uint GroupPermissions = 0x00; - - internal XmlInventoryCollection() - { - Folders = new List(); - Items = new List(); - Assets = new List(); - } - - internal void init(InventoryRequestData p_rdata) - { - rdata = p_rdata; - UserID = rdata.uuid; - stk = new Stack(); - rdata.initXmlReader(); - xml = rdata.reader; - initPermissions(); - } - - internal void initPermissions() - { - CurrentPermissions = DefaultCurrent; - NextPermissions = DefaultNext; - BasePermissions = DefaultBase; - GroupPermissions = DefaultGroup; - EveryOnePermissions = DefaultEveryOne; - } - - internal UUID Parent() - { - if (stk.Count != 0) - { - return stk.Peek().ID; - } - else - { - return UUID.Zero; - } - } - - internal void Push(InventoryFolderBase folder) - { - stk.Push(folder); - Folders.Add(folder); - reset(); - } - - internal void Push(InventoryItemBase item) - { - Item = item; - Items.Add(item); - } - - internal void Push(AssetBase asset) - { - Asset = asset; - Assets.Add(asset); - } - - internal void Pop() - { - stk.Pop(); - reset(); - } - - internal void reset() - { - Item = null; - Asset = null; - initPermissions(); - } - - internal void Fail(int code, string addendum) - { - rdata.Fail(code, addendum); - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs deleted file mode 100644 index 81596a367a..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.Reflection; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestTestServices : IRest - { - private bool enabled = false; - private string qPrefix = "test"; - - // A simple constructor is used to handle any once-only - // initialization of working classes. - - public RestTestServices() - { - Rest.Log.InfoFormat("{0} Test services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // If a relative path was specified, make it absolute by adding - // the standard prefix, e.g. /admin - - if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) - { - Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); - } - - // Load test cases - - loadTests(); - foreach (ITest test in tests) - { - test.Initialize(); - } - - // Register interface - - Rest.Plugin.AddPathHandler(DoTests,qPrefix,Allocate); - - // Activate - - enabled = true; - - Rest.Log.InfoFormat("{0} Test services initialization complete", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - foreach (ITest test in tests) - { - test.Close(); - } - Rest.Log.InfoFormat("{0} Test services closing down", MsgId); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return new RequestData(request, response, prefix); - } - - // Inventory Handler - - private void DoTests(RequestData rdata) - { - if (!enabled) - return; - - // Now that we know this is a serious attempt to - // access inventory data, we should find out who - // is asking, and make sure they are authorized - // to do so. We need to validate the caller's - // identity before revealing anything about the - // status quo. Authenticate throws an exception - // via Fail if no identity information is present. - // - // With the present HTTP server we can't use the - // builtin authentication mechanisms because they - // would be enforced for all in-bound requests. - // Instead we look at the headers ourselves and - // handle authentication directly. - - try - { - if (!rdata.IsAuthenticated) - { - rdata.Fail(Rest.HttpStatusCodeNotAuthorized, - String.Format("user \"{0}\" could not be authenticated", rdata.userName)); - } - } - catch (RestException e) - { - if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - { - Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - } - else - { - Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - } - throw (e); - } - - // Check that a test was specified - - if (rdata.Parameters.Length < 1) - { - Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters"); - } - - // Select the test - - foreach (ITest test in tests) - { - if (!rdata.handled) - test.Execute(rdata); - } - } - - #endregion Interface - - private static bool testsLoaded = false; - private static List classes = new List(); - private static List tests = new List(); - private static Type[] parms = new Type[0]; - private static Object[] args = new Object[0]; - - static RestTestServices() - { - Module[] mods = Assembly.GetExecutingAssembly().GetModules(); - foreach (Module m in mods) - { - Type[] types = m.GetTypes(); - foreach (Type t in types) - { - try - { - if (t.GetInterface("ITest") != null) - { - classes.Add(t); - } - } - catch (Exception e) - { - Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message); - } - } - } - } - - /// - /// This routine loads all of the handlers discovered during - /// instance initialization. Each handler is responsible for - /// registering itself with this handler. - /// I was not able to make this code work in a constructor. - /// - - private void loadTests() - { - lock (tests) - { - if (!testsLoaded) - { - - ConstructorInfo ci; - Object ht; - - foreach (Type t in classes) - { - try - { - if (t.GetInterface("ITest") != null) - { - ci = t.GetConstructor(parms); - ht = ci.Invoke(args); - tests.Add((ITest)ht); - Rest.Log.InfoFormat("{0} Test {1} added", MsgId, t); - } - } - catch (Exception e) - { - Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message); - } - } - testsLoaded = true; - } - } - } - - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs deleted file mode 100644 index eafc1548ee..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* -* 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. -* -*/ - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - /// - /// This interface represents the boundary between the general purpose - /// REST plugin handling, and the functionally specific handlers. The - /// handler knows only to initialzie and terminate all such handlers - /// that it finds. - /// - - internal interface ITest - { - void Initialize(); - void Execute(RequestData rdata); - void Close(); - } - -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs deleted file mode 100644 index 1c1afd033f..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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 OpenMetaverse; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory.Tests -{ - public class Remote : ITest - { - private static readonly int PARM_TESTID = 0; - private static readonly int PARM_COMMAND = 1; - - private static readonly int PARM_MOVE_AVATAR = 2; - private static readonly int PARM_MOVE_X = 3; - private static readonly int PARM_MOVE_Y = 4; - private static readonly int PARM_MOVE_Z = 5; - - private bool enabled = false; - - // No constructor code is required. - - public Remote() - { - Rest.Log.InfoFormat("{0} Remote services constructor", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - enabled = true; - Rest.Log.InfoFormat("{0} Remote services initialized", MsgId); - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - Rest.Log.InfoFormat("{0} Remote services closing down", MsgId); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - // Remote Handler - // Key information of interest here is the Parameters array, each - // entry represents an element of the URI, with element zero being - // the - - public void Execute(RequestData rdata) - { - if (!enabled) return; - - // If we can't relate to what's there, leave it for others. - - if (rdata.Parameters.Length == 0 || rdata.Parameters[PARM_TESTID] != "remote") - return; - - Rest.Log.DebugFormat("{0} REST Remote handler ENTRY", MsgId); - - // Remove the prefix and what's left are the parameters. If we don't have - // the parameters we need, fail the request. Parameters do NOT include - // any supplied query values. - - if (rdata.Parameters.Length > 1) - { - switch (rdata.Parameters[PARM_COMMAND].ToLower()) - { - case "move" : - DoMove(rdata); - break; - default : - DoHelp(rdata); - break; - } - } - else - { - DoHelp(rdata); - } - } - - private void DoHelp(RequestData rdata) - { - rdata.body = Help; - rdata.Complete(); - rdata.Respond("Help"); - } - - private void DoMove(RequestData rdata) - { - if (rdata.Parameters.Length < 6) - { - Rest.Log.WarnFormat("{0} Move: No movement information provided", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "no movement information provided"); - } - else - { - string[] names = rdata.Parameters[PARM_MOVE_AVATAR].Split(Rest.CA_SPACE); - ScenePresence presence = null; - Scene scene = null; - - if (names.Length != 2) - { - rdata.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("invalid avatar name: <{0}>",rdata.Parameters[PARM_MOVE_AVATAR])); - } - - Rest.Log.WarnFormat("{0} '{1}' command received for {2} {3}", - MsgId, rdata.Parameters[0], names[0], names[1]); - - // The first parameter should be an avatar name, look for the - // avatar in the known regions first. - - Rest.main.SceneManager.ForEachScene(delegate(Scene s) - { - s.ForEachRootScenePresence(delegate(ScenePresence sp) - { - if (sp.Firstname == names[0] && sp.Lastname == names[1]) - { - scene = s; - presence = sp; - } - }); - }); - - if (presence != null) - { - Rest.Log.DebugFormat("{0} Move : Avatar {1} located in region {2}", - MsgId, rdata.Parameters[PARM_MOVE_AVATAR], scene.RegionInfo.RegionName); - - try - { - float x = Convert.ToSingle(rdata.Parameters[PARM_MOVE_X]); - float y = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Y]); - float z = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Z]); - Vector3 vector = new Vector3(x, y, z); - presence.MoveToTarget(vector, false, false); - } - catch (Exception e) - { - rdata.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("invalid parameters: {0}", e.Message)); - } - } - else - { - rdata.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("avatar {0} not present", rdata.Parameters[PARM_MOVE_AVATAR])); - } - - rdata.Complete(); - rdata.Respond("OK"); - } - } - - private static readonly string Help = - "" - + "Remote Command Usage" - + "" - + "

Supported commands are:

" - + "
" - + "
move/avatar-name/x/y/z
" - + "
moves the specified avatar to another location
" - + "
" - + "" - + "" - ; - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs b/OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs deleted file mode 100644 index d99ba57eb8..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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.IO; -using System.Xml.Serialization; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - #region GET methods - public string GetHandler(string request, string path, string param, - IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // foreach (string h in httpRequest.Headers.AllKeys) - // foreach (string v in httpRequest.Headers.GetValues(h)) - // m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v); - - MsgID = RequestID; - m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param); - - try - { - // param empty: regions list - if (String.IsNullOrEmpty(param)) return GetHandlerRegions(httpResponse); - - // param not empty: specific region - return GetHandlerRegion(httpResponse, param); - } - catch (Exception e) - { - return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e); - } - } - - public string GetHandlerRegions(IOSHttpResponse httpResponse) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - rxw.WriteStartElement(String.Empty, "regions", String.Empty); - foreach (Scene s in App.SceneManager.Scenes) - { - rxw.WriteStartElement(String.Empty, "uuid", String.Empty); - rxw.WriteString(s.RegionInfo.RegionID.ToString()); - rxw.WriteEndElement(); - } - rxw.WriteEndElement(); - - return rxw.ToString(); - } - - protected string ShortRegionInfo(string key, string value) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - if (String.IsNullOrEmpty(value) || - String.IsNullOrEmpty(key)) return null; - - rxw.WriteStartElement(String.Empty, "region", String.Empty); - rxw.WriteStartElement(String.Empty, key, String.Empty); - rxw.WriteString(value); - rxw.WriteEndDocument(); - - return rxw.ToString(); - } - - public string GetHandlerRegion(IOSHttpResponse httpResponse, string param) - { - // be resilient and don't get confused by a terminating '/' - param = param.TrimEnd(new char[]{'/'}); - string[] comps = param.Split('/'); - UUID regionID = (UUID)comps[0]; - - m_log.DebugFormat("{0} GET region UUID {1}", MsgID, regionID.ToString()); - - if (UUID.Zero == regionID) throw new Exception("missing region ID"); - - Scene scene = null; - App.SceneManager.TryGetScene(regionID, out scene); - if (null == scene) return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound, - "GET", "cannot find region {0}", regionID.ToString()); - - RegionDetails details = new RegionDetails(scene.RegionInfo); - - // m_log.DebugFormat("{0} GET comps {1}", MsgID, comps.Length); - // for (int i = 0; i < comps.Length; i++) m_log.DebugFormat("{0} GET comps[{1}] >{2}<", MsgID, i, comps[i]); - - if (1 == comps.Length) - { - // complete region details requested - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - XmlSerializer xs = new XmlSerializer(typeof(RegionDetails)); - xs.Serialize(rxw, details, _xmlNs); - return rxw.ToString(); - } - - if (2 == comps.Length) - { - string resp = ShortRegionInfo(comps[1], details[comps[1]]); - if (null != resp) return resp; - - // m_log.DebugFormat("{0} GET comps advanced: >{1}<", MsgID, comps[1]); - - // check for {terrain,stats,prims} - switch (comps[1].ToLower()) - { - case "terrain": - return RegionTerrain(httpResponse, scene); - - case "stats": - return RegionStats(httpResponse, scene); - - case "prims": - return RegionPrims(httpResponse, scene, Vector3.Zero, Vector3.Zero); - } - } - - if (3 == comps.Length) - { - switch (comps[1].ToLower()) - { - case "prims": - string[] subregion = comps[2].Split(','); - if (subregion.Length == 6) - { - Vector3 min, max; - try - { - min = new Vector3((float)Double.Parse(subregion[0], Culture.NumberFormatInfo), (float)Double.Parse(subregion[1], Culture.NumberFormatInfo), (float)Double.Parse(subregion[2], Culture.NumberFormatInfo)); - max = new Vector3((float)Double.Parse(subregion[3], Culture.NumberFormatInfo), (float)Double.Parse(subregion[4], Culture.NumberFormatInfo), (float)Double.Parse(subregion[5], Culture.NumberFormatInfo)); - } - catch (Exception) - { - return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest, - "GET", "invalid subregion parameter"); - } - return RegionPrims(httpResponse, scene, min, max); - } - else - { - return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest, - "GET", "invalid subregion parameter"); - } - } - } - - return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest, - "GET", "too many parameters {0}", param); - } - #endregion GET methods - - protected string RegionTerrain(IOSHttpResponse httpResponse, Scene scene) - { - httpResponse.SendChunked = true; - httpResponse.ContentType = "text/xml"; - - return scene.Heightmap.SaveToXmlString(); - //return Failure(httpResponse, OSHttpStatusCode.ServerErrorNotImplemented, - // "GET", "terrain not implemented"); - } - - protected string RegionStats(IOSHttpResponse httpResponse, Scene scene) - { - int users = scene.GetRootAgentCount(); - int objects = scene.Entities.Count - users; - - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - rxw.WriteStartElement(String.Empty, "region", String.Empty); - rxw.WriteStartElement(String.Empty, "stats", String.Empty); - - rxw.WriteStartElement(String.Empty, "users", String.Empty); - rxw.WriteString(users.ToString()); - rxw.WriteEndElement(); - - rxw.WriteStartElement(String.Empty, "objects", String.Empty); - rxw.WriteString(objects.ToString()); - rxw.WriteEndElement(); - - rxw.WriteEndDocument(); - - return rxw.ToString(); - } - - protected string RegionPrims(IOSHttpResponse httpResponse, Scene scene, Vector3 min, Vector3 max) - { - httpResponse.SendChunked = true; - httpResponse.ContentType = "text/xml"; - - IRegionSerialiserModule serialiser = scene.RequestModuleInterface(); - if (serialiser != null) - serialiser.SavePrimsToXml2(scene, new StreamWriter(httpResponse.OutputStream), min, max); - - return ""; - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs b/OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs deleted file mode 100644 index 468faeaa50..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.IO; -using System.Xml.Serialization; -using OpenMetaverse; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - #region GET methods - public string GetRegionInfoHandler(string request, string path, string param, - IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // foreach (string h in httpRequest.Headers.AllKeys) - // foreach (string v in httpRequest.Headers.GetValues(h)) - // m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v); - - MsgID = RequestID; - m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param); - - try - { - // param empty: regions list - // if (String.IsNullOrEmpty(param)) - return GetRegionInfoHandlerRegions(httpResponse); - - // // param not empty: specific region - // return GetRegionInfoHandlerRegion(httpResponse, param); - } - catch (Exception e) - { - return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e); - } - } - - public string GetRegionInfoHandlerRegions(IOSHttpResponse httpResponse) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - // regions info - rxw.WriteStartElement(String.Empty, "regions", String.Empty); - { - // regions info: number of regions - rxw.WriteStartAttribute(String.Empty, "number", String.Empty); - rxw.WriteValue(App.SceneManager.Scenes.Count); - rxw.WriteEndAttribute(); - - // regions info: max number of regions - rxw.WriteStartAttribute(String.Empty, "max", String.Empty); - if (App.ConfigSource.Source.Configs["RemoteAdmin"] != null) - { - rxw.WriteValue(App.ConfigSource.Source.Configs["RemoteAdmin"].GetInt("region_limit", -1)); - } - else - { - rxw.WriteValue(-1); - } - rxw.WriteEndAttribute(); - - // regions info: region - foreach (Scene s in App.SceneManager.Scenes) - { - rxw.WriteStartElement(String.Empty, "region", String.Empty); - - rxw.WriteStartAttribute(String.Empty, "uuid", String.Empty); - rxw.WriteString(s.RegionInfo.RegionID.ToString()); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "name", String.Empty); - rxw.WriteString(s.RegionInfo.RegionName); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "x", String.Empty); - rxw.WriteValue(s.RegionInfo.RegionLocX); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "y", String.Empty); - rxw.WriteValue(s.RegionInfo.RegionLocY); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "external_hostname", String.Empty); - rxw.WriteString(s.RegionInfo.ExternalHostName); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "ip", String.Empty); - rxw.WriteString(s.RegionInfo.InternalEndPoint.ToString()); - rxw.WriteEndAttribute(); - - int users = s.GetRootAgentCount(); - rxw.WriteStartAttribute(String.Empty, "avatars", String.Empty); - rxw.WriteValue(users); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "objects", String.Empty); - rxw.WriteValue(s.Entities.Count - users); - rxw.WriteEndAttribute(); - - rxw.WriteEndElement(); - } - } - return rxw.ToString(); - } - #endregion GET methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs b/OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs deleted file mode 100644 index f666f45e9a..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.IO; -using OpenMetaverse; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - #region POST methods - - public string PostHandler(string request, string path, string param, - IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // foreach (string h in httpRequest.Headers.AllKeys) - // foreach (string v in httpRequest.Headers.GetValues(h)) - // m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v); - - MsgID = RequestID; - m_log.DebugFormat("{0} POST path {1} param {2}", MsgID, path, param); - - try - { - // param empty: new region post - if (!IsGod(httpRequest)) - // XXX: this needs to be turned into a FailureUnauthorized(...) - return Failure(httpResponse, OSHttpStatusCode.ClientErrorUnauthorized, - "GET", "you are not god"); - - if (String.IsNullOrEmpty(param)) return CreateRegion(httpRequest, httpResponse); - - // Parse region ID and other parameters - param = param.TrimEnd(new char[] {'/'}); - string[] comps = param.Split('/'); - UUID regionID = (UUID) comps[0]; - - m_log.DebugFormat("{0} POST region UUID {1}", MsgID, regionID.ToString()); - if (UUID.Zero == regionID) throw new Exception("missing region ID"); - - Scene scene = null; - App.SceneManager.TryGetScene(regionID, out scene); - if (null == scene) - return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound, - "POST", "cannot find region {0}", regionID.ToString()); - - if (2 == comps.Length) - { - // check for {prims} - switch (comps[1].ToLower()) - { - case "prims": - return LoadPrims(request, httpRequest, httpResponse, scene); - } - } - - return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound, - "POST", "url {0} not supported", param); - } - catch (Exception e) - { - return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "POST", e); - } - } - - public string CreateRegion(IOSHttpRequest request, IOSHttpResponse response) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - rxw.WriteStartElement(String.Empty, "regions", String.Empty); - foreach (Scene s in App.SceneManager.Scenes) - { - rxw.WriteStartElement(String.Empty, "uuid", String.Empty); - rxw.WriteString(s.RegionInfo.RegionID.ToString()); - rxw.WriteEndElement(); - } - rxw.WriteEndElement(); - - return rxw.ToString(); - } - - public string LoadPrims(string requestBody, IOSHttpRequest request, IOSHttpResponse response, Scene scene) - { - IRegionSerialiserModule serialiser = scene.RequestModuleInterface(); - if (serialiser != null) - serialiser.LoadPrimsFromXml2(scene, new StringReader(requestBody), true); - - return ""; - } - - #endregion POST methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs b/OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs deleted file mode 100644 index 5e760091b9..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.Xml.Serialization; -using OpenMetaverse; -using OpenSim.Framework; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - [XmlRoot(ElementName="region", IsNullable = false)] - public class RegionDetails - { - public string region_name; - public string region_id; - public uint region_x; - public uint region_y; - public string region_owner; - public string region_owner_id; - public uint region_http_port; - public uint region_port; - public string region_server_uri; - public string region_external_hostname; - - public RegionDetails() - { - } - - public RegionDetails(RegionInfo regInfo) - { - region_name = regInfo.RegionName; - region_id = regInfo.RegionID.ToString(); - region_x = regInfo.RegionLocX; - region_y = regInfo.RegionLocY; - region_owner_id = regInfo.EstateSettings.EstateOwner.ToString(); - region_http_port = regInfo.HttpPort; - region_server_uri = regInfo.ServerURI; - region_external_hostname = regInfo.ExternalHostName; - - Uri uri = new Uri(region_server_uri); - region_port = (uint)uri.Port; - } - - public string this[string idx] - { - get - { - switch (idx.ToLower()) - { - case "name": - return region_name; - case "id": - return region_id; - case "location": - return String.Format("{0}{1}", region_x, region_y); - case "owner": - return region_owner; - case "owner_id": - return region_owner_id; - case "http_port": - return region_http_port.ToString(); - case "server_uri": - return region_server_uri; - case "external_hostname": - case "hostname": - return region_external_hostname; - - default: - return null; - } - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml b/OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml deleted file mode 100644 index 94eca48371..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/OpenSim/ApplicationPlugins/Rest/RestPlugin.cs b/OpenSim/ApplicationPlugins/Rest/RestPlugin.cs deleted file mode 100644 index a2425b5cf3..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/RestPlugin.cs +++ /dev/null @@ -1,417 +0,0 @@ -/* - * 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.IO; -using System.Reflection; -using System.Xml; -using log4net; -using Nini.Config; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest -{ - public abstract class RestPlugin : IApplicationPlugin - { - #region properties - - protected static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private IConfig _config; // Configuration source: Rest Plugins - private IConfig _pluginConfig; // Configuration source: Plugin specific - private OpenSimBase _app; // The 'server' - private BaseHttpServer _httpd; // The server's RPC interface - private string _prefix; // URL prefix below - // which all REST URLs - // are living - // private StringWriter _sw = null; - // private RestXmlWriter _xw = null; - - private string _godkey; - private int _reqk; - - [ThreadStatic] - private static string _threadRequestID = String.Empty; - - /// - /// Return an ever increasing request ID for logging - /// - protected string RequestID - { - get { return _reqk++.ToString(); } - set { _reqk = Convert.ToInt32(value); } - } - - /// - /// Thread-constant message IDs for logging. - /// - protected string MsgID - { - get { return String.Format("[REST-{0}] #{1}", Name, _threadRequestID); } - set { _threadRequestID = value; } - } - - /// - /// Returns true if Rest Plugins are enabled. - /// - public bool PluginsAreEnabled - { - get { return null != _config; } - } - - /// - /// Returns true if specific Rest Plugin is enabled. - /// - public bool IsEnabled - { - get - { - return (null != _pluginConfig) && _pluginConfig.GetBoolean("enabled", false); - } - } - - /// - /// OpenSimMain application - /// - public OpenSimBase App - { - get { return _app; } - } - - /// - /// RPC server - /// - public BaseHttpServer HttpServer - { - get { return _httpd; } - } - - /// - /// URL prefix to use for all REST handlers - /// - public string Prefix - { - get { return _prefix; } - } - - /// - /// Access to GOD password string - /// - protected string GodKey - { - get { return _godkey; } - } - - /// - /// Configuration of the plugin - /// - public IConfig Config - { - get { return _pluginConfig; } - } - - /// - /// Name of the plugin - /// - public abstract string Name { get; } - - /// - /// Return the config section name - /// - public abstract string ConfigName { get; } - - // public XmlTextWriter XmlWriter - // { - // get - // { - // if (null == _xw) - // { - // _sw = new StringWriter(); - // _xw = new RestXmlWriter(_sw); - // _xw.Formatting = Formatting.Indented; - // } - // return _xw; - // } - // } - - // public string XmlWriterResult - // { - // get - // { - // _xw.Flush(); - // _xw.Close(); - // _xw = null; - - // return _sw.ToString(); - // } - // } - - #endregion properties - - #region methods - - // TODO: required by IPlugin, but likely not at all right - private string m_version = "0.0"; - - public string Version - { - get { return m_version; } - } - - public void Initialise() - { - m_log.Info("[RESTPLUGIN]: " + Name + " cannot be default-initialized!"); - throw new PluginNotInitialisedException(Name); - } - - /// - /// This method is called by OpenSimMain immediately after loading the - /// plugin and after basic server setup, but before running any server commands. - /// - /// - /// Note that entries MUST be added to the active configuration files before - /// the plugin can be enabled. - /// - public virtual void Initialise(OpenSimBase openSim) - { - RequestID = "0"; - MsgID = RequestID; - - try - { - if ((_config = openSim.ConfigSource.Source.Configs["RestPlugins"]) == null) - { - m_log.WarnFormat("{0} Rest Plugins not configured", MsgID); - return; - } - - if (!_config.GetBoolean("enabled", false)) - { - //m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID); - return; - } - - _app = openSim; - _httpd = openSim.HttpServer; - - // Retrieve GOD key value, if any. - _godkey = _config.GetString("god_key", String.Empty); - - // Retrive prefix if any. - _prefix = _config.GetString("prefix", "/admin"); - - // Get plugin specific config - _pluginConfig = openSim.ConfigSource.Source.Configs[ConfigName]; - - m_log.InfoFormat("{0} Rest Plugins Enabled", MsgID); - } - catch (Exception e) - { - // we can safely ignore this, as it just means that - // the key lookup in Configs failed, which signals to - // us that noone is interested in our services...they - // don't know what they are missing out on... - // NOTE: Under the present OpenSimulator implementation it is - // not possible for the openSimulator pointer to be null. However - // were the implementation to be changed, this could - // result in a silent initialization failure. Harmless - // except for lack of function and lack of any - // diagnostic indication as to why. The same is true if - // the HTTP server reference is bad. - // We should at least issue a message... - m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message); - m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString()); - } - } - - public virtual void PostInitialise() - { - } - - private List _handlers = new List(); - private Dictionary _agents = new Dictionary(); - - /// - /// Add a REST stream handler to the underlying HTTP server. - /// - /// GET/PUT/POST/DELETE or - /// similar - /// URL prefix - /// RestMethod handler doing the actual work - public virtual void AddRestStreamHandler(string httpMethod, string path, RestMethod method) - { - if (!IsEnabled) return; - - if (!path.StartsWith(_prefix)) - { - path = String.Format("{0}{1}", _prefix, path); - } - - RestStreamHandler h = new RestStreamHandler(httpMethod, path, method); - _httpd.AddStreamHandler(h); - _handlers.Add(h); - - m_log.DebugFormat("{0} Added REST handler {1} {2}", MsgID, httpMethod, path); - } - - /// - /// Add a powerful Agent handler to the underlying HTTP - /// server. - /// - /// name of agent handler - /// agent handler method - /// false when the plugin is disabled or the agent - /// handler could not be added. Any generated exceptions are - /// allowed to drop through to the caller, i.e. ArgumentException. - /// - public bool AddAgentHandler(string agentName, IHttpAgentHandler handler) - { - if (!IsEnabled) return false; - _agents.Add(agentName, handler); -// return _httpd.AddAgentHandler(agentName, handler); - - return false; - } - - /// - /// Remove a powerful Agent handler from the underlying HTTP - /// server. - /// - /// name of agent handler - /// agent handler method - /// false when the plugin is disabled or the agent - /// handler could not be removed. Any generated exceptions are - /// allowed to drop through to the caller, i.e. KeyNotFound. - /// - public bool RemoveAgentHandler(string agentName, IHttpAgentHandler handler) - { - if (!IsEnabled) return false; - if (_agents[agentName] == handler) - { - _agents.Remove(agentName); -// return _httpd.RemoveAgentHandler(agentName, handler); - } - return false; - } - - /// - /// Check whether the HTTP request came from god; that is, is - /// the god_key as configured in the config section supplied - /// via X-OpenSim-Godkey? - /// - /// HTTP request header - /// true when the HTTP request came from god. - protected bool IsGod(IOSHttpRequest request) - { - string[] keys = request.Headers.GetValues("X-OpenSim-Godkey"); - if (null == keys) return false; - - // we take the last key supplied - return keys[keys.Length - 1] == _godkey; - } - - /// - /// Checks wether the X-OpenSim-Password value provided in the - /// HTTP header is indeed the password on file for the avatar - /// specified by the UUID - /// - protected bool IsVerifiedUser(IOSHttpRequest request, UUID uuid) - { - // XXX under construction - return false; - } - - /// - /// Clean up and remove all handlers that were added earlier. - /// - public virtual void Close() - { - foreach (RestStreamHandler h in _handlers) - { - _httpd.RemoveStreamHandler(h.HttpMethod, h.Path); - } - _handlers = null; -// foreach (KeyValuePair h in _agents) -// { -// _httpd.RemoveAgentHandler(h.Key, h.Value); -// } - _agents = null; - } - - public virtual void Dispose() - { - Close(); - } - - /// - /// Return a failure message. - /// - /// origin of the failure message - /// failure message - /// This should probably set a return code as - /// well. (?) - protected string Failure(IOSHttpResponse response, OSHttpStatusCode status, - string method, string format, params string[] msg) - { - string m = String.Format(format, msg); - - response.StatusCode = (int) status; - response.StatusDescription = m; - - m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, m); - return String.Format("{0}", m); - } - - /// - /// Return a failure message. - /// - /// origin of the failure message - /// exception causing the failure message - /// This should probably set a return code as - /// well. (?) - public string Failure(IOSHttpResponse response, OSHttpStatusCode status, - string method, Exception e) - { - string m = String.Format("exception occurred: {0}", e.Message); - - response.StatusCode = (int) status; - response.StatusDescription = m; - - m_log.DebugFormat("{0} {1} failed: {2}", MsgID, method, e.ToString()); - m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, e.Message); - - return String.Format("{0}", e.Message); - } - - #endregion methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/rest.xsd b/OpenSim/ApplicationPlugins/Rest/rest.xsd deleted file mode 100644 index 4dc0ae4d4e..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/rest.xsd +++ /dev/null @@ -1,276 +0,0 @@ - - - - - Open Simulator Export/Import XML schema - August 2008 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs b/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs index 68686c5648..aa6203b3a3 100644 --- a/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs +++ b/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs @@ -240,6 +240,11 @@ namespace OpenSim.Capabilities.Handlers } else { + // Handle the case where no second range value was given. This is equivalent to requesting + // the rest of the entity. + if (end == -1) + end = int.MaxValue; + end = Utils.Clamp(end, 0, texture.Data.Length - 1); start = Utils.Clamp(start, 0, end); int len = end - start + 1; @@ -298,15 +303,43 @@ namespace OpenSim.Capabilities.Handlers // texture.FullID, range, response.StatusCode, response.ContentLength, texture.Data.Length); } + /// + /// Parse a range header. + /// + /// + /// As per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, + /// this obeys range headers with two values (e.g. 533-4165) and no second value (e.g. 533-). + /// Where there is no value, -1 is returned. + /// FIXME: Need to cover the case where only a second value is specified (e.g. -4165), probably by returning -1 + /// for start. + /// + /// + /// Start of the range. Undefined if this was not a number. + /// End of the range. Will be -1 if no end specified. Undefined if there was a raw string but this was not a number. private bool TryParseRange(string header, out int start, out int end) { + start = end = 0; + if (header.StartsWith("bytes=")) { string[] rangeValues = header.Substring(6).Split('-'); + if (rangeValues.Length == 2) { - if (Int32.TryParse(rangeValues[0], out start) && Int32.TryParse(rangeValues[1], out end)) + if (!Int32.TryParse(rangeValues[0], out start)) + return false; + + string rawEnd = rangeValues[1]; + + if (rawEnd == "") + { + end = -1; return true; + } + else if (Int32.TryParse(rawEnd, out end)) + { + return true; + } } } diff --git a/OpenSim/Capabilities/Handlers/GetTexture/Tests/GetTextureHandlerTests.cs b/OpenSim/Capabilities/Handlers/GetTexture/Tests/GetTextureHandlerTests.cs index b6ae41bc51..217a265d5e 100644 --- a/OpenSim/Capabilities/Handlers/GetTexture/Tests/GetTextureHandlerTests.cs +++ b/OpenSim/Capabilities/Handlers/GetTexture/Tests/GetTextureHandlerTests.cs @@ -43,7 +43,7 @@ using OpenSim.Tests.Common.Mock; namespace OpenSim.Capabilities.Handlers.GetTexture.Tests { [TestFixture] - public class GetTextureHandlerTests + public class GetTextureHandlerTests : OpenSimTestCase { [Test] public void TestTextureNotFound() diff --git a/OpenSim/Capabilities/Handlers/Properties/AssemblyInfo.cs b/OpenSim/Capabilities/Handlers/Properties/AssemblyInfo.cs index a681fb643f..f8f63f437b 100644 --- a/OpenSim/Capabilities/Handlers/Properties/AssemblyInfo.cs +++ b/OpenSim/Capabilities/Handlers/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Capabilities/Properties/AssemblyInfo.cs b/OpenSim/Capabilities/Properties/AssemblyInfo.cs index 26254f2fcf..f8a9daeac0 100644 --- a/OpenSim/Capabilities/Properties/AssemblyInfo.cs +++ b/OpenSim/Capabilities/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] +[assembly: AssemblyVersion("0.7.6.*")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/OpenSim/ConsoleClient/Properties/AssemblyInfo.cs b/OpenSim/ConsoleClient/Properties/AssemblyInfo.cs index c240f90ca3..9c0c784838 100644 --- a/OpenSim/ConsoleClient/Properties/AssemblyInfo.cs +++ b/OpenSim/ConsoleClient/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] +[assembly: AssemblyVersion("0.7.6.*")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/OpenSim/ConsoleClient/Requester.cs b/OpenSim/ConsoleClient/Requester.cs index aabb02cbb3..0a213288da 100644 --- a/OpenSim/ConsoleClient/Requester.cs +++ b/OpenSim/ConsoleClient/Requester.cs @@ -44,7 +44,6 @@ namespace OpenSim.ConsoleClient ReplyDelegate action) { WebRequest request = WebRequest.Create(requestUrl); - WebResponse response = null; request.Method = "POST"; @@ -64,16 +63,18 @@ namespace OpenSim.ConsoleClient { string reply = String.Empty; - response = request.EndGetResponse(ar); - - try + using (WebResponse response = request.EndGetResponse(ar)) { - StreamReader r = new StreamReader(response.GetResponseStream()); - reply = r.ReadToEnd(); + try + { + using (Stream s = response.GetResponseStream()) + using (StreamReader r = new StreamReader(s)) + reply = r.ReadToEnd(); - } - catch (System.InvalidOperationException) - { + } + catch (System.InvalidOperationException) + { + } } action(requestUrl, data, reply); diff --git a/OpenSim/Data/IGroupsData.cs b/OpenSim/Data/IGroupsData.cs new file mode 100644 index 0000000000..c11e6499a5 --- /dev/null +++ b/OpenSim/Data/IGroupsData.cs @@ -0,0 +1,144 @@ +/* + * 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.Collections.Generic; +using OpenSim.Data; +using OpenMetaverse; + +namespace OpenSim.Data +{ + public class GroupData + { + public UUID GroupID; + public Dictionary Data; + } + + public class MembershipData + { + public UUID GroupID; + public string PrincipalID; + public Dictionary Data; + } + + public class RoleData + { + public UUID GroupID; + public UUID RoleID; + public Dictionary Data; + } + + public class RoleMembershipData + { + public UUID GroupID; + public UUID RoleID; + public string PrincipalID; + } + + public class PrincipalData + { + public string PrincipalID; + public UUID ActiveGroupID; + } + + public class InvitationData + { + public UUID InviteID; + public UUID GroupID; + public UUID RoleID; + public string PrincipalID; + public Dictionary Data; + } + + public class NoticeData + { + public UUID GroupID; + public UUID NoticeID; + public Dictionary Data; + } + + + public interface IGroupsData + { + // groups table + bool StoreGroup(GroupData data); + GroupData RetrieveGroup(UUID groupID); + GroupData RetrieveGroup(string name); + GroupData[] RetrieveGroups(string pattern); + bool DeleteGroup(UUID groupID); + int GroupsCount(); + + // membership table + MembershipData RetrieveMember(UUID groupID, string pricipalID); + MembershipData[] RetrieveMembers(UUID groupID); + MembershipData[] RetrieveMemberships(string pricipalID); + bool StoreMember(MembershipData data); + bool DeleteMember(UUID groupID, string pricipalID); + int MemberCount(UUID groupID); + + // roles table + bool StoreRole(RoleData data); + RoleData RetrieveRole(UUID groupID, UUID roleID); + RoleData[] RetrieveRoles(UUID groupID); + bool DeleteRole(UUID groupID, UUID roleID); + int RoleCount(UUID groupID); + + // rolememberhip table + RoleMembershipData[] RetrieveRolesMembers(UUID groupID); + RoleMembershipData[] RetrieveRoleMembers(UUID groupID, UUID roleID); + RoleMembershipData[] RetrieveMemberRoles(UUID groupID, string principalID); + RoleMembershipData RetrieveRoleMember(UUID groupID, UUID roleID, string principalID); + int RoleMemberCount(UUID groupID, UUID roleID); + bool StoreRoleMember(RoleMembershipData data); + bool DeleteRoleMember(RoleMembershipData data); + bool DeleteMemberAllRoles(UUID groupID, string principalID); + + // principals table + bool StorePrincipal(PrincipalData data); + PrincipalData RetrievePrincipal(string principalID); + bool DeletePrincipal(string principalID); + + // invites table + bool StoreInvitation(InvitationData data); + InvitationData RetrieveInvitation(UUID inviteID); + InvitationData RetrieveInvitation(UUID groupID, string principalID); + bool DeleteInvite(UUID inviteID); + void DeleteOldInvites(); + + // notices table + bool StoreNotice(NoticeData data); + NoticeData RetrieveNotice(UUID noticeID); + NoticeData[] RetrieveNotices(UUID groupID); + bool DeleteNotice(UUID noticeID); + void DeleteOldNotices(); + + // combinations + MembershipData RetrievePrincipalGroupMembership(string principalID, UUID groupID); + MembershipData[] RetrievePrincipalGroupMemberships(string principalID); + + // Misc + } +} diff --git a/OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs b/OpenSim/Data/IOfflineIMData.cs similarity index 78% rename from OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs rename to OpenSim/Data/IOfflineIMData.cs index f3be02842a..e780304ec8 100644 --- a/OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs +++ b/OpenSim/Data/IOfflineIMData.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -25,22 +25,25 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System.Collections.Generic; +using OpenSim.Data; using OpenMetaverse; -namespace OpenSim.Region.Framework.Scenes.Scripting +namespace OpenSim.Data { - public interface IScriptHost + public class OfflineIMData { - string Name { get; set; } - string Description { get; set; } + public UUID PrincipalID; + public Dictionary Data; + } - UUID UUID { get; } - UUID OwnerID { get; } - UUID CreatorID { get; } - Vector3 AbsolutePosition { get; } - string SitName { get; set; } - string TouchName { get; set; } - void SetText(string text, Vector3 color, double alpha); + public interface IOfflineIMData + { + OfflineIMData[] Get(string field, string val); + long GetCount(string field, string key); + bool Store(OfflineIMData data); + bool Delete(string field, string val); + void DeleteOld(); } } diff --git a/OpenSim/Data/IPresenceData.cs b/OpenSim/Data/IPresenceData.cs index b871f5636c..9ec48b0e2a 100644 --- a/OpenSim/Data/IPresenceData.cs +++ b/OpenSim/Data/IPresenceData.cs @@ -53,5 +53,6 @@ namespace OpenSim.Data bool ReportAgent(UUID sessionID, UUID regionID); PresenceData[] Get(string field, string data); bool Delete(string field, string val); + bool VerifyAgent(UUID agentId, UUID secureSessionID); } } diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs b/OpenSim/Data/IXGroupData.cs similarity index 60% rename from OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs rename to OpenSim/Data/IXGroupData.cs index a88fe88449..2965e8cc3d 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs +++ b/OpenSim/Data/IXGroupData.cs @@ -25,35 +25,47 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using OpenSim.Framework.Servers.HttpServer; +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; -namespace OpenSim.ApplicationPlugins.Rest.Inventory +namespace OpenSim.Data { - - /// - /// The handler delegates are not noteworthy. The allocator allows - /// a given handler to optionally subclass the base RequestData - /// structure to carry any locally required per-request state - /// needed. - /// - - public delegate void RestMethodHandler(RequestData rdata); - public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path); - - /// - /// This interface exports the generic plugin-handling services - /// available to each loaded REST services module (IRest implementation) - /// - - internal interface IRestHandler + public class XGroup { + public UUID groupID; + public UUID ownerRoleID; + public string name; + public string charter; + public bool showInList; + public UUID insigniaID; + public int membershipFee; + public bool openEnrollment; + public bool allowPublish; + public bool maturePublish; + public UUID founderID; + public ulong everyonePowers; + public ulong ownersPowers; - string MsgId { get; } - string RequestId { get; } - - void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ma); - void AddStreamHandler(string httpMethod, string path, RestMethod method); - + public XGroup Clone() + { + return (XGroup)MemberwiseClone(); + } } -} + /// + /// Early stub interface for groups data, not final. + /// + /// + /// Currently in-use only for regression test purposes. Needs to be filled out over time. + /// + public interface IXGroupData + { + bool StoreGroup(XGroup group); + XGroup[] GetGroups(string field, string val); + XGroup[] GetGroups(string[] fields, string[] vals); + bool DeleteGroups(string field, string val); + bool DeleteGroups(string[] fields, string[] vals); + } +} \ No newline at end of file diff --git a/OpenSim/Data/MSSQL/MSSQLAssetData.cs b/OpenSim/Data/MSSQL/MSSQLAssetData.cs index c882555231..12f247772b 100644 --- a/OpenSim/Data/MSSQL/MSSQLAssetData.cs +++ b/OpenSim/Data/MSSQL/MSSQLAssetData.cs @@ -163,14 +163,18 @@ namespace OpenSim.Data.MSSQL if (asset.Name.Length > 64) { assetName = asset.Name.Substring(0, 64); - m_log.Warn("[ASSET DB]: Name field truncated from " + asset.Name.Length + " to " + assetName.Length + " characters on add"); + m_log.WarnFormat( + "[ASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Name, asset.ID, asset.Name.Length, assetName.Length); } string assetDescription = asset.Description; if (asset.Description.Length > 64) { assetDescription = asset.Description.Substring(0, 64); - m_log.Warn("[ASSET DB]: Description field truncated from " + asset.Description.Length + " to " + assetDescription.Length + " characters on add"); + m_log.WarnFormat( + "[ASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Description, asset.ID, asset.Description.Length, assetDescription.Length); } using (SqlConnection conn = new SqlConnection(m_connectionString)) diff --git a/OpenSim/Data/MSSQL/MSSQLPresenceData.cs b/OpenSim/Data/MSSQL/MSSQLPresenceData.cs index e7b3d9c367..deff2ed437 100644 --- a/OpenSim/Data/MSSQL/MSSQLPresenceData.cs +++ b/OpenSim/Data/MSSQL/MSSQLPresenceData.cs @@ -100,5 +100,18 @@ namespace OpenSim.Data.MSSQL return true; } + public bool VerifyAgent(UUID agentId, UUID secureSessionID) + { + PresenceData[] ret = Get("SecureSessionID", + secureSessionID.ToString()); + + if (ret.Length == 0) + return false; + + if(ret[0].UserID != agentId.ToString()) + return false; + + return true; + } } } diff --git a/OpenSim/Data/MSSQL/MSSQLSimulationData.cs b/OpenSim/Data/MSSQL/MSSQLSimulationData.cs index 5bb6ec9ce5..00af3a01e2 100644 --- a/OpenSim/Data/MSSQL/MSSQLSimulationData.cs +++ b/OpenSim/Data/MSSQL/MSSQLSimulationData.cs @@ -351,7 +351,8 @@ IF EXISTS (SELECT UUID FROM prims WHERE UUID = @UUID) ScriptAccessPin = @ScriptAccessPin, AllowedDrop = @AllowedDrop, DieAtEdge = @DieAtEdge, SalePrice = @SalePrice, SaleType = @SaleType, ColorR = @ColorR, ColorG = @ColorG, ColorB = @ColorB, ColorA = @ColorA, ParticleSystem = @ParticleSystem, ClickAction = @ClickAction, Material = @Material, CollisionSound = @CollisionSound, CollisionSoundVolume = @CollisionSoundVolume, PassTouches = @PassTouches, - LinkNumber = @LinkNumber, MediaURL = @MediaURL + LinkNumber = @LinkNumber, MediaURL = @MediaURL, DynAttrs = @DynAttrs, + PhysicsShapeType = @PhysicsShapeType, Density = @Density, GravityModifier = @GravityModifier, Friction = @Friction, Restitution = @Restitution WHERE UUID = @UUID END ELSE @@ -366,7 +367,8 @@ ELSE PayPrice, PayButton1, PayButton2, PayButton3, PayButton4, LoopedSound, LoopedSoundGain, TextureAnimation, OmegaX, OmegaY, OmegaZ, CameraEyeOffsetX, CameraEyeOffsetY, CameraEyeOffsetZ, CameraAtOffsetX, CameraAtOffsetY, CameraAtOffsetZ, ForceMouselook, ScriptAccessPin, AllowedDrop, DieAtEdge, SalePrice, SaleType, ColorR, ColorG, ColorB, ColorA, - ParticleSystem, ClickAction, Material, CollisionSound, CollisionSoundVolume, PassTouches, LinkNumber, MediaURL + ParticleSystem, ClickAction, Material, CollisionSound, CollisionSoundVolume, PassTouches, LinkNumber, MediaURL, DynAttrs, + PhysicsShapeType, Density, GravityModifier, Friction, Restitution ) VALUES ( @UUID, @CreationDate, @Name, @Text, @Description, @SitName, @TouchName, @ObjectFlags, @OwnerMask, @NextOwnerMask, @GroupMask, @EveryoneMask, @BaseMask, @PositionX, @PositionY, @PositionZ, @GroupPositionX, @GroupPositionY, @GroupPositionZ, @VelocityX, @@ -376,7 +378,8 @@ ELSE @PayPrice, @PayButton1, @PayButton2, @PayButton3, @PayButton4, @LoopedSound, @LoopedSoundGain, @TextureAnimation, @OmegaX, @OmegaY, @OmegaZ, @CameraEyeOffsetX, @CameraEyeOffsetY, @CameraEyeOffsetZ, @CameraAtOffsetX, @CameraAtOffsetY, @CameraAtOffsetZ, @ForceMouselook, @ScriptAccessPin, @AllowedDrop, @DieAtEdge, @SalePrice, @SaleType, @ColorR, @ColorG, @ColorB, @ColorA, - @ParticleSystem, @ClickAction, @Material, @CollisionSound, @CollisionSoundVolume, @PassTouches, @LinkNumber, @MediaURL + @ParticleSystem, @ClickAction, @Material, @CollisionSound, @CollisionSoundVolume, @PassTouches, @LinkNumber, @MediaURL, @DynAttrs, + @PhysicsShapeType, @Density, @GravityModifier, @Friction, @Restitution ) END"; @@ -1691,6 +1694,17 @@ VALUES if (!(primRow["MediaURL"] is System.DBNull)) prim.MediaUrl = (string)primRow["MediaURL"]; + + if (!(primRow["DynAttrs"] is System.DBNull)) + prim.DynAttrs = DAMap.FromXml((string)primRow["DynAttrs"]); + else + prim.DynAttrs = new DAMap(); + + prim.PhysicsShapeType = Convert.ToByte(primRow["PhysicsShapeType"]); + prim.Density = Convert.ToSingle(primRow["Density"]); + prim.GravityModifier = Convert.ToSingle(primRow["GravityModifier"]); + prim.Friction = Convert.ToSingle(primRow["Friction"]); + prim.Restitution = Convert.ToSingle(primRow["Restitution"]); return prim; } @@ -1749,7 +1763,6 @@ VALUES baseShape.Media = PrimitiveBaseShape.MediaList.FromXml((string)shapeRow["Media"]); } - return baseShape; } @@ -2086,6 +2099,17 @@ VALUES parameters.Add(_Database.CreateParameter("PassTouches", 0)); parameters.Add(_Database.CreateParameter("LinkNumber", prim.LinkNum)); parameters.Add(_Database.CreateParameter("MediaURL", prim.MediaUrl)); + + if (prim.DynAttrs.Count > 0) + parameters.Add(_Database.CreateParameter("DynAttrs", prim.DynAttrs.ToXml())); + else + parameters.Add(_Database.CreateParameter("DynAttrs", null)); + + parameters.Add(_Database.CreateParameter("PhysicsShapeType", prim.PhysicsShapeType)); + parameters.Add(_Database.CreateParameter("Density", (double)prim.Density)); + parameters.Add(_Database.CreateParameter("GravityModifier", (double)prim.GravityModifier)); + parameters.Add(_Database.CreateParameter("Friction", (double)prim.Friction)); + parameters.Add(_Database.CreateParameter("Restitution", (double)prim.Restitution)); return parameters.ToArray(); } @@ -2143,7 +2167,6 @@ VALUES parameters.Add(_Database.CreateParameter("Media", s.Media.ToXml())); } - return parameters.ToArray(); } diff --git a/OpenSim/Data/MSSQL/Properties/AssemblyInfo.cs b/OpenSim/Data/MSSQL/Properties/AssemblyInfo.cs index 1a67e704e5..9bc580e76a 100644 --- a/OpenSim/Data/MSSQL/Properties/AssemblyInfo.cs +++ b/OpenSim/Data/MSSQL/Properties/AssemblyInfo.cs @@ -61,5 +61,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] +[assembly : AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Data/MSSQL/Resources/RegionStore.migrations b/OpenSim/Data/MSSQL/Resources/RegionStore.migrations index 350e548a0a..b84c2a4dd6 100644 --- a/OpenSim/Data/MSSQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MSSQL/Resources/RegionStore.migrations @@ -1148,3 +1148,23 @@ CREATE TABLE [dbo].[regionenvironment]( ) ON [PRIMARY] COMMIT + +:VERSION 38 #---------------- Dynamic attributes + +BEGIN TRANSACTION + +ALTER TABLE prims ADD COLUMN DynAttrs TEXT; + +COMMIT + +:VERSION 39 #---------------- Extra physics params + +BEGIN TRANSACTION + +ALTER TABLE prims ADD COLUMN `PhysicsShapeType` tinyint(4) NOT NULL default '0'; +ALTER TABLE prims ADD COLUMN `Density` double NOT NULL default '1000'; +ALTER TABLE prims ADD COLUMN `GravityModifier` double NOT NULL default '1'; +ALTER TABLE prims ADD COLUMN `Friction` double NOT NULL default '0.6'; +ALTER TABLE prims ADD COLUMN `Restitution` double NOT NULL default '0.5'; + +COMMIT diff --git a/OpenSim/Data/MySQL/MySQLAssetData.cs b/OpenSim/Data/MySQL/MySQLAssetData.cs index 20df234d5d..21dd5aa52b 100644 --- a/OpenSim/Data/MySQL/MySQLAssetData.cs +++ b/OpenSim/Data/MySQL/MySQLAssetData.cs @@ -173,14 +173,18 @@ namespace OpenSim.Data.MySQL if (asset.Name.Length > 64) { assetName = asset.Name.Substring(0, 64); - m_log.Warn("[ASSET DB]: Name field truncated from " + asset.Name.Length + " to " + assetName.Length + " characters on add"); + m_log.WarnFormat( + "[ASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Name, asset.ID, asset.Name.Length, assetName.Length); } string assetDescription = asset.Description; if (asset.Description.Length > 64) { assetDescription = asset.Description.Substring(0, 64); - m_log.Warn("[ASSET DB]: Description field truncated from " + asset.Description.Length + " to " + assetDescription.Length + " characters on add"); + m_log.WarnFormat( + "[ASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Description, asset.ID, asset.Description.Length, assetDescription.Length); } try diff --git a/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs b/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs index f6731c0fa1..dc657c82a8 100644 --- a/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs +++ b/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs @@ -306,5 +306,65 @@ namespace OpenSim.Data.MySQL return ExecuteNonQuery(cmd) > 0; } } + + public long GetCount(string field, string key) + { + return GetCount(new string[] { field }, new string[] { key }); + } + + public long GetCount(string[] fields, string[] keys) + { + if (fields.Length != keys.Length) + return 0; + + List terms = new List(); + + using (MySqlCommand cmd = new MySqlCommand()) + { + for (int i = 0; i < fields.Length; i++) + { + cmd.Parameters.AddWithValue(fields[i], keys[i]); + terms.Add("`" + fields[i] + "` = ?" + fields[i]); + } + + string where = String.Join(" and ", terms.ToArray()); + + string query = String.Format("select count(*) from {0} where {1}", + m_Realm, where); + + cmd.CommandText = query; + + Object result = DoQueryScalar(cmd); + + return Convert.ToInt64(result); + } + } + + public long GetCount(string where) + { + using (MySqlCommand cmd = new MySqlCommand()) + { + string query = String.Format("select count(*) from {0} where {1}", + m_Realm, where); + + cmd.CommandText = query; + + object result = DoQueryScalar(cmd); + + return Convert.ToInt64(result); + } + } + + public object DoQueryScalar(MySqlCommand cmd) + { + using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) + { + dbcon.Open(); + cmd.Connection = dbcon; + + return cmd.ExecuteScalar(); + } + } + } } diff --git a/OpenSim/Data/MySQL/MySQLGroupsData.cs b/OpenSim/Data/MySQL/MySQLGroupsData.cs new file mode 100644 index 0000000000..2a1bd6c48e --- /dev/null +++ b/OpenSim/Data/MySQL/MySQLGroupsData.cs @@ -0,0 +1,484 @@ +/* + * 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; +using System.Collections.Generic; +using System.Reflection; + +using OpenSim.Framework; +using OpenSim.Data.MySQL; + +using OpenMetaverse; +using MySql.Data.MySqlClient; + +namespace OpenSim.Data.MySQL +{ + public class MySQLGroupsData : IGroupsData + { + private MySqlGroupsGroupsHandler m_Groups; + private MySqlGroupsMembershipHandler m_Membership; + private MySqlGroupsRolesHandler m_Roles; + private MySqlGroupsRoleMembershipHandler m_RoleMembership; + private MySqlGroupsInvitesHandler m_Invites; + private MySqlGroupsNoticesHandler m_Notices; + private MySqlGroupsPrincipalsHandler m_Principals; + + public MySQLGroupsData(string connectionString, string realm) + { + m_Groups = new MySqlGroupsGroupsHandler(connectionString, realm + "_groups", realm + "_Store"); + m_Membership = new MySqlGroupsMembershipHandler(connectionString, realm + "_membership"); + m_Roles = new MySqlGroupsRolesHandler(connectionString, realm + "_roles"); + m_RoleMembership = new MySqlGroupsRoleMembershipHandler(connectionString, realm + "_rolemembership"); + m_Invites = new MySqlGroupsInvitesHandler(connectionString, realm + "_invites"); + m_Notices = new MySqlGroupsNoticesHandler(connectionString, realm + "_notices"); + m_Principals = new MySqlGroupsPrincipalsHandler(connectionString, realm + "_principals"); + } + + #region groups table + public bool StoreGroup(GroupData data) + { + return m_Groups.Store(data); + } + + public GroupData RetrieveGroup(UUID groupID) + { + GroupData[] groups = m_Groups.Get("GroupID", groupID.ToString()); + if (groups.Length > 0) + return groups[0]; + + return null; + } + + public GroupData RetrieveGroup(string name) + { + GroupData[] groups = m_Groups.Get("Name", name); + if (groups.Length > 0) + return groups[0]; + + return null; + } + + public GroupData[] RetrieveGroups(string pattern) + { + if (string.IsNullOrEmpty(pattern)) + pattern = "1 ORDER BY Name LIMIT 100"; + else + pattern = string.Format("Name LIKE %{0}% ORDER BY Name LIMIT 100", pattern); + + return m_Groups.Get(pattern); + } + + public bool DeleteGroup(UUID groupID) + { + return m_Groups.Delete("GroupID", groupID.ToString()); + } + + public int GroupsCount() + { + return (int)m_Groups.GetCount("Location=\"\""); + } + + #endregion + + #region membership table + public MembershipData[] RetrieveMembers(UUID groupID) + { + return m_Membership.Get("GroupID", groupID.ToString()); + } + + public MembershipData RetrieveMember(UUID groupID, string pricipalID) + { + MembershipData[] m = m_Membership.Get(new string[] { "GroupID", "PrincipalID" }, + new string[] { groupID.ToString(), pricipalID }); + if (m != null && m.Length > 0) + return m[0]; + + return null; + } + + public MembershipData[] RetrieveMemberships(string pricipalID) + { + return m_Membership.Get("PrincipalID", pricipalID.ToString()); + } + + public bool StoreMember(MembershipData data) + { + return m_Membership.Store(data); + } + + public bool DeleteMember(UUID groupID, string pricipalID) + { + return m_Membership.Delete(new string[] { "GroupID", "PrincipalID" }, + new string[] { groupID.ToString(), pricipalID }); + } + + public int MemberCount(UUID groupID) + { + return (int)m_Membership.GetCount("GroupID", groupID.ToString()); + } + #endregion + + #region roles table + public bool StoreRole(RoleData data) + { + return m_Roles.Store(data); + } + + public RoleData RetrieveRole(UUID groupID, UUID roleID) + { + RoleData[] data = m_Roles.Get(new string[] { "GroupID", "RoleID" }, + new string[] { groupID.ToString(), roleID.ToString() }); + + if (data != null && data.Length > 0) + return data[0]; + + return null; + } + + public RoleData[] RetrieveRoles(UUID groupID) + { + //return m_Roles.RetrieveRoles(groupID); + return m_Roles.Get("GroupID", groupID.ToString()); + } + + public bool DeleteRole(UUID groupID, UUID roleID) + { + return m_Roles.Delete(new string[] { "GroupID", "RoleID" }, + new string[] { groupID.ToString(), roleID.ToString() }); + } + + public int RoleCount(UUID groupID) + { + return (int)m_Roles.GetCount("GroupID", groupID.ToString()); + } + + + #endregion + + #region rolememberhip table + public RoleMembershipData[] RetrieveRolesMembers(UUID groupID) + { + RoleMembershipData[] data = m_RoleMembership.Get("GroupID", groupID.ToString()); + + return data; + } + + public RoleMembershipData[] RetrieveRoleMembers(UUID groupID, UUID roleID) + { + RoleMembershipData[] data = m_RoleMembership.Get(new string[] { "GroupID", "RoleID" }, + new string[] { groupID.ToString(), roleID.ToString() }); + + return data; + } + + public RoleMembershipData[] RetrieveMemberRoles(UUID groupID, string principalID) + { + RoleMembershipData[] data = m_RoleMembership.Get(new string[] { "GroupID", "PrincipalID" }, + new string[] { groupID.ToString(), principalID.ToString() }); + + return data; + } + + public RoleMembershipData RetrieveRoleMember(UUID groupID, UUID roleID, string principalID) + { + RoleMembershipData[] data = m_RoleMembership.Get(new string[] { "GroupID", "RoleID", "PrincipalID" }, + new string[] { groupID.ToString(), roleID.ToString(), principalID.ToString() }); + + if (data != null && data.Length > 0) + return data[0]; + + return null; + } + + public int RoleMemberCount(UUID groupID, UUID roleID) + { + return (int)m_RoleMembership.GetCount(new string[] { "GroupID", "RoleID" }, + new string[] { groupID.ToString(), roleID.ToString() }); + } + + public bool StoreRoleMember(RoleMembershipData data) + { + return m_RoleMembership.Store(data); + } + + public bool DeleteRoleMember(RoleMembershipData data) + { + return m_RoleMembership.Delete(new string[] { "GroupID", "RoleID", "PrincipalID"}, + new string[] { data.GroupID.ToString(), data.RoleID.ToString(), data.PrincipalID }); + } + + public bool DeleteMemberAllRoles(UUID groupID, string principalID) + { + return m_RoleMembership.Delete(new string[] { "GroupID", "PrincipalID" }, + new string[] { groupID.ToString(), principalID }); + } + + #endregion + + #region principals table + public bool StorePrincipal(PrincipalData data) + { + return m_Principals.Store(data); + } + + public PrincipalData RetrievePrincipal(string principalID) + { + PrincipalData[] p = m_Principals.Get("PrincipalID", principalID); + if (p != null && p.Length > 0) + return p[0]; + + return null; + } + + public bool DeletePrincipal(string principalID) + { + return m_Principals.Delete("PrincipalID", principalID); + } + #endregion + + #region invites table + + public bool StoreInvitation(InvitationData data) + { + return m_Invites.Store(data); + } + + public InvitationData RetrieveInvitation(UUID inviteID) + { + InvitationData[] invites = m_Invites.Get("InviteID", inviteID.ToString()); + + if (invites != null && invites.Length > 0) + return invites[0]; + + return null; + } + + public InvitationData RetrieveInvitation(UUID groupID, string principalID) + { + InvitationData[] invites = m_Invites.Get(new string[] { "GroupID", "PrincipalID" }, + new string[] { groupID.ToString(), principalID }); + + if (invites != null && invites.Length > 0) + return invites[0]; + + return null; + } + + public bool DeleteInvite(UUID inviteID) + { + return m_Invites.Delete("InviteID", inviteID.ToString()); + } + + public void DeleteOldInvites() + { + m_Invites.DeleteOld(); + } + + #endregion + + #region notices table + + public bool StoreNotice(NoticeData data) + { + return m_Notices.Store(data); + } + + public NoticeData RetrieveNotice(UUID noticeID) + { + NoticeData[] notices = m_Notices.Get("NoticeID", noticeID.ToString()); + + if (notices != null && notices.Length > 0) + return notices[0]; + + return null; + } + + public NoticeData[] RetrieveNotices(UUID groupID) + { + NoticeData[] notices = m_Notices.Get("GroupID", groupID.ToString()); + + return notices; + } + + public bool DeleteNotice(UUID noticeID) + { + return m_Notices.Delete("NoticeID", noticeID.ToString()); + } + + public void DeleteOldNotices() + { + m_Notices.DeleteOld(); + } + + #endregion + + #region combinations + public MembershipData RetrievePrincipalGroupMembership(string principalID, UUID groupID) + { + // TODO + return null; + } + public MembershipData[] RetrievePrincipalGroupMemberships(string principalID) + { + // TODO + return null; + } + + #endregion + } + + public class MySqlGroupsGroupsHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsGroupsHandler(string connectionString, string realm, string store) + : base(connectionString, realm, store) + { + } + + } + + public class MySqlGroupsMembershipHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsMembershipHandler(string connectionString, string realm) + : base(connectionString, realm, string.Empty) + { + } + + } + + public class MySqlGroupsRolesHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsRolesHandler(string connectionString, string realm) + : base(connectionString, realm, string.Empty) + { + } + + } + + public class MySqlGroupsRoleMembershipHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsRoleMembershipHandler(string connectionString, string realm) + : base(connectionString, realm, string.Empty) + { + } + + } + + public class MySqlGroupsInvitesHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsInvitesHandler(string connectionString, string realm) + : base(connectionString, realm, string.Empty) + { + } + + public void DeleteOld() + { + uint now = (uint)Util.UnixTimeSinceEpoch(); + + using (MySqlCommand cmd = new MySqlCommand()) + { + cmd.CommandText = String.Format("delete from {0} where TMStamp < ?tstamp", m_Realm); + cmd.Parameters.AddWithValue("?tstamp", now - 14 * 24 * 60 * 60); // > 2 weeks old + + ExecuteNonQuery(cmd); + } + + } + } + + public class MySqlGroupsNoticesHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsNoticesHandler(string connectionString, string realm) + : base(connectionString, realm, string.Empty) + { + } + + public void DeleteOld() + { + uint now = (uint)Util.UnixTimeSinceEpoch(); + + using (MySqlCommand cmd = new MySqlCommand()) + { + cmd.CommandText = String.Format("delete from {0} where TMStamp < ?tstamp", m_Realm); + cmd.Parameters.AddWithValue("?tstamp", now - 14 * 24 * 60 * 60); // > 2 weeks old + + ExecuteNonQuery(cmd); + } + + } + } + + public class MySqlGroupsPrincipalsHandler : MySQLGenericTableHandler + { + protected override Assembly Assembly + { + // WARNING! Moving migrations to this assembly!!! + get { return GetType().Assembly; } + } + + public MySqlGroupsPrincipalsHandler(string connectionString, string realm) + : base(connectionString, realm, string.Empty) + { + } + } +} diff --git a/OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs b/OpenSim/Data/MySQL/MySQLOfflineIMData.cs similarity index 65% rename from OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs rename to OpenSim/Data/MySQL/MySQLOfflineIMData.cs index 283fa2e134..252f358b7c 100644 --- a/OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs +++ b/OpenSim/Data/MySQL/MySQLOfflineIMData.cs @@ -25,48 +25,38 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System.IO; -using System.Text; -using System.Xml; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; -namespace OpenSim.ApplicationPlugins.Rest +using OpenSim.Framework; +using OpenSim.Data.MySQL; + +using OpenMetaverse; +using MySql.Data.MySqlClient; + +namespace OpenSim.Data.MySQL { - public class RestXmlWriter: XmlTextWriter + public class MySQLOfflineIMData : MySQLGenericTableHandler, IOfflineIMData { - private StringWriter m_sw = null; - - public RestXmlWriter(StringWriter sw) : base(sw) - { - m_sw = sw; - Formatting = Formatting.Indented; - } - - public RestXmlWriter(TextWriter textWriter) : base(textWriter) + public MySQLOfflineIMData(string connectionString, string realm) + : base(connectionString, realm, "IM_Store") { } - public RestXmlWriter(Stream stream) - : this(stream, Encoding.UTF8) + public void DeleteOld() { - } + uint now = (uint)Util.UnixTimeSinceEpoch(); - public RestXmlWriter(Stream stream, Encoding enc) : base(stream, enc) - { - } + using (MySqlCommand cmd = new MySqlCommand()) + { + cmd.CommandText = String.Format("delete from {0} where TMStamp < ?tstamp", m_Realm); + cmd.Parameters.AddWithValue("?tstamp", now - 14 * 24 * 60 * 60); // > 2 weeks old - public override void WriteStartDocument() - { - } + ExecuteNonQuery(cmd); + } - public override void WriteStartDocument(bool standalone) - { - } - - public override string ToString() - { - Flush(); - Close(); - return m_sw.ToString(); } } } diff --git a/OpenSim/Data/MySQL/MySQLPresenceData.cs b/OpenSim/Data/MySQL/MySQLPresenceData.cs index 780806087f..3f906399ec 100644 --- a/OpenSim/Data/MySQL/MySQLPresenceData.cs +++ b/OpenSim/Data/MySQL/MySQLPresenceData.cs @@ -95,5 +95,19 @@ namespace OpenSim.Data.MySQL return true; } + + public bool VerifyAgent(UUID agentId, UUID secureSessionID) + { + PresenceData[] ret = Get("SecureSessionID", + secureSessionID.ToString()); + + if (ret.Length == 0) + return false; + + if(ret[0].UserID != agentId.ToString()) + return false; + + return true; + } } } \ No newline at end of file diff --git a/OpenSim/Data/MySQL/MySQLSimulationData.cs b/OpenSim/Data/MySQL/MySQLSimulationData.cs index d5a4f46d12..5320543f4d 100644 --- a/OpenSim/Data/MySQL/MySQLSimulationData.cs +++ b/OpenSim/Data/MySQL/MySQLSimulationData.cs @@ -176,7 +176,7 @@ namespace OpenSim.Data.MySQL "PassCollisions, " + "LinkNumber, MediaURL, KeyframeMotion, " + "PhysicsShapeType, Density, GravityModifier, " + - "Friction, Restitution, Vehicle " + + "Friction, Restitution, Vehicle, DynAttrs " + ") values (" + "?UUID, " + "?CreationDate, ?Name, ?Text, " + "?Description, ?SitName, ?TouchName, " + @@ -211,7 +211,7 @@ namespace OpenSim.Data.MySQL "?CollisionSoundVolume, ?PassTouches, ?PassCollisions, " + "?LinkNumber, ?MediaURL, ?KeyframeMotion, " + "?PhysicsShapeType, ?Density, ?GravityModifier, " + - "?Friction, ?Restitution, ?Vehicle)"; + "?Friction, ?Restitution, ?Vehicle, ?DynAttrs)"; FillPrimCommand(cmd, prim, obj.UUID, regionUUID); @@ -228,7 +228,8 @@ namespace OpenSim.Data.MySQL "PathTaperX, PathTaperY, PathTwist, " + "PathTwistBegin, ProfileBegin, ProfileEnd, " + "ProfileCurve, ProfileHollow, Texture, " + - "ExtraParams, State, Media) values (?UUID, " + + "ExtraParams, State, Media) " + + "values (?UUID, " + "?Shape, ?ScaleX, ?ScaleY, ?ScaleZ, " + "?PCode, ?PathBegin, ?PathEnd, " + "?PathScaleX, ?PathScaleY, " + @@ -1321,6 +1322,11 @@ namespace OpenSim.Data.MySQL if (!(row["MediaURL"] is System.DBNull)) prim.MediaUrl = (string)row["MediaURL"]; + + if (!(row["DynAttrs"] is System.DBNull)) + prim.DynAttrs = DAMap.FromXml((string)row["DynAttrs"]); + else + prim.DynAttrs = new DAMap(); if (!(row["KeyframeMotion"] is DBNull)) { @@ -1721,16 +1727,21 @@ namespace OpenSim.Data.MySQL else cmd.Parameters.AddWithValue("KeyframeMotion", new Byte[0]); + if (prim.VehicleParams != null) + cmd.Parameters.AddWithValue("Vehicle", prim.VehicleParams.ToXml2()); + else + cmd.Parameters.AddWithValue("Vehicle", String.Empty); + + if (prim.DynAttrs.Count > 0) + cmd.Parameters.AddWithValue("DynAttrs", prim.DynAttrs.ToXml()); + else + cmd.Parameters.AddWithValue("DynAttrs", null); + cmd.Parameters.AddWithValue("PhysicsShapeType", prim.PhysicsShapeType); cmd.Parameters.AddWithValue("Density", (double)prim.Density); cmd.Parameters.AddWithValue("GravityModifier", (double)prim.GravityModifier); cmd.Parameters.AddWithValue("Friction", (double)prim.Friction); cmd.Parameters.AddWithValue("Restitution", (double)prim.Restitution); - - if (prim.VehicleParams != null) - cmd.Parameters.AddWithValue("Vehicle", prim.VehicleParams.ToXml2()); - else - cmd.Parameters.AddWithValue("Vehicle", String.Empty); } /// diff --git a/OpenSim/Data/MySQL/MySQLXAssetData.cs b/OpenSim/Data/MySQL/MySQLXAssetData.cs index 9a503734be..15ac9215e2 100644 --- a/OpenSim/Data/MySQL/MySQLXAssetData.cs +++ b/OpenSim/Data/MySQL/MySQLXAssetData.cs @@ -50,6 +50,11 @@ namespace OpenSim.Data.MySQL get { return GetType().Assembly; } } + /// + /// Number of days that must pass before we update the access time on an asset when it has been fetched. + /// + private const int DaysBetweenAccessTimeUpdates = 30; + private bool m_enableCompression = false; private string m_connectionString; private object m_dbLock = new object(); @@ -133,10 +138,10 @@ namespace OpenSim.Data.MySQL dbcon.Open(); using (MySqlCommand cmd = new MySqlCommand( - "SELECT name, description, asset_type, local, temporary, asset_flags, creator_id, data FROM xassetsmeta JOIN xassetsdata ON xassetsmeta.hash = xassetsdata.hash WHERE id=?id", + "SELECT Name, Description, AccessTime, AssetType, Local, Temporary, AssetFlags, CreatorID, Data FROM XAssetsMeta JOIN XAssetsData ON XAssetsMeta.Hash = XAssetsData.Hash WHERE ID=?ID", dbcon)) { - cmd.Parameters.AddWithValue("?id", assetID.ToString()); + cmd.Parameters.AddWithValue("?ID", assetID.ToString()); try { @@ -144,18 +149,18 @@ namespace OpenSim.Data.MySQL { if (dbReader.Read()) { - asset = new AssetBase(assetID, (string)dbReader["name"], (sbyte)dbReader["asset_type"], dbReader["creator_id"].ToString()); - asset.Data = (byte[])dbReader["data"]; - asset.Description = (string)dbReader["description"]; + asset = new AssetBase(assetID, (string)dbReader["Name"], (sbyte)dbReader["AssetType"], dbReader["CreatorID"].ToString()); + asset.Data = (byte[])dbReader["Data"]; + asset.Description = (string)dbReader["Description"]; - string local = dbReader["local"].ToString(); + string local = dbReader["Local"].ToString(); if (local.Equals("1") || local.Equals("true", StringComparison.InvariantCultureIgnoreCase)) asset.Local = true; else asset.Local = false; - asset.Temporary = Convert.ToBoolean(dbReader["temporary"]); - asset.Flags = (AssetFlags)Convert.ToInt32(dbReader["asset_flags"]); + asset.Temporary = Convert.ToBoolean(dbReader["Temporary"]); + asset.Flags = (AssetFlags)Convert.ToInt32(dbReader["AssetFlags"]); if (m_enableCompression) { @@ -171,12 +176,14 @@ namespace OpenSim.Data.MySQL // asset.ID, asset.Name, asset.Data.Length, compressedLength); } } + + UpdateAccessTime(asset.Metadata, (int)dbReader["AccessTime"]); } } } catch (Exception e) { - m_log.Error("[MYSQL XASSET DATA]: MySql failure fetching asset " + assetID + ": " + e.Message); + m_log.Error(string.Format("[MYSQL XASSET DATA]: Failure fetching asset {0}", assetID), e); } } } @@ -204,14 +211,18 @@ namespace OpenSim.Data.MySQL if (asset.Name.Length > 64) { assetName = asset.Name.Substring(0, 64); - m_log.Warn("[XASSET DB]: Name field truncated from " + asset.Name.Length + " to " + assetName.Length + " characters on add"); + m_log.WarnFormat( + "[XASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Name, asset.ID, asset.Name.Length, assetName.Length); } string assetDescription = asset.Description; if (asset.Description.Length > 64) { assetDescription = asset.Description.Substring(0, 64); - m_log.Warn("[XASSET DB]: Description field truncated from " + asset.Description.Length + " to " + assetDescription.Length + " characters on add"); + m_log.WarnFormat( + "[XASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Description, asset.ID, asset.Description.Length, assetDescription.Length); } if (m_enableCompression) @@ -238,23 +249,23 @@ namespace OpenSim.Data.MySQL { using (MySqlCommand cmd = new MySqlCommand( - "replace INTO xassetsmeta(id, hash, name, description, asset_type, local, temporary, create_time, access_time, asset_flags, creator_id)" + - "VALUES(?id, ?hash, ?name, ?description, ?asset_type, ?local, ?temporary, ?create_time, ?access_time, ?asset_flags, ?creator_id)", + "replace INTO XAssetsMeta(ID, Hash, Name, Description, AssetType, Local, Temporary, CreateTime, AccessTime, AssetFlags, CreatorID)" + + "VALUES(?ID, ?Hash, ?Name, ?Description, ?AssetType, ?Local, ?Temporary, ?CreateTime, ?AccessTime, ?AssetFlags, ?CreatorID)", dbcon)) { // create unix epoch time int now = (int)Utils.DateTimeToUnixTime(DateTime.UtcNow); - cmd.Parameters.AddWithValue("?id", asset.ID); - cmd.Parameters.AddWithValue("?hash", hash); - cmd.Parameters.AddWithValue("?name", assetName); - cmd.Parameters.AddWithValue("?description", assetDescription); - cmd.Parameters.AddWithValue("?asset_type", asset.Type); - cmd.Parameters.AddWithValue("?local", asset.Local); - cmd.Parameters.AddWithValue("?temporary", asset.Temporary); - cmd.Parameters.AddWithValue("?create_time", now); - cmd.Parameters.AddWithValue("?access_time", now); - cmd.Parameters.AddWithValue("?creator_id", asset.Metadata.CreatorID); - cmd.Parameters.AddWithValue("?asset_flags", (int)asset.Flags); + cmd.Parameters.AddWithValue("?ID", asset.ID); + cmd.Parameters.AddWithValue("?Hash", hash); + cmd.Parameters.AddWithValue("?Name", assetName); + cmd.Parameters.AddWithValue("?Description", assetDescription); + cmd.Parameters.AddWithValue("?AssetType", asset.Type); + cmd.Parameters.AddWithValue("?Local", asset.Local); + cmd.Parameters.AddWithValue("?Temporary", asset.Temporary); + cmd.Parameters.AddWithValue("?CreateTime", now); + cmd.Parameters.AddWithValue("?AccessTime", now); + cmd.Parameters.AddWithValue("?CreatorID", asset.Metadata.CreatorID); + cmd.Parameters.AddWithValue("?AssetFlags", (int)asset.Flags); cmd.ExecuteNonQuery(); } } @@ -274,11 +285,11 @@ namespace OpenSim.Data.MySQL { using (MySqlCommand cmd = new MySqlCommand( - "INSERT INTO xassetsdata(hash, data) VALUES(?hash, ?data)", + "INSERT INTO XAssetsData(Hash, Data) VALUES(?Hash, ?Data)", dbcon)) { - cmd.Parameters.AddWithValue("?hash", hash); - cmd.Parameters.AddWithValue("?data", asset.Data); + cmd.Parameters.AddWithValue("?Hash", hash); + cmd.Parameters.AddWithValue("?Data", asset.Data); cmd.ExecuteNonQuery(); } } @@ -299,41 +310,49 @@ namespace OpenSim.Data.MySQL } } -// private void UpdateAccessTime(AssetBase asset) -// { -// lock (m_dbLock) -// { -// using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) -// { -// dbcon.Open(); -// MySqlCommand cmd = -// new MySqlCommand("update assets set access_time=?access_time where id=?id", -// dbcon); -// -// // need to ensure we dispose -// try -// { -// using (cmd) -// { -// // create unix epoch time -// int now = (int)Utils.DateTimeToUnixTime(DateTime.UtcNow); -// cmd.Parameters.AddWithValue("?id", asset.ID); -// cmd.Parameters.AddWithValue("?access_time", now); -// cmd.ExecuteNonQuery(); -// cmd.Dispose(); -// } -// } -// catch (Exception e) -// { -// m_log.ErrorFormat( -// "[ASSETS DB]: " + -// "MySql failure updating access_time for asset {0} with name {1}" + Environment.NewLine + e.ToString() -// + Environment.NewLine + "Attempting reconnection", asset.FullID, asset.Name); -// } -// } -// } -// -// } + /// + /// Updates the access time of the asset if it was accessed above a given threshhold amount of time. + /// + /// + /// This gives us some insight into assets which haven't ben accessed for a long period. This is only done + /// over the threshold time to avoid excessive database writes as assets are fetched. + /// + /// + /// + private void UpdateAccessTime(AssetMetadata assetMetadata, int accessTime) + { + DateTime now = DateTime.UtcNow; + + if ((now - Utils.UnixTimeToDateTime(accessTime)).TotalDays < DaysBetweenAccessTimeUpdates) + return; + + lock (m_dbLock) + { + using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) + { + dbcon.Open(); + MySqlCommand cmd = + new MySqlCommand("update XAssetsMeta set AccessTime=?AccessTime where ID=?ID", dbcon); + + try + { + using (cmd) + { + // create unix epoch time + cmd.Parameters.AddWithValue("?ID", assetMetadata.ID); + cmd.Parameters.AddWithValue("?AccessTime", (int)Utils.DateTimeToUnixTime(now)); + cmd.ExecuteNonQuery(); + } + } + catch (Exception e) + { + m_log.ErrorFormat( + "[XASSET MYSQL DB]: Failure updating access_time for asset {0} with name {1}", + assetMetadata.ID, assetMetadata.Name); + } + } + } + } /// /// We assume we already have the m_dbLock. @@ -349,9 +368,9 @@ namespace OpenSim.Data.MySQL bool exists = false; - using (MySqlCommand cmd = new MySqlCommand("SELECT hash FROM xassetsdata WHERE hash=?hash", dbcon)) + using (MySqlCommand cmd = new MySqlCommand("SELECT Hash FROM XAssetsData WHERE Hash=?Hash", dbcon)) { - cmd.Parameters.AddWithValue("?hash", hash); + cmd.Parameters.AddWithValue("?Hash", hash); try { @@ -391,9 +410,9 @@ namespace OpenSim.Data.MySQL using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) { dbcon.Open(); - using (MySqlCommand cmd = new MySqlCommand("SELECT id FROM xassetsmeta WHERE id=?id", dbcon)) + using (MySqlCommand cmd = new MySqlCommand("SELECT ID FROM XAssetsMeta WHERE ID=?ID", dbcon)) { - cmd.Parameters.AddWithValue("?id", uuid.ToString()); + cmd.Parameters.AddWithValue("?ID", uuid.ToString()); try { @@ -408,8 +427,7 @@ namespace OpenSim.Data.MySQL } catch (Exception e) { - m_log.ErrorFormat( - "[XASSETS DB]: MySql failure fetching asset {0}" + Environment.NewLine + e.ToString(), uuid); + m_log.Error(string.Format("[XASSETS DB]: MySql failure fetching asset {0}", uuid), e); } } } @@ -418,6 +436,7 @@ namespace OpenSim.Data.MySQL return assetExists; } + /// /// Returns a list of AssetMetadata objects. The list is a subset of /// the entire data set offset by containing @@ -435,7 +454,7 @@ namespace OpenSim.Data.MySQL using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) { dbcon.Open(); - MySqlCommand cmd = new MySqlCommand("SELECT name,description,asset_type,temporary,id,asset_flags,creator_id FROM xassetsmeta LIMIT ?start, ?count", dbcon); + MySqlCommand cmd = new MySqlCommand("SELECT Name, Description, AccessTime, AssetType, Temporary, ID, AssetFlags, CreatorID FROM XAssetsMeta LIMIT ?start, ?count", dbcon); cmd.Parameters.AddWithValue("?start", start); cmd.Parameters.AddWithValue("?count", count); @@ -446,17 +465,19 @@ namespace OpenSim.Data.MySQL while (dbReader.Read()) { AssetMetadata metadata = new AssetMetadata(); - metadata.Name = (string)dbReader["name"]; - metadata.Description = (string)dbReader["description"]; - metadata.Type = (sbyte)dbReader["asset_type"]; - metadata.Temporary = Convert.ToBoolean(dbReader["temporary"]); // Not sure if this is correct. - metadata.Flags = (AssetFlags)Convert.ToInt32(dbReader["asset_flags"]); - metadata.FullID = DBGuid.FromDB(dbReader["id"]); - metadata.CreatorID = dbReader["creator_id"].ToString(); + metadata.Name = (string)dbReader["Name"]; + metadata.Description = (string)dbReader["Description"]; + metadata.Type = (sbyte)dbReader["AssetType"]; + metadata.Temporary = Convert.ToBoolean(dbReader["Temporary"]); // Not sure if this is correct. + metadata.Flags = (AssetFlags)Convert.ToInt32(dbReader["AssetFlags"]); + metadata.FullID = DBGuid.FromDB(dbReader["ID"]); + metadata.CreatorID = dbReader["CreatorID"].ToString(); // We'll ignore this for now - it appears unused! // metadata.SHA1 = dbReader["hash"]); + UpdateAccessTime(metadata, (int)dbReader["AccessTime"]); + retList.Add(metadata); } } @@ -481,9 +502,9 @@ namespace OpenSim.Data.MySQL { dbcon.Open(); - using (MySqlCommand cmd = new MySqlCommand("delete from xassetsmeta where id=?id", dbcon)) + using (MySqlCommand cmd = new MySqlCommand("delete from XAssetsMeta where ID=?ID", dbcon)) { - cmd.Parameters.AddWithValue("?id", id); + cmd.Parameters.AddWithValue("?ID", id); cmd.ExecuteNonQuery(); } diff --git a/OpenSim/Data/MySQL/Properties/AssemblyInfo.cs b/OpenSim/Data/MySQL/Properties/AssemblyInfo.cs index ab3fe36e52..1146d92e9d 100644 --- a/OpenSim/Data/MySQL/Properties/AssemblyInfo.cs +++ b/OpenSim/Data/MySQL/Properties/AssemblyInfo.cs @@ -61,5 +61,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] +[assembly : AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Data/MySQL/Resources/IM_Store.migrations b/OpenSim/Data/MySQL/Resources/IM_Store.migrations new file mode 100644 index 0000000000..7cfcd43888 --- /dev/null +++ b/OpenSim/Data/MySQL/Resources/IM_Store.migrations @@ -0,0 +1,24 @@ +:VERSION 1 # -------------------------- + +BEGIN; + +CREATE TABLE `im_offline` ( + `ID` MEDIUMINT NOT NULL AUTO_INCREMENT, + `PrincipalID` char(36) NOT NULL default '', + `Message` text NOT NULL, + `TMStamp` timestamp NOT NULL, + PRIMARY KEY (`ID`), + KEY `PrincipalID` (`PrincipalID`) +) ENGINE=MyISAM; + +COMMIT; + +:VERSION 2 # -------------------------- + +BEGIN; + +INSERT INTO `im_offline` SELECT * from `diva_im_offline`; +DROP TABLE `diva_im_offline`; +DELETE FROM `migrations` WHERE name='diva_im_Store'; + +COMMIT; \ No newline at end of file diff --git a/OpenSim/Data/MySQL/Resources/RegionStore.migrations b/OpenSim/Data/MySQL/Resources/RegionStore.migrations index c4b0832013..bda1b6a60d 100644 --- a/OpenSim/Data/MySQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MySQL/Resources/RegionStore.migrations @@ -902,3 +902,23 @@ BEGIN; CREATE TABLE `regionextra` (`RegionID` char(36) not null, `Name` varchar(32) not null, `value` text, primary key(`RegionID`, `Name`)); COMMIT; + +:VERSION 46 #---------------- Dynamic attributes + +BEGIN; + +ALTER TABLE prims ADD COLUMN DynAttrs TEXT; + +COMMIT; + +:VERSION 47 #---------------- Extra physics params + +BEGIN; + +ALTER TABLE prims ADD COLUMN `PhysicsShapeType` tinyint(4) NOT NULL default '0'; +ALTER TABLE prims ADD COLUMN `Density` double NOT NULL default '1000'; +ALTER TABLE prims ADD COLUMN `GravityModifier` double NOT NULL default '1'; +ALTER TABLE prims ADD COLUMN `Friction` double NOT NULL default '0.6'; +ALTER TABLE prims ADD COLUMN `Restitution` double NOT NULL default '0.5'; + +COMMIT; diff --git a/OpenSim/Data/MySQL/Resources/XAssetStore.migrations b/OpenSim/Data/MySQL/Resources/XAssetStore.migrations index d3cca5e884..0c49d0dbc3 100644 --- a/OpenSim/Data/MySQL/Resources/XAssetStore.migrations +++ b/OpenSim/Data/MySQL/Resources/XAssetStore.migrations @@ -3,24 +3,24 @@ BEGIN; -CREATE TABLE `xassetsmeta` ( - `id` char(36) NOT NULL, - `hash` binary(32) NOT NULL, - `name` varchar(64) NOT NULL, - `description` varchar(64) NOT NULL, - `asset_type` tinyint(4) NOT NULL, - `local` tinyint(1) NOT NULL, - `temporary` tinyint(1) NOT NULL, - `create_time` int(11) NOT NULL, - `access_time` int(11) NOT NULL, - `asset_flags` int(11) NOT NULL, - `creator_id` varchar(128) NOT NULL, +CREATE TABLE `XAssetsMeta` ( + `ID` char(36) NOT NULL, + `Hash` binary(32) NOT NULL, + `Name` varchar(64) NOT NULL, + `Description` varchar(64) NOT NULL, + `AssetType` tinyint(4) NOT NULL, + `Local` tinyint(1) NOT NULL, + `Temporary` tinyint(1) NOT NULL, + `CreateTime` int(11) NOT NULL, + `AccessTime` int(11) NOT NULL, + `AssetFlags` int(11) NOT NULL, + `CreatorID` varchar(128) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Version 1'; -CREATE TABLE `xassetsdata` ( - `hash` binary(32) NOT NULL, - `data` longblob NOT NULL, +CREATE TABLE `XAssetsData` ( + `Hash` binary(32) NOT NULL, + `Data` longblob NOT NULL, PRIMARY KEY (`hash`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Version 1'; diff --git a/OpenSim/Data/MySQL/Resources/os_groups_Store.migrations b/OpenSim/Data/MySQL/Resources/os_groups_Store.migrations new file mode 100644 index 0000000000..9e6f1c112c --- /dev/null +++ b/OpenSim/Data/MySQL/Resources/os_groups_Store.migrations @@ -0,0 +1,115 @@ +:VERSION 1 # -------------------------- + +BEGIN; + +CREATE TABLE `os_groups_groups` ( + `GroupID` char(36) NOT NULL default '', + `Location` varchar(255) NOT NULL default '', + `Name` varchar(255) NOT NULL default '', + `Charter` text NOT NULL, + `InsigniaID` char(36) NOT NULL default '', + `FounderID` char(36) NOT NULL default '', + `MembershipFee` int(11) NOT NULL default '0', + `OpenEnrollment` varchar(255) NOT NULL default '', + `ShowInList` int(4) NOT NULL default '0', + `AllowPublish` int(4) NOT NULL default '0', + `MaturePublish` int(4) NOT NULL default '0', + `OwnerRoleID` char(36) NOT NULL default '', + PRIMARY KEY (`GroupID`), + UNIQUE KEY `Name` (`Name`), + FULLTEXT KEY `Name_2` (`Name`) +) ENGINE=MyISAM; + + +CREATE TABLE `os_groups_membership` ( + `GroupID`char(36) NOT NULL default '', + `PrincipalID` VARCHAR(255) NOT NULL default '', + `SelectedRoleID` char(36) NOT NULL default '', + `Contribution` int(11) NOT NULL default '0', + `ListInProfile` int(4) NOT NULL default '1', + `AcceptNotices` int(4) NOT NULL default '1', + `AccessToken` char(36) NOT NULL default '', + PRIMARY KEY (`GroupID`,`PrincipalID`), + KEY `PrincipalID` (`PrincipalID`) +) ENGINE=MyISAM; + + +CREATE TABLE `os_groups_roles` ( + `GroupID` char(36) NOT NULL default '', + `RoleID` char(36) NOT NULL default '', + `Name` varchar(255) NOT NULL default '', + `Description` varchar(255) NOT NULL default '', + `Title` varchar(255) NOT NULL default '', + `Powers` bigint(20) unsigned NOT NULL default '0', + PRIMARY KEY (`GroupID`,`RoleID`), + KEY `GroupID` (`GroupID`) +) ENGINE=MyISAM; + + +CREATE TABLE `os_groups_rolemembership` ( + `GroupID` char(36) NOT NULL default '', + `RoleID` char(36) NOT NULL default '', + `PrincipalID` VARCHAR(255) NOT NULL default '', + PRIMARY KEY (`GroupID`,`RoleID`,`PrincipalID`), + KEY `PrincipalID` (`PrincipalID`) +) ENGINE=MyISAM; + + +CREATE TABLE `os_groups_invites` ( + `InviteID` char(36) NOT NULL default '', + `GroupID` char(36) NOT NULL default '', + `RoleID` char(36) NOT NULL default '', + `PrincipalID` VARCHAR(255) NOT NULL default '', + `TMStamp` timestamp NOT NULL, + PRIMARY KEY (`InviteID`), + UNIQUE KEY `PrincipalGroup` (`GroupID`,`PrincipalID`) +) ENGINE=MyISAM; + + +CREATE TABLE `os_groups_notices` ( + `GroupID` char(36) NOT NULL default '', + `NoticeID` char(36) NOT NULL default '', + `TMStamp` int(10) unsigned NOT NULL default '0', + `FromName` varchar(255) NOT NULL default '', + `Subject` varchar(255) NOT NULL default '', + `Message` text NOT NULL, + `HasAttachment` int(4) NOT NULL default '0', + `AttachmentType` int(4) NOT NULL default '0', + `AttachmentName` varchar(128) NOT NULL default '', + `AttachmentItemID` char(36) NOT NULL default '', + `AttachmentOwnerID` varchar(255) NOT NULL default '', + PRIMARY KEY (`NoticeID`), + KEY `GroupID` (`GroupID`), + KEY `TMStamp` (`TMStamp`) +) ENGINE=MyISAM; + +CREATE TABLE `os_groups_principals` ( + `PrincipalID` VARCHAR(255) NOT NULL default '', + `ActiveGroupID` char(36) NOT NULL default '', + PRIMARY KEY (`PrincipalID`) +) ENGINE=MyISAM; + +COMMIT; + +:VERSION 2 # -------------------------- + +BEGIN; + +INSERT INTO `os_groups_groups` SELECT * from `diva_groups_groups`; +DROP TABLE `diva_groups_groups`; +INSERT INTO `os_groups_membership` SELECT * from `diva_groups_membership`; +DROP TABLE `diva_groups_membership`; +INSERT INTO `os_groups_roles` SELECT * from `diva_groups_roles`; +DROP TABLE `diva_groups_roles`; +INSERT INTO `os_groups_rolemembership` SELECT * from `diva_groups_rolemembership`; +DROP TABLE `diva_groups_rolemembership`; +INSERT INTO `os_groups_invites` SELECT * from `diva_groups_invites`; +DROP TABLE `diva_groups_invites`; +INSERT INTO `os_groups_notices` SELECT * from `diva_groups_notices`; +DROP TABLE `diva_groups_notices`; +INSERT INTO `os_groups_principals` SELECT * from `diva_groups_principals`; +DROP TABLE `diva_groups_principals`; + +DELETE FROM `migrations` WHERE name='diva_im_Store'; + +COMMIT; \ No newline at end of file diff --git a/OpenSim/Data/Null/NullGenericDataHandler.cs b/OpenSim/Data/Null/NullGenericDataHandler.cs new file mode 100644 index 0000000000..dd9d190910 --- /dev/null +++ b/OpenSim/Data/Null/NullGenericDataHandler.cs @@ -0,0 +1,67 @@ +/* + * 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.Linq; +using System.Reflection; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Data; + +namespace OpenSim.Data.Null +{ + /// + /// Not a proper generic data handler yet - probably needs to actually store the data as well instead of relying + /// on descendent classes + /// + public class NullGenericDataHandler + { + protected List Get(string[] fields, string[] vals, List inputEntities) + { + List entities = inputEntities; + + for (int i = 0; i < fields.Length; i++) + { + entities + = entities.Where( + e => + { + FieldInfo fi = typeof(T).GetField(fields[i]); + if (fi == null) + throw new NotImplementedException(string.Format("No field {0} for val {1}", fields[i], vals[i])); + + return fi.GetValue(e).ToString() == vals[i]; + } + ).ToList(); + } + + return entities; + } + } +} \ No newline at end of file diff --git a/OpenSim/Data/Null/NullPresenceData.cs b/OpenSim/Data/Null/NullPresenceData.cs index c06c223a81..b85b95e566 100644 --- a/OpenSim/Data/Null/NullPresenceData.cs +++ b/OpenSim/Data/Null/NullPresenceData.cs @@ -222,5 +222,13 @@ namespace OpenSim.Data.Null return true; } + public bool VerifyAgent(UUID agentId, UUID secureSessionID) + { + if (Instance != this) + return Instance.VerifyAgent(agentId, secureSessionID); + + return false; + } + } } diff --git a/OpenSim/Data/Null/NullRegionData.cs b/OpenSim/Data/Null/NullRegionData.cs index b4d701af50..f707d98cd2 100644 --- a/OpenSim/Data/Null/NullRegionData.cs +++ b/OpenSim/Data/Null/NullRegionData.cs @@ -113,11 +113,14 @@ namespace OpenSim.Data.Null // Find region data List ret = new List(); - foreach (RegionData r in m_regionData.Values) + lock (m_regionData) { -// m_log.DebugFormat("[NULL REGION DATA]: comparing {0} to {1}", cleanName, r.RegionName.ToLower()); + foreach (RegionData r in m_regionData.Values) + { + // m_log.DebugFormat("[NULL REGION DATA]: comparing {0} to {1}", cleanName, r.RegionName.ToLower()); if (queryMatch(r.RegionName.ToLower())) ret.Add(r); + } } if (ret.Count > 0) @@ -133,10 +136,13 @@ namespace OpenSim.Data.Null List ret = new List(); - foreach (RegionData r in m_regionData.Values) + lock (m_regionData) { - if (r.posX == posX && r.posY == posY) - ret.Add(r); + foreach (RegionData r in m_regionData.Values) + { + if (r.posX == posX && r.posY == posY) + ret.Add(r); + } } if (ret.Count > 0) @@ -150,8 +156,11 @@ namespace OpenSim.Data.Null if (m_useStaticInstance && Instance != this) return Instance.Get(regionID, scopeID); - if (m_regionData.ContainsKey(regionID)) - return m_regionData[regionID]; + lock (m_regionData) + { + if (m_regionData.ContainsKey(regionID)) + return m_regionData[regionID]; + } return null; } @@ -163,10 +172,13 @@ namespace OpenSim.Data.Null List ret = new List(); - foreach (RegionData r in m_regionData.Values) + lock (m_regionData) { - if (r.posX >= startX && r.posX <= endX && r.posY >= startY && r.posY <= endY) - ret.Add(r); + foreach (RegionData r in m_regionData.Values) + { + if (r.posX >= startX && r.posX <= endX && r.posY >= startY && r.posY <= endY) + ret.Add(r); + } } return ret; @@ -180,7 +192,10 @@ namespace OpenSim.Data.Null // m_log.DebugFormat( // "[NULL REGION DATA]: Storing region {0} {1}, scope {2}", data.RegionName, data.RegionID, data.ScopeID); - m_regionData[data.RegionID] = data; + lock (m_regionData) + { + m_regionData[data.RegionID] = data; + } return true; } @@ -190,10 +205,13 @@ namespace OpenSim.Data.Null if (m_useStaticInstance && Instance != this) return Instance.SetDataItem(regionID, item, value); - if (!m_regionData.ContainsKey(regionID)) - return false; + lock (m_regionData) + { + if (!m_regionData.ContainsKey(regionID)) + return false; - m_regionData[regionID].Data[item] = value; + m_regionData[regionID].Data[item] = value; + } return true; } @@ -205,10 +223,13 @@ namespace OpenSim.Data.Null // m_log.DebugFormat("[NULL REGION DATA]: Deleting region {0}", regionID); - if (!m_regionData.ContainsKey(regionID)) - return false; + lock (m_regionData) + { + if (!m_regionData.ContainsKey(regionID)) + return false; - m_regionData.Remove(regionID); + m_regionData.Remove(regionID); + } return true; } @@ -238,10 +259,13 @@ namespace OpenSim.Data.Null List ret = new List(); - foreach (RegionData r in m_regionData.Values) + lock (m_regionData) { - if ((Convert.ToInt32(r.Data["flags"]) & regionFlags) != 0) - ret.Add(r); + foreach (RegionData r in m_regionData.Values) + { + if ((Convert.ToInt32(r.Data["flags"]) & regionFlags) != 0) + ret.Add(r); + } } return ret; diff --git a/OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs b/OpenSim/Data/Null/NullXGroupData.cs similarity index 54% rename from OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs rename to OpenSim/Data/Null/NullXGroupData.cs index d7198f0305..7a86b9f230 100644 --- a/OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs +++ b/OpenSim/Data/Null/NullXGroupData.cs @@ -26,66 +26,65 @@ */ using System; -using OpenMetaverse; -using log4net; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Threading; +using log4net; +using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Data; -namespace OpenSim.Region.Framework.Scenes.Scripting +namespace OpenSim.Data.Null { - public class NullScriptHost : IScriptHost + public class NullXGroupData : NullGenericDataHandler, IXGroupData { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private Vector3 m_pos = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 30); + private Dictionary m_groups = new Dictionary(); - public string Name + public NullXGroupData(string connectionString, string realm) {} + + public bool StoreGroup(XGroup group) { - get { return "Object"; } - set { } + lock (m_groups) + { + m_groups[group.groupID] = group.Clone(); + } + + return true; } - public string SitName + public XGroup[] GetGroups(string field, string val) { - get { return String.Empty; } - set { } + return GetGroups(new string[] { field }, new string[] { val }); } - public string TouchName + public XGroup[] GetGroups(string[] fields, string[] vals) { - get { return String.Empty; } - set { } + lock (m_groups) + { + List origGroups = Get(fields, vals, m_groups.Values.ToList()); + + return origGroups.Select(g => g.Clone()).ToArray(); + } } - public string Description + public bool DeleteGroups(string field, string val) { - get { return String.Empty; } - set { } + return DeleteGroups(new string[] { field }, new string[] { val }); } - public UUID UUID + public bool DeleteGroups(string[] fields, string[] vals) { - get { return UUID.Zero; } - } + lock (m_groups) + { + XGroup[] groupsToDelete = GetGroups(fields, vals); + Array.ForEach(groupsToDelete, g => m_groups.Remove(g.groupID)); + } - public UUID OwnerID - { - get { return UUID.Zero; } - } - - public UUID CreatorID - { - get { return UUID.Zero; } - } - - public Vector3 AbsolutePosition - { - get { return m_pos; } - } - - public void SetText(string text, Vector3 color, double alpha) - { - m_log.Warn("Tried to SetText "+text+" on NullScriptHost"); + return true; } } -} +} \ No newline at end of file diff --git a/OpenSim/Data/Null/Properties/AssemblyInfo.cs b/OpenSim/Data/Null/Properties/AssemblyInfo.cs index 43b0bb3214..1e02c31bda 100644 --- a/OpenSim/Data/Null/Properties/AssemblyInfo.cs +++ b/OpenSim/Data/Null/Properties/AssemblyInfo.cs @@ -61,5 +61,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] +[assembly : AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Data/Properties/AssemblyInfo.cs b/OpenSim/Data/Properties/AssemblyInfo.cs index 0da1a6b799..a85f47306c 100644 --- a/OpenSim/Data/Properties/AssemblyInfo.cs +++ b/OpenSim/Data/Properties/AssemblyInfo.cs @@ -61,5 +61,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] +[assembly : AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Data/SQLite/Properties/AssemblyInfo.cs b/OpenSim/Data/SQLite/Properties/AssemblyInfo.cs index c9a8553de6..992982ce76 100644 --- a/OpenSim/Data/SQLite/Properties/AssemblyInfo.cs +++ b/OpenSim/Data/SQLite/Properties/AssemblyInfo.cs @@ -61,5 +61,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] +[assembly : AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Data/SQLite/Resources/RegionStore.migrations b/OpenSim/Data/SQLite/Resources/RegionStore.migrations index e872977634..c6f4b48e18 100644 --- a/OpenSim/Data/SQLite/Resources/RegionStore.migrations +++ b/OpenSim/Data/SQLite/Resources/RegionStore.migrations @@ -575,3 +575,20 @@ CREATE TABLE `regionenvironment` ( ); COMMIT; + +:VERSION 27 +BEGIN; +ALTER TABLE prims ADD COLUMN DynAttrs TEXT; +COMMIT; + +:VERSION 28 + +BEGIN; + +ALTER TABLE prims ADD COLUMN `PhysicsShapeType` tinyint(4) NOT NULL default '0'; +ALTER TABLE prims ADD COLUMN `Density` double NOT NULL default '1000'; +ALTER TABLE prims ADD COLUMN `GravityModifier` double NOT NULL default '1'; +ALTER TABLE prims ADD COLUMN `Friction` double NOT NULL default '0.6'; +ALTER TABLE prims ADD COLUMN `Restitution` double NOT NULL default '0.5'; + +COMMIT; diff --git a/OpenSim/Data/SQLite/SQLiteAssetData.cs b/OpenSim/Data/SQLite/SQLiteAssetData.cs index b94a58cc3d..82320ca88d 100644 --- a/OpenSim/Data/SQLite/SQLiteAssetData.cs +++ b/OpenSim/Data/SQLite/SQLiteAssetData.cs @@ -46,7 +46,7 @@ namespace OpenSim.Data.SQLite /// public class SQLiteAssetData : AssetDataBase { -// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private const string SelectAssetSQL = "select * from assets where UUID=:UUID"; private const string SelectAssetMetadataSQL = "select Name, Description, Type, Temporary, asset_flags, UUID, CreatorID from assets limit :start, :count"; @@ -133,6 +133,24 @@ namespace OpenSim.Data.SQLite /// Asset Base override public bool StoreAsset(AssetBase asset) { + string assetName = asset.Name; + if (asset.Name.Length > 64) + { + assetName = asset.Name.Substring(0, 64); + m_log.WarnFormat( + "[ASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Name, asset.ID, asset.Name.Length, assetName.Length); + } + + string assetDescription = asset.Description; + if (asset.Description.Length > 64) + { + assetDescription = asset.Description.Substring(0, 64); + m_log.WarnFormat( + "[ASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add", + asset.Description, asset.ID, asset.Description.Length, assetDescription.Length); + } + //m_log.Info("[ASSET DB]: Creating Asset " + asset.FullID.ToString()); if (ExistsAsset(asset.FullID)) { @@ -143,8 +161,8 @@ namespace OpenSim.Data.SQLite using (SqliteCommand cmd = new SqliteCommand(UpdateAssetSQL, m_conn)) { cmd.Parameters.Add(new SqliteParameter(":UUID", asset.FullID.ToString())); - cmd.Parameters.Add(new SqliteParameter(":Name", asset.Name)); - cmd.Parameters.Add(new SqliteParameter(":Description", asset.Description)); + cmd.Parameters.Add(new SqliteParameter(":Name", assetName)); + cmd.Parameters.Add(new SqliteParameter(":Description", assetDescription)); cmd.Parameters.Add(new SqliteParameter(":Type", asset.Type)); cmd.Parameters.Add(new SqliteParameter(":Local", asset.Local)); cmd.Parameters.Add(new SqliteParameter(":Temporary", asset.Temporary)); @@ -164,8 +182,8 @@ namespace OpenSim.Data.SQLite using (SqliteCommand cmd = new SqliteCommand(InsertAssetSQL, m_conn)) { cmd.Parameters.Add(new SqliteParameter(":UUID", asset.FullID.ToString())); - cmd.Parameters.Add(new SqliteParameter(":Name", asset.Name)); - cmd.Parameters.Add(new SqliteParameter(":Description", asset.Description)); + cmd.Parameters.Add(new SqliteParameter(":Name", assetName)); + cmd.Parameters.Add(new SqliteParameter(":Description", assetDescription)); cmd.Parameters.Add(new SqliteParameter(":Type", asset.Type)); cmd.Parameters.Add(new SqliteParameter(":Local", asset.Local)); cmd.Parameters.Add(new SqliteParameter(":Temporary", asset.Temporary)); diff --git a/OpenSim/Data/SQLite/SQLiteSimulationData.cs b/OpenSim/Data/SQLite/SQLiteSimulationData.cs index 42cd59de31..99a6598bd2 100644 --- a/OpenSim/Data/SQLite/SQLiteSimulationData.cs +++ b/OpenSim/Data/SQLite/SQLiteSimulationData.cs @@ -1232,6 +1232,14 @@ namespace OpenSim.Data.SQLite createCol(prims, "VolumeDetect", typeof(Int16)); createCol(prims, "MediaURL", typeof(String)); + + createCol(prims, "DynAttrs", typeof(String)); + + createCol(prims, "PhysicsShapeType", typeof(Byte)); + createCol(prims, "Density", typeof(Double)); + createCol(prims, "GravityModifier", typeof(Double)); + createCol(prims, "Friction", typeof(Double)); + createCol(prims, "Restitution", typeof(Double)); // Add in contraints prims.PrimaryKey = new DataColumn[] { prims.Columns["UUID"] }; @@ -1711,6 +1719,22 @@ namespace OpenSim.Data.SQLite // m_log.DebugFormat("[SQLITE]: MediaUrl type [{0}]", row["MediaURL"].GetType()); prim.MediaUrl = (string)row["MediaURL"]; } + + if (!(row["DynAttrs"] is System.DBNull)) + { + //m_log.DebugFormat("[SQLITE]: DynAttrs type [{0}]", row["DynAttrs"].GetType()); + prim.DynAttrs = DAMap.FromXml((string)row["DynAttrs"]); + } + else + { + prim.DynAttrs = new DAMap(); + } + + prim.PhysicsShapeType = Convert.ToByte(row["PhysicsShapeType"]); + prim.Density = Convert.ToSingle(row["Density"]); + prim.GravityModifier = Convert.ToSingle(row["GravityModifier"]); + prim.Friction = Convert.ToSingle(row["Friction"]); + prim.Restitution = Convert.ToSingle(row["Restitution"]); return prim; } @@ -2133,6 +2157,17 @@ namespace OpenSim.Data.SQLite row["VolumeDetect"] = 0; row["MediaURL"] = prim.MediaUrl; + + if (prim.DynAttrs.Count > 0) + row["DynAttrs"] = prim.DynAttrs.ToXml(); + else + row["DynAttrs"] = null; + + row["PhysicsShapeType"] = prim.PhysicsShapeType; + row["Density"] = (double)prim.Density; + row["GravityModifier"] = (double)prim.GravityModifier; + row["Friction"] = (double)prim.Friction; + row["Restitution"] = (double)prim.Restitution; } /// @@ -2392,7 +2427,7 @@ namespace OpenSim.Data.SQLite if (!(row["Media"] is System.DBNull)) s.Media = PrimitiveBaseShape.MediaList.FromXml((string)row["Media"]); - + return s; } diff --git a/OpenSim/Data/Tests/AssetTests.cs b/OpenSim/Data/Tests/AssetTests.cs index 1174e2f302..8cb2ee083d 100644 --- a/OpenSim/Data/Tests/AssetTests.cs +++ b/OpenSim/Data/Tests/AssetTests.cs @@ -49,7 +49,7 @@ using OpenSim.Data.SQLite; namespace OpenSim.Data.Tests { [TestFixture(Description = "Asset store tests (SQLite)")] - public class SQLiteAssetTests : AssetTests + public class SQLiteAssetTests : AssetTests { } diff --git a/OpenSim/Data/Tests/BasicDataServiceTest.cs b/OpenSim/Data/Tests/BasicDataServiceTest.cs index 7d85f0c8eb..69b79bf6e0 100644 --- a/OpenSim/Data/Tests/BasicDataServiceTest.cs +++ b/OpenSim/Data/Tests/BasicDataServiceTest.cs @@ -33,6 +33,7 @@ using NUnit.Framework; using NUnit.Framework.Constraints; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Tests.Common; using log4net; using System.Data; using System.Data.Common; @@ -43,6 +44,12 @@ namespace OpenSim.Data.Tests /// This is a base class for testing any Data service for any DBMS. /// Requires NUnit 2.5 or better (to support the generics). /// + /// + /// FIXME: Should extend OpenSimTestCase but compile on mono 2.4.3 currently fails with + /// AssetTests`2 : System.MemberAccessException : Cannot create an instance of OpenSim.Data.Tests.AssetTests`2[TConn,TAssetData] because Type.ContainsGenericParameters is true. + /// and similar on EstateTests, InventoryTests and RegionTests. + /// Runs fine with mono 2.10.8.1, so easiest thing is to wait until min Mono version uplifts. + /// /// /// public class BasicDataServiceTest diff --git a/OpenSim/Data/Tests/PropertyCompareConstraint.cs b/OpenSim/Data/Tests/PropertyCompareConstraint.cs index 6c79bda9ec..b99525a74a 100644 --- a/OpenSim/Data/Tests/PropertyCompareConstraint.cs +++ b/OpenSim/Data/Tests/PropertyCompareConstraint.cs @@ -36,6 +36,7 @@ using NUnit.Framework; using NUnit.Framework.Constraints; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Tests.Common; namespace OpenSim.Data.Tests { @@ -254,7 +255,7 @@ namespace OpenSim.Data.Tests } [TestFixture] - public class PropertyCompareConstraintTest + public class PropertyCompareConstraintTest : OpenSimTestCase { public class HasInt { diff --git a/OpenSim/Data/Tests/PropertyScrambler.cs b/OpenSim/Data/Tests/PropertyScrambler.cs index c5d40c2373..e0f5862b93 100644 --- a/OpenSim/Data/Tests/PropertyScrambler.cs +++ b/OpenSim/Data/Tests/PropertyScrambler.cs @@ -34,6 +34,7 @@ using System.Text; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Tests.Common; namespace OpenSim.Data.Tests { @@ -158,7 +159,7 @@ namespace OpenSim.Data.Tests } [TestFixture] - public class PropertyScramblerTests + public class PropertyScramblerTests : OpenSimTestCase { [Test] public void TestScramble() diff --git a/OpenSim/Framework/AssemblyInfo.cs b/OpenSim/Framework/AssemblyInfo.cs index 02986d5d2a..d6b4e6adbf 100644 --- a/OpenSim/Framework/AssemblyInfo.cs +++ b/OpenSim/Framework/AssemblyInfo.cs @@ -59,5 +59,4 @@ using System.Runtime.InteropServices; // Revision // -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] \ No newline at end of file +[assembly : AssemblyVersion("0.7.6.*")] diff --git a/OpenSim/Framework/AssetLoader/Filesystem/Properties/AssemblyInfo.cs b/OpenSim/Framework/AssetLoader/Filesystem/Properties/AssemblyInfo.cs index 0498ed4888..feffa26dbc 100644 --- a/OpenSim/Framework/AssetLoader/Filesystem/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/AssetLoader/Filesystem/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/AvatarAppearance.cs b/OpenSim/Framework/AvatarAppearance.cs index ba6d87dac2..b7a0adface 100644 --- a/OpenSim/Framework/AvatarAppearance.cs +++ b/OpenSim/Framework/AvatarAppearance.cs @@ -561,45 +561,59 @@ namespace OpenSim.Framework if (attachpoint == 0) return false; - if (item == UUID.Zero) + lock (m_attachments) { - lock (m_attachments) + if (item == UUID.Zero) { if (m_attachments.ContainsKey(attachpoint)) { m_attachments.Remove(attachpoint); return true; } + + return false; + } + + // When a user logs in, the attachment item ids are pulled from persistence in the Avatars table. However, + // the asset ids are not saved. When the avatar enters a simulator the attachments are set again. If + // we simply perform an item check here then the asset ids (which are now present) are never set, and NPC attachments + // later fail unless the attachment is detached and reattached. + // + // Therefore, we will carry on with the set if the existing attachment has no asset id. + AvatarAttachment existingAttachment = GetAttachmentForItem(item); + if (existingAttachment != null) + { +// m_log.DebugFormat( +// "[AVATAR APPEARANCE]: Found existing attachment for {0}, asset {1} at point {2}", +// existingAttachment.ItemID, existingAttachment.AssetID, existingAttachment.AttachPoint); + + if (existingAttachment.AssetID != UUID.Zero && existingAttachment.AttachPoint == (attachpoint & 0x7F)) + { + m_log.DebugFormat( + "[AVATAR APPEARANCE]: Ignoring attempt to attach an already attached item {0} at point {1}", + item, attachpoint); + + return false; + } + else + { + // Remove it here so that the later append does not add a second attachment but we still update + // the assetID + DetachAttachment(existingAttachment.ItemID); + } } - return false; - } - - // When a user logs in, the attachment item ids are pulled from persistence in the Avatars table. However, - // the asset ids are not saved. When the avatar enters a simulator the attachments are set again. If - // we simply perform an item check here then the asset ids (which are now present) are never set, and NPC attachments - // later fail unless the attachment is detached and reattached. - // - // Therefore, we will carry on with the set if the existing attachment has no asset id. - AvatarAttachment existingAttachment = GetAttachmentForItem(item); - if (existingAttachment != null - && existingAttachment.AssetID != UUID.Zero - && existingAttachment.AttachPoint == (attachpoint & 0x7F)) - { - // m_log.DebugFormat("[AVATAR APPEARANCE] attempt to attach an already attached item {0}",item); - return false; - } - - // check if this is an append or a replace, 0x80 marks it as an append - if ((attachpoint & 0x80) > 0) - { - // strip the append bit - int point = attachpoint & 0x7F; - AppendAttachment(new AvatarAttachment(point, item, asset)); - } - else - { - ReplaceAttachment(new AvatarAttachment(attachpoint,item, asset)); + // check if this is an append or a replace, 0x80 marks it as an append + if ((attachpoint & 0x80) > 0) + { + // strip the append bit + int point = attachpoint & 0x7F; + AppendAttachment(new AvatarAttachment(point, item, asset)); + } + else + { + ReplaceAttachment(new AvatarAttachment(attachpoint,item, asset)); + } } return true; @@ -648,6 +662,10 @@ namespace OpenSim.Framework int index = kvp.Value.FindIndex(delegate(AvatarAttachment a) { return a.ItemID == itemID; }); if (index >= 0) { +// m_log.DebugFormat( +// "[AVATAR APPEARANCE]: Detaching attachment {0}, index {1}, point {2}", +// m_attachments[kvp.Key][index].ItemID, index, m_attachments[kvp.Key][index].AttachPoint); + // Remove it from the list of attachments at that attach point m_attachments[kvp.Key].RemoveAt(index); diff --git a/OpenSim/Framework/Communications/Properties/AssemblyInfo.cs b/OpenSim/Framework/Communications/Properties/AssemblyInfo.cs index 6d1c03ad4b..df8eb520e1 100644 --- a/OpenSim/Framework/Communications/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Communications/Properties/AssemblyInfo.cs @@ -61,5 +61,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly : AssemblyVersion("0.7.5.*")] -[assembly : AssemblyFileVersion("0.6.5.0")] +[assembly : AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Configuration/HTTP/HTTPConfiguration.cs b/OpenSim/Framework/Configuration/HTTP/HTTPConfiguration.cs index 3dce578193..6681c37bc5 100644 --- a/OpenSim/Framework/Configuration/HTTP/HTTPConfiguration.cs +++ b/OpenSim/Framework/Configuration/HTTP/HTTPConfiguration.cs @@ -65,23 +65,27 @@ namespace OpenSim.Framework.Configuration.HTTP byte[] buf = new byte[8192]; HttpWebRequest request = (HttpWebRequest) WebRequest.Create(remoteConfigSettings.baseConfigURL + configFileName); - HttpWebResponse response = (HttpWebResponse) request.GetResponse(); - - Stream resStream = response.GetResponseStream(); - - string tempString = null; - int count = 0; - - do + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { - count = resStream.Read(buf, 0, buf.Length); - if (count != 0) + using (Stream resStream = response.GetResponseStream()) { - tempString = Util.UTF8.GetString(buf, 0, count); - sb.Append(tempString); + string tempString = null; + int count = 0; + + do + { + count = resStream.Read(buf, 0, buf.Length); + if (count != 0) + { + tempString = Util.UTF8.GetString(buf, 0, count); + sb.Append(tempString); + } + } + while (count > 0); + + LoadDataFromString(sb.ToString()); } - } while (count > 0); - LoadDataFromString(sb.ToString()); + } } catch (WebException) { diff --git a/OpenSim/Framework/Configuration/HTTP/Properties/AssemblyInfo.cs b/OpenSim/Framework/Configuration/HTTP/Properties/AssemblyInfo.cs index 0674656c75..3ef9682a95 100644 --- a/OpenSim/Framework/Configuration/HTTP/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Configuration/HTTP/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Configuration/XML/Properties/AssemblyInfo.cs b/OpenSim/Framework/Configuration/XML/Properties/AssemblyInfo.cs index 1095b232e2..cbdffeb78c 100644 --- a/OpenSim/Framework/Configuration/XML/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Configuration/XML/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Console/AssemblyInfo.cs b/OpenSim/Framework/Console/AssemblyInfo.cs index 37c7304424..c618454a7a 100644 --- a/OpenSim/Framework/Console/AssemblyInfo.cs +++ b/OpenSim/Framework/Console/AssemblyInfo.cs @@ -55,4 +55,4 @@ using System.Runtime.InteropServices; // You can specify all values by your own or you can build default build and revision // numbers with the '*' character (the default): -[assembly : AssemblyVersion("0.7.5.*")] +[assembly : AssemblyVersion("0.7.6.*")] diff --git a/OpenSim/Framework/Console/CommandConsole.cs b/OpenSim/Framework/Console/CommandConsole.cs index d703d78b55..9490013900 100644 --- a/OpenSim/Framework/Console/CommandConsole.cs +++ b/OpenSim/Framework/Console/CommandConsole.cs @@ -110,10 +110,11 @@ namespace OpenSim.Framework.Console // Remove initial help keyword helpParts.RemoveAt(0); + help.Add(""); // Will become a newline. + // General help if (helpParts.Count == 0) { - help.Add(""); // Will become a newline. help.Add(GeneralHelpText); help.AddRange(CollectAllCommandsHelp()); } @@ -129,6 +130,8 @@ namespace OpenSim.Framework.Console help.AddRange(CollectHelp(helpParts)); } + help.Add(""); // Will become a newline. + return help; } @@ -199,14 +202,11 @@ namespace OpenSim.Framework.Console string descriptiveHelp = commandInfo.descriptive_help; - // If we do have some descriptive help then insert a spacing line before and after for readability. + // If we do have some descriptive help then insert a spacing line before for readability. if (descriptiveHelp != string.Empty) help.Add(string.Empty); help.Add(commandInfo.descriptive_help); - - if (descriptiveHelp != string.Empty) - help.Add(string.Empty); } else { diff --git a/OpenSim/Framework/Console/ConsoleDisplayTable.cs b/OpenSim/Framework/Console/ConsoleDisplayTable.cs index c620dfe10c..711a337f81 100644 --- a/OpenSim/Framework/Console/ConsoleDisplayTable.cs +++ b/OpenSim/Framework/Console/ConsoleDisplayTable.cs @@ -56,7 +56,7 @@ namespace OpenSim.Framework.Console public List Rows { get; private set; } /// - /// Number of spaces to indent the table. + /// Number of spaces to indent the whole table. /// public int Indent { get; set; } @@ -84,7 +84,7 @@ namespace OpenSim.Framework.Console Columns.Add(new ConsoleDisplayTableColumn(name, width)); } - public void AddRow(params string[] cells) + public void AddRow(params object[] cells) { Rows.Add(new ConsoleDisplayTableRow(cells)); } @@ -113,7 +113,8 @@ namespace OpenSim.Framework.Console for (int i = 0; i < Columns.Count; i++) { - formatSb.Append(' ', TableSpacing); + if (i != 0) + formatSb.Append(' ', TableSpacing); // Can only do left formatting for now formatSb.AppendFormat("{{{0},-{1}}}", i, Columns[i].Width); @@ -139,16 +140,16 @@ namespace OpenSim.Framework.Console public struct ConsoleDisplayTableRow { - public List Cells { get; private set; } + public List Cells { get; private set; } - public ConsoleDisplayTableRow(List cells) : this() + public ConsoleDisplayTableRow(List cells) : this() { Cells = cells; } - public ConsoleDisplayTableRow(params string[] cells) : this() + public ConsoleDisplayTableRow(params object[] cells) : this() { - Cells = new List(cells); + Cells = new List(cells); } } } \ No newline at end of file diff --git a/OpenSim/Framework/Console/ConsoleUtil.cs b/OpenSim/Framework/Console/ConsoleUtil.cs index 16a63e0ca5..97a86a8099 100644 --- a/OpenSim/Framework/Console/ConsoleUtil.cs +++ b/OpenSim/Framework/Console/ConsoleUtil.cs @@ -49,14 +49,14 @@ namespace OpenSim.Framework.Console = @"Each component of the coord is comma separated. There must be no spaces between the commas. If you don't care about the z component you can simply omit it. If you don't care about the x or y components then you can leave them blank (though a comma is still required) - If you want to specify the maxmimum value of a component then you can use ~ instead of a number + If you want to specify the maximum value of a component then you can use ~ instead of a number If you want to specify the minimum value of a component then you can use -~ instead of a number e.g. - delete object pos 20,20,20 to 40,40,40 + show object pos 20,20,20 to 40,40,40 delete object pos 20,20 to 40,40 - delete object pos ,20,20 to ,40,40 + show object pos ,20,20 to ,40,40 delete object pos ,,30 to ,,~ - delete object pos ,,-~ to ,,30"; + show object pos ,,-~ to ,,30"; public const string MinRawConsoleVectorValue = "-~"; public const string MaxRawConsoleVectorValue = "~"; @@ -97,7 +97,7 @@ namespace OpenSim.Framework.Console if (!UUID.TryParse(rawUuid, out uuid)) { if (console != null) - console.OutputFormat("{0} is not a valid uuid", rawUuid); + console.OutputFormat("ERROR: {0} is not a valid uuid", rawUuid); return false; } @@ -110,7 +110,7 @@ namespace OpenSim.Framework.Console if (!uint.TryParse(rawLocalId, out localId)) { if (console != null) - console.OutputFormat("{0} is not a valid local id", localId); + console.OutputFormat("ERROR: {0} is not a valid local id", localId); return false; } @@ -118,7 +118,7 @@ namespace OpenSim.Framework.Console if (localId == 0) { if (console != null) - console.OutputFormat("{0} is not a valid local id - it must be greater than 0", localId); + console.OutputFormat("ERROR: {0} is not a valid local id - it must be greater than 0", localId); return false; } @@ -150,10 +150,30 @@ namespace OpenSim.Framework.Console } if (console != null) - console.OutputFormat("{0} is not a valid UUID or local id", rawId); + console.OutputFormat("ERROR: {0} is not a valid UUID or local id", rawId); return false; } + + /// + /// Convert a minimum vector input from the console to an OpenMetaverse.Vector3 + /// + /// Can be null if no console is available. + /// /param> + /// + /// + public static bool TryParseConsoleInt(ICommandConsole console, string rawConsoleInt, out int i) + { + if (!int.TryParse(rawConsoleInt, out i)) + { + if (console != null) + console.OutputFormat("ERROR: {0} is not a valid integer", rawConsoleInt); + + return false; + } + + return true; + } /// /// Convert a minimum vector input from the console to an OpenMetaverse.Vector3 diff --git a/OpenSim/Framework/DAMap.cs b/OpenSim/Framework/DAMap.cs new file mode 100644 index 0000000000..df4a6bc4a7 --- /dev/null +++ b/OpenSim/Framework/DAMap.cs @@ -0,0 +1,273 @@ +/* + * 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; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace OpenSim.Framework +{ + /// + /// This class stores and retrieves dynamic attributes. + /// + /// + /// Modules that want to use dynamic attributes need to do so in a private data store + /// which is accessed using a unique name. DAMap provides access to the data stores, + /// each of which is an OSDMap. Modules are free to store any type of data they want + /// within their data store. However, avoid storing large amounts of data because that + /// would slow down database access. + /// + public class DAMap : IDictionary, IXmlSerializable + { + private static readonly int MIN_STORE_NAME_LENGTH = 4; + + protected OSDMap m_map; + + public DAMap() { m_map = new OSDMap(); } + + public XmlSchema GetSchema() { return null; } + + public static DAMap FromXml(string rawXml) + { + DAMap map = new DAMap(); + map.ReadXml(rawXml); + return map; + } + + public void ReadXml(string rawXml) + { + // System.Console.WriteLine("Trying to deserialize [{0}]", rawXml); + + lock (this) + m_map = (OSDMap)OSDParser.DeserializeLLSDXml(rawXml); + } + + // WARNING: this is temporary for experimentation only, it will be removed!!!! + public OSDMap TopLevelMap + { + get { return m_map; } + set { m_map = value; } + } + + + public void ReadXml(XmlReader reader) + { + ReadXml(reader.ReadInnerXml()); + } + + public string ToXml() + { + lock (this) + return OSDParser.SerializeLLSDXmlString(m_map); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteRaw(ToXml()); + } + + public void CopyFrom(DAMap other) + { + // Deep copy + + string data = null; + lock (other) + { + if (other.Count > 0) + { + data = OSDParser.SerializeLLSDXmlString(other.m_map); + } + } + + lock (this) + { + if (data == null) + Clear(); + else + m_map = (OSDMap)OSDParser.DeserializeLLSDXml(data); + } + } + + /// + /// Returns the number of data stores. + /// + public int Count { get { lock (this) { return m_map.Count; } } } + + public bool IsReadOnly { get { return false; } } + + /// + /// Returns the names of the data stores. + /// + public ICollection Keys { get { lock (this) { return m_map.Keys; } } } + + /// + /// Returns all the data stores. + /// + public ICollection Values + { + get + { + lock (this) + { + List stores = new List(m_map.Count); + foreach (OSD llsd in m_map.Values) + stores.Add((OSDMap)llsd); + return stores; + } + } + } + + /// + /// Gets or sets one data store. + /// + /// Store name + /// + public OSDMap this[string key] + { + get + { + OSD llsd; + + lock (this) + { + if (m_map.TryGetValue(key, out llsd)) + return (OSDMap)llsd; + else + return null; + } + } + + set + { + ValidateKey(key); + lock (this) + m_map[key] = value; + } + } + + /// + /// Validate the key used for storing separate data stores. + /// + /// + public static void ValidateKey(string key) + { + if (key.Length < MIN_STORE_NAME_LENGTH) + throw new Exception("Minimum store name length is " + MIN_STORE_NAME_LENGTH); + } + + public bool ContainsKey(string key) + { + lock (this) + return m_map.ContainsKey(key); + } + + public void Add(string key, OSDMap store) + { + ValidateKey(key); + lock (this) + m_map.Add(key, store); + } + + public void Add(KeyValuePair kvp) + { + ValidateKey(kvp.Key); + lock (this) + m_map.Add(kvp.Key, kvp.Value); + } + + public bool Remove(string key) + { + lock (this) + return m_map.Remove(key); + } + + public bool TryGetValue(string key, out OSDMap store) + { + lock (this) + { + OSD llsd; + if (m_map.TryGetValue(key, out llsd)) + { + store = (OSDMap)llsd; + return true; + } + else + { + store = null; + return false; + } + } + } + + public void Clear() + { + lock (this) + m_map.Clear(); + } + + public bool Contains(KeyValuePair kvp) + { + lock (this) + return m_map.ContainsKey(kvp.Key); + } + + public void CopyTo(KeyValuePair[] array, int index) + { + throw new NotImplementedException(); + } + + public bool Remove(KeyValuePair kvp) + { + lock (this) + return m_map.Remove(kvp.Key); + } + + public System.Collections.IDictionaryEnumerator GetEnumerator() + { + lock (this) + return m_map.GetEnumerator(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return null; + } + + IEnumerator IEnumerable.GetEnumerator() + { + lock (this) + return m_map.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs b/OpenSim/Framework/DOMap.cs similarity index 51% rename from OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs rename to OpenSim/Framework/DOMap.cs index 02ef588806..755e129a13 100644 --- a/OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs +++ b/OpenSim/Framework/DOMap.cs @@ -26,69 +26,73 @@ */ using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Schema; using System.Xml.Serialization; +using OpenMetaverse; +using OpenMetaverse.StructuredData; -namespace OpenSim.ApplicationPlugins.Rest.Regions +namespace OpenSim.Framework { - public partial class RestRegionPlugin : RestPlugin + /// + /// This class stores and retrieves dynamic objects. + /// + /// + /// Experimental - DO NOT USE. + /// + public class DOMap { - private static XmlSerializerNamespaces _xmlNs; - - static RestRegionPlugin() + private IDictionary m_map; + + public void Add(string key, object dynObj) { - _xmlNs = new XmlSerializerNamespaces(); - _xmlNs.Add(String.Empty, String.Empty); + DAMap.ValidateKey(key); + + lock (this) + { + if (m_map == null) + m_map = new Dictionary(); + + m_map.Add(key, dynObj); + } } - #region overriding properties - public override string Name + public bool ContainsKey(string key) { - get { return "REGION"; } + return Get(key) != null; } - public override string ConfigName - { - get { return "RestRegionPlugin"; } - } - #endregion overriding properties - - #region overriding methods /// - /// This method is called by OpenSimMain immediately after loading the - /// plugin and after basic server setup, but before running any server commands. + /// Get a dynamic object /// /// - /// Note that entries MUST be added to the active configuration files before - /// the plugin can be enabled. + /// Not providing an index method so that users can't casually overwrite each other's objects. /// - public override void Initialise(OpenSimBase openSim) + /// + public object Get(string key) { - try + lock (this) { - base.Initialise(openSim); - if (!IsEnabled) - { - //m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID); - return; - } - - m_log.InfoFormat("{0} REST region plugin enabled", MsgID); - - // add REST method handlers - AddRestStreamHandler("GET", "/regions/", GetHandler); - AddRestStreamHandler("POST", "/regions/", PostHandler); - AddRestStreamHandler("GET", "/regioninfo/", GetRegionInfoHandler); - } - catch (Exception e) - { - m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message); - m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString()); + if (m_map == null) + return null; + else + return m_map[key]; } } - public override void Close() + public bool Remove(string key) { + lock (this) + { + if (m_map == null) + return false; + else + return m_map.Remove(key); + } } - #endregion overriding methods } -} +} \ No newline at end of file diff --git a/OpenSim/Framework/DoubleDictionaryThreadAbortSafe.cs b/OpenSim/Framework/DoubleDictionaryThreadAbortSafe.cs new file mode 100644 index 0000000000..9056548a51 --- /dev/null +++ b/OpenSim/Framework/DoubleDictionaryThreadAbortSafe.cs @@ -0,0 +1,508 @@ +/* + * Copyright (c) 2008, openmetaverse.org, http://opensimulator.org/ + * All rights reserved. + * + * - 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. + * - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.Generic; + +namespace OpenSim.Framework +{ + /// + /// A double dictionary that is thread abort safe. + /// + /// + /// This adapts OpenMetaverse.DoubleDictionary to be thread-abort safe by acquiring ReaderWriterLockSlim within + /// a finally section (which can't be interrupted by Thread.Abort()). + /// + public class DoubleDictionaryThreadAbortSafe + { + Dictionary Dictionary1; + Dictionary Dictionary2; + ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(); + + public DoubleDictionaryThreadAbortSafe() + { + Dictionary1 = new Dictionary(); + Dictionary2 = new Dictionary(); + } + + public DoubleDictionaryThreadAbortSafe(int capacity) + { + Dictionary1 = new Dictionary(capacity); + Dictionary2 = new Dictionary(capacity); + } + + public void Add(TKey1 key1, TKey2 key2, TValue value) + { + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterWriteLock(); + gotLock = true; + } + + if (Dictionary1.ContainsKey(key1)) + { + if (!Dictionary2.ContainsKey(key2)) + throw new ArgumentException("key1 exists in the dictionary but not key2"); + } + else if (Dictionary2.ContainsKey(key2)) + { + if (!Dictionary1.ContainsKey(key1)) + throw new ArgumentException("key2 exists in the dictionary but not key1"); + } + + Dictionary1[key1] = value; + Dictionary2[key2] = value; + } + finally + { + if (gotLock) + rwLock.ExitWriteLock(); + } + } + + public bool Remove(TKey1 key1, TKey2 key2) + { + bool success; + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterWriteLock(); + gotLock = true; + } + + Dictionary1.Remove(key1); + success = Dictionary2.Remove(key2); + } + finally + { + if (gotLock) + rwLock.ExitWriteLock(); + } + + return success; + } + + public bool Remove(TKey1 key1) + { + bool found = false; + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterWriteLock(); + gotLock = true; + } + + // This is an O(n) operation! + TValue value; + if (Dictionary1.TryGetValue(key1, out value)) + { + foreach (KeyValuePair kvp in Dictionary2) + { + if (kvp.Value.Equals(value)) + { + Dictionary1.Remove(key1); + Dictionary2.Remove(kvp.Key); + found = true; + break; + } + } + } + } + finally + { + if (gotLock) + rwLock.ExitWriteLock(); + } + + return found; + } + + public bool Remove(TKey2 key2) + { + bool found = false; + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterWriteLock(); + gotLock = true; + } + + // This is an O(n) operation! + TValue value; + if (Dictionary2.TryGetValue(key2, out value)) + { + foreach (KeyValuePair kvp in Dictionary1) + { + if (kvp.Value.Equals(value)) + { + Dictionary2.Remove(key2); + Dictionary1.Remove(kvp.Key); + found = true; + break; + } + } + } + } + finally + { + if (gotLock) + rwLock.ExitWriteLock(); + } + + return found; + } + + public void Clear() + { + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterWriteLock(); + gotLock = true; + } + + Dictionary1.Clear(); + Dictionary2.Clear(); + } + finally + { + if (gotLock) + rwLock.ExitWriteLock(); + } + } + + public int Count + { + get { return Dictionary1.Count; } + } + + public bool ContainsKey(TKey1 key) + { + return Dictionary1.ContainsKey(key); + } + + public bool ContainsKey(TKey2 key) + { + return Dictionary2.ContainsKey(key); + } + + public bool TryGetValue(TKey1 key, out TValue value) + { + bool success; + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + success = Dictionary1.TryGetValue(key, out value); + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + + return success; + } + + public bool TryGetValue(TKey2 key, out TValue value) + { + bool success; + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + success = Dictionary2.TryGetValue(key, out value); + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + + return success; + } + + public void ForEach(Action action) + { + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + foreach (TValue value in Dictionary1.Values) + action(value); + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + } + + public void ForEach(Action> action) + { + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + foreach (KeyValuePair entry in Dictionary1) + action(entry); + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + } + + public void ForEach(Action> action) + { + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + foreach (KeyValuePair entry in Dictionary2) + action(entry); + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + } + + public TValue FindValue(Predicate predicate) + { + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + foreach (TValue value in Dictionary1.Values) + { + if (predicate(value)) + return value; + } + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + + return default(TValue); + } + + public IList FindAll(Predicate predicate) + { + IList list = new List(); + bool gotLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterReadLock(); + gotLock = true; + } + + foreach (TValue value in Dictionary1.Values) + { + if (predicate(value)) + list.Add(value); + } + } + finally + { + if (gotLock) + rwLock.ExitReadLock(); + } + + return list; + } + + public int RemoveAll(Predicate predicate) + { + IList list = new List(); + bool gotUpgradeableLock = false; + + try + { + // Avoid an asynchronous Thread.Abort() from possibly never existing an acquired lock by placing + // the acquision inside the main try. The inner finally block is needed because thread aborts cannot + // interrupt code in these blocks (hence gotLock is guaranteed to be set correctly). + try {} + finally + { + rwLock.EnterUpgradeableReadLock(); + gotUpgradeableLock = true; + } + + foreach (KeyValuePair kvp in Dictionary1) + { + if (predicate(kvp.Value)) + list.Add(kvp.Key); + } + + IList list2 = new List(list.Count); + foreach (KeyValuePair kvp in Dictionary2) + { + if (predicate(kvp.Value)) + list2.Add(kvp.Key); + } + + bool gotWriteLock = false; + + try + { + try {} + finally + { + rwLock.EnterUpgradeableReadLock(); + gotWriteLock = true; + } + + for (int i = 0; i < list.Count; i++) + Dictionary1.Remove(list[i]); + + for (int i = 0; i < list2.Count; i++) + Dictionary2.Remove(list2[i]); + } + finally + { + if (gotWriteLock) + rwLock.ExitWriteLock(); + } + } + finally + { + if (gotUpgradeableLock) + rwLock.ExitUpgradeableReadLock(); + } + + return list.Count; + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 04650425bf..c88828b73a 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -320,7 +320,7 @@ namespace OpenSim.Framework public delegate void ObjectPermissions( IClientAPI controller, UUID agentID, UUID sessionID, byte field, uint localId, uint mask, byte set); - public delegate void EconomyDataRequest(UUID agentID); + public delegate void EconomyDataRequest(IClientAPI client); public delegate void ObjectIncludeInSearch(IClientAPI remoteClient, bool IncludeInSearch, uint localID); @@ -1129,8 +1129,8 @@ namespace OpenSim.Framework void SendInstantMessage(GridInstantMessage im); - void SendGenericMessage(string method, List message); - void SendGenericMessage(string method, List message); + void SendGenericMessage(string method, UUID invoice, List message); + void SendGenericMessage(string method, UUID invoice, List message); void SendLayerData(float[] map); void SendLayerData(int px, int py, float[] map); diff --git a/OpenSim/Framework/ILandChannel.cs b/OpenSim/Framework/ILandChannel.cs index 869d4c8184..c46c03c4d0 100644 --- a/OpenSim/Framework/ILandChannel.cs +++ b/OpenSim/Framework/ILandChannel.cs @@ -55,6 +55,13 @@ namespace OpenSim.Region.Framework.Interfaces /// Land object at the point supplied ILandObject GetLandObject(float x, float y); + /// + /// Get the parcel at the specified point + /// + /// Vector where x and y components are between 0 and 256. z component is ignored. + /// Land object at the point supplied + ILandObject GetLandObject(Vector3 position); + /// /// Get the parcels near the specified point /// diff --git a/OpenSim/Framework/Monitoring/AssetStatsCollector.cs b/OpenSim/Framework/Monitoring/AssetStatsCollector.cs index 2a4d45bf0c..6a0f676b42 100644 --- a/OpenSim/Framework/Monitoring/AssetStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/AssetStatsCollector.cs @@ -28,6 +28,8 @@ using System; using System.Timers; +using OpenMetaverse.StructuredData; + namespace OpenSim.Framework.Monitoring { /// @@ -100,5 +102,29 @@ Asset requests yesterday : {3} ({4} per hour) of which {5} were not found", AssetRequestsToday, assetRequestsTodayPerHour, AssetRequestsNotFoundToday, AssetRequestsYesterday, assetRequestsYesterdayPerHour, AssetRequestsNotFoundYesterday); } + + public override string XReport(string uptime, string version) + { + return OSDParser.SerializeJsonString(OReport(uptime, version)); + } + + public override OSDMap OReport(string uptime, string version) + { + double elapsedHours = (DateTime.Now - startTime).TotalHours; + if (elapsedHours <= 0) { elapsedHours = 1; } // prevent divide by zero + + long assetRequestsTodayPerHour = (long)Math.Round(AssetRequestsToday / elapsedHours); + long assetRequestsYesterdayPerHour = (long)Math.Round(AssetRequestsYesterday / 24.0); + + OSDMap ret = new OSDMap(); + ret.Add("AssetRequestsToday", OSD.FromLong(AssetRequestsToday)); + ret.Add("AssetRequestsTodayPerHour", OSD.FromLong(assetRequestsTodayPerHour)); + ret.Add("AssetRequestsNotFoundToday", OSD.FromLong(AssetRequestsNotFoundToday)); + ret.Add("AssetRequestsYesterday", OSD.FromLong(AssetRequestsYesterday)); + ret.Add("AssetRequestsYesterdayPerHour", OSD.FromLong(assetRequestsYesterdayPerHour)); + ret.Add("AssetRequestsNotFoundYesterday", OSD.FromLong(assetRequestsNotFoundYesterday)); + + return ret; + } } } diff --git a/OpenSim/Framework/Monitoring/BaseStatsCollector.cs b/OpenSim/Framework/Monitoring/BaseStatsCollector.cs index 446e3c0697..23dba09c13 100644 --- a/OpenSim/Framework/Monitoring/BaseStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/BaseStatsCollector.cs @@ -80,5 +80,12 @@ namespace OpenSim.Framework.Monitoring { return (string) Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0).ToString() ; } + + public virtual OSDMap OReport(string uptime, string version) + { + OSDMap ret = new OSDMap(); + ret.Add("TotalMemory", new OSDReal(Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0))); + return ret; + } } } diff --git a/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs b/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs index 99f75e34b1..40df562a95 100644 --- a/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs @@ -25,6 +25,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using OpenMetaverse.StructuredData; + namespace OpenSim.Framework.Monitoring { /// @@ -45,5 +47,12 @@ namespace OpenSim.Framework.Monitoring /// A /// string XReport(string uptime, string version); + + /// + /// Report back collected statistical information as an OSDMap of key/values + /// + /// + /// + OSDMap OReport(string uptime, string version); } } diff --git a/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs b/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs index 1f2bb4087a..36678bbe3e 100644 --- a/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs index aa862027b8..6a68322531 100644 --- a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs @@ -359,11 +359,12 @@ Asset service request failures: {3}" + Environment.NewLine, inPacketsPerSecond, outPacketsPerSecond, pendingDownloads, pendingUploads, unackedBytes, totalFrameTime, netFrameTime, physicsFrameTime, otherFrameTime, agentFrameTime, imageFrameTime)); - Dictionary> sceneStats; - + /* 20130319 RA: For the moment, disable the dump of 'scene' catagory as they are mostly output by + * the two formatted printouts above. + SortedDictionary> sceneStats; if (StatsManager.TryGetStats("scene", out sceneStats)) { - foreach (KeyValuePair> kvp in sceneStats) + foreach (KeyValuePair> kvp in sceneStats) { foreach (Stat stat in kvp.Value.Values) { @@ -374,6 +375,7 @@ Asset service request failures: {3}" + Environment.NewLine, } } } + */ /* sb.Append(Environment.NewLine); @@ -404,6 +406,15 @@ Asset service request failures: {3}" + Environment.NewLine, /// /// public override string XReport(string uptime, string version) + { + return OSDParser.SerializeJsonString(OReport(uptime, version)); + } + + /// + /// Report back collected statistical information as an OSDMap + /// + /// + public override OSDMap OReport(string uptime, string version) { OSDMap args = new OSDMap(30); // args["AssetsInCache"] = OSD.FromString (String.Format ("{0:0.##}", AssetsInCache)); @@ -442,13 +453,11 @@ Asset service request failures: {3}" + Environment.NewLine, args["Uptime"] = OSD.FromString (uptime); args["Version"] = OSD.FromString (version); - string strBuffer = ""; - strBuffer = OSDParser.SerializeJsonString(args); - - return strBuffer; + return args; } } + /// /// Pull packet queue stats from packet queues and report /// @@ -474,5 +483,11 @@ Asset service request failures: {3}" + Environment.NewLine, { return ""; } + + public OSDMap OReport(string uptime, string version) + { + OSDMap ret = new OSDMap(); + return ret; + } } } diff --git a/OpenSim/Framework/Monitoring/Stats/CounterStat.cs b/OpenSim/Framework/Monitoring/Stats/CounterStat.cs new file mode 100755 index 0000000000..caea30dd5d --- /dev/null +++ b/OpenSim/Framework/Monitoring/Stats/CounterStat.cs @@ -0,0 +1,228 @@ +/* + * 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.Linq; +using System.Text; + +using OpenMetaverse.StructuredData; + +namespace OpenSim.Framework.Monitoring +{ +// Create a time histogram of events. The histogram is built in a wrap-around +// array of equally distributed buckets. +// For instance, a minute long histogram of second sized buckets would be: +// new EventHistogram(60, 1000) +public class EventHistogram +{ + private int m_timeBase; + private int m_numBuckets; + private int m_bucketMilliseconds; + private int m_lastBucket; + private int m_totalHistogramMilliseconds; + private long[] m_histogram; + private object histoLock = new object(); + + public EventHistogram(int numberOfBuckets, int millisecondsPerBucket) + { + m_numBuckets = numberOfBuckets; + m_bucketMilliseconds = millisecondsPerBucket; + m_totalHistogramMilliseconds = m_numBuckets * m_bucketMilliseconds; + + m_histogram = new long[m_numBuckets]; + Zero(); + m_lastBucket = 0; + m_timeBase = Util.EnvironmentTickCount(); + } + + public void Event() + { + this.Event(1); + } + + // Record an event at time 'now' in the histogram. + public void Event(int cnt) + { + lock (histoLock) + { + // The time as displaced from the base of the histogram + int bucketTime = Util.EnvironmentTickCountSubtract(m_timeBase); + + // If more than the total time of the histogram, we just start over + if (bucketTime > m_totalHistogramMilliseconds) + { + Zero(); + m_lastBucket = 0; + m_timeBase = Util.EnvironmentTickCount(); + } + else + { + // To which bucket should we add this event? + int bucket = bucketTime / m_bucketMilliseconds; + + // Advance m_lastBucket to the new bucket. Zero any buckets skipped over. + while (bucket != m_lastBucket) + { + // Zero from just after the last bucket to the new bucket or the end + for (int jj = m_lastBucket + 1; jj <= Math.Min(bucket, m_numBuckets - 1); jj++) + { + m_histogram[jj] = 0; + } + m_lastBucket = bucket; + // If the new bucket is off the end, wrap around to the beginning + if (bucket > m_numBuckets) + { + bucket -= m_numBuckets; + m_lastBucket = 0; + m_histogram[m_lastBucket] = 0; + m_timeBase += m_totalHistogramMilliseconds; + } + } + } + m_histogram[m_lastBucket] += cnt; + } + } + + // Get a copy of the current histogram + public long[] GetHistogram() + { + long[] ret = new long[m_numBuckets]; + lock (histoLock) + { + int indx = m_lastBucket + 1; + for (int ii = 0; ii < m_numBuckets; ii++, indx++) + { + if (indx >= m_numBuckets) + indx = 0; + ret[ii] = m_histogram[indx]; + } + } + return ret; + } + + public OSDMap GetHistogramAsOSDMap() + { + OSDMap ret = new OSDMap(); + + ret.Add("Buckets", OSD.FromInteger(m_numBuckets)); + ret.Add("BucketMilliseconds", OSD.FromInteger(m_bucketMilliseconds)); + ret.Add("TotalMilliseconds", OSD.FromInteger(m_totalHistogramMilliseconds)); + + // Compute a number for the first bucket in the histogram. + // This will allow readers to know how this histogram relates to any previously read histogram. + int baseBucketNum = (m_timeBase / m_bucketMilliseconds) + m_lastBucket + 1; + ret.Add("BaseNumber", OSD.FromInteger(baseBucketNum)); + + ret.Add("Values", GetHistogramAsOSDArray()); + + return ret; + } + // Get a copy of the current histogram + public OSDArray GetHistogramAsOSDArray() + { + OSDArray ret = new OSDArray(m_numBuckets); + lock (histoLock) + { + int indx = m_lastBucket + 1; + for (int ii = 0; ii < m_numBuckets; ii++, indx++) + { + if (indx >= m_numBuckets) + indx = 0; + ret[ii] = OSD.FromLong(m_histogram[indx]); + } + } + return ret; + } + + // Zero out the histogram + public void Zero() + { + lock (histoLock) + { + for (int ii = 0; ii < m_numBuckets; ii++) + m_histogram[ii] = 0; + } + } +} + +// A statistic that wraps a counter. +// Built this way mostly so histograms and history can be created. +public class CounterStat : Stat +{ + private SortedDictionary m_histograms; + private object counterLock = new object(); + + public CounterStat( + string shortName, + string name, + string description, + string unitName, + string category, + string container, + StatVerbosity verbosity) + : base(shortName, name, description, unitName, category, container, StatType.Push, null, verbosity) + { + m_histograms = new SortedDictionary(); + } + + // Histograms are presumably added at intialization time and the list does not change thereafter. + // Thus no locking of the histogram list. + public void AddHistogram(string histoName, EventHistogram histo) + { + m_histograms.Add(histoName, histo); + } + + public delegate void ProcessHistogram(string name, EventHistogram histo); + public void ForEachHistogram(ProcessHistogram process) + { + foreach (KeyValuePair kvp in m_histograms) + { + process(kvp.Key, kvp.Value); + } + } + + public void Event() + { + this.Event(1); + } + + // Count the underlying counter. + public void Event(int cnt) + { + lock (counterLock) + { + base.Value += cnt; + + foreach (EventHistogram histo in m_histograms.Values) + { + histo.Event(cnt); + } + } + } +} +} diff --git a/OpenSim/Framework/Monitoring/Stats/Stat.cs b/OpenSim/Framework/Monitoring/Stats/Stat.cs index f91251b6ea..2e7665f5a1 100644 --- a/OpenSim/Framework/Monitoring/Stats/Stat.cs +++ b/OpenSim/Framework/Monitoring/Stats/Stat.cs @@ -29,12 +29,14 @@ using System; using System.Collections.Generic; using System.Text; +using OpenMetaverse.StructuredData; + namespace OpenSim.Framework.Monitoring { /// /// Holds individual statistic details /// - public class Stat + public class Stat : IDisposable { /// /// Category of this stat (e.g. cache, scene, etc). @@ -181,6 +183,12 @@ namespace OpenSim.Framework.Monitoring Verbosity = verbosity; } + // IDisposable.Dispose() + public virtual void Dispose() + { + return; + } + /// /// Record a value in the sample set. /// @@ -203,13 +211,27 @@ namespace OpenSim.Framework.Monitoring public virtual string ToConsoleString() { StringBuilder sb = new StringBuilder(); - sb.AppendFormat("{0}.{1}.{2} : {3}{4}", Category, Container, ShortName, Value, UnitName); + sb.AppendFormat("{0}.{1}.{2} : {3} {4}", Category, Container, ShortName, Value, UnitName); AppendMeasuresOfInterest(sb); return sb.ToString(); } + public virtual OSDMap ToOSDMap() + { + OSDMap ret = new OSDMap(); + ret.Add("Category", OSD.FromString(Category)); + ret.Add("Container", OSD.FromString(Container)); + ret.Add("ShortName", OSD.FromString(ShortName)); + ret.Add("Name", OSD.FromString(Name)); + ret.Add("Description", OSD.FromString(Description)); + ret.Add("UnitName", OSD.FromString(UnitName)); + ret.Add("Value", OSD.FromReal(Value)); + + return ret; + } + protected void AppendMeasuresOfInterest(StringBuilder sb) { if ((MeasuresOfInterest & MeasuresOfInterest.AverageChangeOverTime) diff --git a/OpenSim/Framework/Monitoring/StatsManager.cs b/OpenSim/Framework/Monitoring/StatsManager.cs index 0762b01c7d..24db6d44c0 100644 --- a/OpenSim/Framework/Monitoring/StatsManager.cs +++ b/OpenSim/Framework/Monitoring/StatsManager.cs @@ -51,8 +51,8 @@ namespace OpenSim.Framework.Monitoring /// /// Do not add or remove directly from this dictionary. /// - public static Dictionary>> RegisteredStats - = new Dictionary>>(); + public static SortedDictionary>> RegisteredStats + = new SortedDictionary>>(); private static AssetStatsCollector assetStats; private static UserStatsCollector userStats; @@ -85,6 +85,7 @@ namespace OpenSim.Framework.Monitoring if (cmd.Length > 2) { var categoryName = cmd[2]; + var containerName = cmd.Length > 3 ? cmd[3] : String.Empty; if (categoryName == AllSubCommand) { @@ -101,14 +102,27 @@ namespace OpenSim.Framework.Monitoring } else { - Dictionary> category; + SortedDictionary> category; if (!RegisteredStats.TryGetValue(categoryName, out category)) { con.OutputFormat("No such category as {0}", categoryName); } else { - OutputCategoryStatsToConsole(con, category); + if (String.IsNullOrEmpty(containerName)) + OutputCategoryStatsToConsole(con, category); + else + { + SortedDictionary container; + if (category.TryGetValue(containerName, out container)) + { + OutputContainerStatsToConsole(con, container); + } + else + { + con.OutputFormat("No such container {0} in category {1}", containerName, categoryName); + } + } } } } @@ -120,14 +134,19 @@ namespace OpenSim.Framework.Monitoring } private static void OutputCategoryStatsToConsole( - ICommandConsole con, Dictionary> category) + ICommandConsole con, SortedDictionary> category) { foreach (var container in category.Values) { - foreach (Stat stat in container.Values) - { - con.Output(stat.ToConsoleString()); - } + OutputContainerStatsToConsole(con, container); + } + } + + private static void OutputContainerStatsToConsole( ICommandConsole con, SortedDictionary container) + { + foreach (Stat stat in container.Values) + { + con.Output(stat.ToConsoleString()); } } @@ -160,8 +179,8 @@ namespace OpenSim.Framework.Monitoring /// public static bool RegisterStat(Stat stat) { - Dictionary> category = null, newCategory; - Dictionary container = null, newContainer; + SortedDictionary> category = null, newCategory; + SortedDictionary container = null, newContainer; lock (RegisteredStats) { @@ -175,14 +194,14 @@ namespace OpenSim.Framework.Monitoring // This means that we don't need to lock or copy them on iteration, which will be a much more // common operation after startup. if (container != null) - newContainer = new Dictionary(container); + newContainer = new SortedDictionary(container); else - newContainer = new Dictionary(); + newContainer = new SortedDictionary(); if (category != null) - newCategory = new Dictionary>(category); + newCategory = new SortedDictionary>(category); else - newCategory = new Dictionary>(); + newCategory = new SortedDictionary>(); newContainer[stat.ShortName] = stat; newCategory[stat.Container] = newContainer; @@ -196,21 +215,21 @@ namespace OpenSim.Framework.Monitoring /// Deregister a statistic /// > /// - /// public static bool DeregisterStat(Stat stat) { - Dictionary> category = null, newCategory; - Dictionary container = null, newContainer; + SortedDictionary> category = null, newCategory; + SortedDictionary container = null, newContainer; lock (RegisteredStats) { if (!TryGetStat(stat, out category, out container)) return false; - newContainer = new Dictionary(container); + newContainer = new SortedDictionary(container); newContainer.Remove(stat.ShortName); - newCategory = new Dictionary>(category); + newCategory = new SortedDictionary>(category); newCategory.Remove(stat.Container); newCategory[stat.Container] = newContainer; @@ -220,15 +239,15 @@ namespace OpenSim.Framework.Monitoring } } - public static bool TryGetStats(string category, out Dictionary> stats) + public static bool TryGetStats(string category, out SortedDictionary> stats) { return RegisteredStats.TryGetValue(category, out stats); } public static bool TryGetStat( Stat stat, - out Dictionary> category, - out Dictionary container) + out SortedDictionary> category, + out SortedDictionary container) { category = null; container = null; @@ -252,9 +271,9 @@ namespace OpenSim.Framework.Monitoring { lock (RegisteredStats) { - foreach (Dictionary> category in RegisteredStats.Values) + foreach (SortedDictionary> category in RegisteredStats.Values) { - foreach (Dictionary container in category.Values) + foreach (SortedDictionary container in category.Values) { foreach (Stat stat in container.Values) { diff --git a/OpenSim/Framework/Monitoring/UserStatsCollector.cs b/OpenSim/Framework/Monitoring/UserStatsCollector.cs index e89c8e6344..81e0fa4e21 100644 --- a/OpenSim/Framework/Monitoring/UserStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/UserStatsCollector.cs @@ -27,6 +27,8 @@ using System.Timers; +using OpenMetaverse.StructuredData; + namespace OpenSim.Framework.Monitoring { /// @@ -88,5 +90,21 @@ namespace OpenSim.Framework.Monitoring Logouts total : {3}", SuccessfulLogins, SuccessfulLoginsToday, SuccessfulLoginsYesterday, Logouts); } + + public override string XReport(string uptime, string version) + { + return OSDParser.SerializeJsonString(OReport(uptime, version)); + } + + public override OSDMap OReport(string uptime, string version) + { + OSDMap ret = new OSDMap(); + ret.Add("SuccessfulLogins", OSD.FromInteger(SuccessfulLogins)); + ret.Add("SuccessfulLoginsToday", OSD.FromInteger(SuccessfulLoginsToday)); + ret.Add("SuccessfulLoginsYesterday", OSD.FromInteger(SuccessfulLoginsYesterday)); + ret.Add("Logouts", OSD.FromInteger(Logouts)); + + return ret; + } } } diff --git a/OpenSim/Framework/PluginManager.cs b/OpenSim/Framework/PluginManager.cs new file mode 100644 index 0000000000..011709602b --- /dev/null +++ b/OpenSim/Framework/PluginManager.cs @@ -0,0 +1,563 @@ + +/* + * 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.Text; +using System.Linq; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Mono.Addins; +using Mono.Addins.Setup; +using Mono.Addins.Description; +using OpenSim.Framework; + + +namespace OpenSim.Framework +{ + /// + /// Manager for registries and plugins + /// + public class PluginManager : SetupService + { + public AddinRegistry PluginRegistry; + + public PluginManager(AddinRegistry registry): base (registry) + { + PluginRegistry = registry; + + } + + /// + /// Installs the plugin. + /// + /// + /// The plugin. + /// + /// + /// Arguments. + /// + public bool InstallPlugin(int ndx, out Dictionary result) + { + Dictionary res = new Dictionary(); + + PackageCollection pack = new PackageCollection(); + PackageCollection toUninstall; + DependencyCollection unresolved; + + IProgressStatus ps = new ConsoleProgressStatus(false); + + AddinRepositoryEntry[] available = GetSortedAvailbleAddins(); + + if (ndx > (available.Length - 1)) + { + MainConsole.Instance.Output("Selection out of range"); + result = res; + return false; + } + + AddinRepositoryEntry aentry = available[ndx]; + + Package p = Package.FromRepository(aentry); + pack.Add(p); + + ResolveDependencies(ps, pack, out toUninstall, out unresolved); + + // Attempt to install the plugin disabled + if (Install(ps, pack) == true) + { + MainConsole.Instance.Output("Ignore the following error..."); + PluginRegistry.Update(ps); + Addin addin = PluginRegistry.GetAddin(aentry.Addin.Id); + PluginRegistry.DisableAddin(addin.Id); + addin.Enabled = false; + + MainConsole.Instance.Output("Installation Success"); + ListInstalledAddins(out res); + result = res; + return true; + } + else + { + MainConsole.Instance.Output("Installation Failed"); + result = res; + return false; + } + } + + // Remove plugin + /// + /// Uns the install. + /// + /// + /// Arguments. + /// + public void UnInstall(int ndx) + { + Addin[] addins = GetSortedAddinList("RobustPlugin"); + + if (ndx > (addins.Length -1)) + { + MainConsole.Instance.Output("Selection out of range"); + return; + } + + Addin addin = addins[ndx]; + MainConsole.Instance.OutputFormat("Uninstalling plugin {0}", addin.Id); + AddinManager.Registry.DisableAddin(addin.Id); + addin.Enabled = false; + IProgressStatus ps = new ConsoleProgressStatus(false); + Uninstall(ps, addin.Id); + MainConsole.Instance.Output("Uninstall Success - restart to complete operation"); + return; + } + + /// + /// Checks the installed. + /// + /// + /// The installed. + /// + public string CheckInstalled() + { + return "CheckInstall"; + } + + /// + /// Lists the installed addins. + /// + /// + /// Result. + /// + public void ListInstalledAddins(out Dictionary result) + { + Dictionary res = new Dictionary(); + + Addin[] addins = GetSortedAddinList("RobustPlugin"); + if(addins.Count() < 1) + { + MainConsole.Instance.Output("Error!"); + } + int count = 0; + foreach (Addin addin in addins) + { + Dictionary r = new Dictionary(); + r["enabled"] = addin.Enabled == true ? true : false; + r["name"] = addin.LocalId; + r["version"] = addin.Version; + + res.Add(count.ToString(), r); + + count++; + } + result = res; + return; + } + + // List compatible plugins in registered repositories + /// + /// Lists the available. + /// + /// + /// Result. + /// + public void ListAvailable(out Dictionary result) + { + Dictionary res = new Dictionary(); + + AddinRepositoryEntry[] addins = GetSortedAvailbleAddins(); + + int count = 0; + foreach (AddinRepositoryEntry addin in addins) + { + Dictionary r = new Dictionary(); + r["name"] = addin.Addin.Name; + r["version"] = addin.Addin.Version; + r["repository"] = addin.RepositoryName; + + res.Add(count.ToString(), r); + count++; + } + result = res; + return; + } + + // List available updates ** 1 + /// + /// Lists the updates. + /// + public void ListUpdates() + { + IProgressStatus ps = new ConsoleProgressStatus(true); + Console.WriteLine ("Looking for updates..."); + Repositories.UpdateAllRepositories (ps); + Console.WriteLine ("Available add-in updates:"); + + AddinRepositoryEntry[] entries = Repositories.GetAvailableUpdates(); + + foreach (AddinRepositoryEntry entry in entries) + { + Console.WriteLine(String.Format("{0}",entry.Addin.Id)); + } + } + + // Sync to repositories + /// + /// Update this instance. + /// + public string Update() + { + IProgressStatus ps = new ConsoleProgressStatus(true); + Repositories.UpdateAllRepositories(ps); + return "Update"; + } + + // Register a repository + /// + /// Register a repository with our server. + /// + /// + /// result of the action + /// + /// + /// The URL of the repository we want to add + /// + public bool AddRepository(string repo) + { + Repositories.RegisterRepository(null, repo, true); + PluginRegistry.Rebuild(null); + + return true; + } + + /// + /// Gets the repository. + /// + public void GetRepository() + { + Repositories.UpdateAllRepositories(new ConsoleProgressStatus(false)); + } + + // Remove a repository from the list + /// + /// Removes the repository. + /// + /// + /// Arguments. + /// + public void RemoveRepository(string[] args) + { + AddinRepository[] reps = Repositories.GetRepositories(); + Array.Sort(reps, (r1,r2) => r1.Title.CompareTo(r2.Title)); + if (reps.Length == 0) + { + MainConsole.Instance.Output("No repositories have been registered."); + return; + } + + int n = Convert.ToInt16(args[2]); + if (n > (reps.Length -1)) + { + MainConsole.Instance.Output("Selection out of range"); + return; + } + + AddinRepository rep = reps[n]; + Repositories.RemoveRepository(rep.Url); + return; + } + + // Enable repository + /// + /// Enables the repository. + /// + /// + /// Arguments. + /// + public void EnableRepository(string[] args) + { + AddinRepository[] reps = Repositories.GetRepositories(); + Array.Sort(reps, (r1,r2) => r1.Title.CompareTo(r2.Title)); + if (reps.Length == 0) + { + MainConsole.Instance.Output("No repositories have been registered."); + return; + } + + int n = Convert.ToInt16(args[2]); + if (n > (reps.Length -1)) + { + MainConsole.Instance.Output("Selection out of range"); + return; + } + + AddinRepository rep = reps[n]; + Repositories.SetRepositoryEnabled(rep.Url, true); + return; + } + + // Disable a repository + /// + /// Disables the repository. + /// + /// + /// Arguments. + /// + public void DisableRepository(string[] args) + { + AddinRepository[] reps = Repositories.GetRepositories(); + Array.Sort(reps, (r1,r2) => r1.Title.CompareTo(r2.Title)); + if (reps.Length == 0) + { + MainConsole.Instance.Output("No repositories have been registered."); + return; + } + + int n = Convert.ToInt16(args[2]); + if (n > (reps.Length -1)) + { + MainConsole.Instance.Output("Selection out of range"); + return; + } + + AddinRepository rep = reps[n]; + Repositories.SetRepositoryEnabled(rep.Url, false); + return; + } + + // List registered repositories + /// + /// Lists the repositories. + /// + /// + /// Result. + /// + public void ListRepositories(out Dictionary result) + { + Dictionary res = new Dictionary(); + result = res; + + AddinRepository[] reps = GetSortedAddinRepo(); + if (reps.Length == 0) + { + MainConsole.Instance.Output("No repositories have been registered."); + return; + } + + int count = 0; + foreach (AddinRepository rep in reps) + { + Dictionary r = new Dictionary(); + r["enabled"] = rep.Enabled == true ? true : false; + r["name"] = rep.Name; + r["url"] = rep.Url; + + res.Add(count.ToString(), r); + count++; + } + return; + } + + /// + /// Updates the registry. + /// + public void UpdateRegistry() + { + PluginRegistry.Update(); + } + + // Show plugin info + /// + /// Addins the info. + /// + /// + /// The info. + /// + /// + /// Arguments. + /// + public bool AddinInfo(int ndx, out Dictionary result) + { + Dictionary res = new Dictionary(); + result = res; + + Addin[] addins = GetSortedAddinList("RobustPlugin"); + + if (ndx > (addins.Length - 1)) + { + MainConsole.Instance.Output("Selection out of range"); + return false; + } + // author category description + Addin addin = addins[ndx]; + + res["author"] = addin.Description.Author; + res["category"] = addin.Description.Category; + res["description"] = addin.Description.Description; + res["name"] = addin.Name; + res["url"] = addin.Description.Url; + res["file_name"] = addin.Description.FileName; + + result = res; + return true; + } + + // Disable a plugin + /// + /// Disables the plugin. + /// + /// + /// Arguments. + /// + public void DisablePlugin(string[] args) + { + Addin[] addins = GetSortedAddinList("RobustPlugin"); + + int n = Convert.ToInt16(args[2]); + if (n > (addins.Length -1)) + { + MainConsole.Instance.Output("Selection out of range"); + return; + } + + Addin addin = addins[n]; + AddinManager.Registry.DisableAddin(addin.Id); + addin.Enabled = false; + return; + } + + // Enable plugin + /// + /// Enables the plugin. + /// + /// + /// Arguments. + /// + public void EnablePlugin(string[] args) + { + Addin[] addins = GetSortedAddinList("RobustPlugin"); + + int n = Convert.ToInt16(args[2]); + if (n > (addins.Length -1)) + { + MainConsole.Instance.Output("Selection out of range"); + return; + } + + Addin addin = addins[n]; + + addin.Enabled = true; + AddinManager.Registry.EnableAddin(addin.Id); + // AddinManager.Registry.Update(); + if(PluginRegistry.IsAddinEnabled(addin.Id)) + { + ConsoleProgressStatus ps = new ConsoleProgressStatus(false); + if (!AddinManager.AddinEngine.IsAddinLoaded(addin.Id)) + { + MainConsole.Instance.Output("Ignore the following error..."); + AddinManager.Registry.Rebuild(ps); + AddinManager.AddinEngine.LoadAddin(ps, addin.Id); + } + } + else + { + MainConsole.Instance.OutputFormat("Not Enabled in this domain {0}", addin.Name); + } + return; + } + + + + #region Util + private void Testing() + { + Addin[] list = Registry.GetAddins(); + + var addins = list.Where( a => a.Description.Category == "RobustPlugin"); + + foreach (Addin addin in addins) + { + MainConsole.Instance.OutputFormat("Addin {0}", addin.Name); + } + } + + // These will let us deal with numbered lists instead + // of needing to type in the full ids + private AddinRepositoryEntry[] GetSortedAvailbleAddins() + { + ArrayList list = new ArrayList(); + list.AddRange(Repositories.GetAvailableAddins()); + + AddinRepositoryEntry[] addins = list.ToArray(typeof(AddinRepositoryEntry)) as AddinRepositoryEntry[]; + + Array.Sort(addins,(r1,r2) => r1.Addin.Id.CompareTo(r2.Addin.Id)); + + return addins; + } + + private AddinRepository[] GetSortedAddinRepo() + { + ArrayList list = new ArrayList(); + list.AddRange(Repositories.GetRepositories()); + + AddinRepository[] repos = list.ToArray(typeof(AddinRepository)) as AddinRepository[]; + Array.Sort (repos,(r1,r2) => r1.Name.CompareTo(r2.Name)); + + return repos; + } + + private Addin[] GetSortedAddinList(string category) + { + + ArrayList xlist = new ArrayList(); + ArrayList list = new ArrayList(); + try + { + list.AddRange(PluginRegistry.GetAddins()); + } + catch (Exception) + { + Addin[] x = xlist.ToArray(typeof(Addin)) as Addin[]; + return x; + } + + foreach (Addin addin in list) + { + if (addin.Description.Category == category) + xlist.Add(addin); + } + + Addin[] addins = xlist.ToArray(typeof(Addin)) as Addin[]; + Array.Sort(addins,(r1,r2) => r1.Id.CompareTo(r2.Id)); + + return addins; + } + #endregion Util + } +} diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index fcc9873441..df928dc226 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -192,18 +192,7 @@ namespace OpenSim.Framework public PrimitiveBaseShape() { - PCode = (byte) PCodeEnum.Primitive; - ExtraParams = new byte[1]; - m_textureEntry = DEFAULT_TEXTURE; - } - - public PrimitiveBaseShape(bool noShape) - { - if (noShape) - return; - PCode = (byte)PCodeEnum.Primitive; - ExtraParams = new byte[1]; m_textureEntry = DEFAULT_TEXTURE; } @@ -216,7 +205,6 @@ namespace OpenSim.Framework // m_log.DebugFormat("[PRIMITIVE BASE SHAPE]: Creating from {0}", prim.ID); PCode = (byte)prim.PrimData.PCode; - ExtraParams = new byte[1]; State = prim.PrimData.State; PathBegin = Primitive.PackBeginCut(prim.PrimData.PathBegin); @@ -248,7 +236,10 @@ namespace OpenSim.Framework SculptTexture = prim.Sculpt.SculptTexture; SculptType = (byte)prim.Sculpt.Type; } - else SculptType = (byte)OpenMetaverse.SculptType.None; + else + { + SculptType = (byte)OpenMetaverse.SculptType.None; + } } [XmlIgnore] @@ -340,9 +331,9 @@ namespace OpenSim.Framework _scale = new Vector3(side, side, side); } - public void SetHeigth(float heigth) + public void SetHeigth(float height) { - _scale.Z = heigth; + _scale.Z = height; } public void SetRadius(float radius) @@ -631,6 +622,8 @@ namespace OpenSim.Framework } } + // This is only used at runtime. For sculpties this holds the texture data, and for meshes + // the mesh data. public byte[] SculptData { get @@ -1184,14 +1177,13 @@ namespace OpenSim.Framework public void ReadSculptData(byte[] data, int pos) { - byte[] SculptTextureUUID = new byte[16]; - UUID SculptUUID = UUID.Zero; - byte SculptTypel = data[16+pos]; + UUID SculptUUID; + byte SculptTypel; - if (data.Length+pos >= 17) + if (data.Length-pos >= 17) { _sculptEntry = true; - SculptTextureUUID = new byte[16]; + byte[] SculptTextureUUID = new byte[16]; SculptTypel = data[16 + pos]; Array.Copy(data, pos, SculptTextureUUID,0, 16); SculptUUID = new UUID(SculptTextureUUID, 0); diff --git a/OpenSim/Framework/RegionInfo.cs b/OpenSim/Framework/RegionInfo.cs index e7bed6aeb0..6dde62fec1 100644 --- a/OpenSim/Framework/RegionInfo.cs +++ b/OpenSim/Framework/RegionInfo.cs @@ -145,6 +145,7 @@ namespace OpenSim.Framework public UUID RegionID = UUID.Zero; public string RemotingAddress; public UUID ScopeID = UUID.Zero; + private UUID m_maptileStaticUUID = UUID.Zero; private Dictionary m_otherSettings = new Dictionary(); @@ -340,6 +341,11 @@ namespace OpenSim.Framework get { return m_regionType; } } + public UUID MaptileStaticUUID + { + get { return m_maptileStaticUUID; } + } + /// /// The port by which http communication occurs with the region (most noticeably, CAPS communication) /// @@ -643,7 +649,7 @@ namespace OpenSim.Framework m_regionType = config.GetString("RegionType", String.Empty); allKeys.Remove("RegionType"); - #region Prim stuff + #region Prim and map stuff m_nonphysPrimMin = config.GetFloat("NonPhysicalPrimMin", 0); allKeys.Remove("NonPhysicalPrimMin"); @@ -665,6 +671,13 @@ namespace OpenSim.Framework m_linksetCapacity = config.GetInt("LinksetPrims", 0); allKeys.Remove("LinksetPrims"); + + allKeys.Remove("MaptileStaticUUID"); + string mapTileStaticUUID = config.GetString("MaptileStaticUUID", UUID.Zero.ToString()); + if (UUID.TryParse(mapTileStaticUUID.Trim(), out m_maptileStaticUUID)) + { + config.Set("MaptileStaticUUID", m_maptileStaticUUID.ToString()); + } #endregion @@ -734,6 +747,9 @@ namespace OpenSim.Framework if (RegionType != String.Empty) config.Set("RegionType", RegionType); + + if (m_maptileStaticUUID != UUID.Zero) + config.Set("MaptileStaticUUID", m_maptileStaticUUID.ToString()); } public bool ignoreIncomingConfiguration(string configuration_key, object configuration_result) @@ -832,6 +848,9 @@ namespace OpenSim.Framework configMember.addConfigurationOption("region_type", ConfigurationOption.ConfigurationTypes.TYPE_STRING, "Free form string describing the type of region", String.Empty, true); + + configMember.addConfigurationOption("region_static_maptile", ConfigurationOption.ConfigurationTypes.TYPE_UUID, + "UUID of a texture to use as the map for this region", m_maptileStaticUUID.ToString(), true); } public void loadConfigurationOptions() @@ -885,6 +904,9 @@ namespace OpenSim.Framework configMember.addConfigurationOption("region_type", ConfigurationOption.ConfigurationTypes.TYPE_STRING, "Region Type", String.Empty, true); + + configMember.addConfigurationOption("region_static_maptile", ConfigurationOption.ConfigurationTypes.TYPE_UUID, + "UUID of a texture to use as the map for this region", String.Empty, true); } public bool handleIncomingConfiguration(string configuration_key, object configuration_result) @@ -957,6 +979,9 @@ namespace OpenSim.Framework case "region_type": m_regionType = (string)configuration_result; break; + case "region_static_maptile": + m_maptileStaticUUID = (UUID)configuration_result; + break; } return true; diff --git a/OpenSim/Framework/RegionLoader/Filesystem/Properties/AssemblyInfo.cs b/OpenSim/Framework/RegionLoader/Filesystem/Properties/AssemblyInfo.cs index d670f2f129..d4806f17f6 100644 --- a/OpenSim/Framework/RegionLoader/Filesystem/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/RegionLoader/Filesystem/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/RegionLoader/Web/Properties/AssemblyInfo.cs b/OpenSim/Framework/RegionLoader/Web/Properties/AssemblyInfo.cs index 7309a12e88..1541a5bced 100644 --- a/OpenSim/Framework/RegionLoader/Web/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/RegionLoader/Web/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Serialization/ArchiveConstants.cs b/OpenSim/Framework/Serialization/ArchiveConstants.cs index 48f1c4f94f..0c12787882 100644 --- a/OpenSim/Framework/Serialization/ArchiveConstants.cs +++ b/OpenSim/Framework/Serialization/ArchiveConstants.cs @@ -154,6 +154,11 @@ namespace OpenSim.Framework.Serialization EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"] = (sbyte)AssetType.TrashFolder; } + public static string CreateOarLandDataPath(LandData ld) + { + return string.Format("{0}{1}.xml", ArchiveConstants.LANDDATA_PATH, ld.GlobalID); + } + /// /// Create the filename used to store an object in an OpenSim Archive. /// diff --git a/OpenSim/Framework/Serialization/Properties/AssemblyInfo.cs b/OpenSim/Framework/Serialization/Properties/AssemblyInfo.cs index 11efa4beb7..a8dff93458 100644 --- a/OpenSim/Framework/Serialization/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Serialization/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Serialization/Tests/LandDataSerializerTests.cs b/OpenSim/Framework/Serialization/Tests/LandDataSerializerTests.cs index 8b9756b11d..ea100ee71c 100644 --- a/OpenSim/Framework/Serialization/Tests/LandDataSerializerTests.cs +++ b/OpenSim/Framework/Serialization/Tests/LandDataSerializerTests.cs @@ -37,7 +37,7 @@ using OpenSim.Tests.Common; namespace OpenSim.Framework.Serialization.Tests { [TestFixture] - public class LandDataSerializerTest + public class LandDataSerializerTest : OpenSimTestCase { private LandData land; private LandData landWithParcelAccessList; diff --git a/OpenSim/Framework/Serialization/Tests/RegionSettingsSerializerTests.cs b/OpenSim/Framework/Serialization/Tests/RegionSettingsSerializerTests.cs index 09b6f6ddad..142726bbe7 100644 --- a/OpenSim/Framework/Serialization/Tests/RegionSettingsSerializerTests.cs +++ b/OpenSim/Framework/Serialization/Tests/RegionSettingsSerializerTests.cs @@ -37,7 +37,7 @@ using OpenSim.Tests.Common; namespace OpenSim.Framework.Serialization.Tests { [TestFixture] - public class RegionSettingsSerializerTests + public class RegionSettingsSerializerTests : OpenSimTestCase { private string m_serializedRs = @" diff --git a/OpenSim/Framework/Servers/BaseOpenSimServer.cs b/OpenSim/Framework/Servers/BaseOpenSimServer.cs index 2c21800bbd..6c04c694f8 100644 --- a/OpenSim/Framework/Servers/BaseOpenSimServer.cs +++ b/OpenSim/Framework/Servers/BaseOpenSimServer.cs @@ -27,7 +27,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; @@ -99,34 +98,6 @@ namespace OpenSim.Framework.Servers m_console.Commands.AddCommand("General", false, "shutdown", "shutdown", "Quit the application", HandleQuit); - - m_console.Commands.AddCommand("General", false, "show threads", - "show threads", - "Show thread status", HandleShow); - - m_console.Commands.AddCommand("General", false, "show version", - "show version", - "Show server version", HandleShow); - - m_console.Commands.AddCommand("General", false, "threads abort", - "threads abort ", - "Abort a managed thread. Use \"show threads\" to find possible threads.", HandleThreadsAbort); - - m_console.Commands.AddCommand("General", false, "threads show", - "threads show", - "Show thread status. Synonym for \"show threads\"", - (string module, string[] args) => Notice(GetThreadsReport())); - - m_console.Commands.AddCommand("General", false, "force gc", - "force gc", - "Manually invoke runtime garbage collection. For debugging purposes", - HandleForceGc); - } - - private void HandleForceGc(string module, string[] args) - { - MainConsole.Instance.Output("Manually invoking runtime garbage collection"); - GC.Collect(); } /// @@ -158,54 +129,6 @@ namespace OpenSim.Framework.Servers m_log.Debug(sb); } - /// - /// Get a report about the registered threads in this server. - /// - protected string GetThreadsReport() - { - // This should be a constant field. - string reportFormat = "{0,6} {1,35} {2,16} {3,13} {4,10} {5,30}"; - - StringBuilder sb = new StringBuilder(); - Watchdog.ThreadWatchdogInfo[] threads = Watchdog.GetThreadsInfo(); - - sb.Append(threads.Length + " threads are being tracked:" + Environment.NewLine); - - int timeNow = Environment.TickCount & Int32.MaxValue; - - sb.AppendFormat(reportFormat, "ID", "NAME", "LAST UPDATE (MS)", "LIFETIME (MS)", "PRIORITY", "STATE"); - sb.Append(Environment.NewLine); - - foreach (Watchdog.ThreadWatchdogInfo twi in threads) - { - Thread t = twi.Thread; - - sb.AppendFormat( - reportFormat, - t.ManagedThreadId, - t.Name, - timeNow - twi.LastTick, - timeNow - twi.FirstTick, - t.Priority, - t.ThreadState); - - sb.Append("\n"); - } - - sb.Append("\n"); - - // For some reason mono 2.6.7 returns an empty threads set! Not going to confuse people by reporting - // zero active threads. - int totalThreads = Process.GetCurrentProcess().Threads.Count; - if (totalThreads > 0) - sb.AppendFormat("Total threads active: {0}\n\n", totalThreads); - - sb.Append("Main threadpool (excluding script engine pools)\n"); - sb.Append(Util.GetThreadPoolReport()); - - return sb.ToString(); - } - /// /// Performs initialisation of the scene, such as loading configuration from disk. /// @@ -246,50 +169,7 @@ namespace OpenSim.Framework.Servers private void HandleQuit(string module, string[] args) { Shutdown(); - } - - public override void HandleShow(string module, string[] cmd) - { - base.HandleShow(module, cmd); - - List args = new List(cmd); - - args.RemoveAt(0); - - string[] showParams = args.ToArray(); - - switch (showParams[0]) - { - case "threads": - Notice(GetThreadsReport()); - break; - - case "version": - Notice(GetVersionText()); - break; - } - } - - public virtual void HandleThreadsAbort(string module, string[] cmd) - { - if (cmd.Length != 3) - { - MainConsole.Instance.Output("Usage: threads abort "); - return; - } - - int threadId; - if (!int.TryParse(cmd[2], out threadId)) - { - MainConsole.Instance.Output("ERROR: Thread id must be an integer"); - return; - } - - if (Watchdog.AbortThread(threadId)) - MainConsole.Instance.OutputFormat("Aborted thread with id {0}", threadId); - else - MainConsole.Instance.OutputFormat("ERROR - Thread with id {0} not found in managed threads", threadId); - } + } public string osSecret { // Secret uuid for the simulator @@ -309,4 +189,4 @@ namespace OpenSim.Framework.Servers } } } -} \ No newline at end of file +} diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index 77fce9e6e4..97035e385c 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -54,6 +54,16 @@ namespace OpenSim.Framework.Servers.HttpServer private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private HttpServerLogWriter httpserverlog = new HttpServerLogWriter(); + + /// + /// This is a pending websocket request before it got an sucessful upgrade response. + /// The consumer must call handler.HandshakeAndUpgrade() to signal to the handler to + /// start the connection and optionally provide an origin authentication method. + /// + /// + /// + public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHttpServerHandler handler); + /// /// Gets or sets the debug level. /// @@ -77,6 +87,7 @@ namespace OpenSim.Framework.Servers.HttpServer // protected HttpListener m_httpListener; protected CoolHTTPListener m_httpListener2; protected Dictionary m_rpcHandlers = new Dictionary(); + protected Dictionary jsonRpcHandlers = new Dictionary(); protected Dictionary m_rpcHandlersKeepAlive = new Dictionary(); protected DefaultLLSDMethod m_defaultLlsdHandler = null; // <-- Moving away from the monolithic.. and going to /registered/ protected Dictionary m_llsdHandlers = new Dictionary(); @@ -86,6 +97,9 @@ namespace OpenSim.Framework.Servers.HttpServer protected Dictionary m_pollHandlers = new Dictionary(); + protected Dictionary m_WebSocketHandlers = + new Dictionary(); + protected uint m_port; protected uint m_sslport; protected bool m_ssl; @@ -169,6 +183,22 @@ namespace OpenSim.Framework.Servers.HttpServer } } + public void AddWebSocketHandler(string servicepath, WebSocketRequestDelegate handler) + { + lock (m_WebSocketHandlers) + { + if (!m_WebSocketHandlers.ContainsKey(servicepath)) + m_WebSocketHandlers.Add(servicepath, handler); + } + } + + public void RemoveWebSocketHandler(string servicepath) + { + lock (m_WebSocketHandlers) + if (m_WebSocketHandlers.ContainsKey(servicepath)) + m_WebSocketHandlers.Remove(servicepath); + } + public List GetStreamHandlerKeys() { lock (m_streamHandlers) @@ -217,6 +247,37 @@ namespace OpenSim.Framework.Servers.HttpServer return new List(m_rpcHandlers.Keys); } + // JsonRPC + public bool AddJsonRPCHandler(string method, JsonRPCMethod handler) + { + lock(jsonRpcHandlers) + { + jsonRpcHandlers.Add(method, handler); + } + return true; + } + + public JsonRPCMethod GetJsonRPCHandler(string method) + { + lock (jsonRpcHandlers) + { + if (jsonRpcHandlers.ContainsKey(method)) + { + return jsonRpcHandlers[method]; + } + else + { + return null; + } + } + } + + public List GetJsonRpcHandlerKeys() + { + lock (jsonRpcHandlers) + return new List(jsonRpcHandlers.Keys); + } + public bool AddHTTPHandler(string methodName, GenericHTTPMethod handler) { //m_log.DebugFormat("[BASE HTTP SERVER]: Registering {0}", methodName); @@ -378,9 +439,24 @@ namespace OpenSim.Framework.Servers.HttpServer public void OnHandleRequestIOThread(IHttpClientContext context, IHttpRequest request) { + OSHttpRequest req = new OSHttpRequest(context, request); + WebSocketRequestDelegate dWebSocketRequestDelegate = null; + lock (m_WebSocketHandlers) + { + if (m_WebSocketHandlers.ContainsKey(req.RawUrl)) + dWebSocketRequestDelegate = m_WebSocketHandlers[req.RawUrl]; + } + if (dWebSocketRequestDelegate != null) + { + dWebSocketRequestDelegate(req.Url.AbsolutePath, new WebSocketHttpServerHandler(req, context, 8192)); + return; + } + OSHttpResponse resp = new OSHttpResponse(new HttpResponse(context, request),context); + HandleRequest(req, resp); + // !!!HACK ALERT!!! // There seems to be a bug in the underlying http code that makes subsequent requests @@ -411,7 +487,9 @@ namespace OpenSim.Framework.Servers.HttpServer { try { - SendHTML500(response); + byte[] buffer500 = SendHTML500(response); + response.Body.Write(buffer500,0,buffer500.Length); + response.Body.Close(); } catch { @@ -437,7 +515,7 @@ namespace OpenSim.Framework.Servers.HttpServer // reqnum = String.Format("{0}:{1}",request.RemoteIPEndPoint,request.Headers["opensim-request-id"]); //m_log.DebugFormat("[BASE HTTP SERVER]: <{0}> handle request for {1}",reqnum,request.RawUrl); - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US", true); + Culture.SetCurrentCulture(); // // This is the REST agent interface. We require an agent to properly identify // // itself. If the REST handler recognizes the prefix it will attempt to @@ -469,7 +547,7 @@ namespace OpenSim.Framework.Servers.HttpServer LogIncomingToStreamHandler(request, requestHandler); response.ContentType = requestHandler.ContentType; // Lets do this defaulting before in case handler has varying content type. - + if (requestHandler is IStreamedRequestHandler) { IStreamedRequestHandler streamedRequestHandler = requestHandler as IStreamedRequestHandler; @@ -557,10 +635,18 @@ namespace OpenSim.Framework.Servers.HttpServer buffer = HandleLLSDRequests(request, response); break; + + case "application/json-rpc": + if (DebugLevel >= 3) + LogIncomingToContentTypeHandler(request); + + buffer = HandleJsonRpcRequests(request, response); + break; case "text/xml": case "application/xml": case "application/json": + default: //m_log.Info("[Debug BASE HTTP SERVER]: in default handler"); // Point of note.. the DoWeHaveA methods check for an EXACT path @@ -636,7 +722,15 @@ namespace OpenSim.Framework.Servers.HttpServer catch (Exception e) { m_log.Error(String.Format("[BASE HTTP SERVER]: HandleRequest() threw {0} ", e.StackTrace), e); - SendHTML500(response); + try + { + byte[] buffer500 = SendHTML500(response); + response.Body.Write(buffer500, 0, buffer500.Length); + response.Body.Close(); + } + catch + { + } } finally { @@ -986,6 +1080,93 @@ namespace OpenSim.Framework.Servers.HttpServer return buffer; } + // JsonRpc (v2.0 only) + // Batch requests not yet supported + private byte[] HandleJsonRpcRequests(OSHttpRequest request, OSHttpResponse response) + { + Stream requestStream = request.InputStream; + JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(); + OSDMap jsonRpcRequest = null; + + try + { + jsonRpcRequest = (OSDMap)OSDParser.DeserializeJson(requestStream); + } + catch (LitJson.JsonException e) + { + jsonRpcResponse.Error.Code = ErrorCode.InternalError; + jsonRpcResponse.Error.Message = e.Message; + } + + requestStream.Close(); + + if (jsonRpcRequest != null) + { + if (jsonRpcRequest.ContainsKey("jsonrpc") || jsonRpcRequest["jsonrpc"].AsString() == "2.0") + { + jsonRpcResponse.JsonRpc = "2.0"; + + // If we have no id, then it's a "notification" + if (jsonRpcRequest.ContainsKey("id")) + { + jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); + } + + string methodname = jsonRpcRequest["method"]; + JsonRPCMethod method; + + if (jsonRpcHandlers.ContainsKey(methodname)) + { + lock(jsonRpcHandlers) + { + jsonRpcHandlers.TryGetValue(methodname, out method); + } + bool res = false; + try + { + res = method(jsonRpcRequest, ref jsonRpcResponse); + if(!res) + { + // The handler sent back an unspecified error + if(jsonRpcResponse.Error.Code == 0) + { + jsonRpcResponse.Error.Code = ErrorCode.InternalError; + } + } + } + catch (Exception e) + { + string ErrorMessage = string.Format("[BASE HTTP SERVER]: Json-Rpc Handler Error method {0} - {1}", methodname, e.Message); + m_log.Error(ErrorMessage); + jsonRpcResponse.Error.Code = ErrorCode.InternalError; + jsonRpcResponse.Error.Message = ErrorMessage; + } + } + else // Error no hanlder defined for requested method + { + jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; + jsonRpcResponse.Error.Message = string.Format ("No handler defined for {0}", methodname); + } + } + else // not json-rpc 2.0 could be v1 + { + jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; + jsonRpcResponse.Error.Message = "Must be valid json-rpc 2.0 see: http://www.jsonrpc.org/specification"; + + if (jsonRpcRequest.ContainsKey("id")) + jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); + } + } + + response.KeepAlive = true; + string responseData = string.Empty; + + responseData = jsonRpcResponse.Serialize(); + + byte[] buffer = Encoding.UTF8.GetBytes(responseData); + return buffer; + } + private byte[] HandleLLSDRequests(OSHttpRequest request, OSHttpResponse response) { //m_log.Warn("[BASE HTTP SERVER]: We've figured out it's a LLSD Request"); @@ -1283,59 +1464,6 @@ namespace OpenSim.Framework.Servers.HttpServer map["login"] = OSD.FromString("false"); return map; } - /// - /// A specific agent handler was provided. Such a handler is expecetd to have an - /// intimate, and highly specific relationship with the client. Consequently, - /// nothing is done here. - /// - /// - /// - /// - - private bool HandleAgentRequest(IHttpAgentHandler handler, OSHttpRequest request, OSHttpResponse response) - { - // In the case of REST, then handler is responsible for ALL aspects of - // the request/response handling. Nothing is done here, not even encoding. - - try - { - return handler.Handle(request, response); - } - catch (Exception e) - { - // If the handler did in fact close the stream, then this will blow - // chunks. So that that doesn't disturb anybody we throw away any - // and all exceptions raised. We've done our best to release the - // client. - try - { - m_log.Warn("[HTTP-AGENT]: Error - " + e.Message); - response.SendChunked = false; - response.KeepAlive = true; - response.StatusCode = (int)OSHttpStatusCode.ServerErrorInternalError; - //response.OutputStream.Close(); - try - { - response.Send(); - //response.FreeContext(); - } - catch (SocketException f) - { - // This has to be here to prevent a Linux/Mono crash - m_log.Warn( - String.Format("[BASE HTTP SERVER]: XmlRpcRequest issue {0}.\nNOTE: this may be spurious on Linux. ", f.Message), f); - } - } - catch(Exception) - { - } - } - - // Indicate that the request has been "handled" - - return true; - - } public byte[] HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) { @@ -1674,7 +1802,8 @@ namespace OpenSim.Framework.Servers.HttpServer response.SendChunked = false; response.ContentLength64 = buffer.Length; response.ContentEncoding = Encoding.UTF8; - + + return buffer; } @@ -1775,6 +1904,8 @@ namespace OpenSim.Framework.Servers.HttpServer HTTPDRunning = false; try { +// m_PollServiceManager.Stop(); + m_httpListener2.ExceptionThrown -= httpServerException; //m_httpListener2.DisconnectHandler = null; @@ -1840,6 +1971,12 @@ namespace OpenSim.Framework.Servers.HttpServer m_rpcHandlers.Remove(method); } + public void RemoveJsonRPCHandler(string method) + { + lock(jsonRpcHandlers) + jsonRpcHandlers.Remove(method); + } + public bool RemoveLLSDHandler(string path, LLSDMethod handler) { lock (m_llsdHandlers) diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs index 0bd3aae7d5..d162bc12fb 100644 --- a/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs @@ -97,6 +97,18 @@ namespace OpenSim.Framework.Servers.HttpServer bool AddXmlRPCHandler(string method, XmlRpcMethod handler); bool AddXmlRPCHandler(string method, XmlRpcMethod handler, bool keepAlive); + bool AddJsonRPCHandler(string method, JsonRPCMethod handler); + + /// + /// Websocket HTTP server handlers. + /// + /// + /// + void AddWebSocketHandler(string servicepath, BaseHttpServer.WebSocketRequestDelegate handler); + + + void RemoveWebSocketHandler(string servicepath); + /// /// Gets the XML RPC handler for given method name /// @@ -128,6 +140,8 @@ namespace OpenSim.Framework.Servers.HttpServer void RemoveStreamHandler(string httpMethod, string path); void RemoveXmlRPCHandler(string method); + + void RemoveJsonRPCHandler(string method); string GetHTTP404(string host); diff --git a/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs b/OpenSim/Framework/Servers/HttpServer/JsonRPCMethod.cs similarity index 85% rename from OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs rename to OpenSim/Framework/Servers/HttpServer/JsonRPCMethod.cs index 812a21c97e..5bab50871f 100644 --- a/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs +++ b/OpenSim/Framework/Servers/HttpServer/JsonRPCMethod.cs @@ -25,14 +25,10 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//TODO: WHERE TO PLACE THIS? +using System.Net; +using OpenMetaverse.StructuredData; -namespace OpenSim.Region.Framework.Scenes.Scripting +namespace OpenSim.Framework.Servers.HttpServer { - public interface ScriptEngineInterface - { - void InitializeEngine(Scene Sceneworld); - void Shutdown(); -// void StartScript(string ScriptID, IScriptHost ObjectID); - } + public delegate bool JsonRPCMethod(OSDMap jsonRpcRequest, ref JsonRpcResponse response); } diff --git a/OpenSim/Framework/Servers/HttpServer/JsonRpcResponse.cs b/OpenSim/Framework/Servers/HttpServer/JsonRpcResponse.cs new file mode 100644 index 0000000000..2c50587794 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/JsonRpcResponse.cs @@ -0,0 +1,150 @@ +/* + * 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.Net; +using OpenMetaverse.StructuredData; + +namespace OpenSim.Framework.Servers.HttpServer +{ + public sealed class ErrorCode + { + private ErrorCode() {} + + public const int ParseError = -32700; + public const int InvalidRequest = -32600; + public const int MethodNotFound = -32601; + public const int InvalidParams = -32602; + public const int InternalError = -32604; + + } + + public class JsonRpcError + { + internal OSDMap Error = new OSDMap(); + + public int Code + { + get + { + if (Error.ContainsKey("code")) + return Error["code"].AsInteger(); + else + return 0; + } + set + { + Error["code"] = OSD.FromInteger(value); + } + } + + public string Message + { + get + { + if (Error.ContainsKey("message")) + return Error["message"].AsString(); + else + return null; + } + set + { + Error["message"] = OSD.FromString(value); + } + } + + public OSD Data + { + get; set; + } + } + + public class JsonRpcResponse + { + public string JsonRpc + { + get + { + return Reply["jsonrpc"].AsString(); + } + set + { + Reply["jsonrpc"] = OSD.FromString(value); + } + } + + public string Id + { + get + { + return Reply["id"].AsString(); + } + set + { + Reply["id"] = OSD.FromString(value); + } + } + + public OSD Result + { + get; set; + } + + public JsonRpcError Error + { + get; set; + } + + public OSDMap Reply = new OSDMap(); + + public JsonRpcResponse() + { + Error = new JsonRpcError(); + } + + public string Serialize() + { + if (Result != null) + Reply["result"] = Result; + + if (Error.Code != 0) + { + Reply["error"] = (OSD)Error.Error; + } + + string result = string.Empty; + try + { + result = OSDParser.SerializeJsonString(Reply); + } + catch + { + + } + return result; + } + } +} diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs index 4be8bf40d9..7628e23c4d 100644 --- a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs @@ -50,19 +50,26 @@ namespace OpenSim.Framework.Servers.HttpServer private uint m_WorkerThreadCount = 0; private Thread[] m_workerThreads; private PollServiceWorkerThread[] m_PollServiceWorkerThreads; - private bool m_running = true; + private volatile bool m_running = true; + private int m_pollTimeout; public PollServiceRequestManager(BaseHttpServer pSrv, uint pWorkerThreadCount, int pTimeout) { m_server = pSrv; m_WorkerThreadCount = pWorkerThreadCount; + m_pollTimeout = pTimeout; + } + + public void Start() + { + m_running = true; m_workerThreads = new Thread[m_WorkerThreadCount]; m_PollServiceWorkerThreads = new PollServiceWorkerThread[m_WorkerThreadCount]; //startup worker threads for (uint i = 0; i < m_WorkerThreadCount; i++) { - m_PollServiceWorkerThreads[i] = new PollServiceWorkerThread(m_server, pTimeout); + m_PollServiceWorkerThreads[i] = new PollServiceWorkerThread(m_server, m_pollTimeout); m_PollServiceWorkerThreads[i].ReQueue += ReQueueEvent; m_workerThreads[i] @@ -141,8 +148,10 @@ namespace OpenSim.Framework.Servers.HttpServer } - ~PollServiceRequestManager() + public void Stop() { + m_running = false; + foreach (object o in m_requests) { PollServiceHttpRequest req = (PollServiceHttpRequest) o; @@ -157,7 +166,6 @@ namespace OpenSim.Framework.Servers.HttpServer { t.Abort(); } - m_running = false; } } } @@ -337,7 +345,7 @@ namespace OpenSim.Framework.Servers.HttpServer if (responsedata == null) continue; - if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.Normal) + if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.Normal) // This is the event queue { try { diff --git a/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs b/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs index 02ecc250a1..8e592c1e07 100644 --- a/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.7.6.*")] + diff --git a/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs b/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs index 19c03a8e1a..edcd134cae 100644 --- a/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs +++ b/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs @@ -101,20 +101,11 @@ namespace OpenSim.Framework.Servers.HttpServer using (WebResponse resp = request.GetResponse()) { XmlSerializer deserializer = new XmlSerializer(typeof(TResponse)); - Stream respStream = null; - try - { - respStream = resp.GetResponseStream(); + + using (Stream respStream = resp.GetResponseStream()) deserial = (TResponse)deserializer.Deserialize(respStream); - } - catch { } - finally - { - if (respStream != null) - respStream.Close(); - resp.Close(); - } } + return deserial; } } diff --git a/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs new file mode 100644 index 0000000000..ee96b47c69 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs @@ -0,0 +1,1102 @@ +/* + * 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.IO; +using System.Security.Cryptography; +using System.Text; +using HttpServer; + +namespace OpenSim.Framework.Servers.HttpServer +{ + // Sealed class. If you're going to unseal it, implement IDisposable. + /// + /// This class implements websockets. It grabs the network context from C#Webserver and utilizes it directly as a tcp streaming service + /// + public sealed class WebSocketHttpServerHandler : BaseRequestHandler + { + + private class WebSocketState + { + public List ReceivedBytes; + public int ExpectedBytes; + public WebsocketFrameHeader Header; + public bool FrameComplete; + public WebSocketFrame ContinuationFrame; + } + + /// + /// Binary Data will trigger this event + /// + public event DataDelegate OnData; + + /// + /// Textual Data will trigger this event + /// + public event TextDelegate OnText; + + /// + /// A ping request form the other side will trigger this event. + /// This class responds to the ping automatically. You shouldn't send a pong. + /// it's informational. + /// + public event PingDelegate OnPing; + + /// + /// This is a response to a ping you sent. + /// + public event PongDelegate OnPong; + + /// + /// This is a regular HTTP Request... This may be removed in the future. + /// + public event RegularHttpRequestDelegate OnRegularHttpRequest; + + /// + /// When the upgrade from a HTTP request to a Websocket is completed, this will be fired + /// + public event UpgradeCompletedDelegate OnUpgradeCompleted; + + /// + /// If the upgrade failed, this will be fired + /// + public event UpgradeFailedDelegate OnUpgradeFailed; + + /// + /// When the websocket is closed, this will be fired. + /// + public event CloseDelegate OnClose; + + /// + /// Set this delegate to allow your module to validate the origin of the + /// Websocket request. Primary line of defense against cross site scripting + /// + public ValidateHandshake HandshakeValidateMethodOverride = null; + + private OSHttpRequest _request; + private HTTPNetworkContext _networkContext; + private IHttpClientContext _clientContext; + + private int _pingtime = 0; + private byte[] _buffer; + private int _bufferPosition; + private int _bufferLength; + private bool _closing; + private bool _upgraded; + private int _maxPayloadBytes = 41943040; + + private const string HandshakeAcceptText = + "HTTP/1.1 101 Switching Protocols\r\n" + + "upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "sec-websocket-accept: {0}\r\n\r\n";// + + //"{1}"; + + private const string HandshakeDeclineText = + "HTTP/1.1 {0} {1}\r\n" + + "Connection: close\r\n\r\n"; + + /// + /// Mysterious constant defined in RFC6455 to append to the client provided security key + /// + private const string WebsocketHandshakeAcceptHashConstant = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + public WebSocketHttpServerHandler(OSHttpRequest preq, IHttpClientContext pContext, int bufferlen) + : base(preq.HttpMethod, preq.Url.OriginalString) + { + _request = preq; + _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing(); + _clientContext = pContext; + _bufferLength = bufferlen; + _buffer = new byte[_bufferLength]; + } + + // Sealed class implments destructor and an internal dispose method. complies with C# unmanaged resource best practices. + ~WebSocketHttpServerHandler() + { + Dispose(); + + } + + /// + /// Sets the length of the stream buffer + /// + /// Byte length. + public void SetChunksize(int pChunk) + { + if (!_upgraded) + { + _buffer = new byte[pChunk]; + } + else + { + throw new InvalidOperationException("You must set the chunksize before the connection is upgraded"); + } + } + + /// + /// This is the famous nagle. + /// + public bool NoDelay_TCP_Nagle + { + get + { + if (_networkContext != null && _networkContext.Socket != null) + { + return _networkContext.Socket.NoDelay; + } + else + { + throw new InvalidOperationException("The socket has been shutdown"); + } + } + set + { + if (_networkContext != null && _networkContext.Socket != null) + _networkContext.Socket.NoDelay = value; + else + { + throw new InvalidOperationException("The socket has been shutdown"); + } + } + } + + /// + /// This triggers the websocket to start the upgrade process... + /// This is a Generalized Networking 'common sense' helper method. Some people expect to call Start() instead + /// of the more context appropriate HandshakeAndUpgrade() + /// + public void Start() + { + HandshakeAndUpgrade(); + } + + /// + /// Max Payload Size in bytes. Defaults to 40MB, but could be set upon connection before calling handshake and upgrade. + /// + public int MaxPayloadSize + { + get { return _maxPayloadBytes; } + set { _maxPayloadBytes = value; } + } + + /// + /// This triggers the websocket start the upgrade process + /// + public void HandshakeAndUpgrade() + { + string webOrigin = string.Empty; + string websocketKey = string.Empty; + string acceptKey = string.Empty; + string accepthost = string.Empty; + if (!string.IsNullOrEmpty(_request.Headers["origin"])) + webOrigin = _request.Headers["origin"]; + + if (!string.IsNullOrEmpty(_request.Headers["sec-websocket-key"])) + websocketKey = _request.Headers["sec-websocket-key"]; + + if (!string.IsNullOrEmpty(_request.Headers["host"])) + accepthost = _request.Headers["host"]; + + if (string.IsNullOrEmpty(_request.Headers["upgrade"])) + { + FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no upgrade request submitted"); + } + + string connectionheader = _request.Headers["upgrade"]; + if (connectionheader.ToLower() != "websocket") + { + FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no connection upgrade request submitted"); + } + + // If the object consumer provided a method to validate the origin, we should call it and give the client a success or fail. + // If not.. we should accept any. The assumption here is that there would be no Websocket handlers registered in baseHTTPServer unless + // Something asked for it... + if (HandshakeValidateMethodOverride != null) + { + if (HandshakeValidateMethodOverride(webOrigin, websocketKey, accepthost)) + { + acceptKey = GenerateAcceptKey(websocketKey); + string rawaccept = string.Format(HandshakeAcceptText, acceptKey); + SendUpgradeSuccess(rawaccept); + + } + else + { + FailUpgrade(OSHttpStatusCode.ClientErrorForbidden, "Origin Validation Failed"); + } + } + else + { + acceptKey = GenerateAcceptKey(websocketKey); + string rawaccept = string.Format(HandshakeAcceptText, acceptKey); + SendUpgradeSuccess(rawaccept); + } + } + + /// + /// Generates a handshake response key string based on the client's + /// provided key to prove to the client that we're allowing the Websocket + /// upgrade of our own free will and we were not coerced into doing it. + /// + /// Client provided security key + /// + private static string GenerateAcceptKey(string key) + { + if (string.IsNullOrEmpty(key)) + return string.Empty; + + string acceptkey = key + WebsocketHandshakeAcceptHashConstant; + + SHA1 hashobj = SHA1.Create(); + string ret = Convert.ToBase64String(hashobj.ComputeHash(Encoding.UTF8.GetBytes(acceptkey))); + hashobj.Clear(); + + return ret; + } + + /// + /// Informs the otherside that we accepted their upgrade request + /// + /// The HTTP 1.1 101 response that says Yay \o/ + private void SendUpgradeSuccess(string pHandshakeResponse) + { + // Create a new websocket state so we can keep track of data in between network reads. + WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true}; + + byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse); + try + { + + // Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream. + _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState); + + // Write the upgrade handshake success message + _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length); + _networkContext.Stream.Flush(); + _upgraded = true; + UpgradeCompletedDelegate d = OnUpgradeCompleted; + if (d != null) + d(this, new UpgradeCompletedEventArgs()); + } + catch (IOException fail) + { + Close(string.Empty); + } + catch (ObjectDisposedException fail) + { + Close(string.Empty); + } + + } + + /// + /// The server has decided not to allow the upgrade to a websocket for some reason. The Http 1.1 response that says Nay >:( + /// + /// HTTP Status reflecting the reason why + /// Textual reason for the upgrade fail + private void FailUpgrade(OSHttpStatusCode pCode, string pMessage ) + { + string handshakeResponse = string.Format(HandshakeDeclineText, (int)pCode, pMessage.Replace("\n", string.Empty).Replace("\r", string.Empty)); + byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(handshakeResponse); + _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length); + _networkContext.Stream.Flush(); + _networkContext.Stream.Dispose(); + + UpgradeFailedDelegate d = OnUpgradeFailed; + if (d != null) + d(this,new UpgradeFailedEventArgs()); + } + + + /// + /// This is our ugly Async OnReceive event handler. + /// This chunks the input stream based on the length of the provided buffer and processes out + /// as many frames as it can. It then moves the unprocessed data to the beginning of the buffer. + /// + /// Our Async State from beginread + private void OnReceive(IAsyncResult ar) + { + WebSocketState _socketState = ar.AsyncState as WebSocketState; + try + { + int bytesRead = _networkContext.Stream.EndRead(ar); + if (bytesRead == 0) + { + // Do Disconnect + _networkContext.Stream.Dispose(); + _networkContext = null; + return; + } + _bufferPosition += bytesRead; + + if (_bufferPosition > _bufferLength) + { + // Message too big for chunksize.. not sure how this happened... + //Close(string.Empty); + } + + int offset = 0; + bool headerread = true; + int headerforwardposition = 0; + while (headerread && offset < bytesRead) + { + if (_socketState.FrameComplete) + { + WebsocketFrameHeader pheader = WebsocketFrameHeader.ZeroHeader; + + headerread = WebSocketReader.TryReadHeader(_buffer, offset, _bufferPosition - offset, out pheader, + out headerforwardposition); + offset += headerforwardposition; + + if (headerread) + { + _socketState.FrameComplete = false; + if (pheader.PayloadLen > (ulong) _maxPayloadBytes) + { + Close("Invalid Payload size"); + + return; + } + if (pheader.PayloadLen > 0) + { + if ((int) pheader.PayloadLen > _bufferPosition - offset) + { + byte[] writebytes = new byte[_bufferPosition - offset]; + + Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition - offset); + _socketState.ExpectedBytes = (int) pheader.PayloadLen; + _socketState.ReceivedBytes.AddRange(writebytes); + _socketState.Header = pheader; // We need to add the header so that we can unmask it + offset += (int) _bufferPosition - offset; + } + else + { + byte[] writebytes = new byte[pheader.PayloadLen]; + Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) pheader.PayloadLen); + WebSocketReader.Mask(pheader.Mask, writebytes); + pheader.IsMasked = false; + _socketState.FrameComplete = true; + _socketState.ReceivedBytes.AddRange(writebytes); + _socketState.Header = pheader; + offset += (int) pheader.PayloadLen; + } + } + else + { + pheader.Mask = 0; + _socketState.FrameComplete = true; + _socketState.Header = pheader; + } + + + + if (_socketState.FrameComplete) + { + ProcessFrame(_socketState); + _socketState.Header.SetDefault(); + _socketState.ReceivedBytes.Clear(); + _socketState.ExpectedBytes = 0; + + } + + } + } + else + { + WebsocketFrameHeader frameHeader = _socketState.Header; + int bytesleft = _socketState.ExpectedBytes - _socketState.ReceivedBytes.Count; + + if (bytesleft > _bufferPosition) + { + byte[] writebytes = new byte[_bufferPosition]; + + Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition); + _socketState.ReceivedBytes.AddRange(writebytes); + _socketState.Header = frameHeader; // We need to add the header so that we can unmask it + offset += (int) _bufferPosition; + } + else + { + byte[] writebytes = new byte[_bufferPosition]; + Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition); + _socketState.FrameComplete = true; + _socketState.ReceivedBytes.AddRange(writebytes); + _socketState.Header = frameHeader; + offset += (int) _bufferPosition; + } + if (_socketState.FrameComplete) + { + ProcessFrame(_socketState); + _socketState.Header.SetDefault(); + _socketState.ReceivedBytes.Clear(); + _socketState.ExpectedBytes = 0; + // do some processing + } + + } + } + if (offset > 0) + { + // If the buffer is maxed out.. we can just move the cursor. Nothing to move to the beginning. + if (offset <_buffer.Length) + Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bufferPosition - offset); + _bufferPosition -= offset; + } + if (_networkContext.Stream != null && _networkContext.Stream.CanRead && !_closing) + { + _networkContext.Stream.BeginRead(_buffer, _bufferPosition, _bufferLength - _bufferPosition, OnReceive, + _socketState); + } + else + { + // We can't read the stream anymore... + } + + } + catch (IOException fail) + { + Close(string.Empty); + } + catch (ObjectDisposedException fail) + { + Close(string.Empty); + } + } + + /// + /// Sends a string to the other side + /// + /// the string message that is to be sent + public void SendMessage(string message) + { + byte[] messagedata = Encoding.UTF8.GetBytes(message); + WebSocketFrame textMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata }; + textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text; + textMessageFrame.Header.IsEnd = true; + SendSocket(textMessageFrame.ToBytes()); + + } + + public void SendData(byte[] data) + { + WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data}; + dataMessageFrame.Header.IsEnd = true; + dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary; + SendSocket(dataMessageFrame.ToBytes()); + + } + + /// + /// Writes raw bytes to the websocket. Unframed data will cause disconnection + /// + /// + private void SendSocket(byte[] data) + { + if (!_closing) + { + try + { + + _networkContext.Stream.Write(data, 0, data.Length); + } + catch (IOException) + { + + } + } + } + + /// + /// Sends a Ping check to the other side. The other side SHOULD respond as soon as possible with a pong frame. This interleaves with incoming fragmented frames. + /// + public void SendPingCheck() + { + WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] }; + pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping; + pingFrame.Header.IsEnd = true; + _pingtime = Util.EnvironmentTickCount(); + SendSocket(pingFrame.ToBytes()); + } + + /// + /// Closes the websocket connection. Sends a close message to the other side if it hasn't already done so. + /// + /// + public void Close(string message) + { + if (_networkContext == null) + return; + if (_networkContext.Stream != null) + { + if (_networkContext.Stream.CanWrite) + { + byte[] messagedata = Encoding.UTF8.GetBytes(message); + WebSocketFrame closeResponseFrame = new WebSocketFrame() + { + Header = WebsocketFrameHeader.HeaderDefault(), + WebSocketPayload = messagedata + }; + closeResponseFrame.Header.Opcode = WebSocketReader.OpCode.Close; + closeResponseFrame.Header.PayloadLen = (ulong) messagedata.Length; + closeResponseFrame.Header.IsEnd = true; + SendSocket(closeResponseFrame.ToBytes()); + } + } + CloseDelegate closeD = OnClose; + if (closeD != null) + { + closeD(this, new CloseEventArgs()); + } + + _closing = true; + } + + /// + /// Processes a websocket frame and triggers consumer events + /// + /// We need to modify the websocket state here depending on the frame + private void ProcessFrame(WebSocketState psocketState) + { + if (psocketState.Header.IsMasked) + { + byte[] unmask = psocketState.ReceivedBytes.ToArray(); + WebSocketReader.Mask(psocketState.Header.Mask, unmask); + psocketState.ReceivedBytes = new List(unmask); + } + + switch (psocketState.Header.Opcode) + { + case WebSocketReader.OpCode.Ping: + PingDelegate pingD = OnPing; + if (pingD != null) + { + pingD(this, new PingEventArgs()); + } + + WebSocketFrame pongFrame = new WebSocketFrame(){Header = WebsocketFrameHeader.HeaderDefault(),WebSocketPayload = new byte[0]}; + pongFrame.Header.Opcode = WebSocketReader.OpCode.Pong; + pongFrame.Header.IsEnd = true; + SendSocket(pongFrame.ToBytes()); + break; + case WebSocketReader.OpCode.Pong: + + PongDelegate pongD = OnPong; + if (pongD != null) + { + pongD(this, new PongEventArgs(){PingResponseMS = Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),_pingtime)}); + } + break; + case WebSocketReader.OpCode.Binary: + if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame. + { + psocketState.ContinuationFrame = new WebSocketFrame + { + Header = psocketState.Header, + WebSocketPayload = + psocketState.ReceivedBytes.ToArray() + }; + } + else + { + // Send Done Event! + DataDelegate dataD = OnData; + if (dataD != null) + { + dataD(this,new WebsocketDataEventArgs(){Data = psocketState.ReceivedBytes.ToArray()}); + } + } + break; + case WebSocketReader.OpCode.Text: + if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame. + { + psocketState.ContinuationFrame = new WebSocketFrame + { + Header = psocketState.Header, + WebSocketPayload = + psocketState.ReceivedBytes.ToArray() + }; + } + else + { + TextDelegate textD = OnText; + if (textD != null) + { + textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(psocketState.ReceivedBytes.ToArray()) }); + } + + // Send Done Event! + } + break; + case WebSocketReader.OpCode.Continue: // Continuation. Multiple frames worth of data for one message. Only valid when not using Control Opcodes + //Console.WriteLine("currhead " + psocketState.Header.IsEnd); + //Console.WriteLine("Continuation! " + psocketState.ContinuationFrame.Header.IsEnd); + byte[] combineddata = new byte[psocketState.ReceivedBytes.Count+psocketState.ContinuationFrame.WebSocketPayload.Length]; + byte[] newdata = psocketState.ReceivedBytes.ToArray(); + Buffer.BlockCopy(psocketState.ContinuationFrame.WebSocketPayload, 0, combineddata, 0, psocketState.ContinuationFrame.WebSocketPayload.Length); + Buffer.BlockCopy(newdata, 0, combineddata, + psocketState.ContinuationFrame.WebSocketPayload.Length, newdata.Length); + psocketState.ContinuationFrame.WebSocketPayload = combineddata; + psocketState.Header.PayloadLen = (ulong)combineddata.Length; + if (psocketState.Header.IsEnd) + { + if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Text) + { + // Send Done event + TextDelegate textD = OnText; + if (textD != null) + { + textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(combineddata) }); + } + } + else if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Binary) + { + // Send Done event + DataDelegate dataD = OnData; + if (dataD != null) + { + dataD(this, new WebsocketDataEventArgs() { Data = combineddata }); + } + } + else + { + // protocol violation + } + psocketState.ContinuationFrame = null; + } + break; + case WebSocketReader.OpCode.Close: + Close(string.Empty); + + break; + + } + psocketState.Header.SetDefault(); + psocketState.ReceivedBytes.Clear(); + psocketState.ExpectedBytes = 0; + } + public void Dispose() + { + if (_networkContext != null && _networkContext.Stream != null) + { + if (_networkContext.Stream.CanWrite) + _networkContext.Stream.Flush(); + _networkContext.Stream.Close(); + _networkContext.Stream.Dispose(); + _networkContext.Stream = null; + } + + if (_request != null && _request.InputStream != null) + { + _request.InputStream.Close(); + _request.InputStream.Dispose(); + _request = null; + } + + if (_clientContext != null) + { + _clientContext.Close(); + _clientContext = null; + } + } + } + + /// + /// Reads a byte stream and returns Websocket frames. + /// + public class WebSocketReader + { + /// + /// Bit to determine if the frame read on the stream is the last frame in a sequence of fragmented frames + /// + private const byte EndBit = 0x80; + + /// + /// These are the Frame Opcodes + /// + public enum OpCode + { + // Data Opcodes + Continue = 0x0, + Text = 0x1, + Binary = 0x2, + + // Control flow Opcodes + Close = 0x8, + Ping = 0x9, + Pong = 0xA + } + + /// + /// Masks and Unmasks data using the frame mask. Mask is applied per octal + /// Note: Frames from clients MUST be masked + /// Note: Frames from servers MUST NOT be masked + /// + /// Int representing 32 bytes of mask data. Mask is applied per octal + /// + public static void Mask(int pMask, byte[] pBuffer) + { + byte[] maskKey = BitConverter.GetBytes(pMask); + int currentMaskIndex = 0; + for (int i = 0; i < pBuffer.Length; i++) + { + pBuffer[i] = (byte)(pBuffer[i] ^ maskKey[currentMaskIndex]); + if (currentMaskIndex == 3) + { + currentMaskIndex = 0; + } + else + { + currentMaskIndex++; + + } + + } + } + + /// + /// Attempts to read a header off the provided buffer. Returns true, exports a WebSocketFrameheader, + /// and an int to move the buffer forward when it reads a header. False when it can't read a header + /// + /// Bytes read from the stream + /// Starting place in the stream to begin trying to read from + /// Lenth in the stream to try and read from. Provided for cases where the + /// buffer's length is larger then the data in it + /// Outputs the read WebSocket frame header + /// Informs the calling stream to move the buffer forward + /// True if it got a header, False if it didn't get a header + public static bool TryReadHeader(byte[] pBuffer, int pOffset, int length, out WebsocketFrameHeader oHeader, + out int moveBuffer) + { + oHeader = WebsocketFrameHeader.ZeroHeader; + int minumheadersize = 2; + if (length > pBuffer.Length - pOffset) + throw new ArgumentOutOfRangeException("The Length specified was larger the byte array supplied"); + if (length < minumheadersize) + { + moveBuffer = 0; + return false; + } + + byte nibble1 = (byte)(pBuffer[pOffset] & 0xF0); //FIN/RSV1/RSV2/RSV3 + byte nibble2 = (byte)(pBuffer[pOffset] & 0x0F); // Opcode block + + oHeader = new WebsocketFrameHeader(); + oHeader.SetDefault(); + + if ((nibble1 & WebSocketReader.EndBit) == WebSocketReader.EndBit) + { + oHeader.IsEnd = true; + } + else + { + oHeader.IsEnd = false; + } + //Opcode + oHeader.Opcode = (WebSocketReader.OpCode)nibble2; + //Mask + oHeader.IsMasked = Convert.ToBoolean((pBuffer[pOffset + 1] & 0x80) >> 7); + + // Payload length + oHeader.PayloadLen = (byte)(pBuffer[pOffset + 1] & 0x7F); + + int index = 2; // LargerPayload length starts at byte 3 + + switch (oHeader.PayloadLen) + { + case 126: + minumheadersize += 2; + if (length < minumheadersize) + { + moveBuffer = 0; + return false; + } + Array.Reverse(pBuffer, pOffset + index, 2); // two bytes + oHeader.PayloadLen = BitConverter.ToUInt16(pBuffer, pOffset + index); + index += 2; + break; + case 127: // we got more this is a bigger frame + // 8 bytes - uint64 - most significant bit 0 network byte order + minumheadersize += 8; + if (length < minumheadersize) + { + moveBuffer = 0; + return false; + } + Array.Reverse(pBuffer, pOffset + index, 8); + oHeader.PayloadLen = BitConverter.ToUInt64(pBuffer, pOffset + index); + index += 8; + break; + + } + //oHeader.PayloadLeft = oHeader.PayloadLen; // Start the count in case it's chunked over the network. This is different then frame fragmentation + if (oHeader.IsMasked) + { + minumheadersize += 4; + if (length < minumheadersize) + { + moveBuffer = 0; + return false; + } + oHeader.Mask = BitConverter.ToInt32(pBuffer, pOffset + index); + index += 4; + } + moveBuffer = index; + return true; + + } + } + + /// + /// RFC6455 Websocket Frame + /// + public class WebSocketFrame + { + /* + * RFC6455 +nib 0 1 2 3 4 5 6 7 +byt 0 1 2 3 +dec 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) + + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | + + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + + * When reading these, the frames are possibly fragmented and interleaved with control frames + * the fragmented frames are not interleaved with data frames. Just control frames + */ + public static readonly WebSocketFrame DefaultFrame = new WebSocketFrame(){Header = new WebsocketFrameHeader(),WebSocketPayload = new byte[0]}; + public WebsocketFrameHeader Header; + public byte[] WebSocketPayload; + + public byte[] ToBytes() + { + Header.PayloadLen = (ulong)WebSocketPayload.Length; + return Header.ToBytes(WebSocketPayload); + } + + } + + public struct WebsocketFrameHeader + { + //public byte CurrentMaskIndex; + /// + /// The last frame in a sequence of fragmented frames or the one and only frame for this message. + /// + public bool IsEnd; + + /// + /// Returns whether the payload data is masked or not. Data from Clients MUST be masked, Data from Servers MUST NOT be masked + /// + public bool IsMasked; + + /// + /// A set of cryptologically sound random bytes XoR-ed against the payload octally. Looped + /// + public int Mask; + /* +byt 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +---------------+---------------+---------------+---------------+ + | Octal 1 | Octal 2 | Octal 3 | Octal 4 | + +---------------+---------------+---------------+---------------+ +*/ + + + public WebSocketReader.OpCode Opcode; + + public UInt64 PayloadLen; + //public UInt64 PayloadLeft; + // Payload is X + Y + //public UInt64 ExtensionDataLength; + //public UInt64 ApplicationDataLength; + public static readonly WebsocketFrameHeader ZeroHeader = WebsocketFrameHeader.HeaderDefault(); + + public void SetDefault() + { + + //CurrentMaskIndex = 0; + IsEnd = true; + IsMasked = true; + Mask = 0; + Opcode = WebSocketReader.OpCode.Close; + // PayloadLeft = 0; + PayloadLen = 0; + // ExtensionDataLength = 0; + // ApplicationDataLength = 0; + + } + + /// + /// Returns a byte array representing the Frame header + /// + /// This is the frame data payload. The header describes the size of the payload. + /// If payload is null, a Zero sized payload is assumed + /// Returns a byte array representing the frame header + public byte[] ToBytes(byte[] payload) + { + List result = new List(); + + // Squeeze in our opcode and our ending bit. + result.Add((byte)((byte)Opcode | (IsEnd?0x80:0x00) )); + + // Again with the three different byte interpretations of size.. + + //bytesize + if (PayloadLen <= 125) + { + result.Add((byte) PayloadLen); + } //Uint16 + else if (PayloadLen <= ushort.MaxValue) + { + result.Add(126); + byte[] payloadLengthByte = BitConverter.GetBytes(Convert.ToUInt16(PayloadLen)); + Array.Reverse(payloadLengthByte); + result.AddRange(payloadLengthByte); + } //UInt64 + else + { + result.Add(127); + byte[] payloadLengthByte = BitConverter.GetBytes(PayloadLen); + Array.Reverse(payloadLengthByte); + result.AddRange(payloadLengthByte); + } + + // Only add a payload if it's not null + if (payload != null) + { + result.AddRange(payload); + } + return result.ToArray(); + } + + /// + /// A Helper method to define the defaults + /// + /// + + public static WebsocketFrameHeader HeaderDefault() + { + return new WebsocketFrameHeader + { + //CurrentMaskIndex = 0, + IsEnd = false, + IsMasked = true, + Mask = 0, + Opcode = WebSocketReader.OpCode.Close, + //PayloadLeft = 0, + PayloadLen = 0, + // ExtensionDataLength = 0, + // ApplicationDataLength = 0 + }; + } + } + + public delegate void DataDelegate(object sender, WebsocketDataEventArgs data); + + public delegate void TextDelegate(object sender, WebsocketTextEventArgs text); + + public delegate void PingDelegate(object sender, PingEventArgs pingdata); + + public delegate void PongDelegate(object sender, PongEventArgs pongdata); + + public delegate void RegularHttpRequestDelegate(object sender, RegularHttpRequestEvnetArgs request); + + public delegate void UpgradeCompletedDelegate(object sender, UpgradeCompletedEventArgs completeddata); + + public delegate void UpgradeFailedDelegate(object sender, UpgradeFailedEventArgs faileddata); + + public delegate void CloseDelegate(object sender, CloseEventArgs closedata); + + public delegate bool ValidateHandshake(string pWebOrigin, string pWebSocketKey, string pHost); + + + public class WebsocketDataEventArgs : EventArgs + { + public byte[] Data; + } + + public class WebsocketTextEventArgs : EventArgs + { + public string Data; + } + + public class PingEventArgs : EventArgs + { + /// + /// The ping event can arbitrarily contain data + /// + public byte[] Data; + } + + public class PongEventArgs : EventArgs + { + /// + /// The pong event can arbitrarily contain data + /// + public byte[] Data; + + public int PingResponseMS; + + } + + public class RegularHttpRequestEvnetArgs : EventArgs + { + + } + + public class UpgradeCompletedEventArgs : EventArgs + { + + } + + public class UpgradeFailedEventArgs : EventArgs + { + + } + + public class CloseEventArgs : EventArgs + { + + } + + +} diff --git a/OpenSim/Framework/Servers/MainServer.cs b/OpenSim/Framework/Servers/MainServer.cs index ae7d515e13..cfd34bb5b4 100644 --- a/OpenSim/Framework/Servers/MainServer.cs +++ b/OpenSim/Framework/Servers/MainServer.cs @@ -227,9 +227,16 @@ namespace OpenSim.Framework.Servers handlers.AppendFormat("\t{0}\n", s); handlers.AppendFormat("* HTTP:\n"); - List poll = httpServer.GetPollServiceHandlerKeys(); foreach (String s in httpServer.GetHTTPHandlerKeys()) - handlers.AppendFormat("\t{0} {1}\n", s, (poll.Contains(s) ? "(poll service)" : string.Empty)); + handlers.AppendFormat("\t{0}\n", s); + + handlers.AppendFormat("* HTTP (poll):\n"); + foreach (String s in httpServer.GetPollServiceHandlerKeys()) + handlers.AppendFormat("\t{0}\n", s); + + handlers.AppendFormat("* JSONRPC:\n"); + foreach (String s in httpServer.GetJsonRpcHandlerKeys()) + handlers.AppendFormat("\t{0}\n", s); // handlers.AppendFormat("* Agent:\n"); // foreach (String s in httpServer.GetAgentHandlerKeys()) diff --git a/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs b/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs index 021f63c632..792c62e2c7 100644 --- a/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs +++ b/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] +[assembly: AssemblyVersion("0.7.6.*")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/OpenSim/Framework/Servers/ServerBase.cs b/OpenSim/Framework/Servers/ServerBase.cs index 9eb2281914..1ff8acae78 100644 --- a/OpenSim/Framework/Servers/ServerBase.cs +++ b/OpenSim/Framework/Servers/ServerBase.cs @@ -27,16 +27,19 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using log4net; using log4net.Appender; using log4net.Core; using log4net.Repository; using Nini.Config; using OpenSim.Framework.Console; +using OpenSim.Framework.Monitoring; namespace OpenSim.Framework.Servers { @@ -110,6 +113,26 @@ namespace OpenSim.Framework.Servers } } + /// + /// Log information about the circumstances in which we're running (OpenSimulator version number, CLR details, + /// etc.). + /// + public void LogEnvironmentInformation() + { + // FIXME: This should be done down in ServerBase but we need to sort out and refactor the log4net + // XmlConfigurator calls first accross servers. + m_log.InfoFormat("[SERVER BASE]: Starting in {0}", m_startupDirectory); + + m_log.InfoFormat("[SERVER BASE]: OpenSimulator version: {0}", m_version); + + // clr version potentially is more confusing than helpful, since it doesn't tell us if we're running under Mono/MS .NET and + // the clr version number doesn't match the project version number under Mono. + //m_log.Info("[STARTUP]: Virtual machine runtime version: " + Environment.Version + Environment.NewLine); + m_log.InfoFormat( + "[SERVER BASE]: Operating system version: {0}, .NET platform {1}, {2}-bit", + Environment.OSVersion, Environment.OSVersion.Platform, Util.Is64BitProcess() ? "64" : "32"); + } + public void RegisterCommonAppenders(IConfig startupConfig) { ILoggerRepository repository = LogManager.GetRepository(); @@ -167,6 +190,9 @@ namespace OpenSim.Framework.Servers m_console.Commands.AddCommand( "General", false, "show info", "show info", "Show general information about the server", HandleShow); + m_console.Commands.AddCommand( + "General", false, "show version", "show version", "Show server version", HandleShow); + m_console.Commands.AddCommand( "General", false, "show uptime", "show uptime", "Show server uptime", HandleShow); @@ -206,6 +232,34 @@ namespace OpenSim.Framework.Servers "General", false, "command-script", "command-script + + + + + +
+
+ + + + + +
+Region Stats +
+
+
+ + +
+Sessions +
+
+
+ + +
+Log File +
+
+
+ + +
+
+
+ + diff --git a/bin/enter_uuid.xml b/bin/enter_uuid.xml new file mode 100644 index 0000000000..84475ad7ca --- /dev/null +++ b/bin/enter_uuid.xml @@ -0,0 +1,7 @@ + + + Enter UUID + +