From 5925aac859ee493fd7f6b10026c84a6a22626c79 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 30 Jun 2010 00:10:44 +0100 Subject: [PATCH 001/120] Add --merge switch to load iar. When this switch is used, iar folders are merged with existing same-name user inventory folders. This makes it a little easier to back and restore entire individual user inventories, among other things Added unit test to check behaviour --- .../Serialization/ArchiveConstants.cs | 29 ++++- .../Archiver/InventoryArchiveReadRequest.cs | 112 ++++++++++-------- .../Archiver/InventoryArchiverModule.cs | 60 +++++----- .../Archiver/Tests/InventoryArchiverTests.cs | 50 +++++++- .../Framework/Library/LibraryModule.cs | 8 +- .../Shared/Api/Implementation/LS_Api.cs | 2 +- 6 files changed, 170 insertions(+), 91 deletions(-) diff --git a/OpenSim/Framework/Serialization/ArchiveConstants.cs b/OpenSim/Framework/Serialization/ArchiveConstants.cs index 475a9de0cb..3143e3bec7 100644 --- a/OpenSim/Framework/Serialization/ArchiveConstants.cs +++ b/OpenSim/Framework/Serialization/ArchiveConstants.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Text; using OpenMetaverse; namespace OpenSim.Framework.Serialization @@ -171,6 +172,30 @@ namespace OpenSim.Framework.Serialization public static string CreateOarObjectPath(string objectName, UUID uuid, Vector3 pos) { return OBJECTS_PATH + CreateOarObjectFilename(objectName, uuid, pos); - } + } + + /// + /// Extract a plain path from an IAR path + /// + /// + /// + public static string ExtractPlainPathFromIarPath(string iarPath) + { + List plainDirs = new List(); + + string[] iarDirs = iarPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string iarDir in iarDirs) + { + if (!iarDir.Contains(ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR)) + plainDirs.Add(iarDir); + + int i = iarDir.LastIndexOf(ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR); + + plainDirs.Add(iarDir.Remove(i)); + } + + return string.Join("/", plainDirs.ToArray()); + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs index 9996074860..f130b3e0ee 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs @@ -54,6 +54,11 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver private UserAccount m_userInfo; private string m_invPath; + + /// + /// Do we want to merge this load with existing inventory? + /// + protected bool m_merge; /// /// We only use this to request modules @@ -66,19 +71,21 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver private Stream m_loadStream; public InventoryArchiveReadRequest( - Scene scene, UserAccount userInfo, string invPath, string loadPath) + Scene scene, UserAccount userInfo, string invPath, string loadPath, bool merge) : this( scene, userInfo, invPath, - new GZipStream(ArchiveHelpers.GetStream(loadPath), CompressionMode.Decompress)) + new GZipStream(ArchiveHelpers.GetStream(loadPath), CompressionMode.Decompress), + merge) { } public InventoryArchiveReadRequest( - Scene scene, UserAccount userInfo, string invPath, Stream loadStream) + Scene scene, UserAccount userInfo, string invPath, Stream loadStream, bool merge) { m_scene = scene; + m_merge = merge; m_userInfo = userInfo; m_invPath = invPath; m_loadStream = loadStream; @@ -91,14 +98,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// A list of the inventory nodes loaded. If folders were loaded then only the root folders are /// returned /// - public List Execute() + public HashSet Execute() { string filePath = "ERROR"; int successfulAssetRestores = 0; int failedAssetRestores = 0; int successfulItemRestores = 0; - List loadedNodes = new List(); + HashSet loadedNodes = new HashSet(); List folderCandidates = InventoryArchiveUtils.FindFolderByPath( @@ -158,9 +165,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { successfulItemRestores++; - // If we're loading an item directly into the given destination folder then we need to record - // it separately from any loaded root folders - if (rootDestinationFolder == foundFolder) + // If we aren't loading the folder containing the item then well need to update the + // viewer separately for that item. + if (!loadedNodes.Contains(foundFolder)) loadedNodes.Add(item); } } @@ -203,14 +210,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver string iarPath, InventoryFolderBase rootDestFolder, Dictionary resolvedFolders, - List loadedNodes) + HashSet loadedNodes) { string iarPathExisting = iarPath; // m_log.DebugFormat( // "[INVENTORY ARCHIVER]: Loading folder {0} {1}", rootDestFolder.Name, rootDestFolder.ID); - InventoryFolderBase destFolder = ResolveDestinationFolder(rootDestFolder, ref iarPathExisting, resolvedFolders); + InventoryFolderBase destFolder + = ResolveDestinationFolder(rootDestFolder, ref iarPathExisting, resolvedFolders); m_log.DebugFormat( "[INVENTORY ARCHIVER]: originalArchivePath [{0}], section already loaded [{1}]", @@ -249,46 +257,55 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { string originalArchivePath = archivePath; - InventoryFolderBase destFolder = null; - - if (archivePath.Length > 0) + while (archivePath.Length > 0) { - while (null == destFolder && archivePath.Length > 0) + m_log.DebugFormat("[INVENTORY ARCHIVER]: Trying to resolve destination folder {0}", archivePath); + + if (resolvedFolders.ContainsKey(archivePath)) { - m_log.DebugFormat("[INVENTORY ARCHIVER]: Trying to resolve destination folder {0}", archivePath); - - if (resolvedFolders.ContainsKey(archivePath)) + m_log.DebugFormat( + "[INVENTORY ARCHIVER]: Found previously created folder from archive path {0}", archivePath); + return resolvedFolders[archivePath]; + } + else + { + if (m_merge) { - m_log.DebugFormat( - "[INVENTORY ARCHIVER]: Found previously created folder from archive path {0}", archivePath); - destFolder = resolvedFolders[archivePath]; + // TODO: Using m_invPath is totally wrong - what we need to do is strip the uuid from the + // iar name and try to find that instead. + string plainPath = ArchiveConstants.ExtractPlainPathFromIarPath(archivePath); + List folderCandidates + = InventoryArchiveUtils.FindFolderByPath( + m_scene.InventoryService, m_userInfo.PrincipalID, plainPath); + + if (folderCandidates.Count != 0) + { + InventoryFolderBase destFolder = folderCandidates[0]; + resolvedFolders[archivePath] = destFolder; + return destFolder; + } + } + + // Don't include the last slash so find the penultimate one + int penultimateSlashIndex = archivePath.LastIndexOf("/", archivePath.Length - 2); + + if (penultimateSlashIndex >= 0) + { + // Remove the last section of path so that we can see if we've already resolved the parent + archivePath = archivePath.Remove(penultimateSlashIndex + 1); } else { - // Don't include the last slash so find the penultimate one - int penultimateSlashIndex = archivePath.LastIndexOf("/", archivePath.Length - 2); - - if (penultimateSlashIndex >= 0) - { - // Remove the last section of path so that we can see if we've already resolved the parent - archivePath = archivePath.Remove(penultimateSlashIndex + 1); - } - else - { - m_log.DebugFormat( - "[INVENTORY ARCHIVER]: Found no previously created folder for archive path {0}", - originalArchivePath); - archivePath = string.Empty; - destFolder = rootDestFolder; - } + m_log.DebugFormat( + "[INVENTORY ARCHIVER]: Found no previously created folder for archive path {0}", + originalArchivePath); + archivePath = string.Empty; + return rootDestFolder; } } } - if (null == destFolder) - destFolder = rootDestFolder; - - return destFolder; + return rootDestFolder; } /// @@ -314,24 +331,21 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver string iarPathExisting, string iarPathToReplicate, Dictionary resolvedFolders, - List loadedNodes) + HashSet loadedNodes) { string[] rawDirsToCreate = iarPathToReplicate.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - int i = 0; - while (i < rawDirsToCreate.Length) + for (int i = 0; i < rawDirsToCreate.Length; i++) { // m_log.DebugFormat("[INVENTORY ARCHIVER]: Creating folder {0} from IAR", rawDirsToCreate[i]); + if (!rawDirsToCreate[i].Contains(ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR)) + continue; + int identicalNameIdentifierIndex = rawDirsToCreate[i].LastIndexOf( ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR); - if (identicalNameIdentifierIndex < 0) - { - i++; - continue; - } string newFolderName = rawDirsToCreate[i].Remove(identicalNameIdentifierIndex); newFolderName = InventoryArchiveUtils.UnescapeArchivePath(newFolderName); @@ -354,8 +368,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (0 == i) loadedNodes.Add(destFolder); - - i++; } } diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs index cfefbe9d3b..668c344d39 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs @@ -91,12 +91,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver scene.AddCommand( this, "load iar", - "load iar []", + "load iar []", //"load iar [--merge] []", "Load user inventory archive (IAR).", - //"--merge is an option which merges the loaded IAR with existing inventory folders where possible, rather than always creating new ones" + //"--merge is an option which merges the loaded IAR with existing inventory folders where possible, rather than always creating new ones" //+ " is user's first name." + Environment.NewLine - " is user's first name." + Environment.NewLine + " is user's first name." + Environment.NewLine + " is user's last name." + Environment.NewLine + " is the path inside the user's inventory where the IAR should be loaded." + Environment.NewLine + " is the user's password." + Environment.NewLine @@ -136,16 +136,16 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (handlerInventoryArchiveSaved != null) handlerInventoryArchiveSaved(id, succeeded, userInfo, invPath, saveStream, reportedException); } - + public bool ArchiveInventory( - Guid id, string firstName, string lastName, string invPath, string pass, Stream saveStream) - { - return ArchiveInventory(id, firstName, lastName, invPath, pass, saveStream, new Dictionary()); - } + Guid id, string firstName, string lastName, string invPath, string pass, Stream saveStream) + { + return ArchiveInventory(id, firstName, lastName, invPath, pass, saveStream, new Dictionary()); + } public bool ArchiveInventory( - Guid id, string firstName, string lastName, string invPath, string pass, Stream saveStream, - Dictionary options) + Guid id, string firstName, string lastName, string invPath, string pass, Stream saveStream, + Dictionary options) { if (m_scenes.Count > 0) { @@ -184,8 +184,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } public bool ArchiveInventory( - Guid id, string firstName, string lastName, string invPath, string pass, string savePath, - Dictionary options) + Guid id, string firstName, string lastName, string invPath, string pass, string savePath, + Dictionary options) { if (m_scenes.Count > 0) { @@ -224,13 +224,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } public bool DearchiveInventory(string firstName, string lastName, string invPath, string pass, Stream loadStream) - { - return DearchiveInventory(firstName, lastName, invPath, pass, loadStream, new Dictionary()); - } - + { + return DearchiveInventory(firstName, lastName, invPath, pass, loadStream, new Dictionary()); + } + public bool DearchiveInventory( - string firstName, string lastName, string invPath, string pass, Stream loadStream, - Dictionary options) + string firstName, string lastName, string invPath, string pass, Stream loadStream, + Dictionary options) { if (m_scenes.Count > 0) { @@ -241,10 +241,11 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (CheckPresence(userInfo.PrincipalID)) { InventoryArchiveReadRequest request; + bool merge = (options.ContainsKey("merge") ? (bool)options["merge"] : false); try { - request = new InventoryArchiveReadRequest(m_aScene, userInfo, invPath, loadStream); + request = new InventoryArchiveReadRequest(m_aScene, userInfo, invPath, loadStream, merge); } catch (EntryPointNotFoundException e) { @@ -273,8 +274,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } public bool DearchiveInventory( - string firstName, string lastName, string invPath, string pass, string loadPath, - Dictionary options) + string firstName, string lastName, string invPath, string pass, string loadPath, + Dictionary options) { if (m_scenes.Count > 0) { @@ -285,10 +286,11 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (CheckPresence(userInfo.PrincipalID)) { InventoryArchiveReadRequest request; + bool merge = (options.ContainsKey("merge") ? (bool)options["merge"] : false); try - { - request = new InventoryArchiveReadRequest(m_aScene, userInfo, invPath, loadPath); + { + request = new InventoryArchiveReadRequest(m_aScene, userInfo, invPath, loadPath, merge); } catch (EntryPointNotFoundException e) { @@ -322,13 +324,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// protected void HandleLoadInvConsoleCommand(string module, string[] cmdparams) { - m_log.Info("[INVENTORY ARCHIVER]: PLEASE NOTE THAT THIS FACILITY IS EXPERIMENTAL. BUG REPORTS WELCOME."); - - Dictionary options = new Dictionary(); + m_log.Info("[INVENTORY ARCHIVER]: PLEASE NOTE THAT THIS FACILITY IS EXPERIMENTAL. BUG REPORTS WELCOME."); + + Dictionary options = new Dictionary(); OptionSet optionSet = new OptionSet().Add("m|merge", delegate (string v) { options["merge"] = v != null; }); List mainParams = optionSet.Parse(cmdparams); - + if (mainParams.Count < 6) { m_log.Error( @@ -349,7 +351,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (DearchiveInventory(firstName, lastName, invPath, pass, loadPath, options)) m_log.InfoFormat( "[INVENTORY ARCHIVER]: Loaded archive {0} for {1} {2}", - loadPath, firstName, lastName); + loadPath, firstName, lastName); } /// @@ -454,7 +456,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// Notify the client of loaded nodes if they are logged in /// /// Can be empty. In which case, nothing happens - private void UpdateClientWithLoadedNodes(UserAccount userInfo, List loadedNodes) + private void UpdateClientWithLoadedNodes(UserAccount userInfo, HashSet loadedNodes) { if (loadedNodes.Count == 0) return; diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs index 5130fa5522..5fad0a9e86 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs @@ -514,7 +514,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests UserAccount ua1 = UserProfileTestUtils.CreateUserWithInventory(scene); Dictionary foldersCreated = new Dictionary(); - List nodesLoaded = new List(); + HashSet nodesLoaded = new HashSet(); string folder1Name = "1"; string folder2aName = "2a"; @@ -529,7 +529,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests { // Test replication of path1 - new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null) + new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null, false) .ReplicateArchivePathToUserInventory( iarPath1, scene.InventoryService.GetRootFolder(ua1.PrincipalID), foldersCreated, nodesLoaded); @@ -546,7 +546,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests { // Test replication of path2 - new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null) + new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null, false) .ReplicateArchivePathToUserInventory( iarPath2, scene.InventoryService.GetRootFolder(ua1.PrincipalID), foldersCreated, nodesLoaded); @@ -592,10 +592,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); - new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null) + new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null, false) .ReplicateArchivePathToUserInventory( itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), - new Dictionary(), new List()); + new Dictionary(), new HashSet()); List folder1PostCandidates = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); @@ -617,5 +617,45 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1Post, "b"); Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); } + + /// + /// Test replication of a partly existing archive path to the user's inventory. This should create + /// a merged path. + /// + [Test] + public void TestMergeIarPath() + { + TestHelper.InMethod(); + log4net.Config.XmlConfigurator.Configure(); + + Scene scene = SceneSetupHelpers.SetupScene("inventory"); + UserAccount ua1 = UserProfileTestUtils.CreateUserWithInventory(scene); + + string folder1ExistingName = "a"; + string folder2Name = "b"; + + InventoryFolderBase folder1 + = UserInventoryTestUtils.CreateInventoryFolder( + scene.InventoryService, ua1.PrincipalID, folder1ExistingName); + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); + string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); + + string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); + + new InventoryArchiveReadRequest(scene, ua1, folder1ExistingName, (Stream)null, true) + .ReplicateArchivePathToUserInventory( + itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + new Dictionary(), new HashSet()); + + List folder1PostCandidates + = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); + Assert.That(folder1PostCandidates.Count, Is.EqualTo(1)); + Assert.That(folder1PostCandidates[0].ID, Is.EqualTo(folder1.ID)); + + List folder2PostCandidates + = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1PostCandidates[0], "b"); + Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); + } } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs b/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs index 36dae6ba9f..9c20d68ce8 100644 --- a/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -173,16 +173,16 @@ namespace OpenSim.Region.CoreModules.Framework.Library m_log.InfoFormat("[LIBRARY MODULE]: Loading library archive {0} ({1})...", iarFileName, simpleName); simpleName = GetInventoryPathFromName(simpleName); - InventoryArchiveReadRequest archread = new InventoryArchiveReadRequest(m_MockScene, uinfo, simpleName, iarFileName); + InventoryArchiveReadRequest archread = new InventoryArchiveReadRequest(m_MockScene, uinfo, simpleName, iarFileName, false); try { - List nodes = archread.Execute(); + HashSet nodes = archread.Execute(); if (nodes != null && nodes.Count == 0) { // didn't find the subfolder with the given name; place it on the top m_log.InfoFormat("[LIBRARY MODULE]: Didn't find {0} in library. Placing archive on the top level", simpleName); archread.Close(); - archread = new InventoryArchiveReadRequest(m_MockScene, uinfo, "/", iarFileName); + archread = new InventoryArchiveReadRequest(m_MockScene, uinfo, "/", iarFileName, false); archread.Execute(); } foreach (InventoryNodeBase node in nodes) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs index fe71ed54cd..5ae64395e7 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * From 1077a8a3925a4fede6ee0d775550820ec4e541a2 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 30 Jun 2010 20:46:35 +0100 Subject: [PATCH 002/120] add stub media-on-a-prim (shared media) module --- .../World/Media/Moap/MoapModule.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs new file mode 100644 index 0000000000..1e5c767bc0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -0,0 +1,56 @@ +/* + * 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 log4net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using Mono.Addins; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.Media.Moap +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")] + public class MoapModule : INonSharedRegionModule + { + public string Name { get { return "MoapModule"; } } + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource config) {} + + public void AddRegion(Scene scene) { Console.WriteLine("YEAH I'M HERE, BABY!"); } + + public void RemoveRegion(Scene scene) {} + + public void RegionLoaded(Scene scene) {} + + public void Close() {} + } +} \ No newline at end of file From e098d339293435add487312119d77cb5d18cfe8a Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 30 Jun 2010 22:30:05 +0100 Subject: [PATCH 003/120] Register ObjectMedia and ObjectMediaNavigate capabilities from moap module. Not sure if these are correct, but just supplying these to the viewer is enough to allow it to put media textures on prims (previously the icons were greyed out). This is not yet persisted even in-memory, so no other avatars will see it yet. --- .../World/Media/Moap/MoapModule.cs | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 1e5c767bc0..68b9b430d6 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -26,31 +26,79 @@ */ using System; +using System.Collections; +using System.Collections.Specialized; using System.Reflection; -using Nini.Config; +using System.IO; +using System.Web; using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using Mono.Addins; -using OpenMetaverse; +using OpenSim.Services.Interfaces; +using Caps = OpenSim.Framework.Capabilities.Caps; namespace OpenSim.Region.CoreModules.Media.Moap { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")] public class MoapModule : INonSharedRegionModule { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + protected Scene m_scene; + public void Initialise(IConfigSource config) {} - public void AddRegion(Scene scene) { Console.WriteLine("YEAH I'M HERE, BABY!"); } + public void AddRegion(Scene scene) + { + m_scene = scene; + } public void RemoveRegion(Scene scene) {} - public void RegionLoaded(Scene scene) {} + public void RegionLoaded(Scene scene) + { + m_scene.EventManager.OnRegisterCaps += RegisterCaps; + } public void Close() {} + + public void RegisterCaps(UUID agentID, Caps caps) + { + m_log.DebugFormat( + "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); + + caps.RegisterHandler( + "ObjectMedia", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaRequest)); + caps.RegisterHandler( + "ObjectMediaNavigate", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaNavigateRequest)); + } + + protected string OnObjectMediaRequest( + string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + m_log.DebugFormat("[MOAP]: Got ObjectMedia request for {0}", path); + //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + + return string.Empty; + } + + protected string OnObjectMediaNavigateRequest( + string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request for {0}", path); + //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + + return string.Empty; + } } } \ No newline at end of file From 94646599f09cf8a4cc930a1150195036134c28b5 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 00:24:30 +0100 Subject: [PATCH 004/120] do a whole load of crappy hacking to get cubes to display google.com currently, for smoe reason the page only appears when you click a face. also, actually navigating anywhere always snaps you back to the google search box, for some unknown reason you can still change the url and normal navigation will work again --- .../Servers/HttpServer/BaseHttpServer.cs | 2 +- .../World/Media/Moap/MoapModule.cs | 95 +++++++++++++++++-- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index 8123f2fed7..f85cb57bd8 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -362,7 +362,7 @@ namespace OpenSim.Framework.Servers.HttpServer string path = request.RawUrl; string handlerKey = GetHandlerKey(request.HttpMethod, path); - //m_log.DebugFormat("[BASE HTTP SERVER]: Handling {0} request for {1}", request.HttpMethod, path); + m_log.DebugFormat("[BASE HTTP SERVER]: Handling {0} request for {1}", request.HttpMethod, path); if (TryGetStreamHandler(handlerKey, out requestHandler)) { diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 68b9b430d6..b6fa53fafa 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -35,8 +35,10 @@ using log4net; using Mono.Addins; using Nini.Config; using OpenMetaverse; +using OpenMetaverse.Messages.Linden; using OpenMetaverse.StructuredData; using OpenSim.Framework; +using OpenSim.Framework.Capabilities; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; @@ -77,28 +79,109 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat( "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); + // We do receive a post to ObjectMedia when a new avatar enters the region - though admittedly this is the + // avatar that set the texture in the first place. + // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMedia", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaRequest)); + "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaRequest)); + + // We do get these posts when the url has been changed. + // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMediaNavigate", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaNavigateRequest)); + "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateRequest)); } - protected string OnObjectMediaRequest( + /// + /// Sets or gets per face media textures. + /// + /// + /// + /// + /// + /// + /// + protected string HandleObjectMediaRequest( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - m_log.DebugFormat("[MOAP]: Got ObjectMedia request for {0}", path); + m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); + + Hashtable osdParams = new Hashtable(); + osdParams = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); + + foreach (Object key in osdParams.Keys) + m_log.DebugFormat("[MOAP]: Param {0}={1}", key, osdParams[key]); + + string verb = (string)osdParams["verb"]; + + if ("GET" == verb) + return HandleObjectMediaRequestGet(path, osdParams, httpRequest, httpResponse); + //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + // TODO: Persist in memory + // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message + // TODO: Persist in database + return string.Empty; } - protected string OnObjectMediaNavigateRequest( + protected string HandleObjectMediaRequestGet( + string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + // Yeah, only for cubes right now. I know it's dumb. + int faces = 6; + + MediaEntry[] media = new MediaEntry[faces]; + for (int i = 0; i < faces; i++) + { + MediaEntry me = new MediaEntry(); + me.HomeURL = "google.com"; + me.CurrentURL = "google.com"; + me.AutoScale = true; + //me.Height = 300; + //me.Width = 240; + media[i] = me; + } + + ObjectMediaResponse resp = new ObjectMediaResponse(); + + resp.PrimID = (UUID)osdParams["object_id"]; + resp.FaceMedia = media; + + // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying + // to minimally satisfy for now to get something working + resp.Version = "x-mv:0000000016/" + UUID.Random(); + + //string rawResp = resp.Serialize().ToString(); + string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); + + m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp); + + return rawResp; + } + + /// + /// Received from the viewer if a user has changed the url of a media texture. + /// + /// + /// + /// + /// /param> + /// /param> + /// + protected string HandleObjectMediaNavigateRequest( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request for {0}", path); //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + // TODO: Persist in memory + // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message + // TODO: Persist in database + return string.Empty; - } + } + + } } \ No newline at end of file From 92502687538e2f9dd8d473aad9e9ef1c75091dd8 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 00:47:12 +0100 Subject: [PATCH 005/120] have a stab at sending the correct number of media entries to shapes actually, this is probably wrong anyway if there's a default texture it's going to be easier just to gather the object media updates and retain those in-memory now but what the hell --- .../CoreModules/World/Media/Moap/MoapModule.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index b6fa53fafa..30507a4aa3 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -128,8 +128,20 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected string HandleObjectMediaRequestGet( string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - // Yeah, only for cubes right now. I know it's dumb. - int faces = 6; + UUID primId = (UUID)osdParams["object_id"]; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + + if (null == part) + { + m_log.WarnFormat( + "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in the scene", + primId); + return string.Empty; + } + + int faces = part.GetNumberOfSides(); + m_log.DebugFormat("[MOAP]: Faces [{0}] for [{1}]", faces, primId); MediaEntry[] media = new MediaEntry[faces]; for (int i = 0; i < faces; i++) From 214db91503148f0bad42f951965f4acfc3cdd7e1 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 02:06:51 +0100 Subject: [PATCH 006/120] start storing incoming MediaEntry on a new Media field on PrimitiveBaseShape This allows the media texture to persist in memory - logging in and out will redisplay it (after a click) though navigation will be lost Next need to implement media uri on prim and delegate more incoming llsd parsing to libomv --- OpenSim/Framework/PrimitiveBaseShape.cs | 7 +++ .../World/Media/Moap/MoapModule.cs | 63 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index 4d1de22eeb..517dbf6e03 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -26,12 +26,14 @@ */ using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Reflection; using System.Xml.Serialization; using log4net; using OpenMetaverse; +using OpenMetaverse.StructuredData; namespace OpenSim.Framework { @@ -170,6 +172,11 @@ namespace OpenSim.Framework } } } + + /// + /// Entries to store media textures on each face + /// + public List Media { get; set; } public PrimitiveBaseShape() { diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 30507a4aa3..90626f4129 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; using System.IO; @@ -115,6 +116,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap if ("GET" == verb) return HandleObjectMediaRequestGet(path, osdParams, httpRequest, httpResponse); + if ("UPDATE" == verb) + return HandleObjectMediaRequestUpdate(path, osdParams, httpRequest, httpResponse); //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); @@ -140,6 +143,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } + /* int faces = part.GetNumberOfSides(); m_log.DebugFormat("[MOAP]: Faces [{0}] for [{1}]", faces, primId); @@ -154,17 +158,20 @@ namespace OpenSim.Region.CoreModules.Media.Moap //me.Width = 240; media[i] = me; } + */ + + if (null == part.Shape.Media) + return string.Empty; ObjectMediaResponse resp = new ObjectMediaResponse(); - resp.PrimID = (UUID)osdParams["object_id"]; - resp.FaceMedia = media; + resp.PrimID = primId; + resp.FaceMedia = part.Shape.Media.ToArray(); // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying // to minimally satisfy for now to get something working resp.Version = "x-mv:0000000016/" + UUID.Random(); - //string rawResp = resp.Serialize().ToString(); string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp); @@ -172,6 +179,56 @@ namespace OpenSim.Region.CoreModules.Media.Moap return rawResp; } + protected string HandleObjectMediaRequestUpdate( + string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + UUID primId = (UUID)osdParams["object_id"]; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + + if (null == part) + { + m_log.WarnFormat( + "[MOAP]: Received am UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in the scene", + primId); + return string.Empty; + } + + List cookedMediaEntries = new List(); + + ArrayList rawMediaEntries = (ArrayList)osdParams["object_media_data"]; + foreach (Object obj in rawMediaEntries) + { + Hashtable rawMe = (Hashtable)obj; + + // TODO: Yeah, I know this is silly. Very soon use existing better code in libomv to do this. + MediaEntry cookedMe = new MediaEntry(); + cookedMe.EnableAlterntiveImage = (bool)rawMe["alt_image_enable"]; + cookedMe.AutoLoop = (bool)rawMe["auto_loop"]; + cookedMe.AutoPlay = (bool)rawMe["auto_play"]; + cookedMe.AutoScale = (bool)rawMe["auto_scale"]; + cookedMe.AutoZoom = (bool)rawMe["auto_zoom"]; + cookedMe.InteractOnFirstClick = (bool)rawMe["first_click_interact"]; + cookedMe.Controls = (MediaControls)rawMe["controls"]; + cookedMe.HomeURL = (string)rawMe["home_url"]; + cookedMe.CurrentURL = (string)rawMe["current_url"]; + cookedMe.Height = (int)rawMe["height_pixels"]; + cookedMe.Width = (int)rawMe["width_pixels"]; + cookedMe.ControlPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_control"].ToString()); + cookedMe.InteractPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_interact"].ToString()); + cookedMe.EnableWhiteList = (bool)rawMe["whitelist_enable"]; + //cookedMe.WhiteList = (string[])rawMe["whitelist"]; + + cookedMediaEntries.Add(cookedMe); + } + + m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", cookedMediaEntries.Count, primId); + + part.Shape.Media = cookedMediaEntries; + + return string.Empty; + } + /// /// Received from the viewer if a user has changed the url of a media texture. /// From de769d56f5b99f4eb38f62bddebd075385f0e0f4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 18:42:47 +0100 Subject: [PATCH 007/120] replace hand parsing of incoming object media messages with parsing code in libopenmetaverse --- .../World/Media/Moap/MoapModule.cs | 89 +++++++------------ 1 file changed, 30 insertions(+), 59 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 90626f4129..568170ef1a 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -46,6 +46,7 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Media.Moap { @@ -59,7 +60,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected Scene m_scene; - public void Initialise(IConfigSource config) {} + public void Initialise(IConfigSource config) + { + // TODO: Add config switches to enable/disable this module + } public void AddRegion(Scene scene) { @@ -73,7 +77,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_scene.EventManager.OnRegisterCaps += RegisterCaps; } - public void Close() {} + public void Close() + { + m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + } public void RegisterCaps(UUID agentID, Caps caps) { @@ -105,33 +112,26 @@ namespace OpenSim.Region.CoreModules.Media.Moap string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); + + OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); + ObjectMediaMessage omm = new ObjectMediaMessage(); + omm.Deserialize(osd); - Hashtable osdParams = new Hashtable(); - osdParams = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); - - foreach (Object key in osdParams.Keys) - m_log.DebugFormat("[MOAP]: Param {0}={1}", key, osdParams[key]); - - string verb = (string)osdParams["verb"]; - - if ("GET" == verb) - return HandleObjectMediaRequestGet(path, osdParams, httpRequest, httpResponse); - if ("UPDATE" == verb) - return HandleObjectMediaRequestUpdate(path, osdParams, httpRequest, httpResponse); - - //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); - - // TODO: Persist in memory - // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message - // TODO: Persist in database - - return string.Empty; + if (omm.Request is ObjectMediaRequest) + return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest); + else if (omm.Request is ObjectMediaUpdate) + return HandleObjectMediaUpdate(omm.Request as ObjectMediaUpdate); + + throw new Exception( + string.Format( + "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}", + omm.Request.GetType())); } - protected string HandleObjectMediaRequestGet( - string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) - { - UUID primId = (UUID)osdParams["object_id"]; + protected string HandleObjectMediaRequest(ObjectMediaRequest omr) + { + //UUID primId = (UUID)osdParams["object_id"]; + UUID primId = omr.PrimID; SceneObjectPart part = m_scene.GetSceneObjectPart(primId); @@ -179,10 +179,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap return rawResp; } - protected string HandleObjectMediaRequestUpdate( - string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + protected string HandleObjectMediaUpdate(ObjectMediaUpdate omu) { - UUID primId = (UUID)osdParams["object_id"]; + UUID primId = omu.PrimID; SceneObjectPart part = m_scene.GetSceneObjectPart(primId); @@ -194,37 +193,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } - List cookedMediaEntries = new List(); + m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); - ArrayList rawMediaEntries = (ArrayList)osdParams["object_media_data"]; - foreach (Object obj in rawMediaEntries) - { - Hashtable rawMe = (Hashtable)obj; - - // TODO: Yeah, I know this is silly. Very soon use existing better code in libomv to do this. - MediaEntry cookedMe = new MediaEntry(); - cookedMe.EnableAlterntiveImage = (bool)rawMe["alt_image_enable"]; - cookedMe.AutoLoop = (bool)rawMe["auto_loop"]; - cookedMe.AutoPlay = (bool)rawMe["auto_play"]; - cookedMe.AutoScale = (bool)rawMe["auto_scale"]; - cookedMe.AutoZoom = (bool)rawMe["auto_zoom"]; - cookedMe.InteractOnFirstClick = (bool)rawMe["first_click_interact"]; - cookedMe.Controls = (MediaControls)rawMe["controls"]; - cookedMe.HomeURL = (string)rawMe["home_url"]; - cookedMe.CurrentURL = (string)rawMe["current_url"]; - cookedMe.Height = (int)rawMe["height_pixels"]; - cookedMe.Width = (int)rawMe["width_pixels"]; - cookedMe.ControlPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_control"].ToString()); - cookedMe.InteractPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_interact"].ToString()); - cookedMe.EnableWhiteList = (bool)rawMe["whitelist_enable"]; - //cookedMe.WhiteList = (string[])rawMe["whitelist"]; - - cookedMediaEntries.Add(cookedMe); - } - - m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", cookedMediaEntries.Count, primId); - - part.Shape.Media = cookedMediaEntries; + part.Shape.Media = new List(omu.FaceMedia); return string.Empty; } From 18b22e26cbc79bc0fbebc1cc0acbdb4b966038c0 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 19:25:46 +0100 Subject: [PATCH 008/120] start storing a mediaurl on the scene object part not yet persisted or sent in the update --- .../World/Media/Moap/MoapModule.cs | 30 +++++++++++++++---- .../Framework/Scenes/SceneObjectPart.cs | 7 ++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 568170ef1a..edd03973a4 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -91,7 +91,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap // avatar that set the texture in the first place. // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaRequest)); + "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaMessage)); // We do get these posts when the url has been changed. // Even though we're registering for POST we're going to get GETS and UPDATES too @@ -108,7 +108,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// /// - protected string HandleObjectMediaRequest( + protected string HandleObjectMediaMessage( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); @@ -167,10 +167,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap resp.PrimID = primId; resp.FaceMedia = part.Shape.Media.ToArray(); - - // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying - // to minimally satisfy for now to get something working - resp.Version = "x-mv:0000000016/" + UUID.Random(); + resp.Version = part.MediaUrl; string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); @@ -197,6 +194,27 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media = new List(omu.FaceMedia); + if (null == part.MediaUrl) + { + // TODO: We can't set the last changer until we start tracking which cap we give to which agent id + part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + } + else + { + string rawVersion = part.MediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:10D}/{1}", version, UUID.Zero); + } + + m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); + + // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying + // to minimally satisfy for now to get something working + //resp.Version = "x-mv:0000000016/" + UUID.Random(); + + // TODO: schedule full object update for all other avatars. This will trigger them to send an + // ObjectMediaRequest once they see that the MediaUrl is different. + return string.Empty; } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 59fd805bb3..f83c4cf779 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -967,13 +967,18 @@ namespace OpenSim.Region.Framework.Scenes get { return m_updateFlag; } set { m_updateFlag = value; } } + + /// + /// Used for media on a prim + /// + public string MediaUrl { get; set; } [XmlIgnore] public bool CreateSelected { get { return m_createSelected; } set { m_createSelected = value; } - } + } #endregion From 81f727416d4e59a3fe4d9e3481a3cbba9af3de3f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 19:33:41 +0100 Subject: [PATCH 009/120] start sending media url in object full updates --- OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index d2824bd8c7..ddc963a64c 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -4314,8 +4314,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendLandObjectOwners(LandData land, List groups, Dictionary ownersAndCount) { - - int notifyCount = ownersAndCount.Count; ParcelObjectOwnersReplyPacket pack = (ParcelObjectOwnersReplyPacket)PacketPool.Instance.GetPacket(PacketType.ParcelObjectOwnersReply); @@ -4587,6 +4585,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP update.TextureEntry = data.Shape.TextureEntry ?? Utils.EmptyBytes; update.Scale = data.Shape.Scale; update.Text = Util.StringToBytes256(data.Text); + update.MediaURL = Util.StringToBytes256(data.MediaUrl); #region PrimFlags From 4fb7c1a9e1a26c3afa850685926944508a469b2e Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 19:53:03 +0100 Subject: [PATCH 010/120] send a full object update out to avatars when a media texture is initially set this allows other avatars to see it, but still only after they've clicked on the face still not handling navigation yet --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index edd03973a4..0d317329f4 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -208,12 +208,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); - // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying - // to minimally satisfy for now to get something working - //resp.Version = "x-mv:0000000016/" + UUID.Random(); - - // TODO: schedule full object update for all other avatars. This will trigger them to send an - // ObjectMediaRequest once they see that the MediaUrl is different. + // Arguably we don't need to send a full update to the avatar that just changed the texture. + part.ScheduleFullUpdate(); return string.Empty; } From ca5d1411a6eb19408033bfa22c8ed765fcc0de99 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 20:25:35 +0100 Subject: [PATCH 011/120] handle ObjectMediaNavigateMessage Other avatars can now see the webpages that you're navigating to. The requirement for an initial prim click before the texture displayed has gone away. Flash (e.g. YouTube) appears to work fine. Still not persisting any media data so this all disappears on server restart --- .../World/Media/Moap/MoapModule.cs | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 0d317329f4..c3ec2a6244 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -96,7 +96,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap // We do get these posts when the url has been changed. // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateRequest)); + "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateMessage)); } /// @@ -128,6 +128,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap omm.Request.GetType())); } + /// + /// Handle a request for media textures + /// + /// + /// protected string HandleObjectMediaRequest(ObjectMediaRequest omr) { //UUID primId = (UUID)osdParams["object_id"]; @@ -138,8 +143,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (null == part) { m_log.WarnFormat( - "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in the scene", - primId); + "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", + primId, m_scene.RegionInfo.RegionName); return string.Empty; } @@ -176,6 +181,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap return rawResp; } + /// + /// Handle an update of media textures. + /// + /// /param> + /// protected string HandleObjectMediaUpdate(ObjectMediaUpdate omu) { UUID primId = omu.PrimID; @@ -185,8 +195,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (null == part) { m_log.WarnFormat( - "[MOAP]: Received am UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in the scene", - primId); + "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", + primId, m_scene.RegionInfo.RegionName); return string.Empty; } @@ -203,7 +213,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap { string rawVersion = part.MediaUrl.Substring(5, 10); int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:10D}/{1}", version, UUID.Zero); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); } m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); @@ -223,19 +233,50 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /param> /// /param> /// - protected string HandleObjectMediaNavigateRequest( + protected string HandleObjectMediaNavigateMessage( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request for {0}", path); - //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request [{0}]", request); + + OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); + ObjectMediaNavigateMessage omn = new ObjectMediaNavigateMessage(); + omn.Deserialize(osd); + + UUID primId = omn.PrimID; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + + if (null == part) + { + m_log.WarnFormat( + "[MOAP]: Received an ObjectMediaNavigateMessage for prim {0} but this doesn't exist in region {1}", + primId, m_scene.RegionInfo.RegionName); + return string.Empty; + } + + m_log.DebugFormat( + "[MOAP]: Updating media entry for face {0} on prim {1} {2} to {3}", + omn.Face, part.Name, part.UUID, omn.URL); + + MediaEntry me = part.Shape.Media[omn.Face]; + me.CurrentURL = omn.URL; + + string oldMediaUrl = part.MediaUrl; + + // TODO: refactor into common method + string rawVersion = oldMediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + + m_log.DebugFormat( + "[MOAP]: Updating media url in prim {0} {1} from [{2}] to [{3}]", + part.Name, part.UUID, oldMediaUrl, part.MediaUrl); + + part.ScheduleFullUpdate(); - // TODO: Persist in memory - // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message // TODO: Persist in database return string.Empty; - } - - + } } } \ No newline at end of file From 53ddcf6d25b5ff85847f3762b15f22d1dd2f49a5 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 22:52:31 +0100 Subject: [PATCH 012/120] Implement media texture persistence over server restarts for sqlite This is currently persisting media as an OSDArray serialized to LLSD XML. --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 36 ++++++++++++++++--- .../Framework/Scenes/SceneObjectPart.cs | 22 +++++++++++- prebuild.xml | 1 + 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index 81d0ac4f6e..fc9667b6d0 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -34,6 +34,7 @@ using System.Reflection; using log4net; using Mono.Data.Sqlite; using OpenMetaverse; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -974,6 +975,8 @@ namespace OpenSim.Data.SQLite createCol(prims, "CollisionSoundVolume", typeof(Double)); createCol(prims, "VolumeDetect", typeof(Int16)); + + createCol(prims, "MediaURL", typeof(String)); // Add in contraints prims.PrimaryKey = new DataColumn[] {prims.Columns["UUID"]}; @@ -1021,6 +1024,7 @@ namespace OpenSim.Data.SQLite // way to specify this as a blob atm createCol(shapes, "Texture", typeof (Byte[])); createCol(shapes, "ExtraParams", typeof (Byte[])); + createCol(shapes, "Media", typeof(String)); shapes.PrimaryKey = new DataColumn[] {shapes.Columns["UUID"]}; @@ -1339,6 +1343,12 @@ namespace OpenSim.Data.SQLite if (Convert.ToInt16(row["VolumeDetect"]) != 0) prim.VolumeDetectActive = true; + + if (!(row["MediaURL"] is System.DBNull)) + { + m_log.DebugFormat("[SQLITE]: MediaUrl type [{0}]", row["MediaURL"].GetType()); + prim.MediaUrl = (string)row["MediaURL"]; + } return prim; } @@ -1614,7 +1624,6 @@ namespace OpenSim.Data.SQLite row["PayButton3"] = prim.PayPrice[3]; row["PayButton4"] = prim.PayPrice[4]; - row["TextureAnimation"] = Convert.ToBase64String(prim.TextureAnimation); row["ParticleSystem"] = Convert.ToBase64String(prim.ParticleSystem); @@ -1674,7 +1683,8 @@ namespace OpenSim.Data.SQLite row["VolumeDetect"] = 1; else row["VolumeDetect"] = 0; - + + row["MediaURL"] = prim.MediaUrl; } /// @@ -1849,6 +1859,19 @@ namespace OpenSim.Data.SQLite s.TextureEntry = textureEntry; s.ExtraParams = (byte[]) row["ExtraParams"]; + + if (!(row["Media"] is System.DBNull)) + { + string rawMeArray = (string)row["Media"]; + OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(rawMeArray); + + List mediaEntries = new List(); + foreach (OSD osdMe in osdMeArray) + mediaEntries.Add(MediaEntry.FromOSD(osdMe)); + + s.Media = mediaEntries; + } + return s; } @@ -1892,17 +1915,22 @@ namespace OpenSim.Data.SQLite row["Texture"] = s.TextureEntry; row["ExtraParams"] = s.ExtraParams; + + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in s.Media) + meArray.Add(me.GetOSD()); + + row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } /// - /// + /// Persistently store a prim. /// /// /// /// private void addPrim(SceneObjectPart prim, UUID sceneGroupID, UUID regionUUID) { - DataTable prims = ds.Tables["prims"]; DataTable shapes = ds.Tables["primshapes"]; diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index f83c4cf779..1e5133b34d 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -317,6 +317,11 @@ namespace OpenSim.Region.Framework.Scenes protected Vector3 m_lastAcceleration; protected Vector3 m_lastAngularVelocity; protected int m_lastTerseSent; + + /// + /// Stores media texture data + /// + protected string m_mediaUrl; // TODO: Those have to be changed into persistent properties at some later point, // or sit-camera on vehicles will break on sim-crossing. @@ -962,6 +967,7 @@ namespace OpenSim.Region.Framework.Scenes TriggerScriptChangedEvent(Changed.SCALE); } } + public byte UpdateFlag { get { return m_updateFlag; } @@ -971,7 +977,21 @@ namespace OpenSim.Region.Framework.Scenes /// /// Used for media on a prim /// - public string MediaUrl { get; set; } + public string MediaUrl + { + get + { + return m_mediaUrl; + } + + set + { + m_mediaUrl = value; + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + } + } [XmlIgnore] public bool CreateSelected diff --git a/prebuild.xml b/prebuild.xml index 1eb8fee64b..1eabc4b8de 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -2238,6 +2238,7 @@ + From 7e3c54213c115fd8edb0423a352b2393fae60363 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 15:47:56 +0100 Subject: [PATCH 013/120] Implement llGetPrimMediaParams() Exposes method to get media entry via IMoapModule As yet untested. --- .../World/Media/Moap/MoapModule.cs | 19 +++- .../Framework/Interfaces/IMoapModule.cs | 47 ++++++++ .../Shared/Api/Implementation/LSL_Api.cs | 104 ++++++++++++++++++ .../Shared/Api/Runtime/LSL_Constants.cs | 25 +++++ 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 OpenSim/Region/Framework/Interfaces/IMoapModule.cs diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index c3ec2a6244..9f7436714f 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -51,7 +51,7 @@ using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Media.Moap { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")] - public class MoapModule : INonSharedRegionModule + public class MoapModule : INonSharedRegionModule, IMoapModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -97,7 +97,22 @@ namespace OpenSim.Region.CoreModules.Media.Moap // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateMessage)); - } + } + + public MediaEntry GetMediaEntry(SceneObjectPart part, int face) + { + if (face < 0) + throw new ArgumentException("Face cannot be less than zero"); + + List media = part.Shape.Media; + + if (face > media.Count - 1) + throw new ArgumentException( + string.Format("Face argument was {0} but max is {1}", face, media.Count - 1)); + + // TODO: Really need a proper copy constructor down in libopenmetaverse + return MediaEntry.FromOSD(media[face].GetOSD()); + } /// /// Sets or gets per face media textures. diff --git a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs new file mode 100644 index 0000000000..4447f3475e --- /dev/null +++ b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs @@ -0,0 +1,47 @@ +/* + * 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.Region.Framework.Interfaces +{ + /// + /// Provides methods from manipulating media-on-a-prim + /// + public interface IMoapModule + { + /// + /// Get the media entry for a given prim face. + /// + /// + /// + /// + MediaEntry GetMediaEntry(SceneObjectPart part, int face); + } +} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 712bd7d383..9290fc37b9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -7808,6 +7808,110 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return res; } + public LSL_List llGetPrimMediaParams(int face, LSL_List rules) + { + m_host.AddScriptLPS(1); + ScriptSleep(1000); + + // LSL Spec http://wiki.secondlife.com/wiki/LlGetPrimMediaParams says to fail silently if face is invalid + // TODO: Need to correctly handle case where a face has no media (which gives back an empty list). + // Assuming silently fail means give back an empty list. Ideally, need to check this. + if (face < 0 || face > m_host.Shape.Media.Count - 1) + return new LSL_List(); + + return GetLinkPrimMediaParams(face, rules); + } + + public LSL_List GetLinkPrimMediaParams(int face, LSL_List rules) + { + IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); + if (null == module) + throw new Exception("Media on a prim functions not available"); + + MediaEntry me = module.GetMediaEntry(m_host, face); + + LSL_List res = new LSL_List(); + + for (int i = 0; i < rules.Length; i++) + { + int code = (int)rules.GetLSLIntegerItem(i); + + switch (code) + { + case ScriptBaseClass.PRIM_MEDIA_ALT_IMAGE_ENABLE: + // Not implemented + res.Add(new LSL_Integer(0)); + break; + + case ScriptBaseClass.PRIM_MEDIA_CONTROLS: + if (me.Controls == MediaControls.Standard) + res.Add(new LSL_Integer(ScriptBaseClass.PRIM_MEDIA_CONTROLS_STANDARD)); + else + res.Add(new LSL_Integer(ScriptBaseClass.PRIM_MEDIA_CONTROLS_MINI)); + break; + + case ScriptBaseClass.PRIM_MEDIA_CURRENT_URL: + res.Add(new LSL_String(me.CurrentURL)); + break; + + case ScriptBaseClass.PRIM_MEDIA_HOME_URL: + res.Add(new LSL_String(me.HomeURL)); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_LOOP: + res.Add(me.AutoLoop ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_PLAY: + res.Add(me.AutoPlay ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_SCALE: + res.Add(me.AutoScale ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_ZOOM: + res.Add(me.AutoZoom ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_FIRST_CLICK_INTERACT: + res.Add(me.InteractOnFirstClick ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_WIDTH_PIXELS: + res.Add(new LSL_Integer(me.Width)); + break; + + case ScriptBaseClass.PRIM_MEDIA_HEIGHT_PIXELS: + res.Add(new LSL_Integer(me.Height)); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST_ENABLE: + res.Add(me.EnableWhiteList ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST: + string[] urls = (string[])me.WhiteList.Clone(); + + for (int j = 0; j < urls.Length; j++) + urls[j] = Uri.EscapeDataString(urls[j]); + + res.Add(new LSL_String(string.Join(", ", urls))); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_INTERACT: + res.Add(new LSL_Integer((int)me.InteractPermissions)); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_CONTROL: + res.Add(new LSL_Integer((int)me.ControlPermissions)); + break; + } + } + + return res; + } + // // // The .NET definition of base 64 is: diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index dba6502f4b..9a64f8c40f 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -517,6 +517,31 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public const int TOUCH_INVALID_FACE = -1; public static readonly vector TOUCH_INVALID_TEXCOORD = new vector(-1.0, -1.0, 0.0); public static readonly vector TOUCH_INVALID_VECTOR = ZERO_VECTOR; + + // constants for llGetPrimMediaParams + public const int PRIM_MEDIA_ALT_IMAGE_ENABLE = 0; + public const int PRIM_MEDIA_CONTROLS = 1; + public const int PRIM_MEDIA_CURRENT_URL = 2; + public const int PRIM_MEDIA_HOME_URL = 3; + public const int PRIM_MEDIA_AUTO_LOOP = 4; + public const int PRIM_MEDIA_AUTO_PLAY = 5; + public const int PRIM_MEDIA_AUTO_SCALE = 6; + public const int PRIM_MEDIA_AUTO_ZOOM = 7; + public const int PRIM_MEDIA_FIRST_CLICK_INTERACT = 8; + public const int PRIM_MEDIA_WIDTH_PIXELS = 9; + public const int PRIM_MEDIA_HEIGHT_PIXELS = 10; + public const int PRIM_MEDIA_WHITELIST_ENABLE = 11; + public const int PRIM_MEDIA_WHITELIST = 12; + public const int PRIM_MEDIA_PERMS_INTERACT = 13; + public const int PRIM_MEDIA_PERMS_CONTROL = 14; + + public const int PRIM_MEDIA_CONTROLS_STANDARD = 0; + public const int PRIM_MEDIA_CONTROLS_MINI = 1; + + public const int PRIM_MEDIA_PERM_NONE = 0; + public const int PRIM_MEDIA_PERM_OWNER = 1; + public const int PRIM_MEDIA_PERM_GROUP = 2; + public const int PRIM_MEDIA_PERM_ANYONE = 4; // Constants for default textures public const string TEXTURE_BLANK = "5748decc-f629-461c-9a36-a35a221fe21f"; From 4a92046b589a0ced240cbf5b174855daf5504a4f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 19:46:23 +0100 Subject: [PATCH 014/120] implement llSetPrimMediaParams() Untested --- OpenSim/Framework/PrimitiveBaseShape.cs | 1 + .../World/Media/Moap/MoapModule.cs | 56 +++++++-- .../Framework/Interfaces/IMoapModule.cs | 14 ++- .../Framework/Scenes/SceneObjectPart.cs | 3 +- .../Shared/Api/Implementation/LSL_Api.cs | 113 +++++++++++++++++- .../Shared/Api/Runtime/LSL_Constants.cs | 12 +- 6 files changed, 185 insertions(+), 14 deletions(-) diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index 517dbf6e03..85638ca243 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -176,6 +176,7 @@ namespace OpenSim.Framework /// /// Entries to store media textures on each face /// + /// Do not change this value directly - always do it through an IMoapModule. public List Media { get; set; } public PrimitiveBaseShape() diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 9f7436714f..064047da25 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -102,16 +102,54 @@ namespace OpenSim.Region.CoreModules.Media.Moap public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { if (face < 0) - throw new ArgumentException("Face cannot be less than zero"); + throw new ArgumentException("Face cannot be less than zero"); - List media = part.Shape.Media; - - if (face > media.Count - 1) + int maxFaces = part.GetNumberOfSides() - 1; + if (face > maxFaces) throw new ArgumentException( - string.Format("Face argument was {0} but max is {1}", face, media.Count - 1)); + string.Format("Face argument was {0} but max is {1}", face, maxFaces)); - // TODO: Really need a proper copy constructor down in libopenmetaverse - return MediaEntry.FromOSD(media[face].GetOSD()); + List media = part.Shape.Media; + + if (null == media) + { + return null; + } + else + { + // TODO: Really need a proper copy constructor down in libopenmetaverse + return MediaEntry.FromOSD(media[face].GetOSD()); + } + } + + public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) + { + if (face < 0) + throw new ArgumentException("Face cannot be less than zero"); + + int maxFaces = part.GetNumberOfSides() - 1; + if (face > maxFaces) + throw new ArgumentException( + string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + + if (null == part.Shape.Media) + part.Shape.Media = new List(maxFaces); + + part.Shape.Media[face] = me; + + if (null == part.MediaUrl) + { + // TODO: We can't set the last changer until we start tracking which cap we give to which agent id + part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + } + else + { + string rawVersion = part.MediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + } + + part.ScheduleFullUpdate(); } /// @@ -140,7 +178,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap throw new Exception( string.Format( "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}", - omm.Request.GetType())); + omm.Request.GetType())); } /// @@ -233,7 +271,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); - // Arguably we don't need to send a full update to the avatar that just changed the texture. + // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); return string.Empty; diff --git a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs index 4447f3475e..31bb6d87c0 100644 --- a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs @@ -39,9 +39,19 @@ namespace OpenSim.Region.Framework.Interfaces /// /// Get the media entry for a given prim face. /// + /// A copy of the media entry is returned rather than the original, so this can be altered at will without + /// affecting the original settings. /// /// /// - MediaEntry GetMediaEntry(SceneObjectPart part, int face); - } + MediaEntry GetMediaEntry(SceneObjectPart part, int face); + + /// + /// Set the media entry for a given prim face. + /// + /// + /// + /// + void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me); + } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 1e5133b34d..8830fb098e 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -975,8 +975,9 @@ namespace OpenSim.Region.Framework.Scenes } /// - /// Used for media on a prim + /// Used for media on a prim. /// + /// Do not change this value directly - always do it through an IMoapModule. public string MediaUrl { get diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 9290fc37b9..26007901d3 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -7816,7 +7816,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api // LSL Spec http://wiki.secondlife.com/wiki/LlGetPrimMediaParams says to fail silently if face is invalid // TODO: Need to correctly handle case where a face has no media (which gives back an empty list). // Assuming silently fail means give back an empty list. Ideally, need to check this. - if (face < 0 || face > m_host.Shape.Media.Count - 1) + if (face < 0 || face > m_host.GetNumberOfSides() - 1) return new LSL_List(); return GetLinkPrimMediaParams(face, rules); @@ -7830,6 +7830,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api MediaEntry me = module.GetMediaEntry(m_host, face); + // As per http://wiki.secondlife.com/wiki/LlGetPrimMediaParams + if (null == me) + return new LSL_List(); + LSL_List res = new LSL_List(); for (int i = 0; i < rules.Length; i++) @@ -7912,6 +7916,113 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return res; } + public LSL_Integer llSetPrimMediaParams(int face, LSL_List rules) + { + m_host.AddScriptLPS(1); + ScriptSleep(1000); + + // LSL Spec http://wiki.secondlife.com/wiki/LlSetPrimMediaParams says to fail silently if face is invalid + // Assuming silently fail means sending back LSL_STATUS_OK. Ideally, need to check this. + // Don't perform the media check directly + if (face < 0 || face > m_host.GetNumberOfSides() - 1) + return ScriptBaseClass.LSL_STATUS_OK; + + return SetPrimMediaParams(face, rules); + } + + public LSL_Integer SetPrimMediaParams(int face, LSL_List rules) + { + IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); + if (null == module) + throw new Exception("Media on a prim functions not available"); + + MediaEntry me = module.GetMediaEntry(m_host, face); + if (null == me) + me = new MediaEntry(); + + int i = 0; + + while (i < rules.Length - 1) + { + int code = rules.GetLSLIntegerItem(i++); + + switch (code) + { + case ScriptBaseClass.PRIM_MEDIA_ALT_IMAGE_ENABLE: + me.EnableAlterntiveImage = (rules.GetLSLIntegerItem(i++) != 0 ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_CONTROLS: + int v = rules.GetLSLIntegerItem(i++); + if (ScriptBaseClass.PRIM_MEDIA_CONTROLS_STANDARD == v) + me.Controls = MediaControls.Standard; + else + me.Controls = MediaControls.Mini; + break; + + case ScriptBaseClass.PRIM_MEDIA_CURRENT_URL: + me.CurrentURL = rules.GetLSLStringItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_HOME_URL: + me.HomeURL = rules.GetLSLStringItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_LOOP: + me.AutoLoop = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_PLAY: + me.AutoPlay = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_SCALE: + me.AutoScale = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_ZOOM: + me.AutoZoom = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_FIRST_CLICK_INTERACT: + me.InteractOnFirstClick = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_WIDTH_PIXELS: + me.Width = (int)rules.GetLSLIntegerItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_HEIGHT_PIXELS: + me.Height = (int)rules.GetLSLIntegerItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST_ENABLE: + me.EnableWhiteList = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST: + string[] rawWhiteListUrls = rules.GetLSLStringItem(i++).ToString().Split(new char[] { ',' }); + List whiteListUrls = new List(); + Array.ForEach( + rawWhiteListUrls, delegate(string rawUrl) { whiteListUrls.Add(rawUrl.Trim()); }); + me.WhiteList = whiteListUrls.ToArray(); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_INTERACT: + me.InteractPermissions = (MediaPermission)(byte)(int)rules.GetLSLIntegerItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_CONTROL: + me.ControlPermissions = (MediaPermission)(byte)(int)rules.GetLSLIntegerItem(i++); + break; + } + } + + module.SetMediaEntry(m_host, face, me); + + return ScriptBaseClass.LSL_STATUS_OK; + } + // // // The .NET definition of base 64 is: diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index 9a64f8c40f..6ef786a49a 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -518,7 +518,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public static readonly vector TOUCH_INVALID_TEXCOORD = new vector(-1.0, -1.0, 0.0); public static readonly vector TOUCH_INVALID_VECTOR = ZERO_VECTOR; - // constants for llGetPrimMediaParams + // constants for llGetPrimMediaParams/llSetPrimMediaParams public const int PRIM_MEDIA_ALT_IMAGE_ENABLE = 0; public const int PRIM_MEDIA_CONTROLS = 1; public const int PRIM_MEDIA_CURRENT_URL = 2; @@ -542,6 +542,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public const int PRIM_MEDIA_PERM_OWNER = 1; public const int PRIM_MEDIA_PERM_GROUP = 2; public const int PRIM_MEDIA_PERM_ANYONE = 4; + + // extra constants for llSetPrimMediaParams + public static readonly LSLInteger LSL_STATUS_OK = new LSLInteger(0); + public static readonly LSLInteger LSL_STATUS_MALFORMED_PARAMS = new LSLInteger(1000); + public static readonly LSLInteger LSL_STATUS_TYPE_MISMATCH = new LSLInteger(1001); + public static readonly LSLInteger LSL_STATUS_BOUNDS_ERROR = new LSLInteger(1002); + public static readonly LSLInteger LSL_STATUS_NOT_FOUND = new LSLInteger(1003); + public static readonly LSLInteger LSL_STATUS_NOT_SUPPORTED = new LSLInteger(1004); + public static readonly LSLInteger LSL_STATUS_INTERNAL_ERROR = new LSLInteger(1999); + public static readonly LSLInteger LSL_STATUS_WHITELIST_FAILED = new LSLInteger(2001); // Constants for default textures public const string TEXTURE_BLANK = "5748decc-f629-461c-9a36-a35a221fe21f"; From 312eb5e42ee3812f33ced0c2250f908daf23a864 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 19:48:20 +0100 Subject: [PATCH 015/120] minor: correct a few method names and change accessability --- .../ScriptEngine/Shared/Api/Implementation/LSL_Api.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 26007901d3..ff2f6a2071 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -7819,10 +7819,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api if (face < 0 || face > m_host.GetNumberOfSides() - 1) return new LSL_List(); - return GetLinkPrimMediaParams(face, rules); + return GetPrimMediaParams(face, rules); } - public LSL_List GetLinkPrimMediaParams(int face, LSL_List rules) + private LSL_List GetPrimMediaParams(int face, LSL_List rules) { IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); if (null == module) @@ -7930,7 +7930,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return SetPrimMediaParams(face, rules); } - public LSL_Integer SetPrimMediaParams(int face, LSL_List rules) + private LSL_Integer SetPrimMediaParams(int face, LSL_List rules) { IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); if (null == module) From 9231fc0f3127f5c6263ce630ee7a5c246d7e823f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 20:15:10 +0100 Subject: [PATCH 016/120] factor out common face parameter checking code --- .../World/Media/Moap/MoapModule.cs | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 064047da25..9dd46ebf89 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -101,13 +101,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { - if (face < 0) - throw new ArgumentException("Face cannot be less than zero"); - - int maxFaces = part.GetNumberOfSides() - 1; - if (face > maxFaces) - throw new ArgumentException( - string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + CheckFaceParam(part, face); List media = part.Shape.Media; @@ -124,16 +118,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) { - if (face < 0) - throw new ArgumentException("Face cannot be less than zero"); - - int maxFaces = part.GetNumberOfSides() - 1; - if (face > maxFaces) - throw new ArgumentException( - string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + CheckFaceParam(part, face); if (null == part.Shape.Media) - part.Shape.Media = new List(maxFaces); + part.Shape.Media = new List(part.GetNumberOfSides()); part.Shape.Media[face] = me; @@ -187,8 +175,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// protected string HandleObjectMediaRequest(ObjectMediaRequest omr) - { - //UUID primId = (UUID)osdParams["object_id"]; + { UUID primId = omr.PrimID; SceneObjectPart part = m_scene.GetSceneObjectPart(primId); @@ -200,23 +187,6 @@ namespace OpenSim.Region.CoreModules.Media.Moap primId, m_scene.RegionInfo.RegionName); return string.Empty; } - - /* - int faces = part.GetNumberOfSides(); - m_log.DebugFormat("[MOAP]: Faces [{0}] for [{1}]", faces, primId); - - MediaEntry[] media = new MediaEntry[faces]; - for (int i = 0; i < faces; i++) - { - MediaEntry me = new MediaEntry(); - me.HomeURL = "google.com"; - me.CurrentURL = "google.com"; - me.AutoScale = true; - //me.Height = 300; - //me.Width = 240; - media[i] = me; - } - */ if (null == part.Shape.Media) return string.Empty; @@ -330,6 +300,22 @@ namespace OpenSim.Region.CoreModules.Media.Moap // TODO: Persist in database return string.Empty; - } + } + + /// + /// Check that the face number is valid for the given prim. + /// + /// + /// + protected void CheckFaceParam(SceneObjectPart part, int face) + { + if (face < 0) + throw new ArgumentException("Face cannot be less than zero"); + + int maxFaces = part.GetNumberOfSides() - 1; + if (face > maxFaces) + throw new ArgumentException( + string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + } } } \ No newline at end of file From 7691a638e316ff9580e348b570c4a0c727587bc6 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 20:18:10 +0100 Subject: [PATCH 017/120] factor out common code for updating the media url --- .../World/Media/Moap/MoapModule.cs | 68 ++++++++----------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 9dd46ebf89..242ff6c40b 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -124,19 +124,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media = new List(part.GetNumberOfSides()); part.Shape.Media[face] = me; - - if (null == part.MediaUrl) - { - // TODO: We can't set the last changer until we start tracking which cap we give to which agent id - part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; - } - else - { - string rawVersion = part.MediaUrl.Substring(5, 10); - int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); - } - + UpdateMediaUrl(part); part.ScheduleFullUpdate(); } @@ -223,23 +211,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } - m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); + m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); part.Shape.Media = new List(omu.FaceMedia); - if (null == part.MediaUrl) - { - // TODO: We can't set the last changer until we start tracking which cap we give to which agent id - part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; - } - else - { - string rawVersion = part.MediaUrl.Substring(5, 10); - int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); - } - - m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); + UpdateMediaUrl(part); // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); @@ -267,7 +243,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap UUID primId = omn.PrimID; - SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); if (null == part) { @@ -284,20 +260,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap MediaEntry me = part.Shape.Media[omn.Face]; me.CurrentURL = omn.URL; - string oldMediaUrl = part.MediaUrl; + UpdateMediaUrl(part); - // TODO: refactor into common method - string rawVersion = oldMediaUrl.Substring(5, 10); - int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); - - m_log.DebugFormat( - "[MOAP]: Updating media url in prim {0} {1} from [{2}] to [{3}]", - part.Name, part.UUID, oldMediaUrl, part.MediaUrl); - - part.ScheduleFullUpdate(); - - // TODO: Persist in database + part.ScheduleFullUpdate(); return string.Empty; } @@ -317,5 +282,26 @@ namespace OpenSim.Region.CoreModules.Media.Moap throw new ArgumentException( string.Format("Face argument was {0} but max is {1}", face, maxFaces)); } + + /// + /// Update the media url of the given part + /// + /// + protected void UpdateMediaUrl(SceneObjectPart part) + { + if (null == part.MediaUrl) + { + // TODO: We can't set the last changer until we start tracking which cap we give to which agent id + part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + } + else + { + string rawVersion = part.MediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + } + + m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); + } } } \ No newline at end of file From d4684da8bd73ff088192da9f06657d32d6672342 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 21:33:27 +0100 Subject: [PATCH 018/120] fix problem persisting when only one face had a media texture --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 10 ++++++++-- .../Region/CoreModules/World/Media/Moap/MoapModule.cs | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index fc9667b6d0..51f4ceff27 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -1867,7 +1867,10 @@ namespace OpenSim.Data.SQLite List mediaEntries = new List(); foreach (OSD osdMe in osdMeArray) - mediaEntries.Add(MediaEntry.FromOSD(osdMe)); + { + MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); + mediaEntries.Add(me); + } s.Media = mediaEntries; } @@ -1918,7 +1921,10 @@ namespace OpenSim.Data.SQLite OSDArray meArray = new OSDArray(); foreach (MediaEntry me in s.Media) - meArray.Add(me.GetOSD()); + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 242ff6c40b..93a1ae867c 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -212,6 +212,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap } m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); + +// for (int i = 0; i < omu.FaceMedia.Length; i++) +// { +// MediaEntry me = omu.FaceMedia[i]; +// string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); +// m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); +// } part.Shape.Media = new List(omu.FaceMedia); From 01ff3c9f9f7fb223e81164647391e52ec4e975d6 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 21:43:36 +0100 Subject: [PATCH 019/120] fix issue with GetMediaEntry if the face requested wasn't set to a media texture --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 93a1ae867c..43953a7eff 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -112,7 +112,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap else { // TODO: Really need a proper copy constructor down in libopenmetaverse - return MediaEntry.FromOSD(media[face].GetOSD()); + MediaEntry me = media[face]; + return (null == me ? null : MediaEntry.FromOSD(me.GetOSD())); } } From 4b0c5711b5099f3611783e103629deaa9033cd7f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 22:00:45 +0100 Subject: [PATCH 020/120] implement llClearPrimMedia() untested --- .../World/Media/Moap/MoapModule.cs | 5 +++++ .../Framework/Interfaces/IMoapModule.cs | 10 ++++++++++ .../Shared/Api/Implementation/LSL_Api.cs | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 43953a7eff..8699800704 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -129,6 +129,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.ScheduleFullUpdate(); } + public void ClearMediaEntry(SceneObjectPart part, int face) + { + SetMediaEntry(part, face, null); + } + /// /// Sets or gets per face media textures. /// diff --git a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs index 31bb6d87c0..24b6860742 100644 --- a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs @@ -53,5 +53,15 @@ namespace OpenSim.Region.Framework.Interfaces /// /// void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me); + + /// + /// Clear the media entry for a given prim face. + /// + /// + /// This is the equivalent of setting a media entry of null + /// + /// + /// /param> + void ClearMediaEntry(SceneObjectPart part, int face); } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index ff2f6a2071..4773a503af 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -8023,6 +8023,26 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return ScriptBaseClass.LSL_STATUS_OK; } + public LSL_Integer llClearPrimMedia(LSL_Integer face) + { + m_host.AddScriptLPS(1); + ScriptSleep(1000); + + // LSL Spec http://wiki.secondlife.com/wiki/LlClearPrimMedia says to fail silently if face is invalid + // Assuming silently fail means sending back LSL_STATUS_OK. Ideally, need to check this. + // FIXME: Don't perform the media check directly + if (face < 0 || face > m_host.GetNumberOfSides() - 1) + return ScriptBaseClass.LSL_STATUS_OK; + + IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); + if (null == module) + throw new Exception("Media on a prim functions not available"); + + module.ClearMediaEntry(m_host, face); + + return ScriptBaseClass.LSL_STATUS_OK; + } + // // // The .NET definition of base 64 is: From 55caab1efdf9a397141d6e56193dff4513107d95 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 22:27:11 +0100 Subject: [PATCH 021/120] Fire CHANGED_MEDIA event if a media texture is set or cleared --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 5 +++++ OpenSim/Region/Framework/Scenes/SceneObjectPart.cs | 3 ++- .../Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 8699800704..8bccab4604 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -127,6 +127,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media[face] = me; UpdateMediaUrl(part); part.ScheduleFullUpdate(); + part.TriggerScriptChangedEvent(Changed.MEDIA); } public void ClearMediaEntry(SceneObjectPart part, int face) @@ -233,6 +234,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); + part.TriggerScriptChangedEvent(Changed.MEDIA); + return string.Empty; } @@ -277,6 +280,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.ScheduleFullUpdate(); + part.TriggerScriptChangedEvent(Changed.MEDIA); + return string.Empty; } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 8830fb098e..3165f4d467 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -58,7 +58,8 @@ namespace OpenSim.Region.Framework.Scenes OWNER = 128, REGION_RESTART = 256, REGION = 512, - TELEPORT = 1024 + TELEPORT = 1024, + MEDIA = 2048 } // I don't really know where to put this except here. diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index 6ef786a49a..06f9426fb9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -276,6 +276,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public const int CHANGED_REGION_RESTART = 256; public const int CHANGED_REGION = 512; public const int CHANGED_TELEPORT = 1024; + public const int CHANGED_MEDIA = 2048; public const int CHANGED_ANIMATION = 16384; public const int TYPE_INVALID = 0; public const int TYPE_INTEGER = 1; From 1a1d42db8382c806cbb9c00f0c1c2250cab795e9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 19:28:07 +0100 Subject: [PATCH 022/120] discard an object media update message if it tries to set more media textures than the prim has faces --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 8bccab4604..378ff4a915 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -227,6 +227,14 @@ namespace OpenSim.Region.CoreModules.Media.Moap // m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); // } + if (omu.FaceMedia.Length > part.GetNumberOfSides()) + { + m_log.WarnFormat( + "[MOAP]: Received {0} media entries from client for prim {1} {2} but this prim has only {3} faces. Dropping request.", + omu.FaceMedia.Length, part.Name, part.UUID, part.GetNumberOfSides()); + return string.Empty; + } + part.Shape.Media = new List(omu.FaceMedia); UpdateMediaUrl(part); From 6f644f5322ee0c5ffe6c654387981f1c13f7112d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 23:19:45 +0100 Subject: [PATCH 023/120] implement prim media control permissions serverside in order to stop bad clients --- .../World/Media/Moap/MoapModule.cs | 87 ++++++++++++++----- .../World/Permissions/PermissionsModule.cs | 43 ++++++++- .../Framework/Scenes/Scene.Permissions.cs | 21 ++++- 3 files changed, 127 insertions(+), 24 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 378ff4a915..d7aede91dd 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -58,8 +58,21 @@ namespace OpenSim.Region.CoreModules.Media.Moap public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + /// + /// The scene to which this module is attached + /// protected Scene m_scene; + /// + /// Track the ObjectMedia capabilities given to users + /// + protected Dictionary m_omCapUsers = new Dictionary(); + + /// + /// Track the ObjectMediaUpdate capabilities given to users + /// + protected Dictionary m_omuCapUsers = new Dictionary(); + public void Initialise(IConfigSource config) { // TODO: Add config switches to enable/disable this module @@ -87,16 +100,27 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat( "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); - // We do receive a post to ObjectMedia when a new avatar enters the region - though admittedly this is the - // avatar that set the texture in the first place. - // Even though we're registering for POST we're going to get GETS and UPDATES too - caps.RegisterHandler( - "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaMessage)); + string omCapUrl = "/CAPS/" + UUID.Random(); - // We do get these posts when the url has been changed. - // Even though we're registering for POST we're going to get GETS and UPDATES too - caps.RegisterHandler( - "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateMessage)); + lock (m_omCapUsers) + { + m_omCapUsers[omCapUrl] = agentID; + + // Even though we're registering for POST we're going to get GETS and UPDATES too + caps.RegisterHandler( + "ObjectMedia", new RestStreamHandler("POST", omCapUrl, HandleObjectMediaMessage)); + } + + string omuCapUrl = "/CAPS/" + UUID.Random(); + + lock (m_omuCapUsers) + { + m_omuCapUsers[omuCapUrl] = agentID; + + // Even though we're registering for POST we're going to get GETS and UPDATES too + caps.RegisterHandler( + "ObjectMediaNavigate", new RestStreamHandler("POST", omuCapUrl, HandleObjectMediaNavigateMessage)); + } } public MediaEntry GetMediaEntry(SceneObjectPart part, int face) @@ -147,7 +171,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected string HandleObjectMediaMessage( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); + m_log.DebugFormat("[MOAP]: Got ObjectMedia path [{0}], raw request [{1}]", path, request); OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); ObjectMediaMessage omm = new ObjectMediaMessage(); @@ -156,7 +180,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (omm.Request is ObjectMediaRequest) return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest); else if (omm.Request is ObjectMediaUpdate) - return HandleObjectMediaUpdate(omm.Request as ObjectMediaUpdate); + return HandleObjectMediaUpdate(path, omm.Request as ObjectMediaUpdate); throw new Exception( string.Format( @@ -165,7 +189,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap } /// - /// Handle a request for media textures + /// Handle a fetch request for media textures /// /// /// @@ -202,9 +226,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// Handle an update of media textures. /// + /// Path on which this request was made /// /param> /// - protected string HandleObjectMediaUpdate(ObjectMediaUpdate omu) + protected string HandleObjectMediaUpdate(string path, ObjectMediaUpdate omu) { UUID primId = omu.PrimID; @@ -216,16 +241,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", primId, m_scene.RegionInfo.RegionName); return string.Empty; - } + } m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); -// for (int i = 0; i < omu.FaceMedia.Length; i++) -// { -// MediaEntry me = omu.FaceMedia[i]; -// string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); -// m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); -// } + for (int i = 0; i < omu.FaceMedia.Length; i++) + { + MediaEntry me = omu.FaceMedia[i]; + string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); + m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); + } if (omu.FaceMedia.Length > part.GetNumberOfSides()) { @@ -235,7 +260,27 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } - part.Shape.Media = new List(omu.FaceMedia); + List media = part.Shape.Media; + + if (null == media) + { + part.Shape.Media = new List(omu.FaceMedia); + } + else + { + // We need to go through the media textures one at a time to make sure that we have permission + // to change them + UUID agentId = default(UUID); + + lock (m_omCapUsers) + agentId = m_omCapUsers[path]; + + for (int i = 0; i < media.Count; i++) + { + if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) + media[i] = omu.FaceMedia[i]; + } + } UpdateMediaUrl(part); diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 69b247c350..358ea59216 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -164,6 +164,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions private Dictionary GrantYP = new Dictionary(); private IFriendsModule m_friendsModule; private IGroupsModule m_groupsModule; + private IMoapModule m_moapModule; #endregion @@ -248,6 +249,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_scene.Permissions.OnDeleteUserInventory += CanDeleteUserInventory; //NOT YET IMPLEMENTED m_scene.Permissions.OnTeleport += CanTeleport; //NOT YET IMPLEMENTED + + m_scene.Permissions.OnControlPrimMedia += CanControlPrimMedia; m_scene.AddCommand(this, "bypass permissions", "bypass permissions ", @@ -393,6 +396,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (m_groupsModule == null) m_log.Warn("[PERMISSIONS]: Groups module not found, group permissions will not work"); + + m_moapModule = m_scene.RequestModuleInterface(); } public void Close() @@ -1893,5 +1898,41 @@ namespace OpenSim.Region.CoreModules.World.Permissions } return(false); } + + private bool CanControlPrimMedia(UUID agentID, UUID primID, int face) + { + if (null == m_moapModule) + return false; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primID); + if (null == part) + return false; + + MediaEntry me = m_moapModule.GetMediaEntry(part, face); + + // If there is no existing media entry then it can be controlled (in this context, created). + if (null == me) + return true; + + if (IsAdministrator(agentID)) + return true; + + if ((me.ControlPermissions & MediaPermission.Anyone) == MediaPermission.Anyone) + return true; + + if ((me.ControlPermissions & MediaPermission.Owner) == MediaPermission.Owner) + { + if (agentID == part.OwnerID) + return true; + } + + if ((me.ControlPermissions & MediaPermission.Group) == MediaPermission.Group) + { + if (IsGroupMember(part.GroupID, agentID, 0)) + return true; + } + + return false; + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs index 7dab04fbd2..70af978719 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -81,6 +81,7 @@ namespace OpenSim.Region.Framework.Scenes public delegate bool CopyUserInventoryHandler(UUID itemID, UUID userID); public delegate bool DeleteUserInventoryHandler(UUID itemID, UUID userID); public delegate bool TeleportHandler(UUID userID, Scene scene); + public delegate bool ControlPrimMediaHandler(UUID userID, UUID primID, int face); #endregion public class ScenePermissions @@ -139,6 +140,7 @@ namespace OpenSim.Region.Framework.Scenes public event CopyUserInventoryHandler OnCopyUserInventory; public event DeleteUserInventoryHandler OnDeleteUserInventory; public event TeleportHandler OnTeleport; + public event ControlPrimMediaHandler OnControlPrimMedia; #endregion #region Object Permission Checks @@ -947,5 +949,20 @@ namespace OpenSim.Region.Framework.Scenes } return true; } + + public bool CanControlPrimMedia(UUID userID, UUID primID, int face) + { + ControlPrimMediaHandler handler = OnControlPrimMedia; + if (handler != null) + { + Delegate[] list = handler.GetInvocationList(); + foreach (ControlPrimMediaHandler h in list) + { + if (h(userID, primID, face) == false) + return false; + } + } + return true; + } } -} +} \ No newline at end of file From 2ad9789b1ca85f64a8fd15be66d62b52bf8363f7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 23:46:49 +0100 Subject: [PATCH 024/120] factor out soon to be common media permissions check code --- .../World/Permissions/PermissionsModule.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 358ea59216..2344e9681f 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -1914,25 +1914,30 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (null == me) return true; + return GenericPrimMediaPermission(part, agentID, me.ControlPermissions); + } + + private bool GenericPrimMediaPermission(SceneObjectPart part, UUID agentID, MediaPermission perms) + { if (IsAdministrator(agentID)) return true; - if ((me.ControlPermissions & MediaPermission.Anyone) == MediaPermission.Anyone) + if ((perms & MediaPermission.Anyone) == MediaPermission.Anyone) return true; - if ((me.ControlPermissions & MediaPermission.Owner) == MediaPermission.Owner) + if ((perms & MediaPermission.Owner) == MediaPermission.Owner) { if (agentID == part.OwnerID) return true; } - if ((me.ControlPermissions & MediaPermission.Group) == MediaPermission.Group) + if ((perms & MediaPermission.Group) == MediaPermission.Group) { if (IsGroupMember(part.GroupID, agentID, 0)) return true; } - return false; + return false; } } } \ No newline at end of file From a4c6c4de9114497de831594e5673788d323d8e65 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 23:58:19 +0100 Subject: [PATCH 025/120] implement serverside checks for media texture navigation in order to stop naughty clients --- .../World/Media/Moap/MoapModule.cs | 20 ++++++++++++------ .../World/Permissions/PermissionsModule.cs | 21 ++++++++++++++++++- .../Framework/Scenes/Scene.Permissions.cs | 19 ++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index d7aede91dd..09786ec096 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -245,12 +245,12 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); - for (int i = 0; i < omu.FaceMedia.Length; i++) - { - MediaEntry me = omu.FaceMedia[i]; - string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); - m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); - } +// for (int i = 0; i < omu.FaceMedia.Length; i++) +// { +// MediaEntry me = omu.FaceMedia[i]; +// string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); +// m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); +// } if (omu.FaceMedia.Length > part.GetNumberOfSides()) { @@ -322,6 +322,14 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } + UUID agentId = default(UUID); + + lock (m_omuCapUsers) + agentId = m_omuCapUsers[path]; + + if (!m_scene.Permissions.CanInteractWithPrimMedia(agentId, part.UUID, omn.Face)) + return string.Empty; + m_log.DebugFormat( "[MOAP]: Updating media entry for face {0} on prim {1} {2} to {3}", omn.Face, part.Name, part.UUID, omn.URL); diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 2344e9681f..3a690afc73 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -251,6 +251,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_scene.Permissions.OnTeleport += CanTeleport; //NOT YET IMPLEMENTED m_scene.Permissions.OnControlPrimMedia += CanControlPrimMedia; + m_scene.Permissions.OnInteractWithPrimMedia += CanInteractWithPrimMedia; m_scene.AddCommand(this, "bypass permissions", "bypass permissions ", @@ -1915,7 +1916,25 @@ namespace OpenSim.Region.CoreModules.World.Permissions return true; return GenericPrimMediaPermission(part, agentID, me.ControlPermissions); - } + } + + private bool CanInteractWithPrimMedia(UUID agentID, UUID primID, int face) + { + if (null == m_moapModule) + return false; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primID); + if (null == part) + return false; + + MediaEntry me = m_moapModule.GetMediaEntry(part, face); + + // If there is no existing media entry then it can be controlled (in this context, created). + if (null == me) + return true; + + return GenericPrimMediaPermission(part, agentID, me.InteractPermissions); + } private bool GenericPrimMediaPermission(SceneObjectPart part, UUID agentID, MediaPermission perms) { diff --git a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs index 70af978719..003390047f 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs @@ -82,6 +82,7 @@ namespace OpenSim.Region.Framework.Scenes public delegate bool DeleteUserInventoryHandler(UUID itemID, UUID userID); public delegate bool TeleportHandler(UUID userID, Scene scene); public delegate bool ControlPrimMediaHandler(UUID userID, UUID primID, int face); + public delegate bool InteractWithPrimMediaHandler(UUID userID, UUID primID, int face); #endregion public class ScenePermissions @@ -141,6 +142,7 @@ namespace OpenSim.Region.Framework.Scenes public event DeleteUserInventoryHandler OnDeleteUserInventory; public event TeleportHandler OnTeleport; public event ControlPrimMediaHandler OnControlPrimMedia; + public event InteractWithPrimMediaHandler OnInteractWithPrimMedia; #endregion #region Object Permission Checks @@ -963,6 +965,21 @@ namespace OpenSim.Region.Framework.Scenes } } return true; - } + } + + public bool CanInteractWithPrimMedia(UUID userID, UUID primID, int face) + { + InteractWithPrimMediaHandler handler = OnInteractWithPrimMedia; + if (handler != null) + { + Delegate[] list = handler.GetInvocationList(); + foreach (InteractWithPrimMediaHandler h in list) + { + if (h(userID, primID, face) == false) + return false; + } + } + return true; + } } } \ No newline at end of file From e44e1ccbd32745e25ecf750a6bd0e212e4dfd535 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 14 Jul 2010 00:16:37 +0100 Subject: [PATCH 026/120] implement code to deregister users on DeregisterCaps --- .../World/Media/Moap/MoapModule.cs | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 09786ec096..ce4e921b85 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -64,15 +64,25 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected Scene m_scene; /// - /// Track the ObjectMedia capabilities given to users + /// Track the ObjectMedia capabilities given to users keyed by path /// protected Dictionary m_omCapUsers = new Dictionary(); /// - /// Track the ObjectMediaUpdate capabilities given to users + /// Track the ObjectMedia capabilities given to users keyed by agent. Lock m_omCapUsers to manipulate. + /// + protected Dictionary m_omCapUrls = new Dictionary(); + + /// + /// Track the ObjectMediaUpdate capabilities given to users keyed by path /// protected Dictionary m_omuCapUsers = new Dictionary(); + /// + /// Track the ObjectMediaUpdate capabilities given to users keyed by agent. Lock m_omuCapUsers to manipulate + /// + protected Dictionary m_omuCapUrls = new Dictionary(); + public void Initialise(IConfigSource config) { // TODO: Add config switches to enable/disable this module @@ -88,11 +98,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void RegionLoaded(Scene scene) { m_scene.EventManager.OnRegisterCaps += RegisterCaps; + m_scene.EventManager.OnDeregisterCaps += DeregisterCaps; } public void Close() { m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps; } public void RegisterCaps(UUID agentID, Caps caps) @@ -105,6 +117,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap lock (m_omCapUsers) { m_omCapUsers[omCapUrl] = agentID; + m_omCapUrls[agentID] = omCapUrl; // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( @@ -116,12 +129,30 @@ namespace OpenSim.Region.CoreModules.Media.Moap lock (m_omuCapUsers) { m_omuCapUsers[omuCapUrl] = agentID; + m_omuCapUrls[agentID] = omuCapUrl; // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( "ObjectMediaNavigate", new RestStreamHandler("POST", omuCapUrl, HandleObjectMediaNavigateMessage)); } - } + } + + public void DeregisterCaps(UUID agentID, Caps caps) + { + lock (m_omCapUsers) + { + string path = m_omCapUrls[agentID]; + m_omCapUrls.Remove(agentID); + m_omCapUsers.Remove(path); + } + + lock (m_omuCapUsers) + { + string path = m_omuCapUrls[agentID]; + m_omuCapUrls.Remove(agentID); + m_omuCapUsers.Remove(path); + } + } public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { From c3ee451325893df8f102c52f05f791289b7c61f6 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 14 Jul 2010 23:26:24 +0100 Subject: [PATCH 027/120] fix previous media interact serverside checking. perform very basic serverside url whitelist checks at the moment, only checking for the exact name prefix is implemented for some reason, whitelists are not persisting this commit also fixes a very recent problem where setting any media texture parameters after the initial configuration would not work --- .../World/Media/Moap/MoapModule.cs | 71 +++++++++++++++++-- .../World/Permissions/PermissionsModule.cs | 30 ++++++-- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index ce4e921b85..3c546c48e3 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -91,6 +91,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void AddRegion(Scene scene) { m_scene = scene; + m_scene.RegisterModuleInterface(this); } public void RemoveRegion(Scene scene) {} @@ -156,20 +157,28 @@ namespace OpenSim.Region.CoreModules.Media.Moap public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { + MediaEntry me = null; + CheckFaceParam(part, face); List media = part.Shape.Media; if (null == media) { - return null; + me = null; } else - { + { + me = media[face]; + // TODO: Really need a proper copy constructor down in libopenmetaverse - MediaEntry me = media[face]; - return (null == me ? null : MediaEntry.FromOSD(me.GetOSD())); + if (me != null) + me = MediaEntry.FromOSD(me.GetOSD()); } + +// m_log.DebugFormat("[MOAP]: GetMediaEntry for {0} face {1} found {2}", part.Name, face, me); + + return me; } public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) @@ -295,6 +304,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (null == media) { + m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name); part.Shape.Media = new List(omu.FaceMedia); } else @@ -309,7 +319,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap for (int i = 0; i < media.Count; i++) { if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) + { media[i] = omu.FaceMedia[i]; +// m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name); + } } } @@ -362,10 +375,31 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; m_log.DebugFormat( - "[MOAP]: Updating media entry for face {0} on prim {1} {2} to {3}", + "[MOAP]: Received request to update media entry for face {0} on prim {1} {2} to {3}", omn.Face, part.Name, part.UUID, omn.URL); + // If media has never been set for this prim, then just return. + if (null == part.Shape.Media) + return string.Empty; + MediaEntry me = part.Shape.Media[omn.Face]; + + // Do the same if media has not been set up for a specific face + if (null == me) + return string.Empty; + + if (me.EnableWhiteList) + { + if (!CheckUrlAgainstWhitelist(omn.URL, me.WhiteList)) + { + m_log.DebugFormat( + "[MOAP]: Blocking change of face {0} on prim {1} {2} to {3} since it's not on the enabled whitelist", + omn.Face, part.Name, part.UUID, omn.URL); + + return string.Empty; + } + } + me.CurrentURL = omn.URL; UpdateMediaUrl(part); @@ -413,5 +447,32 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); } + + /// + /// Check the given url against the given whitelist. + /// + /// + /// + /// true if the url matches an entry on the whitelist, false otherwise + protected bool CheckUrlAgainstWhitelist(string url, string[] whitelist) + { + foreach (string rawWlUrl in whitelist) + { + string wlUrl = rawWlUrl; + + if (!wlUrl.StartsWith("http://")) + wlUrl = "http://" + wlUrl; + + m_log.DebugFormat("[MOAP]: Checking whitelist URL {0}", wlUrl); + + if (url.StartsWith(wlUrl)) + { + m_log.DebugFormat("[MOAP]: Whitelist url {0} matches requested url {1}", wlUrl, url); + return true; + } + } + + return false; + } } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 3a690afc73..7f6f851520 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -178,7 +178,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions string permissionModules = myConfig.GetString("permissionmodules", "DefaultPermissionsModule"); - List modules=new List(permissionModules.Split(',')); + List modules = new List(permissionModules.Split(',')); if (!modules.Contains("DefaultPermissionsModule")) return; @@ -399,6 +399,10 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_log.Warn("[PERMISSIONS]: Groups module not found, group permissions will not work"); m_moapModule = m_scene.RequestModuleInterface(); + + // This log line will be commented out when no longer required for debugging + if (m_moapModule == null) + m_log.Warn("[PERMISSIONS]: Media on a prim module not found, media on a prim permissions will not work"); } public void Close() @@ -1901,7 +1905,11 @@ namespace OpenSim.Region.CoreModules.World.Permissions } private bool CanControlPrimMedia(UUID agentID, UUID primID, int face) - { + { +// m_log.DebugFormat( +// "[PERMISSONS]: Performing CanControlPrimMedia check with agentID {0}, primID {1}, face {2}", +// agentID, primID, face); + if (null == m_moapModule) return false; @@ -1909,17 +1917,25 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (null == part) return false; - MediaEntry me = m_moapModule.GetMediaEntry(part, face); + MediaEntry me = m_moapModule.GetMediaEntry(part, face); // If there is no existing media entry then it can be controlled (in this context, created). if (null == me) return true; + m_log.DebugFormat( + "[PERMISSIONS]: Checking CanControlPrimMedia for {0} on {1} face {2} with control permissions {3}", + agentID, primID, face, me.ControlPermissions); + return GenericPrimMediaPermission(part, agentID, me.ControlPermissions); } private bool CanInteractWithPrimMedia(UUID agentID, UUID primID, int face) { +// m_log.DebugFormat( +// "[PERMISSONS]: Performing CanInteractWithPrimMedia check with agentID {0}, primID {1}, face {2}", +// agentID, primID, face); + if (null == m_moapModule) return false; @@ -1933,13 +1949,17 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (null == me) return true; + m_log.DebugFormat( + "[PERMISSIONS]: Checking CanInteractWithPrimMedia for {0} on {1} face {2} with interact permissions {3}", + agentID, primID, face, me.InteractPermissions); + return GenericPrimMediaPermission(part, agentID, me.InteractPermissions); } private bool GenericPrimMediaPermission(SceneObjectPart part, UUID agentID, MediaPermission perms) { - if (IsAdministrator(agentID)) - return true; +// if (IsAdministrator(agentID)) +// return true; if ((perms & MediaPermission.Anyone) == MediaPermission.Anyone) return true; From aec3b330119a6c4e799939329bd0748ce1a265be Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 14 Jul 2010 23:48:24 +0100 Subject: [PATCH 028/120] fix bug where prim persistence would fail if media had never been set --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index 51f4ceff27..7acbd22105 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -1919,14 +1919,17 @@ namespace OpenSim.Data.SQLite row["Texture"] = s.TextureEntry; row["ExtraParams"] = s.ExtraParams; - OSDArray meArray = new OSDArray(); - foreach (MediaEntry me in s.Media) + if (null != s.Media) { - OSD osd = (null == me ? new OSD() : me.GetOSD()); - meArray.Add(osd); + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in s.Media) + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } + + row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } - - row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } /// From e74e591e0b2196bcf58e17f604e2d8d4819c3917 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 00:15:23 +0100 Subject: [PATCH 029/120] properly expose prim media LSL functions to scripts scripts using these functions should now compile but I don't know how well the methods themselves work yet llSetPrimMedia(), at least, appears to have problems when a current url is set for a face that doesn't yet have a texture --- .../CoreModules/World/Media/Moap/MoapModule.cs | 2 +- .../ScriptEngine/Shared/Api/Interface/ILSL_Api.cs | 3 +++ .../ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 3c546c48e3..4bbac6e3a1 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -186,7 +186,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap CheckFaceParam(part, face); if (null == part.Shape.Media) - part.Shape.Media = new List(part.GetNumberOfSides()); + part.Shape.Media = new List(new MediaEntry[part.GetNumberOfSides()]); part.Shape.Media[face] = me; UpdateMediaUrl(part); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs index cba46a36c7..561e3b3213 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs @@ -62,6 +62,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces void llBreakLink(int linknum); LSL_Integer llCeil(double f); void llClearCameraParams(); + LSL_Integer llClearPrimMedia(LSL_Integer face); void llCloseRemoteDataChannel(string channel); LSL_Float llCloud(LSL_Vector offset); void llCollisionFilter(string name, string id, int accept); @@ -162,6 +163,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces LSL_List llGetParcelPrimOwners(LSL_Vector pos); LSL_Integer llGetPermissions(); LSL_Key llGetPermissionsKey(); + LSL_List llGetPrimMediaParams(int face, LSL_List rules); LSL_Vector llGetPos(); LSL_List llGetPrimitiveParams(LSL_List rules); LSL_Integer llGetRegionAgentCount(); @@ -332,6 +334,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces void llSetParcelMusicURL(string url); void llSetPayPrice(int price, LSL_List quick_pay_buttons); void llSetPos(LSL_Vector pos); + LSL_Integer llSetPrimMediaParams(int face, LSL_List rules); void llSetPrimitiveParams(LSL_List rules); void llSetLinkPrimitiveParamsFast(int linknum, LSL_List rules); void llSetPrimURL(string url); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs index 3339995f66..451163fe99 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs @@ -1832,5 +1832,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase { return m_LSL_Functions.llXorBase64StringsCorrect(str1, str2); } + + public LSL_List llGetPrimMediaParams(int face, LSL_List rules) + { + return m_LSL_Functions.llGetPrimMediaParams(face, rules); + } + + public LSL_Integer llSetPrimMediaParams(int face, LSL_List rules) + { + return m_LSL_Functions.llSetPrimMediaParams(face, rules); + } + + public LSL_Integer llClearPrimMedia(LSL_Integer face) + { + return m_LSL_Functions.llClearPrimMedia(face); + } } } From 610a2626a5e60cf5c3511ecf4d4187e9da71d336 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 21:14:55 +0100 Subject: [PATCH 030/120] Implement * end wildcard for whitelist urls --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 4bbac6e3a1..c24e6d55ef 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -460,6 +460,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap { string wlUrl = rawWlUrl; + // Deal with a line-ending wildcard + if (wlUrl.EndsWith("*")) + wlUrl = wlUrl.Remove(wlUrl.Length - 1); + if (!wlUrl.StartsWith("http://")) wlUrl = "http://" + wlUrl; @@ -467,7 +471,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (url.StartsWith(wlUrl)) { - m_log.DebugFormat("[MOAP]: Whitelist url {0} matches requested url {1}", wlUrl, url); + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, url); return true; } } From 2ec6e3b4402ae7acf9e3f0da77c12203c2b44ff9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 21:40:44 +0100 Subject: [PATCH 031/120] refactor: simplify current whitelist url checking by using System.Uri --- .../CoreModules/World/Media/Moap/MoapModule.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index c24e6d55ef..c8e72ca895 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -451,11 +451,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// Check the given url against the given whitelist. /// - /// + /// /// /// true if the url matches an entry on the whitelist, false otherwise - protected bool CheckUrlAgainstWhitelist(string url, string[] whitelist) + protected bool CheckUrlAgainstWhitelist(string rawUrl, string[] whitelist) { + Uri url = new Uri(rawUrl); + foreach (string rawWlUrl in whitelist) { string wlUrl = rawWlUrl; @@ -464,14 +466,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (wlUrl.EndsWith("*")) wlUrl = wlUrl.Remove(wlUrl.Length - 1); - if (!wlUrl.StartsWith("http://")) - wlUrl = "http://" + wlUrl; - m_log.DebugFormat("[MOAP]: Checking whitelist URL {0}", wlUrl); + + string urlToMatch = url.Authority + url.AbsolutePath; - if (url.StartsWith(wlUrl)) + if (urlToMatch.StartsWith(wlUrl)) { - m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, url); + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, urlToMatch); return true; } } From 0bec4f5ea5b2586bcc3899ad084e30d42218cb44 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 21:51:57 +0100 Subject: [PATCH 032/120] Handle checking of line starting "*" wildcard for whitelist patterns A line starting * can only be applied to the domain, not the path --- .../World/Media/Moap/MoapModule.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index c8e72ca895..cbe9af2864 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -458,22 +458,36 @@ namespace OpenSim.Region.CoreModules.Media.Moap { Uri url = new Uri(rawUrl); - foreach (string rawWlUrl in whitelist) + foreach (string origWlUrl in whitelist) { - string wlUrl = rawWlUrl; + string wlUrl = origWlUrl; // Deal with a line-ending wildcard if (wlUrl.EndsWith("*")) wlUrl = wlUrl.Remove(wlUrl.Length - 1); - m_log.DebugFormat("[MOAP]: Checking whitelist URL {0}", wlUrl); + m_log.DebugFormat("[MOAP]: Checking whitelist URL pattern {0}", origWlUrl); - string urlToMatch = url.Authority + url.AbsolutePath; - - if (urlToMatch.StartsWith(wlUrl)) + // Handle a line starting wildcard slightly differently since this can only match the domain, not the path + if (wlUrl.StartsWith("*")) { - m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, urlToMatch); - return true; + wlUrl = wlUrl.Substring(1); + + if (url.Host.Contains(wlUrl)) + { + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); + return true; + } + } + else + { + string urlToMatch = url.Authority + url.AbsolutePath; + + if (urlToMatch.StartsWith(wlUrl)) + { + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); + return true; + } } } From 2c3207df076b6633c7b8a777adb8583eba6a6d82 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 23:28:36 +0100 Subject: [PATCH 033/120] add missing regionstore migration file for new fields. D'oh! this should enable persistence now --- OpenSim/Data/SQLite/Resources/020_RegionStore.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 OpenSim/Data/SQLite/Resources/020_RegionStore.sql diff --git a/OpenSim/Data/SQLite/Resources/020_RegionStore.sql b/OpenSim/Data/SQLite/Resources/020_RegionStore.sql new file mode 100644 index 0000000000..39cb752979 --- /dev/null +++ b/OpenSim/Data/SQLite/Resources/020_RegionStore.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE prims ADD COLUMN MediaURL varchar(255); +ALTER TABLE primshapes ADD COLUMN Media TEXT; + +COMMIT; \ No newline at end of file From 8e67f6dc44424a53f07cd0c2af0ecf53d7ca30b7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 21 Jul 2010 14:25:21 +0100 Subject: [PATCH 034/120] start adding user ids to the media urls --- .../World/Media/Moap/MoapModule.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index cbe9af2864..6755df70d6 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -189,7 +189,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media = new List(new MediaEntry[part.GetNumberOfSides()]); part.Shape.Media[face] = me; - UpdateMediaUrl(part); + UpdateMediaUrl(part, UUID.Zero); part.ScheduleFullUpdate(); part.TriggerScriptChangedEvent(Changed.MEDIA); } @@ -300,6 +300,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } + UUID agentId = default(UUID); + + lock (m_omCapUsers) + agentId = m_omCapUsers[path]; + List media = part.Shape.Media; if (null == media) @@ -310,12 +315,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap else { // We need to go through the media textures one at a time to make sure that we have permission - // to change them - UUID agentId = default(UUID); - - lock (m_omCapUsers) - agentId = m_omCapUsers[path]; - + // to change them for (int i = 0; i < media.Count; i++) { if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) @@ -326,7 +326,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } - UpdateMediaUrl(part); + UpdateMediaUrl(part, agentId); // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); @@ -402,13 +402,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap me.CurrentURL = omn.URL; - UpdateMediaUrl(part); + UpdateMediaUrl(part, agentId); part.ScheduleFullUpdate(); part.TriggerScriptChangedEvent(Changed.MEDIA); - return string.Empty; + return OSDParser.SerializeLLSDXmlString(new OSD()); } /// @@ -431,12 +431,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// Update the media url of the given part /// /// - protected void UpdateMediaUrl(SceneObjectPart part) + /// + /// The id to attach to this update. Normally, this is the user that changed the + /// texture + /// + protected void UpdateMediaUrl(SceneObjectPart part, UUID updateId) { if (null == part.MediaUrl) { // TODO: We can't set the last changer until we start tracking which cap we give to which agent id - part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + part.MediaUrl = "x-mv:0000000000/" + updateId; } else { From 6ef2a72c70882810c1ca1b940d6afdbecdbe78a4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 21 Jul 2010 17:12:43 +0100 Subject: [PATCH 035/120] Properly set TextureEntry.MediaFlags when a media texture is set Media flags is cleared via a direct TextureEntry update from the client. If the clearing leaves no media textures on the prim, then a CAP ObjectMediaUpdate is not received. If there are still media textures present then one is received. This change fixes drag-and-drop on Windows (and Mac?) clients. It may also fix problems with clearing and then subsequently setting new media textures. --- .../World/Media/Moap/MoapModule.cs | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 6755df70d6..4818546f9c 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -311,19 +311,59 @@ namespace OpenSim.Region.CoreModules.Media.Moap { m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name); part.Shape.Media = new List(omu.FaceMedia); + + for (int i = 0; i < omu.FaceMedia.Length; i++) + { + if (omu.FaceMedia[i] != null) + { + // FIXME: Race condition here since some other texture entry manipulator may overwrite/get + // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry + // directly. + Primitive.TextureEntry te = part.Shape.Textures; + Primitive.TextureEntryFace face = te.CreateFace((uint)i); + face.MediaFlags = true; + part.Shape.Textures = te; + m_log.DebugFormat( + "[MOAP]: Media flags for face {0} is {1}", + i, part.Shape.Textures.FaceTextures[i].MediaFlags); + } + } } else - { + { // We need to go through the media textures one at a time to make sure that we have permission // to change them + + // FIXME: Race condition here since some other texture entry manipulator may overwrite/get + // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry + // directly. + Primitive.TextureEntry te = part.Shape.Textures; + for (int i = 0; i < media.Count; i++) - { + { if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) - { + { media[i] = omu.FaceMedia[i]; + + // When a face is cleared this is done by setting the MediaFlags in the TextureEntry via a normal + // texture update, so we don't need to worry about clearing MediaFlags here. + if (null == media[i]) + continue; + + Primitive.TextureEntryFace face = te.CreateFace((uint)i); + face.MediaFlags = true; + + m_log.DebugFormat( + "[MOAP]: Media flags for face {0} is {1}", + i, face.MediaFlags); // m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name); } } + + part.Shape.Textures = te; + +// for (int i2 = 0; i2 < part.Shape.Textures.FaceTextures.Length; i2++) +// m_log.DebugFormat("[MOAP]: FaceTexture[{0}] is {1}", i2, part.Shape.Textures.FaceTextures[i2]); } UpdateMediaUrl(part, agentId); From 8e8076c947462980d849ac2fac33fc0640ea1a31 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 21 Jul 2010 19:32:05 +0100 Subject: [PATCH 036/120] also add avatar id to an updated media url - not just new ones --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 4818546f9c..0130ff95fa 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -486,7 +486,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap { string rawVersion = part.MediaUrl.Substring(5, 10); int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, updateId); } m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); From 4736e38e794925946675a8d510f3f9d2fc4e9d8c Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 19:56:55 +0100 Subject: [PATCH 037/120] Put a wrapper around the media texture region serialization THIS WILL BREAK EXISTING MEDIA TEXTURE PERSISTENCE. Please delete your existing sqlite databases if you are experimenting with this branch. This wrapper will make it easier to maintain compatibility if the media texture data evolves. This will also make it easier to store non-sl media texture data. --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 58 ++++++++++++++++++------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index 7acbd22105..b5644195da 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -31,6 +31,7 @@ using System.Data; using System.Drawing; using System.IO; using System.Reflection; +using System.Xml; using log4net; using Mono.Data.Sqlite; using OpenMetaverse; @@ -1862,17 +1863,26 @@ namespace OpenSim.Data.SQLite if (!(row["Media"] is System.DBNull)) { - string rawMeArray = (string)row["Media"]; - OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(rawMeArray); - - List mediaEntries = new List(); - foreach (OSD osdMe in osdMeArray) + using (StringReader sr = new StringReader((string)row["Media"])) { - MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); - mediaEntries.Add(me); + using (XmlTextReader xtr = new XmlTextReader(sr)) + { + xtr.ReadStartElement("osmedia"); + + OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); + + List mediaEntries = new List(); + foreach (OSD osdMe in osdMeArray) + { + MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); + mediaEntries.Add(me); + } + + s.Media = mediaEntries; + + xtr.ReadEndElement(); + } } - - s.Media = mediaEntries; } return s; @@ -1921,14 +1931,32 @@ namespace OpenSim.Data.SQLite if (null != s.Media) { - OSDArray meArray = new OSDArray(); - foreach (MediaEntry me in s.Media) + using (StringWriter sw = new StringWriter()) { - OSD osd = (null == me ? new OSD() : me.GetOSD()); - meArray.Add(osd); + using (XmlTextWriter xtw = new XmlTextWriter(sw)) + { + xtw.WriteStartElement("osmedia"); + xtw.WriteAttributeString("type", "sl"); + xtw.WriteAttributeString("major_version", "0"); + xtw.WriteAttributeString("minor_version", "1"); + + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in s.Media) + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } + + xtw.WriteStartElement("osdata"); + xtw.WriteRaw(OSDParser.SerializeLLSDXmlString(meArray)); + xtw.WriteEndElement(); + + xtw.WriteEndElement(); + + xtw.Flush(); + row["Media"] = sw.ToString(); + } } - - row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } } From 491b8181ad4fcb0a8002ee2406703b89c4928219 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 20:13:26 +0100 Subject: [PATCH 038/120] Add EventManager.OnSceneObjectLoaded() for future use. This is fired immediately after a scene object is loaded from storage. --- .../World/Media/Moap/MoapModule.cs | 19 +++++++---- .../Region/Framework/Scenes/EventManager.cs | 32 +++++++++++++++++-- OpenSim/Region/Framework/Scenes/Scene.cs | 4 ++- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 0130ff95fa..2771492f6d 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -98,17 +98,19 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void RegionLoaded(Scene scene) { - m_scene.EventManager.OnRegisterCaps += RegisterCaps; - m_scene.EventManager.OnDeregisterCaps += DeregisterCaps; + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; + m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; + m_scene.EventManager.OnSceneObjectLoaded += OnSceneObjectLoaded; } public void Close() { - m_scene.EventManager.OnRegisterCaps -= RegisterCaps; - m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps; + m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; + m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; + m_scene.EventManager.OnSceneObjectLoaded -= OnSceneObjectLoaded; } - public void RegisterCaps(UUID agentID, Caps caps) + public void OnRegisterCaps(UUID agentID, Caps caps) { m_log.DebugFormat( "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); @@ -138,7 +140,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } - public void DeregisterCaps(UUID agentID, Caps caps) + public void OnDeregisterCaps(UUID agentID, Caps caps) { lock (m_omCapUsers) { @@ -155,6 +157,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } + public void OnSceneObjectLoaded(SceneObjectGroup sog) + { + m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", sog.Name, sog.UUID); + } + public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { MediaEntry me = null; diff --git a/OpenSim/Region/Framework/Scenes/EventManager.cs b/OpenSim/Region/Framework/Scenes/EventManager.cs index ef125cd357..46e17c5ea8 100644 --- a/OpenSim/Region/Framework/Scenes/EventManager.cs +++ b/OpenSim/Region/Framework/Scenes/EventManager.cs @@ -331,9 +331,16 @@ namespace OpenSim.Region.Framework.Scenes /// the avatarID is UUID.Zero (I know, this doesn't make much sense but now it's historical). public delegate void Attach(uint localID, UUID itemID, UUID avatarID); public event Attach OnAttach; + + public delegate void SceneObjectDelegate(SceneObjectGroup so); + + /// + /// Called immediately after an object is loaded from storage. + /// + public event SceneObjectDelegate OnSceneObjectLoaded; public delegate void RegionUp(GridRegion region); - public event RegionUp OnRegionUp; + public event RegionUp OnRegionUp; public class MoneyTransferArgs : EventArgs { @@ -2013,5 +2020,26 @@ namespace OpenSim.Region.Framework.Scenes } } } + + public void TriggerOnSceneObjectLoaded(SceneObjectGroup so) + { + SceneObjectDelegate handler = OnSceneObjectLoaded; + if (handler != null) + { + foreach (SceneObjectDelegate d in handler.GetInvocationList()) + { + try + { + d(so); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerOnSceneObjectLoaded failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index e2ab643ebd..5542a0c48a 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -1887,9 +1887,11 @@ namespace OpenSim.Region.Framework.Scenes foreach (SceneObjectGroup group in PrimsFromDB) { + EventManager.TriggerOnSceneObjectLoaded(group); + if (group.RootPart == null) { - m_log.ErrorFormat("[SCENE] Found a SceneObjectGroup with m_rootPart == null and {0} children", + m_log.ErrorFormat("[SCENE]: Found a SceneObjectGroup with m_rootPart == null and {0} children", group.Children == null ? 0 : group.Children.Count); } From c70d57ff983d42b6898d766eb5536e56868b3213 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 20:36:28 +0100 Subject: [PATCH 039/120] Add EventManager.OnSceneObjectPreSave() for future use. This is triggered immediately before a copy of the group is persisted to storage --- .../World/Media/Moap/MoapModule.cs | 11 +++++- .../Region/Framework/Scenes/EventManager.cs | 39 +++++++++++++++++-- .../Framework/Scenes/SceneObjectGroup.cs | 1 + 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 2771492f6d..263ee579e7 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -101,6 +101,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded += OnSceneObjectLoaded; + m_scene.EventManager.OnSceneObjectPreSave += OnSceneObjectPreSave; } public void Close() @@ -108,6 +109,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded -= OnSceneObjectLoaded; + m_scene.EventManager.OnSceneObjectPreSave -= OnSceneObjectPreSave; } public void OnRegisterCaps(UUID agentID, Caps caps) @@ -157,11 +159,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } - public void OnSceneObjectLoaded(SceneObjectGroup sog) + public void OnSceneObjectLoaded(SceneObjectGroup so) { - m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", sog.Name, sog.UUID); + m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", so.Name, so.UUID); } + public void OnSceneObjectPreSave(SceneObjectGroup persistingSo, SceneObjectGroup originalSo) + { + m_log.DebugFormat("[MOAP]: OnSceneObjectPreSave fired for {0} {1}", persistingSo.Name, persistingSo.UUID); + } + public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { MediaEntry me = null; diff --git a/OpenSim/Region/Framework/Scenes/EventManager.cs b/OpenSim/Region/Framework/Scenes/EventManager.cs index 46e17c5ea8..a4dd170945 100644 --- a/OpenSim/Region/Framework/Scenes/EventManager.cs +++ b/OpenSim/Region/Framework/Scenes/EventManager.cs @@ -330,14 +330,26 @@ namespace OpenSim.Region.Framework.Scenes /// If the object is being attached, then the avatarID will be present. If the object is being detached then /// the avatarID is UUID.Zero (I know, this doesn't make much sense but now it's historical). public delegate void Attach(uint localID, UUID itemID, UUID avatarID); - public event Attach OnAttach; - - public delegate void SceneObjectDelegate(SceneObjectGroup so); + public event Attach OnAttach; /// /// Called immediately after an object is loaded from storage. /// public event SceneObjectDelegate OnSceneObjectLoaded; + public delegate void SceneObjectDelegate(SceneObjectGroup so); + + /// + /// Called immediately before an object is saved to storage. + /// + /// + /// The scene object being persisted. + /// This is actually a copy of the original scene object so changes made here will be saved to storage but will not be kept in memory. + /// + /// + /// The original scene object being persisted. Changes here will stay in memory but will not be saved to storage on this save. + /// + public event SceneObjectPreSaveDelegate OnSceneObjectPreSave; + public delegate void SceneObjectPreSaveDelegate(SceneObjectGroup persistingSo, SceneObjectGroup originalSo); public delegate void RegionUp(GridRegion region); public event RegionUp OnRegionUp; @@ -2040,6 +2052,27 @@ namespace OpenSim.Region.Framework.Scenes } } } + } + + public void TriggerOnSceneObjectPreSave(SceneObjectGroup persistingSo, SceneObjectGroup originalSo) + { + SceneObjectPreSaveDelegate handler = OnSceneObjectPreSave; + if (handler != null) + { + foreach (SceneObjectPreSaveDelegate d in handler.GetInvocationList()) + { + try + { + d(persistingSo, originalSo); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerOnSceneObjectPreSave failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } } } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 1ca390a7c1..451b93efb0 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -1479,6 +1479,7 @@ namespace OpenSim.Region.Framework.Scenes backup_group.RootPart.ParticleSystem = RootPart.ParticleSystem; HasGroupChanged = false; + m_scene.EventManager.TriggerOnSceneObjectPreSave(backup_group, this); datastore.StoreObject(backup_group, m_scene.RegionInfo.RegionID); backup_group.ForEachPart(delegate(SceneObjectPart part) From d5e8272ad44e87a4ac1a7b142ca36b5c5978fca7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 21:09:54 +0100 Subject: [PATCH 040/120] relocate serialization code from SQLiteRegionData to MoapModule using load and save events. This is better modularity. It also allows MoapModule to be replaced with some other media module that may behave completely differently in the future. Remaining non-modularity: PrimitiveBaseShape needs explicit Media and MediaRaw fields. MediaRaw is required in order to shuttle the pre-serialization data back and forth from the database layer. The database also needs to know about MediaRaw though not about Media. IMO, it would be extremely nice to remove these hard codings but this is a bridge too far at the present time. --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 55 +-------------- OpenSim/Framework/PrimitiveBaseShape.cs | 6 ++ .../World/Media/Moap/MoapModule.cs | 70 ++++++++++++++++++- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index b5644195da..f63d35e3bd 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -31,7 +31,6 @@ using System.Data; using System.Drawing; using System.IO; using System.Reflection; -using System.Xml; using log4net; using Mono.Data.Sqlite; using OpenMetaverse; @@ -1862,28 +1861,7 @@ namespace OpenSim.Data.SQLite s.ExtraParams = (byte[]) row["ExtraParams"]; if (!(row["Media"] is System.DBNull)) - { - using (StringReader sr = new StringReader((string)row["Media"])) - { - using (XmlTextReader xtr = new XmlTextReader(sr)) - { - xtr.ReadStartElement("osmedia"); - - OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); - - List mediaEntries = new List(); - foreach (OSD osdMe in osdMeArray) - { - MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); - mediaEntries.Add(me); - } - - s.Media = mediaEntries; - - xtr.ReadEndElement(); - } - } - } + s.MediaRaw = (string)row["Media"]; return s; } @@ -1928,36 +1906,7 @@ namespace OpenSim.Data.SQLite row["Texture"] = s.TextureEntry; row["ExtraParams"] = s.ExtraParams; - - if (null != s.Media) - { - using (StringWriter sw = new StringWriter()) - { - using (XmlTextWriter xtw = new XmlTextWriter(sw)) - { - xtw.WriteStartElement("osmedia"); - xtw.WriteAttributeString("type", "sl"); - xtw.WriteAttributeString("major_version", "0"); - xtw.WriteAttributeString("minor_version", "1"); - - OSDArray meArray = new OSDArray(); - foreach (MediaEntry me in s.Media) - { - OSD osd = (null == me ? new OSD() : me.GetOSD()); - meArray.Add(osd); - } - - xtw.WriteStartElement("osdata"); - xtw.WriteRaw(OSDParser.SerializeLLSDXmlString(meArray)); - xtw.WriteEndElement(); - - xtw.WriteEndElement(); - - xtw.Flush(); - row["Media"] = sw.ToString(); - } - } - } + row["Media"] = s.MediaRaw; } /// diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index 85638ca243..03ddb3394e 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -173,6 +173,12 @@ namespace OpenSim.Framework } } + /// + /// Raw media data suitable for serialization operations. This should only ever be used by an IMoapModule. + /// + [XmlIgnore] + public string MediaRaw { get; set; } + /// /// Entries to store media textures on each face /// diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 263ee579e7..0e03318643 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -32,6 +32,7 @@ using System.Collections.Specialized; using System.Reflection; using System.IO; using System.Web; +using System.Xml; using log4net; using Mono.Addins; using Nini.Config; @@ -46,6 +47,7 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Media.Moap @@ -162,12 +164,76 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void OnSceneObjectLoaded(SceneObjectGroup so) { m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", so.Name, so.UUID); + + so.ForEachPart(OnSceneObjectPartLoaded); } public void OnSceneObjectPreSave(SceneObjectGroup persistingSo, SceneObjectGroup originalSo) { - m_log.DebugFormat("[MOAP]: OnSceneObjectPreSave fired for {0} {1}", persistingSo.Name, persistingSo.UUID); - } + m_log.DebugFormat("[MOAP]: OnSceneObjectPreSave fired for {0} {1}", persistingSo.Name, persistingSo.UUID); + + persistingSo.ForEachPart(OnSceneObjectPartPreSave); + } + + protected void OnSceneObjectPartLoaded(SceneObjectPart part) + { + if (null == part.Shape.MediaRaw) + return; + + using (StringReader sr = new StringReader(part.Shape.MediaRaw)) + { + using (XmlTextReader xtr = new XmlTextReader(sr)) + { + xtr.ReadStartElement("osmedia"); + + OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); + + List mediaEntries = new List(); + foreach (OSD osdMe in osdMeArray) + { + MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); + mediaEntries.Add(me); + } + + xtr.ReadEndElement(); + + part.Shape.Media = mediaEntries; + } + } + } + + protected void OnSceneObjectPartPreSave(SceneObjectPart part) + { + if (null == part.Shape.Media) + return; + + using (StringWriter sw = new StringWriter()) + { + using (XmlTextWriter xtw = new XmlTextWriter(sw)) + { + xtw.WriteStartElement("osmedia"); + xtw.WriteAttributeString("type", "sl"); + xtw.WriteAttributeString("major_version", "0"); + xtw.WriteAttributeString("minor_version", "1"); + + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in part.Shape.Media) + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } + + xtw.WriteStartElement("osdata"); + xtw.WriteRaw(OSDParser.SerializeLLSDXmlString(meArray)); + xtw.WriteEndElement(); + + xtw.WriteEndElement(); + + xtw.Flush(); + part.Shape.MediaRaw = sw.ToString(); + } + } + } public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { From f34795c6b3cd022bd9f6ed4cea02631f5b17bf72 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 21:41:39 +0100 Subject: [PATCH 041/120] provide config option for media on a prim --- .../World/Media/Moap/MoapModule.cs | 21 +++++++++++++++++-- .../World/Permissions/PermissionsModule.cs | 4 ++-- bin/OpenSim.ini.example | 5 +++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 0e03318643..7afeeec014 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -60,6 +60,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + /// + /// Is this module enabled? + /// + protected bool m_isEnabled = true; + /// /// The scene to which this module is attached /// @@ -85,13 +90,19 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// protected Dictionary m_omuCapUrls = new Dictionary(); - public void Initialise(IConfigSource config) + public void Initialise(IConfigSource configSource) { - // TODO: Add config switches to enable/disable this module + IConfig config = configSource.Configs["MediaOnAPrim"]; + + if (config != null && !config.GetBoolean("Enabled", false)) + m_isEnabled = false; } public void AddRegion(Scene scene) { + if (!m_isEnabled) + return; + m_scene = scene; m_scene.RegisterModuleInterface(this); } @@ -100,6 +111,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void RegionLoaded(Scene scene) { + if (!m_isEnabled) + return; + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded += OnSceneObjectLoaded; @@ -108,6 +122,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void Close() { + if (!m_isEnabled) + return; + m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded -= OnSceneObjectLoaded; diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 7f6f851520..982ac523a4 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -401,8 +401,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_moapModule = m_scene.RequestModuleInterface(); // This log line will be commented out when no longer required for debugging - if (m_moapModule == null) - m_log.Warn("[PERMISSIONS]: Media on a prim module not found, media on a prim permissions will not work"); +// if (m_moapModule == null) +// m_log.Warn("[PERMISSIONS]: Media on a prim module not found, media on a prim permissions will not work"); } public void Close() diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index f4d9a18b6c..a533f45231 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -1248,6 +1248,11 @@ ; enabled=false +[MediaOnAPrim] + ; Enable media on a prim facilities + Enabled = true; + + ;; ;; These are defaults that are overwritten below in [Architecture]. ;; These defaults allow OpenSim to work out of the box with From d00466f09af214da8bedac89440ecad1d8c7b00d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 23:19:31 +0100 Subject: [PATCH 042/120] add mysql support for media on a prim --- OpenSim/Data/MySQL/MySQLLegacyRegionData.cs | 8 ++++++-- OpenSim/Data/MySQL/Resources/RegionStore.migrations | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs b/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs index bfeae12385..f17e8aebe2 100644 --- a/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs +++ b/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs @@ -222,7 +222,7 @@ namespace OpenSim.Data.MySQL "PathTaperX, PathTaperY, PathTwist, " + "PathTwistBegin, ProfileBegin, ProfileEnd, " + "ProfileCurve, ProfileHollow, Texture, " + - "ExtraParams, State) values (?UUID, " + + "ExtraParams, State, Media) values (?UUID, " + "?Shape, ?ScaleX, ?ScaleY, ?ScaleZ, " + "?PCode, ?PathBegin, ?PathEnd, " + "?PathScaleX, ?PathScaleY, " + @@ -233,7 +233,7 @@ namespace OpenSim.Data.MySQL "?PathTwistBegin, ?ProfileBegin, " + "?ProfileEnd, ?ProfileCurve, " + "?ProfileHollow, ?Texture, ?ExtraParams, " + - "?State)"; + "?State, ?Media)"; FillShapeCommand(cmd, prim); @@ -1700,6 +1700,9 @@ namespace OpenSim.Data.MySQL s.ExtraParams = (byte[])row["ExtraParams"]; s.State = (byte)(int)row["State"]; + + if (!(row["Media"] is System.DBNull)) + s.MediaRaw = (string)row["Media"]; return s; } @@ -1743,6 +1746,7 @@ namespace OpenSim.Data.MySQL cmd.Parameters.AddWithValue("Texture", s.TextureEntry); cmd.Parameters.AddWithValue("ExtraParams", s.ExtraParams); cmd.Parameters.AddWithValue("State", s.State); + cmd.Parameters.AddWithValue("Media", s.MediaRaw); } public void StorePrimInventory(UUID primID, ICollection items) diff --git a/OpenSim/Data/MySQL/Resources/RegionStore.migrations b/OpenSim/Data/MySQL/Resources/RegionStore.migrations index 3f644f903f..13697043f1 100644 --- a/OpenSim/Data/MySQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MySQL/Resources/RegionStore.migrations @@ -1,4 +1,4 @@ - + :VERSION 1 #--------------------- BEGIN; @@ -800,3 +800,10 @@ BEGIN; ALTER TABLE `regionwindlight` CHANGE COLUMN `cloud_scroll_x` `cloud_scroll_x` FLOAT(4,2) NOT NULL DEFAULT '0.20' AFTER `cloud_detail_density`, CHANGE COLUMN `cloud_scroll_y` `cloud_scroll_y` FLOAT(4,2) NOT NULL DEFAULT '0.01' AFTER `cloud_scroll_x_lock`; COMMIT; +:VERSION 35 #--------------------- +-- Added post 0.7 + +BEGIN; +ALTER TABLE prims ADD COLUMN MediaURL varchar(255); +ALTER TABLE primshapes ADD COLUMN Media TEXT; +COMMIT; \ No newline at end of file From 09231b1d123263a04804977904429d975647b22e Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 23:26:22 +0100 Subject: [PATCH 043/120] add mssql support for media on a prim compiles but not tested. please test and correct if necessary! --- OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs | 10 +++++++--- OpenSim/Data/MSSQL/Resources/RegionStore.migrations | 9 ++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs b/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs index d6cb91f27f..e61b4d9259 100644 --- a/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs +++ b/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs @@ -385,7 +385,7 @@ IF EXISTS (SELECT UUID FROM primshapes WHERE UUID = @UUID) PathSkew = @PathSkew, PathCurve = @PathCurve, PathRadiusOffset = @PathRadiusOffset, PathRevolutions = @PathRevolutions, PathTaperX = @PathTaperX, PathTaperY = @PathTaperY, PathTwist = @PathTwist, PathTwistBegin = @PathTwistBegin, ProfileBegin = @ProfileBegin, ProfileEnd = @ProfileEnd, ProfileCurve = @ProfileCurve, ProfileHollow = @ProfileHollow, - Texture = @Texture, ExtraParams = @ExtraParams, State = @State + Texture = @Texture, ExtraParams = @ExtraParams, State = @State, Media = @Media WHERE UUID = @UUID END ELSE @@ -394,11 +394,11 @@ ELSE primshapes ( UUID, Shape, ScaleX, ScaleY, ScaleZ, PCode, PathBegin, PathEnd, PathScaleX, PathScaleY, PathShearX, PathShearY, PathSkew, PathCurve, PathRadiusOffset, PathRevolutions, PathTaperX, PathTaperY, PathTwist, PathTwistBegin, ProfileBegin, - ProfileEnd, ProfileCurve, ProfileHollow, Texture, ExtraParams, State + ProfileEnd, ProfileCurve, ProfileHollow, Texture, ExtraParams, State, Media ) VALUES ( @UUID, @Shape, @ScaleX, @ScaleY, @ScaleZ, @PCode, @PathBegin, @PathEnd, @PathScaleX, @PathScaleY, @PathShearX, @PathShearY, @PathSkew, @PathCurve, @PathRadiusOffset, @PathRevolutions, @PathTaperX, @PathTaperY, @PathTwist, @PathTwistBegin, @ProfileBegin, - @ProfileEnd, @ProfileCurve, @ProfileHollow, @Texture, @ExtraParams, @State + @ProfileEnd, @ProfileCurve, @ProfileHollow, @Texture, @ExtraParams, @State, @Media ) END"; @@ -1180,6 +1180,9 @@ VALUES { } + if (!(shapeRow["Media"] is System.DBNull)) + baseShape.MediaRaw = (string)shapeRow["Media"]; + return baseShape; } @@ -1557,6 +1560,7 @@ VALUES parameters.Add(_Database.CreateParameter("Texture", s.TextureEntry)); parameters.Add(_Database.CreateParameter("ExtraParams", s.ExtraParams)); parameters.Add(_Database.CreateParameter("State", s.State)); + parameters.Add(_Database.CreateParameter("Media", s.MediaRaw)); return parameters.ToArray(); } diff --git a/OpenSim/Data/MSSQL/Resources/RegionStore.migrations b/OpenSim/Data/MSSQL/Resources/RegionStore.migrations index e912d646a0..e2e8cbb117 100644 --- a/OpenSim/Data/MSSQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MSSQL/Resources/RegionStore.migrations @@ -1,4 +1,4 @@ - + :VERSION 1 CREATE TABLE [dbo].[prims]( @@ -925,5 +925,12 @@ ALTER TABLE regionsettings ADD loaded_creation_datetime int NOT NULL default 0 COMMIT +:VERSION 24 +-- Added post 0.7 +BEGIN TRANSACTION +ALTER TABLE prims ADD COLUMN MediaURL varchar(255) +ALTER TABLE primshapes ADD COLUMN Media TEXT + +COMMIT \ No newline at end of file From ede446cad6ecb987badbc04b8436d449dee5e08b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 30 Jun 2010 20:46:35 +0100 Subject: [PATCH 044/120] add stub media-on-a-prim (shared media) module --- .../World/Media/Moap/MoapModule.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs new file mode 100644 index 0000000000..1e5c767bc0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -0,0 +1,56 @@ +/* + * 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 log4net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using Mono.Addins; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.Media.Moap +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")] + public class MoapModule : INonSharedRegionModule + { + public string Name { get { return "MoapModule"; } } + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource config) {} + + public void AddRegion(Scene scene) { Console.WriteLine("YEAH I'M HERE, BABY!"); } + + public void RemoveRegion(Scene scene) {} + + public void RegionLoaded(Scene scene) {} + + public void Close() {} + } +} \ No newline at end of file From 2be7d0cdd43f1b3037fa06cca87bcc0c84ac47e5 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 30 Jun 2010 22:30:05 +0100 Subject: [PATCH 045/120] Register ObjectMedia and ObjectMediaNavigate capabilities from moap module. Not sure if these are correct, but just supplying these to the viewer is enough to allow it to put media textures on prims (previously the icons were greyed out). This is not yet persisted even in-memory, so no other avatars will see it yet. --- .../World/Media/Moap/MoapModule.cs | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 1e5c767bc0..68b9b430d6 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -26,31 +26,79 @@ */ using System; +using System.Collections; +using System.Collections.Specialized; using System.Reflection; -using Nini.Config; +using System.IO; +using System.Web; using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using Mono.Addins; -using OpenMetaverse; +using OpenSim.Services.Interfaces; +using Caps = OpenSim.Framework.Capabilities.Caps; namespace OpenSim.Region.CoreModules.Media.Moap { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")] public class MoapModule : INonSharedRegionModule { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + protected Scene m_scene; + public void Initialise(IConfigSource config) {} - public void AddRegion(Scene scene) { Console.WriteLine("YEAH I'M HERE, BABY!"); } + public void AddRegion(Scene scene) + { + m_scene = scene; + } public void RemoveRegion(Scene scene) {} - public void RegionLoaded(Scene scene) {} + public void RegionLoaded(Scene scene) + { + m_scene.EventManager.OnRegisterCaps += RegisterCaps; + } public void Close() {} + + public void RegisterCaps(UUID agentID, Caps caps) + { + m_log.DebugFormat( + "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); + + caps.RegisterHandler( + "ObjectMedia", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaRequest)); + caps.RegisterHandler( + "ObjectMediaNavigate", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaNavigateRequest)); + } + + protected string OnObjectMediaRequest( + string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + m_log.DebugFormat("[MOAP]: Got ObjectMedia request for {0}", path); + //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + + return string.Empty; + } + + protected string OnObjectMediaNavigateRequest( + string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request for {0}", path); + //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + + return string.Empty; + } } } \ No newline at end of file From 701f39c8c256d77669fc88f73deb5217a785d0d6 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 00:24:30 +0100 Subject: [PATCH 046/120] do a whole load of crappy hacking to get cubes to display google.com currently, for smoe reason the page only appears when you click a face. also, actually navigating anywhere always snaps you back to the google search box, for some unknown reason you can still change the url and normal navigation will work again --- .../Servers/HttpServer/BaseHttpServer.cs | 2 +- .../World/Media/Moap/MoapModule.cs | 95 +++++++++++++++++-- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index 8123f2fed7..f85cb57bd8 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -362,7 +362,7 @@ namespace OpenSim.Framework.Servers.HttpServer string path = request.RawUrl; string handlerKey = GetHandlerKey(request.HttpMethod, path); - //m_log.DebugFormat("[BASE HTTP SERVER]: Handling {0} request for {1}", request.HttpMethod, path); + m_log.DebugFormat("[BASE HTTP SERVER]: Handling {0} request for {1}", request.HttpMethod, path); if (TryGetStreamHandler(handlerKey, out requestHandler)) { diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 68b9b430d6..b6fa53fafa 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -35,8 +35,10 @@ using log4net; using Mono.Addins; using Nini.Config; using OpenMetaverse; +using OpenMetaverse.Messages.Linden; using OpenMetaverse.StructuredData; using OpenSim.Framework; +using OpenSim.Framework.Capabilities; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; @@ -77,28 +79,109 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat( "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); + // We do receive a post to ObjectMedia when a new avatar enters the region - though admittedly this is the + // avatar that set the texture in the first place. + // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMedia", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaRequest)); + "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaRequest)); + + // We do get these posts when the url has been changed. + // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMediaNavigate", new RestStreamHandler("GET", "/CAPS/" + UUID.Random(), OnObjectMediaNavigateRequest)); + "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateRequest)); } - protected string OnObjectMediaRequest( + /// + /// Sets or gets per face media textures. + /// + /// + /// + /// + /// + /// + /// + protected string HandleObjectMediaRequest( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - m_log.DebugFormat("[MOAP]: Got ObjectMedia request for {0}", path); + m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); + + Hashtable osdParams = new Hashtable(); + osdParams = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); + + foreach (Object key in osdParams.Keys) + m_log.DebugFormat("[MOAP]: Param {0}={1}", key, osdParams[key]); + + string verb = (string)osdParams["verb"]; + + if ("GET" == verb) + return HandleObjectMediaRequestGet(path, osdParams, httpRequest, httpResponse); + //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + // TODO: Persist in memory + // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message + // TODO: Persist in database + return string.Empty; } - protected string OnObjectMediaNavigateRequest( + protected string HandleObjectMediaRequestGet( + string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + // Yeah, only for cubes right now. I know it's dumb. + int faces = 6; + + MediaEntry[] media = new MediaEntry[faces]; + for (int i = 0; i < faces; i++) + { + MediaEntry me = new MediaEntry(); + me.HomeURL = "google.com"; + me.CurrentURL = "google.com"; + me.AutoScale = true; + //me.Height = 300; + //me.Width = 240; + media[i] = me; + } + + ObjectMediaResponse resp = new ObjectMediaResponse(); + + resp.PrimID = (UUID)osdParams["object_id"]; + resp.FaceMedia = media; + + // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying + // to minimally satisfy for now to get something working + resp.Version = "x-mv:0000000016/" + UUID.Random(); + + //string rawResp = resp.Serialize().ToString(); + string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); + + m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp); + + return rawResp; + } + + /// + /// Received from the viewer if a user has changed the url of a media texture. + /// + /// + /// + /// + /// /param> + /// /param> + /// + protected string HandleObjectMediaNavigateRequest( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request for {0}", path); //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + // TODO: Persist in memory + // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message + // TODO: Persist in database + return string.Empty; - } + } + + } } \ No newline at end of file From 9301e36fbc545b0bbdb14e1651e1ff1f4ca7c04f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 00:47:12 +0100 Subject: [PATCH 047/120] have a stab at sending the correct number of media entries to shapes actually, this is probably wrong anyway if there's a default texture it's going to be easier just to gather the object media updates and retain those in-memory now but what the hell --- .../CoreModules/World/Media/Moap/MoapModule.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index b6fa53fafa..30507a4aa3 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -128,8 +128,20 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected string HandleObjectMediaRequestGet( string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - // Yeah, only for cubes right now. I know it's dumb. - int faces = 6; + UUID primId = (UUID)osdParams["object_id"]; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + + if (null == part) + { + m_log.WarnFormat( + "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in the scene", + primId); + return string.Empty; + } + + int faces = part.GetNumberOfSides(); + m_log.DebugFormat("[MOAP]: Faces [{0}] for [{1}]", faces, primId); MediaEntry[] media = new MediaEntry[faces]; for (int i = 0; i < faces; i++) From acac47830e3d7d9103c30729ad0cff50b0e8fcdc Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 02:06:51 +0100 Subject: [PATCH 048/120] start storing incoming MediaEntry on a new Media field on PrimitiveBaseShape This allows the media texture to persist in memory - logging in and out will redisplay it (after a click) though navigation will be lost Next need to implement media uri on prim and delegate more incoming llsd parsing to libomv --- OpenSim/Framework/PrimitiveBaseShape.cs | 7 +++ .../World/Media/Moap/MoapModule.cs | 63 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index 4d1de22eeb..517dbf6e03 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -26,12 +26,14 @@ */ using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Reflection; using System.Xml.Serialization; using log4net; using OpenMetaverse; +using OpenMetaverse.StructuredData; namespace OpenSim.Framework { @@ -170,6 +172,11 @@ namespace OpenSim.Framework } } } + + /// + /// Entries to store media textures on each face + /// + public List Media { get; set; } public PrimitiveBaseShape() { diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 30507a4aa3..90626f4129 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; using System.IO; @@ -115,6 +116,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap if ("GET" == verb) return HandleObjectMediaRequestGet(path, osdParams, httpRequest, httpResponse); + if ("UPDATE" == verb) + return HandleObjectMediaRequestUpdate(path, osdParams, httpRequest, httpResponse); //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); @@ -140,6 +143,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } + /* int faces = part.GetNumberOfSides(); m_log.DebugFormat("[MOAP]: Faces [{0}] for [{1}]", faces, primId); @@ -154,17 +158,20 @@ namespace OpenSim.Region.CoreModules.Media.Moap //me.Width = 240; media[i] = me; } + */ + + if (null == part.Shape.Media) + return string.Empty; ObjectMediaResponse resp = new ObjectMediaResponse(); - resp.PrimID = (UUID)osdParams["object_id"]; - resp.FaceMedia = media; + resp.PrimID = primId; + resp.FaceMedia = part.Shape.Media.ToArray(); // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying // to minimally satisfy for now to get something working resp.Version = "x-mv:0000000016/" + UUID.Random(); - //string rawResp = resp.Serialize().ToString(); string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp); @@ -172,6 +179,56 @@ namespace OpenSim.Region.CoreModules.Media.Moap return rawResp; } + protected string HandleObjectMediaRequestUpdate( + string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + UUID primId = (UUID)osdParams["object_id"]; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + + if (null == part) + { + m_log.WarnFormat( + "[MOAP]: Received am UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in the scene", + primId); + return string.Empty; + } + + List cookedMediaEntries = new List(); + + ArrayList rawMediaEntries = (ArrayList)osdParams["object_media_data"]; + foreach (Object obj in rawMediaEntries) + { + Hashtable rawMe = (Hashtable)obj; + + // TODO: Yeah, I know this is silly. Very soon use existing better code in libomv to do this. + MediaEntry cookedMe = new MediaEntry(); + cookedMe.EnableAlterntiveImage = (bool)rawMe["alt_image_enable"]; + cookedMe.AutoLoop = (bool)rawMe["auto_loop"]; + cookedMe.AutoPlay = (bool)rawMe["auto_play"]; + cookedMe.AutoScale = (bool)rawMe["auto_scale"]; + cookedMe.AutoZoom = (bool)rawMe["auto_zoom"]; + cookedMe.InteractOnFirstClick = (bool)rawMe["first_click_interact"]; + cookedMe.Controls = (MediaControls)rawMe["controls"]; + cookedMe.HomeURL = (string)rawMe["home_url"]; + cookedMe.CurrentURL = (string)rawMe["current_url"]; + cookedMe.Height = (int)rawMe["height_pixels"]; + cookedMe.Width = (int)rawMe["width_pixels"]; + cookedMe.ControlPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_control"].ToString()); + cookedMe.InteractPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_interact"].ToString()); + cookedMe.EnableWhiteList = (bool)rawMe["whitelist_enable"]; + //cookedMe.WhiteList = (string[])rawMe["whitelist"]; + + cookedMediaEntries.Add(cookedMe); + } + + m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", cookedMediaEntries.Count, primId); + + part.Shape.Media = cookedMediaEntries; + + return string.Empty; + } + /// /// Received from the viewer if a user has changed the url of a media texture. /// From c290cdd9971ad876fffb3aff24c404675a1c1196 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 18:42:47 +0100 Subject: [PATCH 049/120] replace hand parsing of incoming object media messages with parsing code in libopenmetaverse --- .../World/Media/Moap/MoapModule.cs | 89 +++++++------------ 1 file changed, 30 insertions(+), 59 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 90626f4129..568170ef1a 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -46,6 +46,7 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Media.Moap { @@ -59,7 +60,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected Scene m_scene; - public void Initialise(IConfigSource config) {} + public void Initialise(IConfigSource config) + { + // TODO: Add config switches to enable/disable this module + } public void AddRegion(Scene scene) { @@ -73,7 +77,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_scene.EventManager.OnRegisterCaps += RegisterCaps; } - public void Close() {} + public void Close() + { + m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + } public void RegisterCaps(UUID agentID, Caps caps) { @@ -105,33 +112,26 @@ namespace OpenSim.Region.CoreModules.Media.Moap string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); + + OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); + ObjectMediaMessage omm = new ObjectMediaMessage(); + omm.Deserialize(osd); - Hashtable osdParams = new Hashtable(); - osdParams = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); - - foreach (Object key in osdParams.Keys) - m_log.DebugFormat("[MOAP]: Param {0}={1}", key, osdParams[key]); - - string verb = (string)osdParams["verb"]; - - if ("GET" == verb) - return HandleObjectMediaRequestGet(path, osdParams, httpRequest, httpResponse); - if ("UPDATE" == verb) - return HandleObjectMediaRequestUpdate(path, osdParams, httpRequest, httpResponse); - - //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); - - // TODO: Persist in memory - // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message - // TODO: Persist in database - - return string.Empty; + if (omm.Request is ObjectMediaRequest) + return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest); + else if (omm.Request is ObjectMediaUpdate) + return HandleObjectMediaUpdate(omm.Request as ObjectMediaUpdate); + + throw new Exception( + string.Format( + "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}", + omm.Request.GetType())); } - protected string HandleObjectMediaRequestGet( - string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) - { - UUID primId = (UUID)osdParams["object_id"]; + protected string HandleObjectMediaRequest(ObjectMediaRequest omr) + { + //UUID primId = (UUID)osdParams["object_id"]; + UUID primId = omr.PrimID; SceneObjectPart part = m_scene.GetSceneObjectPart(primId); @@ -179,10 +179,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap return rawResp; } - protected string HandleObjectMediaRequestUpdate( - string path, Hashtable osdParams, OSHttpRequest httpRequest, OSHttpResponse httpResponse) + protected string HandleObjectMediaUpdate(ObjectMediaUpdate omu) { - UUID primId = (UUID)osdParams["object_id"]; + UUID primId = omu.PrimID; SceneObjectPart part = m_scene.GetSceneObjectPart(primId); @@ -194,37 +193,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } - List cookedMediaEntries = new List(); + m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); - ArrayList rawMediaEntries = (ArrayList)osdParams["object_media_data"]; - foreach (Object obj in rawMediaEntries) - { - Hashtable rawMe = (Hashtable)obj; - - // TODO: Yeah, I know this is silly. Very soon use existing better code in libomv to do this. - MediaEntry cookedMe = new MediaEntry(); - cookedMe.EnableAlterntiveImage = (bool)rawMe["alt_image_enable"]; - cookedMe.AutoLoop = (bool)rawMe["auto_loop"]; - cookedMe.AutoPlay = (bool)rawMe["auto_play"]; - cookedMe.AutoScale = (bool)rawMe["auto_scale"]; - cookedMe.AutoZoom = (bool)rawMe["auto_zoom"]; - cookedMe.InteractOnFirstClick = (bool)rawMe["first_click_interact"]; - cookedMe.Controls = (MediaControls)rawMe["controls"]; - cookedMe.HomeURL = (string)rawMe["home_url"]; - cookedMe.CurrentURL = (string)rawMe["current_url"]; - cookedMe.Height = (int)rawMe["height_pixels"]; - cookedMe.Width = (int)rawMe["width_pixels"]; - cookedMe.ControlPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_control"].ToString()); - cookedMe.InteractPermissions = (MediaPermission)Enum.Parse(typeof(MediaPermission), rawMe["perms_interact"].ToString()); - cookedMe.EnableWhiteList = (bool)rawMe["whitelist_enable"]; - //cookedMe.WhiteList = (string[])rawMe["whitelist"]; - - cookedMediaEntries.Add(cookedMe); - } - - m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", cookedMediaEntries.Count, primId); - - part.Shape.Media = cookedMediaEntries; + part.Shape.Media = new List(omu.FaceMedia); return string.Empty; } From 4a6adff4cd66a3bfeaa99af10caf9136e011df46 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 19:25:46 +0100 Subject: [PATCH 050/120] start storing a mediaurl on the scene object part not yet persisted or sent in the update --- .../World/Media/Moap/MoapModule.cs | 30 +++++++++++++++---- .../Framework/Scenes/SceneObjectPart.cs | 7 ++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 568170ef1a..edd03973a4 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -91,7 +91,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap // avatar that set the texture in the first place. // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaRequest)); + "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaMessage)); // We do get these posts when the url has been changed. // Even though we're registering for POST we're going to get GETS and UPDATES too @@ -108,7 +108,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// /// - protected string HandleObjectMediaRequest( + protected string HandleObjectMediaMessage( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); @@ -167,10 +167,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap resp.PrimID = primId; resp.FaceMedia = part.Shape.Media.ToArray(); - - // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying - // to minimally satisfy for now to get something working - resp.Version = "x-mv:0000000016/" + UUID.Random(); + resp.Version = part.MediaUrl; string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); @@ -197,6 +194,27 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media = new List(omu.FaceMedia); + if (null == part.MediaUrl) + { + // TODO: We can't set the last changer until we start tracking which cap we give to which agent id + part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + } + else + { + string rawVersion = part.MediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:10D}/{1}", version, UUID.Zero); + } + + m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); + + // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying + // to minimally satisfy for now to get something working + //resp.Version = "x-mv:0000000016/" + UUID.Random(); + + // TODO: schedule full object update for all other avatars. This will trigger them to send an + // ObjectMediaRequest once they see that the MediaUrl is different. + return string.Empty; } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index e331bb05ec..c25c9734c6 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -970,13 +970,18 @@ namespace OpenSim.Region.Framework.Scenes get { return m_updateFlag; } set { m_updateFlag = value; } } + + /// + /// Used for media on a prim + /// + public string MediaUrl { get; set; } [XmlIgnore] public bool CreateSelected { get { return m_createSelected; } set { m_createSelected = value; } - } + } #endregion From b1eb83ed6cd5e25c22a858eefd1fba1e6bf622b7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 19:33:41 +0100 Subject: [PATCH 051/120] start sending media url in object full updates --- OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 0aec01a021..c59eedf188 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -4288,8 +4288,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendLandObjectOwners(LandData land, List groups, Dictionary ownersAndCount) { - - int notifyCount = ownersAndCount.Count; ParcelObjectOwnersReplyPacket pack = (ParcelObjectOwnersReplyPacket)PacketPool.Instance.GetPacket(PacketType.ParcelObjectOwnersReply); @@ -4561,6 +4559,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP update.TextureEntry = data.Shape.TextureEntry ?? Utils.EmptyBytes; update.Scale = data.Shape.Scale; update.Text = Util.StringToBytes256(data.Text); + update.MediaURL = Util.StringToBytes256(data.MediaUrl); #region PrimFlags From 468450a94db3b103d19f44f55156bf305d55ecb9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 19:53:03 +0100 Subject: [PATCH 052/120] send a full object update out to avatars when a media texture is initially set this allows other avatars to see it, but still only after they've clicked on the face still not handling navigation yet --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index edd03973a4..0d317329f4 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -208,12 +208,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); - // I know this has to end with the last avatar to edit and the version code shouldn't always be 16. Just trying - // to minimally satisfy for now to get something working - //resp.Version = "x-mv:0000000016/" + UUID.Random(); - - // TODO: schedule full object update for all other avatars. This will trigger them to send an - // ObjectMediaRequest once they see that the MediaUrl is different. + // Arguably we don't need to send a full update to the avatar that just changed the texture. + part.ScheduleFullUpdate(); return string.Empty; } From 4ebae14a530f8f5909e93a0dd852297f4f30a736 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 20:25:35 +0100 Subject: [PATCH 053/120] handle ObjectMediaNavigateMessage Other avatars can now see the webpages that you're navigating to. The requirement for an initial prim click before the texture displayed has gone away. Flash (e.g. YouTube) appears to work fine. Still not persisting any media data so this all disappears on server restart --- .../World/Media/Moap/MoapModule.cs | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 0d317329f4..c3ec2a6244 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -96,7 +96,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap // We do get these posts when the url has been changed. // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( - "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateRequest)); + "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateMessage)); } /// @@ -128,6 +128,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap omm.Request.GetType())); } + /// + /// Handle a request for media textures + /// + /// + /// protected string HandleObjectMediaRequest(ObjectMediaRequest omr) { //UUID primId = (UUID)osdParams["object_id"]; @@ -138,8 +143,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (null == part) { m_log.WarnFormat( - "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in the scene", - primId); + "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", + primId, m_scene.RegionInfo.RegionName); return string.Empty; } @@ -176,6 +181,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap return rawResp; } + /// + /// Handle an update of media textures. + /// + /// /param> + /// protected string HandleObjectMediaUpdate(ObjectMediaUpdate omu) { UUID primId = omu.PrimID; @@ -185,8 +195,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (null == part) { m_log.WarnFormat( - "[MOAP]: Received am UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in the scene", - primId); + "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", + primId, m_scene.RegionInfo.RegionName); return string.Empty; } @@ -203,7 +213,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap { string rawVersion = part.MediaUrl.Substring(5, 10); int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:10D}/{1}", version, UUID.Zero); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); } m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); @@ -223,19 +233,50 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /param> /// /param> /// - protected string HandleObjectMediaNavigateRequest( + protected string HandleObjectMediaNavigateMessage( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request for {0}", path); - //NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); + m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request [{0}]", request); + + OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); + ObjectMediaNavigateMessage omn = new ObjectMediaNavigateMessage(); + omn.Deserialize(osd); + + UUID primId = omn.PrimID; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + + if (null == part) + { + m_log.WarnFormat( + "[MOAP]: Received an ObjectMediaNavigateMessage for prim {0} but this doesn't exist in region {1}", + primId, m_scene.RegionInfo.RegionName); + return string.Empty; + } + + m_log.DebugFormat( + "[MOAP]: Updating media entry for face {0} on prim {1} {2} to {3}", + omn.Face, part.Name, part.UUID, omn.URL); + + MediaEntry me = part.Shape.Media[omn.Face]; + me.CurrentURL = omn.URL; + + string oldMediaUrl = part.MediaUrl; + + // TODO: refactor into common method + string rawVersion = oldMediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + + m_log.DebugFormat( + "[MOAP]: Updating media url in prim {0} {1} from [{2}] to [{3}]", + part.Name, part.UUID, oldMediaUrl, part.MediaUrl); + + part.ScheduleFullUpdate(); - // TODO: Persist in memory - // TODO: Tell other agents in the region about the change via the ObjectMediaResponse (?) message // TODO: Persist in database return string.Empty; - } - - + } } } \ No newline at end of file From 9682e0c73310dae496912d7b8bc54add0fd0c3e7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 1 Jul 2010 22:52:31 +0100 Subject: [PATCH 054/120] Implement media texture persistence over server restarts for sqlite This is currently persisting media as an OSDArray serialized to LLSD XML. --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 36 ++++++++++++++++--- .../Framework/Scenes/SceneObjectPart.cs | 22 +++++++++++- prebuild.xml | 1 + 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index 81d0ac4f6e..fc9667b6d0 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -34,6 +34,7 @@ using System.Reflection; using log4net; using Mono.Data.Sqlite; using OpenMetaverse; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -974,6 +975,8 @@ namespace OpenSim.Data.SQLite createCol(prims, "CollisionSoundVolume", typeof(Double)); createCol(prims, "VolumeDetect", typeof(Int16)); + + createCol(prims, "MediaURL", typeof(String)); // Add in contraints prims.PrimaryKey = new DataColumn[] {prims.Columns["UUID"]}; @@ -1021,6 +1024,7 @@ namespace OpenSim.Data.SQLite // way to specify this as a blob atm createCol(shapes, "Texture", typeof (Byte[])); createCol(shapes, "ExtraParams", typeof (Byte[])); + createCol(shapes, "Media", typeof(String)); shapes.PrimaryKey = new DataColumn[] {shapes.Columns["UUID"]}; @@ -1339,6 +1343,12 @@ namespace OpenSim.Data.SQLite if (Convert.ToInt16(row["VolumeDetect"]) != 0) prim.VolumeDetectActive = true; + + if (!(row["MediaURL"] is System.DBNull)) + { + m_log.DebugFormat("[SQLITE]: MediaUrl type [{0}]", row["MediaURL"].GetType()); + prim.MediaUrl = (string)row["MediaURL"]; + } return prim; } @@ -1614,7 +1624,6 @@ namespace OpenSim.Data.SQLite row["PayButton3"] = prim.PayPrice[3]; row["PayButton4"] = prim.PayPrice[4]; - row["TextureAnimation"] = Convert.ToBase64String(prim.TextureAnimation); row["ParticleSystem"] = Convert.ToBase64String(prim.ParticleSystem); @@ -1674,7 +1683,8 @@ namespace OpenSim.Data.SQLite row["VolumeDetect"] = 1; else row["VolumeDetect"] = 0; - + + row["MediaURL"] = prim.MediaUrl; } /// @@ -1849,6 +1859,19 @@ namespace OpenSim.Data.SQLite s.TextureEntry = textureEntry; s.ExtraParams = (byte[]) row["ExtraParams"]; + + if (!(row["Media"] is System.DBNull)) + { + string rawMeArray = (string)row["Media"]; + OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(rawMeArray); + + List mediaEntries = new List(); + foreach (OSD osdMe in osdMeArray) + mediaEntries.Add(MediaEntry.FromOSD(osdMe)); + + s.Media = mediaEntries; + } + return s; } @@ -1892,17 +1915,22 @@ namespace OpenSim.Data.SQLite row["Texture"] = s.TextureEntry; row["ExtraParams"] = s.ExtraParams; + + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in s.Media) + meArray.Add(me.GetOSD()); + + row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } /// - /// + /// Persistently store a prim. /// /// /// /// private void addPrim(SceneObjectPart prim, UUID sceneGroupID, UUID regionUUID) { - DataTable prims = ds.Tables["prims"]; DataTable shapes = ds.Tables["primshapes"]; diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index c25c9734c6..a8c20ddf32 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -320,6 +320,11 @@ namespace OpenSim.Region.Framework.Scenes protected Vector3 m_lastAcceleration; protected Vector3 m_lastAngularVelocity; protected int m_lastTerseSent; + + /// + /// Stores media texture data + /// + protected string m_mediaUrl; // TODO: Those have to be changed into persistent properties at some later point, // or sit-camera on vehicles will break on sim-crossing. @@ -965,6 +970,7 @@ namespace OpenSim.Region.Framework.Scenes TriggerScriptChangedEvent(Changed.SCALE); } } + public byte UpdateFlag { get { return m_updateFlag; } @@ -974,7 +980,21 @@ namespace OpenSim.Region.Framework.Scenes /// /// Used for media on a prim /// - public string MediaUrl { get; set; } + public string MediaUrl + { + get + { + return m_mediaUrl; + } + + set + { + m_mediaUrl = value; + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + } + } [XmlIgnore] public bool CreateSelected diff --git a/prebuild.xml b/prebuild.xml index 1eb8fee64b..1eabc4b8de 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -2238,6 +2238,7 @@ + From 8f403cb4b87fc99c0274929464229b1497395b86 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 15:47:56 +0100 Subject: [PATCH 055/120] Implement llGetPrimMediaParams() Exposes method to get media entry via IMoapModule As yet untested. --- .../World/Media/Moap/MoapModule.cs | 19 +++- .../Framework/Interfaces/IMoapModule.cs | 47 ++++++++ .../Shared/Api/Implementation/LSL_Api.cs | 104 ++++++++++++++++++ .../Shared/Api/Runtime/LSL_Constants.cs | 25 +++++ 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 OpenSim/Region/Framework/Interfaces/IMoapModule.cs diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index c3ec2a6244..9f7436714f 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -51,7 +51,7 @@ using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Media.Moap { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")] - public class MoapModule : INonSharedRegionModule + public class MoapModule : INonSharedRegionModule, IMoapModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -97,7 +97,22 @@ namespace OpenSim.Region.CoreModules.Media.Moap // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateMessage)); - } + } + + public MediaEntry GetMediaEntry(SceneObjectPart part, int face) + { + if (face < 0) + throw new ArgumentException("Face cannot be less than zero"); + + List media = part.Shape.Media; + + if (face > media.Count - 1) + throw new ArgumentException( + string.Format("Face argument was {0} but max is {1}", face, media.Count - 1)); + + // TODO: Really need a proper copy constructor down in libopenmetaverse + return MediaEntry.FromOSD(media[face].GetOSD()); + } /// /// Sets or gets per face media textures. diff --git a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs new file mode 100644 index 0000000000..4447f3475e --- /dev/null +++ b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs @@ -0,0 +1,47 @@ +/* + * 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.Region.Framework.Interfaces +{ + /// + /// Provides methods from manipulating media-on-a-prim + /// + public interface IMoapModule + { + /// + /// Get the media entry for a given prim face. + /// + /// + /// + /// + MediaEntry GetMediaEntry(SceneObjectPart part, int face); + } +} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 417cef4e64..e18e33eb29 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -7808,6 +7808,110 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return res; } + public LSL_List llGetPrimMediaParams(int face, LSL_List rules) + { + m_host.AddScriptLPS(1); + ScriptSleep(1000); + + // LSL Spec http://wiki.secondlife.com/wiki/LlGetPrimMediaParams says to fail silently if face is invalid + // TODO: Need to correctly handle case where a face has no media (which gives back an empty list). + // Assuming silently fail means give back an empty list. Ideally, need to check this. + if (face < 0 || face > m_host.Shape.Media.Count - 1) + return new LSL_List(); + + return GetLinkPrimMediaParams(face, rules); + } + + public LSL_List GetLinkPrimMediaParams(int face, LSL_List rules) + { + IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); + if (null == module) + throw new Exception("Media on a prim functions not available"); + + MediaEntry me = module.GetMediaEntry(m_host, face); + + LSL_List res = new LSL_List(); + + for (int i = 0; i < rules.Length; i++) + { + int code = (int)rules.GetLSLIntegerItem(i); + + switch (code) + { + case ScriptBaseClass.PRIM_MEDIA_ALT_IMAGE_ENABLE: + // Not implemented + res.Add(new LSL_Integer(0)); + break; + + case ScriptBaseClass.PRIM_MEDIA_CONTROLS: + if (me.Controls == MediaControls.Standard) + res.Add(new LSL_Integer(ScriptBaseClass.PRIM_MEDIA_CONTROLS_STANDARD)); + else + res.Add(new LSL_Integer(ScriptBaseClass.PRIM_MEDIA_CONTROLS_MINI)); + break; + + case ScriptBaseClass.PRIM_MEDIA_CURRENT_URL: + res.Add(new LSL_String(me.CurrentURL)); + break; + + case ScriptBaseClass.PRIM_MEDIA_HOME_URL: + res.Add(new LSL_String(me.HomeURL)); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_LOOP: + res.Add(me.AutoLoop ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_PLAY: + res.Add(me.AutoPlay ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_SCALE: + res.Add(me.AutoScale ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_ZOOM: + res.Add(me.AutoZoom ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_FIRST_CLICK_INTERACT: + res.Add(me.InteractOnFirstClick ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_WIDTH_PIXELS: + res.Add(new LSL_Integer(me.Width)); + break; + + case ScriptBaseClass.PRIM_MEDIA_HEIGHT_PIXELS: + res.Add(new LSL_Integer(me.Height)); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST_ENABLE: + res.Add(me.EnableWhiteList ? ScriptBaseClass.TRUE : ScriptBaseClass.FALSE); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST: + string[] urls = (string[])me.WhiteList.Clone(); + + for (int j = 0; j < urls.Length; j++) + urls[j] = Uri.EscapeDataString(urls[j]); + + res.Add(new LSL_String(string.Join(", ", urls))); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_INTERACT: + res.Add(new LSL_Integer((int)me.InteractPermissions)); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_CONTROL: + res.Add(new LSL_Integer((int)me.ControlPermissions)); + break; + } + } + + return res; + } + // // // The .NET definition of base 64 is: diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index dba6502f4b..9a64f8c40f 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -517,6 +517,31 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public const int TOUCH_INVALID_FACE = -1; public static readonly vector TOUCH_INVALID_TEXCOORD = new vector(-1.0, -1.0, 0.0); public static readonly vector TOUCH_INVALID_VECTOR = ZERO_VECTOR; + + // constants for llGetPrimMediaParams + public const int PRIM_MEDIA_ALT_IMAGE_ENABLE = 0; + public const int PRIM_MEDIA_CONTROLS = 1; + public const int PRIM_MEDIA_CURRENT_URL = 2; + public const int PRIM_MEDIA_HOME_URL = 3; + public const int PRIM_MEDIA_AUTO_LOOP = 4; + public const int PRIM_MEDIA_AUTO_PLAY = 5; + public const int PRIM_MEDIA_AUTO_SCALE = 6; + public const int PRIM_MEDIA_AUTO_ZOOM = 7; + public const int PRIM_MEDIA_FIRST_CLICK_INTERACT = 8; + public const int PRIM_MEDIA_WIDTH_PIXELS = 9; + public const int PRIM_MEDIA_HEIGHT_PIXELS = 10; + public const int PRIM_MEDIA_WHITELIST_ENABLE = 11; + public const int PRIM_MEDIA_WHITELIST = 12; + public const int PRIM_MEDIA_PERMS_INTERACT = 13; + public const int PRIM_MEDIA_PERMS_CONTROL = 14; + + public const int PRIM_MEDIA_CONTROLS_STANDARD = 0; + public const int PRIM_MEDIA_CONTROLS_MINI = 1; + + public const int PRIM_MEDIA_PERM_NONE = 0; + public const int PRIM_MEDIA_PERM_OWNER = 1; + public const int PRIM_MEDIA_PERM_GROUP = 2; + public const int PRIM_MEDIA_PERM_ANYONE = 4; // Constants for default textures public const string TEXTURE_BLANK = "5748decc-f629-461c-9a36-a35a221fe21f"; From a5ad792e6c90eb9412325e636c6e4eafc4a8a91d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 19:46:23 +0100 Subject: [PATCH 056/120] implement llSetPrimMediaParams() Untested --- OpenSim/Framework/PrimitiveBaseShape.cs | 1 + .../World/Media/Moap/MoapModule.cs | 56 +++++++-- .../Framework/Interfaces/IMoapModule.cs | 14 ++- .../Framework/Scenes/SceneObjectPart.cs | 3 +- .../Shared/Api/Implementation/LSL_Api.cs | 113 +++++++++++++++++- .../Shared/Api/Runtime/LSL_Constants.cs | 12 +- 6 files changed, 185 insertions(+), 14 deletions(-) diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index 517dbf6e03..85638ca243 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -176,6 +176,7 @@ namespace OpenSim.Framework /// /// Entries to store media textures on each face /// + /// Do not change this value directly - always do it through an IMoapModule. public List Media { get; set; } public PrimitiveBaseShape() diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 9f7436714f..064047da25 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -102,16 +102,54 @@ namespace OpenSim.Region.CoreModules.Media.Moap public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { if (face < 0) - throw new ArgumentException("Face cannot be less than zero"); + throw new ArgumentException("Face cannot be less than zero"); - List media = part.Shape.Media; - - if (face > media.Count - 1) + int maxFaces = part.GetNumberOfSides() - 1; + if (face > maxFaces) throw new ArgumentException( - string.Format("Face argument was {0} but max is {1}", face, media.Count - 1)); + string.Format("Face argument was {0} but max is {1}", face, maxFaces)); - // TODO: Really need a proper copy constructor down in libopenmetaverse - return MediaEntry.FromOSD(media[face].GetOSD()); + List media = part.Shape.Media; + + if (null == media) + { + return null; + } + else + { + // TODO: Really need a proper copy constructor down in libopenmetaverse + return MediaEntry.FromOSD(media[face].GetOSD()); + } + } + + public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) + { + if (face < 0) + throw new ArgumentException("Face cannot be less than zero"); + + int maxFaces = part.GetNumberOfSides() - 1; + if (face > maxFaces) + throw new ArgumentException( + string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + + if (null == part.Shape.Media) + part.Shape.Media = new List(maxFaces); + + part.Shape.Media[face] = me; + + if (null == part.MediaUrl) + { + // TODO: We can't set the last changer until we start tracking which cap we give to which agent id + part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + } + else + { + string rawVersion = part.MediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + } + + part.ScheduleFullUpdate(); } /// @@ -140,7 +178,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap throw new Exception( string.Format( "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}", - omm.Request.GetType())); + omm.Request.GetType())); } /// @@ -233,7 +271,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); - // Arguably we don't need to send a full update to the avatar that just changed the texture. + // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); return string.Empty; diff --git a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs index 4447f3475e..31bb6d87c0 100644 --- a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs @@ -39,9 +39,19 @@ namespace OpenSim.Region.Framework.Interfaces /// /// Get the media entry for a given prim face. /// + /// A copy of the media entry is returned rather than the original, so this can be altered at will without + /// affecting the original settings. /// /// /// - MediaEntry GetMediaEntry(SceneObjectPart part, int face); - } + MediaEntry GetMediaEntry(SceneObjectPart part, int face); + + /// + /// Set the media entry for a given prim face. + /// + /// + /// + /// + void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me); + } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index a8c20ddf32..e6a1696bca 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -978,8 +978,9 @@ namespace OpenSim.Region.Framework.Scenes } /// - /// Used for media on a prim + /// Used for media on a prim. /// + /// Do not change this value directly - always do it through an IMoapModule. public string MediaUrl { get diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index e18e33eb29..4d5719314c 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -7816,7 +7816,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api // LSL Spec http://wiki.secondlife.com/wiki/LlGetPrimMediaParams says to fail silently if face is invalid // TODO: Need to correctly handle case where a face has no media (which gives back an empty list). // Assuming silently fail means give back an empty list. Ideally, need to check this. - if (face < 0 || face > m_host.Shape.Media.Count - 1) + if (face < 0 || face > m_host.GetNumberOfSides() - 1) return new LSL_List(); return GetLinkPrimMediaParams(face, rules); @@ -7830,6 +7830,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api MediaEntry me = module.GetMediaEntry(m_host, face); + // As per http://wiki.secondlife.com/wiki/LlGetPrimMediaParams + if (null == me) + return new LSL_List(); + LSL_List res = new LSL_List(); for (int i = 0; i < rules.Length; i++) @@ -7912,6 +7916,113 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return res; } + public LSL_Integer llSetPrimMediaParams(int face, LSL_List rules) + { + m_host.AddScriptLPS(1); + ScriptSleep(1000); + + // LSL Spec http://wiki.secondlife.com/wiki/LlSetPrimMediaParams says to fail silently if face is invalid + // Assuming silently fail means sending back LSL_STATUS_OK. Ideally, need to check this. + // Don't perform the media check directly + if (face < 0 || face > m_host.GetNumberOfSides() - 1) + return ScriptBaseClass.LSL_STATUS_OK; + + return SetPrimMediaParams(face, rules); + } + + public LSL_Integer SetPrimMediaParams(int face, LSL_List rules) + { + IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); + if (null == module) + throw new Exception("Media on a prim functions not available"); + + MediaEntry me = module.GetMediaEntry(m_host, face); + if (null == me) + me = new MediaEntry(); + + int i = 0; + + while (i < rules.Length - 1) + { + int code = rules.GetLSLIntegerItem(i++); + + switch (code) + { + case ScriptBaseClass.PRIM_MEDIA_ALT_IMAGE_ENABLE: + me.EnableAlterntiveImage = (rules.GetLSLIntegerItem(i++) != 0 ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_CONTROLS: + int v = rules.GetLSLIntegerItem(i++); + if (ScriptBaseClass.PRIM_MEDIA_CONTROLS_STANDARD == v) + me.Controls = MediaControls.Standard; + else + me.Controls = MediaControls.Mini; + break; + + case ScriptBaseClass.PRIM_MEDIA_CURRENT_URL: + me.CurrentURL = rules.GetLSLStringItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_HOME_URL: + me.HomeURL = rules.GetLSLStringItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_LOOP: + me.AutoLoop = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_PLAY: + me.AutoPlay = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_SCALE: + me.AutoScale = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_AUTO_ZOOM: + me.AutoZoom = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_FIRST_CLICK_INTERACT: + me.InteractOnFirstClick = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_WIDTH_PIXELS: + me.Width = (int)rules.GetLSLIntegerItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_HEIGHT_PIXELS: + me.Height = (int)rules.GetLSLIntegerItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST_ENABLE: + me.EnableWhiteList = (ScriptBaseClass.TRUE == rules.GetLSLIntegerItem(i++) ? true : false); + break; + + case ScriptBaseClass.PRIM_MEDIA_WHITELIST: + string[] rawWhiteListUrls = rules.GetLSLStringItem(i++).ToString().Split(new char[] { ',' }); + List whiteListUrls = new List(); + Array.ForEach( + rawWhiteListUrls, delegate(string rawUrl) { whiteListUrls.Add(rawUrl.Trim()); }); + me.WhiteList = whiteListUrls.ToArray(); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_INTERACT: + me.InteractPermissions = (MediaPermission)(byte)(int)rules.GetLSLIntegerItem(i++); + break; + + case ScriptBaseClass.PRIM_MEDIA_PERMS_CONTROL: + me.ControlPermissions = (MediaPermission)(byte)(int)rules.GetLSLIntegerItem(i++); + break; + } + } + + module.SetMediaEntry(m_host, face, me); + + return ScriptBaseClass.LSL_STATUS_OK; + } + // // // The .NET definition of base 64 is: diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index 9a64f8c40f..6ef786a49a 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -518,7 +518,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public static readonly vector TOUCH_INVALID_TEXCOORD = new vector(-1.0, -1.0, 0.0); public static readonly vector TOUCH_INVALID_VECTOR = ZERO_VECTOR; - // constants for llGetPrimMediaParams + // constants for llGetPrimMediaParams/llSetPrimMediaParams public const int PRIM_MEDIA_ALT_IMAGE_ENABLE = 0; public const int PRIM_MEDIA_CONTROLS = 1; public const int PRIM_MEDIA_CURRENT_URL = 2; @@ -542,6 +542,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public const int PRIM_MEDIA_PERM_OWNER = 1; public const int PRIM_MEDIA_PERM_GROUP = 2; public const int PRIM_MEDIA_PERM_ANYONE = 4; + + // extra constants for llSetPrimMediaParams + public static readonly LSLInteger LSL_STATUS_OK = new LSLInteger(0); + public static readonly LSLInteger LSL_STATUS_MALFORMED_PARAMS = new LSLInteger(1000); + public static readonly LSLInteger LSL_STATUS_TYPE_MISMATCH = new LSLInteger(1001); + public static readonly LSLInteger LSL_STATUS_BOUNDS_ERROR = new LSLInteger(1002); + public static readonly LSLInteger LSL_STATUS_NOT_FOUND = new LSLInteger(1003); + public static readonly LSLInteger LSL_STATUS_NOT_SUPPORTED = new LSLInteger(1004); + public static readonly LSLInteger LSL_STATUS_INTERNAL_ERROR = new LSLInteger(1999); + public static readonly LSLInteger LSL_STATUS_WHITELIST_FAILED = new LSLInteger(2001); // Constants for default textures public const string TEXTURE_BLANK = "5748decc-f629-461c-9a36-a35a221fe21f"; From cfb79cd411d433b82129de6f3a54db4e8a86fab4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 19:48:20 +0100 Subject: [PATCH 057/120] minor: correct a few method names and change accessability --- .../ScriptEngine/Shared/Api/Implementation/LSL_Api.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 4d5719314c..f5089aaae9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -7819,10 +7819,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api if (face < 0 || face > m_host.GetNumberOfSides() - 1) return new LSL_List(); - return GetLinkPrimMediaParams(face, rules); + return GetPrimMediaParams(face, rules); } - public LSL_List GetLinkPrimMediaParams(int face, LSL_List rules) + private LSL_List GetPrimMediaParams(int face, LSL_List rules) { IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); if (null == module) @@ -7930,7 +7930,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return SetPrimMediaParams(face, rules); } - public LSL_Integer SetPrimMediaParams(int face, LSL_List rules) + private LSL_Integer SetPrimMediaParams(int face, LSL_List rules) { IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); if (null == module) From 74bc4f61fd65e67d41918e73390b3fb48df63dd2 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 20:15:10 +0100 Subject: [PATCH 058/120] factor out common face parameter checking code --- .../World/Media/Moap/MoapModule.cs | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 064047da25..9dd46ebf89 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -101,13 +101,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { - if (face < 0) - throw new ArgumentException("Face cannot be less than zero"); - - int maxFaces = part.GetNumberOfSides() - 1; - if (face > maxFaces) - throw new ArgumentException( - string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + CheckFaceParam(part, face); List media = part.Shape.Media; @@ -124,16 +118,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) { - if (face < 0) - throw new ArgumentException("Face cannot be less than zero"); - - int maxFaces = part.GetNumberOfSides() - 1; - if (face > maxFaces) - throw new ArgumentException( - string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + CheckFaceParam(part, face); if (null == part.Shape.Media) - part.Shape.Media = new List(maxFaces); + part.Shape.Media = new List(part.GetNumberOfSides()); part.Shape.Media[face] = me; @@ -187,8 +175,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// protected string HandleObjectMediaRequest(ObjectMediaRequest omr) - { - //UUID primId = (UUID)osdParams["object_id"]; + { UUID primId = omr.PrimID; SceneObjectPart part = m_scene.GetSceneObjectPart(primId); @@ -200,23 +187,6 @@ namespace OpenSim.Region.CoreModules.Media.Moap primId, m_scene.RegionInfo.RegionName); return string.Empty; } - - /* - int faces = part.GetNumberOfSides(); - m_log.DebugFormat("[MOAP]: Faces [{0}] for [{1}]", faces, primId); - - MediaEntry[] media = new MediaEntry[faces]; - for (int i = 0; i < faces; i++) - { - MediaEntry me = new MediaEntry(); - me.HomeURL = "google.com"; - me.CurrentURL = "google.com"; - me.AutoScale = true; - //me.Height = 300; - //me.Width = 240; - media[i] = me; - } - */ if (null == part.Shape.Media) return string.Empty; @@ -330,6 +300,22 @@ namespace OpenSim.Region.CoreModules.Media.Moap // TODO: Persist in database return string.Empty; - } + } + + /// + /// Check that the face number is valid for the given prim. + /// + /// + /// + protected void CheckFaceParam(SceneObjectPart part, int face) + { + if (face < 0) + throw new ArgumentException("Face cannot be less than zero"); + + int maxFaces = part.GetNumberOfSides() - 1; + if (face > maxFaces) + throw new ArgumentException( + string.Format("Face argument was {0} but max is {1}", face, maxFaces)); + } } } \ No newline at end of file From c76e2ce250fc2d0d7880fa1125628f0a13d53f9a Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 20:18:10 +0100 Subject: [PATCH 059/120] factor out common code for updating the media url --- .../World/Media/Moap/MoapModule.cs | 68 ++++++++----------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 9dd46ebf89..242ff6c40b 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -124,19 +124,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media = new List(part.GetNumberOfSides()); part.Shape.Media[face] = me; - - if (null == part.MediaUrl) - { - // TODO: We can't set the last changer until we start tracking which cap we give to which agent id - part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; - } - else - { - string rawVersion = part.MediaUrl.Substring(5, 10); - int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); - } - + UpdateMediaUrl(part); part.ScheduleFullUpdate(); } @@ -223,23 +211,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } - m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); + m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); part.Shape.Media = new List(omu.FaceMedia); - if (null == part.MediaUrl) - { - // TODO: We can't set the last changer until we start tracking which cap we give to which agent id - part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; - } - else - { - string rawVersion = part.MediaUrl.Substring(5, 10); - int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); - } - - m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); + UpdateMediaUrl(part); // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); @@ -267,7 +243,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap UUID primId = omn.PrimID; - SceneObjectPart part = m_scene.GetSceneObjectPart(primId); + SceneObjectPart part = m_scene.GetSceneObjectPart(primId); if (null == part) { @@ -284,20 +260,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap MediaEntry me = part.Shape.Media[omn.Face]; me.CurrentURL = omn.URL; - string oldMediaUrl = part.MediaUrl; + UpdateMediaUrl(part); - // TODO: refactor into common method - string rawVersion = oldMediaUrl.Substring(5, 10); - int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); - - m_log.DebugFormat( - "[MOAP]: Updating media url in prim {0} {1} from [{2}] to [{3}]", - part.Name, part.UUID, oldMediaUrl, part.MediaUrl); - - part.ScheduleFullUpdate(); - - // TODO: Persist in database + part.ScheduleFullUpdate(); return string.Empty; } @@ -317,5 +282,26 @@ namespace OpenSim.Region.CoreModules.Media.Moap throw new ArgumentException( string.Format("Face argument was {0} but max is {1}", face, maxFaces)); } + + /// + /// Update the media url of the given part + /// + /// + protected void UpdateMediaUrl(SceneObjectPart part) + { + if (null == part.MediaUrl) + { + // TODO: We can't set the last changer until we start tracking which cap we give to which agent id + part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + } + else + { + string rawVersion = part.MediaUrl.Substring(5, 10); + int version = int.Parse(rawVersion); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + } + + m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); + } } } \ No newline at end of file From 43f480864bcca2990b809568eaed04bd27cecf60 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 21:33:27 +0100 Subject: [PATCH 060/120] fix problem persisting when only one face had a media texture --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 10 ++++++++-- .../Region/CoreModules/World/Media/Moap/MoapModule.cs | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index fc9667b6d0..51f4ceff27 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -1867,7 +1867,10 @@ namespace OpenSim.Data.SQLite List mediaEntries = new List(); foreach (OSD osdMe in osdMeArray) - mediaEntries.Add(MediaEntry.FromOSD(osdMe)); + { + MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); + mediaEntries.Add(me); + } s.Media = mediaEntries; } @@ -1918,7 +1921,10 @@ namespace OpenSim.Data.SQLite OSDArray meArray = new OSDArray(); foreach (MediaEntry me in s.Media) - meArray.Add(me.GetOSD()); + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 242ff6c40b..93a1ae867c 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -212,6 +212,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap } m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); + +// for (int i = 0; i < omu.FaceMedia.Length; i++) +// { +// MediaEntry me = omu.FaceMedia[i]; +// string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); +// m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); +// } part.Shape.Media = new List(omu.FaceMedia); From d1a879927ccc97e90f19d916a6a0e579dd7b4a56 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 21:43:36 +0100 Subject: [PATCH 061/120] fix issue with GetMediaEntry if the face requested wasn't set to a media texture --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 93a1ae867c..43953a7eff 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -112,7 +112,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap else { // TODO: Really need a proper copy constructor down in libopenmetaverse - return MediaEntry.FromOSD(media[face].GetOSD()); + MediaEntry me = media[face]; + return (null == me ? null : MediaEntry.FromOSD(me.GetOSD())); } } From 39a38c4901f00eae15c2eed38191944f8f419f8b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 22:00:45 +0100 Subject: [PATCH 062/120] implement llClearPrimMedia() untested --- .../World/Media/Moap/MoapModule.cs | 5 +++++ .../Framework/Interfaces/IMoapModule.cs | 10 ++++++++++ .../Shared/Api/Implementation/LSL_Api.cs | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 43953a7eff..8699800704 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -129,6 +129,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.ScheduleFullUpdate(); } + public void ClearMediaEntry(SceneObjectPart part, int face) + { + SetMediaEntry(part, face, null); + } + /// /// Sets or gets per face media textures. /// diff --git a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs index 31bb6d87c0..24b6860742 100644 --- a/OpenSim/Region/Framework/Interfaces/IMoapModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IMoapModule.cs @@ -53,5 +53,15 @@ namespace OpenSim.Region.Framework.Interfaces /// /// void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me); + + /// + /// Clear the media entry for a given prim face. + /// + /// + /// This is the equivalent of setting a media entry of null + /// + /// + /// /param> + void ClearMediaEntry(SceneObjectPart part, int face); } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index f5089aaae9..8903c3b6c2 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -8023,6 +8023,26 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return ScriptBaseClass.LSL_STATUS_OK; } + public LSL_Integer llClearPrimMedia(LSL_Integer face) + { + m_host.AddScriptLPS(1); + ScriptSleep(1000); + + // LSL Spec http://wiki.secondlife.com/wiki/LlClearPrimMedia says to fail silently if face is invalid + // Assuming silently fail means sending back LSL_STATUS_OK. Ideally, need to check this. + // FIXME: Don't perform the media check directly + if (face < 0 || face > m_host.GetNumberOfSides() - 1) + return ScriptBaseClass.LSL_STATUS_OK; + + IMoapModule module = m_ScriptEngine.World.RequestModuleInterface(); + if (null == module) + throw new Exception("Media on a prim functions not available"); + + module.ClearMediaEntry(m_host, face); + + return ScriptBaseClass.LSL_STATUS_OK; + } + // // // The .NET definition of base 64 is: From eb5e39d6efed2516883c729eded38454d05aec68 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 12 Jul 2010 22:27:11 +0100 Subject: [PATCH 063/120] Fire CHANGED_MEDIA event if a media texture is set or cleared --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 5 +++++ OpenSim/Region/Framework/Scenes/SceneObjectPart.cs | 3 ++- .../Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 8699800704..8bccab4604 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -127,6 +127,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media[face] = me; UpdateMediaUrl(part); part.ScheduleFullUpdate(); + part.TriggerScriptChangedEvent(Changed.MEDIA); } public void ClearMediaEntry(SceneObjectPart part, int face) @@ -233,6 +234,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); + part.TriggerScriptChangedEvent(Changed.MEDIA); + return string.Empty; } @@ -277,6 +280,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.ScheduleFullUpdate(); + part.TriggerScriptChangedEvent(Changed.MEDIA); + return string.Empty; } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index e6a1696bca..444a23938c 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -58,7 +58,8 @@ namespace OpenSim.Region.Framework.Scenes OWNER = 128, REGION_RESTART = 256, REGION = 512, - TELEPORT = 1024 + TELEPORT = 1024, + MEDIA = 2048 } // I don't really know where to put this except here. diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index 6ef786a49a..06f9426fb9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -276,6 +276,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase public const int CHANGED_REGION_RESTART = 256; public const int CHANGED_REGION = 512; public const int CHANGED_TELEPORT = 1024; + public const int CHANGED_MEDIA = 2048; public const int CHANGED_ANIMATION = 16384; public const int TYPE_INVALID = 0; public const int TYPE_INTEGER = 1; From e5615d3a9b013efef5a76a92eb398b331370bae4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 19:28:07 +0100 Subject: [PATCH 064/120] discard an object media update message if it tries to set more media textures than the prim has faces --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 8bccab4604..378ff4a915 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -227,6 +227,14 @@ namespace OpenSim.Region.CoreModules.Media.Moap // m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); // } + if (omu.FaceMedia.Length > part.GetNumberOfSides()) + { + m_log.WarnFormat( + "[MOAP]: Received {0} media entries from client for prim {1} {2} but this prim has only {3} faces. Dropping request.", + omu.FaceMedia.Length, part.Name, part.UUID, part.GetNumberOfSides()); + return string.Empty; + } + part.Shape.Media = new List(omu.FaceMedia); UpdateMediaUrl(part); From 51b208e96c881bd322b3769b843f0ebae3c09a84 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 23:19:45 +0100 Subject: [PATCH 065/120] implement prim media control permissions serverside in order to stop bad clients --- .../World/Media/Moap/MoapModule.cs | 87 ++++++++++++++----- .../World/Permissions/PermissionsModule.cs | 43 ++++++++- .../Framework/Scenes/Scene.Permissions.cs | 21 ++++- 3 files changed, 127 insertions(+), 24 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 378ff4a915..d7aede91dd 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -58,8 +58,21 @@ namespace OpenSim.Region.CoreModules.Media.Moap public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + /// + /// The scene to which this module is attached + /// protected Scene m_scene; + /// + /// Track the ObjectMedia capabilities given to users + /// + protected Dictionary m_omCapUsers = new Dictionary(); + + /// + /// Track the ObjectMediaUpdate capabilities given to users + /// + protected Dictionary m_omuCapUsers = new Dictionary(); + public void Initialise(IConfigSource config) { // TODO: Add config switches to enable/disable this module @@ -87,16 +100,27 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat( "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); - // We do receive a post to ObjectMedia when a new avatar enters the region - though admittedly this is the - // avatar that set the texture in the first place. - // Even though we're registering for POST we're going to get GETS and UPDATES too - caps.RegisterHandler( - "ObjectMedia", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaMessage)); + string omCapUrl = "/CAPS/" + UUID.Random(); - // We do get these posts when the url has been changed. - // Even though we're registering for POST we're going to get GETS and UPDATES too - caps.RegisterHandler( - "ObjectMediaNavigate", new RestStreamHandler("POST", "/CAPS/" + UUID.Random(), HandleObjectMediaNavigateMessage)); + lock (m_omCapUsers) + { + m_omCapUsers[omCapUrl] = agentID; + + // Even though we're registering for POST we're going to get GETS and UPDATES too + caps.RegisterHandler( + "ObjectMedia", new RestStreamHandler("POST", omCapUrl, HandleObjectMediaMessage)); + } + + string omuCapUrl = "/CAPS/" + UUID.Random(); + + lock (m_omuCapUsers) + { + m_omuCapUsers[omuCapUrl] = agentID; + + // Even though we're registering for POST we're going to get GETS and UPDATES too + caps.RegisterHandler( + "ObjectMediaNavigate", new RestStreamHandler("POST", omuCapUrl, HandleObjectMediaNavigateMessage)); + } } public MediaEntry GetMediaEntry(SceneObjectPart part, int face) @@ -147,7 +171,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected string HandleObjectMediaMessage( string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { - m_log.DebugFormat("[MOAP]: Got ObjectMedia raw request [{0}]", request); + m_log.DebugFormat("[MOAP]: Got ObjectMedia path [{0}], raw request [{1}]", path, request); OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); ObjectMediaMessage omm = new ObjectMediaMessage(); @@ -156,7 +180,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (omm.Request is ObjectMediaRequest) return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest); else if (omm.Request is ObjectMediaUpdate) - return HandleObjectMediaUpdate(omm.Request as ObjectMediaUpdate); + return HandleObjectMediaUpdate(path, omm.Request as ObjectMediaUpdate); throw new Exception( string.Format( @@ -165,7 +189,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap } /// - /// Handle a request for media textures + /// Handle a fetch request for media textures /// /// /// @@ -202,9 +226,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// Handle an update of media textures. /// + /// Path on which this request was made /// /param> /// - protected string HandleObjectMediaUpdate(ObjectMediaUpdate omu) + protected string HandleObjectMediaUpdate(string path, ObjectMediaUpdate omu) { UUID primId = omu.PrimID; @@ -216,16 +241,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", primId, m_scene.RegionInfo.RegionName); return string.Empty; - } + } m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); -// for (int i = 0; i < omu.FaceMedia.Length; i++) -// { -// MediaEntry me = omu.FaceMedia[i]; -// string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); -// m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); -// } + for (int i = 0; i < omu.FaceMedia.Length; i++) + { + MediaEntry me = omu.FaceMedia[i]; + string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); + m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); + } if (omu.FaceMedia.Length > part.GetNumberOfSides()) { @@ -235,7 +260,27 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } - part.Shape.Media = new List(omu.FaceMedia); + List media = part.Shape.Media; + + if (null == media) + { + part.Shape.Media = new List(omu.FaceMedia); + } + else + { + // We need to go through the media textures one at a time to make sure that we have permission + // to change them + UUID agentId = default(UUID); + + lock (m_omCapUsers) + agentId = m_omCapUsers[path]; + + for (int i = 0; i < media.Count; i++) + { + if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) + media[i] = omu.FaceMedia[i]; + } + } UpdateMediaUrl(part); diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 69b247c350..358ea59216 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -164,6 +164,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions private Dictionary GrantYP = new Dictionary(); private IFriendsModule m_friendsModule; private IGroupsModule m_groupsModule; + private IMoapModule m_moapModule; #endregion @@ -248,6 +249,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_scene.Permissions.OnDeleteUserInventory += CanDeleteUserInventory; //NOT YET IMPLEMENTED m_scene.Permissions.OnTeleport += CanTeleport; //NOT YET IMPLEMENTED + + m_scene.Permissions.OnControlPrimMedia += CanControlPrimMedia; m_scene.AddCommand(this, "bypass permissions", "bypass permissions ", @@ -393,6 +396,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (m_groupsModule == null) m_log.Warn("[PERMISSIONS]: Groups module not found, group permissions will not work"); + + m_moapModule = m_scene.RequestModuleInterface(); } public void Close() @@ -1893,5 +1898,41 @@ namespace OpenSim.Region.CoreModules.World.Permissions } return(false); } + + private bool CanControlPrimMedia(UUID agentID, UUID primID, int face) + { + if (null == m_moapModule) + return false; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primID); + if (null == part) + return false; + + MediaEntry me = m_moapModule.GetMediaEntry(part, face); + + // If there is no existing media entry then it can be controlled (in this context, created). + if (null == me) + return true; + + if (IsAdministrator(agentID)) + return true; + + if ((me.ControlPermissions & MediaPermission.Anyone) == MediaPermission.Anyone) + return true; + + if ((me.ControlPermissions & MediaPermission.Owner) == MediaPermission.Owner) + { + if (agentID == part.OwnerID) + return true; + } + + if ((me.ControlPermissions & MediaPermission.Group) == MediaPermission.Group) + { + if (IsGroupMember(part.GroupID, agentID, 0)) + return true; + } + + return false; + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs index 7dab04fbd2..70af978719 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -81,6 +81,7 @@ namespace OpenSim.Region.Framework.Scenes public delegate bool CopyUserInventoryHandler(UUID itemID, UUID userID); public delegate bool DeleteUserInventoryHandler(UUID itemID, UUID userID); public delegate bool TeleportHandler(UUID userID, Scene scene); + public delegate bool ControlPrimMediaHandler(UUID userID, UUID primID, int face); #endregion public class ScenePermissions @@ -139,6 +140,7 @@ namespace OpenSim.Region.Framework.Scenes public event CopyUserInventoryHandler OnCopyUserInventory; public event DeleteUserInventoryHandler OnDeleteUserInventory; public event TeleportHandler OnTeleport; + public event ControlPrimMediaHandler OnControlPrimMedia; #endregion #region Object Permission Checks @@ -947,5 +949,20 @@ namespace OpenSim.Region.Framework.Scenes } return true; } + + public bool CanControlPrimMedia(UUID userID, UUID primID, int face) + { + ControlPrimMediaHandler handler = OnControlPrimMedia; + if (handler != null) + { + Delegate[] list = handler.GetInvocationList(); + foreach (ControlPrimMediaHandler h in list) + { + if (h(userID, primID, face) == false) + return false; + } + } + return true; + } } -} +} \ No newline at end of file From a9101feb107e5d210c93df5ee3119d827a1c8320 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 23:46:49 +0100 Subject: [PATCH 066/120] factor out soon to be common media permissions check code --- .../World/Permissions/PermissionsModule.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 358ea59216..2344e9681f 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -1914,25 +1914,30 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (null == me) return true; + return GenericPrimMediaPermission(part, agentID, me.ControlPermissions); + } + + private bool GenericPrimMediaPermission(SceneObjectPart part, UUID agentID, MediaPermission perms) + { if (IsAdministrator(agentID)) return true; - if ((me.ControlPermissions & MediaPermission.Anyone) == MediaPermission.Anyone) + if ((perms & MediaPermission.Anyone) == MediaPermission.Anyone) return true; - if ((me.ControlPermissions & MediaPermission.Owner) == MediaPermission.Owner) + if ((perms & MediaPermission.Owner) == MediaPermission.Owner) { if (agentID == part.OwnerID) return true; } - if ((me.ControlPermissions & MediaPermission.Group) == MediaPermission.Group) + if ((perms & MediaPermission.Group) == MediaPermission.Group) { if (IsGroupMember(part.GroupID, agentID, 0)) return true; } - return false; + return false; } } } \ No newline at end of file From ee6cd884c9732b492675e043fe318ffcdfecc45d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 13 Jul 2010 23:58:19 +0100 Subject: [PATCH 067/120] implement serverside checks for media texture navigation in order to stop naughty clients --- .../World/Media/Moap/MoapModule.cs | 20 ++++++++++++------ .../World/Permissions/PermissionsModule.cs | 21 ++++++++++++++++++- .../Framework/Scenes/Scene.Permissions.cs | 19 ++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index d7aede91dd..09786ec096 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -245,12 +245,12 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); - for (int i = 0; i < omu.FaceMedia.Length; i++) - { - MediaEntry me = omu.FaceMedia[i]; - string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); - m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); - } +// for (int i = 0; i < omu.FaceMedia.Length; i++) +// { +// MediaEntry me = omu.FaceMedia[i]; +// string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); +// m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); +// } if (omu.FaceMedia.Length > part.GetNumberOfSides()) { @@ -322,6 +322,14 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } + UUID agentId = default(UUID); + + lock (m_omuCapUsers) + agentId = m_omuCapUsers[path]; + + if (!m_scene.Permissions.CanInteractWithPrimMedia(agentId, part.UUID, omn.Face)) + return string.Empty; + m_log.DebugFormat( "[MOAP]: Updating media entry for face {0} on prim {1} {2} to {3}", omn.Face, part.Name, part.UUID, omn.URL); diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 2344e9681f..3a690afc73 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -251,6 +251,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_scene.Permissions.OnTeleport += CanTeleport; //NOT YET IMPLEMENTED m_scene.Permissions.OnControlPrimMedia += CanControlPrimMedia; + m_scene.Permissions.OnInteractWithPrimMedia += CanInteractWithPrimMedia; m_scene.AddCommand(this, "bypass permissions", "bypass permissions ", @@ -1915,7 +1916,25 @@ namespace OpenSim.Region.CoreModules.World.Permissions return true; return GenericPrimMediaPermission(part, agentID, me.ControlPermissions); - } + } + + private bool CanInteractWithPrimMedia(UUID agentID, UUID primID, int face) + { + if (null == m_moapModule) + return false; + + SceneObjectPart part = m_scene.GetSceneObjectPart(primID); + if (null == part) + return false; + + MediaEntry me = m_moapModule.GetMediaEntry(part, face); + + // If there is no existing media entry then it can be controlled (in this context, created). + if (null == me) + return true; + + return GenericPrimMediaPermission(part, agentID, me.InteractPermissions); + } private bool GenericPrimMediaPermission(SceneObjectPart part, UUID agentID, MediaPermission perms) { diff --git a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs index 70af978719..003390047f 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Permissions.cs @@ -82,6 +82,7 @@ namespace OpenSim.Region.Framework.Scenes public delegate bool DeleteUserInventoryHandler(UUID itemID, UUID userID); public delegate bool TeleportHandler(UUID userID, Scene scene); public delegate bool ControlPrimMediaHandler(UUID userID, UUID primID, int face); + public delegate bool InteractWithPrimMediaHandler(UUID userID, UUID primID, int face); #endregion public class ScenePermissions @@ -141,6 +142,7 @@ namespace OpenSim.Region.Framework.Scenes public event DeleteUserInventoryHandler OnDeleteUserInventory; public event TeleportHandler OnTeleport; public event ControlPrimMediaHandler OnControlPrimMedia; + public event InteractWithPrimMediaHandler OnInteractWithPrimMedia; #endregion #region Object Permission Checks @@ -963,6 +965,21 @@ namespace OpenSim.Region.Framework.Scenes } } return true; - } + } + + public bool CanInteractWithPrimMedia(UUID userID, UUID primID, int face) + { + InteractWithPrimMediaHandler handler = OnInteractWithPrimMedia; + if (handler != null) + { + Delegate[] list = handler.GetInvocationList(); + foreach (InteractWithPrimMediaHandler h in list) + { + if (h(userID, primID, face) == false) + return false; + } + } + return true; + } } } \ No newline at end of file From cf7573c8fda9fb33a0b46bc7a7bd893e974d2561 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 14 Jul 2010 00:16:37 +0100 Subject: [PATCH 068/120] implement code to deregister users on DeregisterCaps --- .../World/Media/Moap/MoapModule.cs | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 09786ec096..ce4e921b85 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -64,15 +64,25 @@ namespace OpenSim.Region.CoreModules.Media.Moap protected Scene m_scene; /// - /// Track the ObjectMedia capabilities given to users + /// Track the ObjectMedia capabilities given to users keyed by path /// protected Dictionary m_omCapUsers = new Dictionary(); /// - /// Track the ObjectMediaUpdate capabilities given to users + /// Track the ObjectMedia capabilities given to users keyed by agent. Lock m_omCapUsers to manipulate. + /// + protected Dictionary m_omCapUrls = new Dictionary(); + + /// + /// Track the ObjectMediaUpdate capabilities given to users keyed by path /// protected Dictionary m_omuCapUsers = new Dictionary(); + /// + /// Track the ObjectMediaUpdate capabilities given to users keyed by agent. Lock m_omuCapUsers to manipulate + /// + protected Dictionary m_omuCapUrls = new Dictionary(); + public void Initialise(IConfigSource config) { // TODO: Add config switches to enable/disable this module @@ -88,11 +98,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void RegionLoaded(Scene scene) { m_scene.EventManager.OnRegisterCaps += RegisterCaps; + m_scene.EventManager.OnDeregisterCaps += DeregisterCaps; } public void Close() { m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps; } public void RegisterCaps(UUID agentID, Caps caps) @@ -105,6 +117,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap lock (m_omCapUsers) { m_omCapUsers[omCapUrl] = agentID; + m_omCapUrls[agentID] = omCapUrl; // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( @@ -116,12 +129,30 @@ namespace OpenSim.Region.CoreModules.Media.Moap lock (m_omuCapUsers) { m_omuCapUsers[omuCapUrl] = agentID; + m_omuCapUrls[agentID] = omuCapUrl; // Even though we're registering for POST we're going to get GETS and UPDATES too caps.RegisterHandler( "ObjectMediaNavigate", new RestStreamHandler("POST", omuCapUrl, HandleObjectMediaNavigateMessage)); } - } + } + + public void DeregisterCaps(UUID agentID, Caps caps) + { + lock (m_omCapUsers) + { + string path = m_omCapUrls[agentID]; + m_omCapUrls.Remove(agentID); + m_omCapUsers.Remove(path); + } + + lock (m_omuCapUsers) + { + string path = m_omuCapUrls[agentID]; + m_omuCapUrls.Remove(agentID); + m_omuCapUsers.Remove(path); + } + } public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { From 049ccba8d3b71583f9f1aa7d13ca4a7f60501871 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 14 Jul 2010 23:26:24 +0100 Subject: [PATCH 069/120] fix previous media interact serverside checking. perform very basic serverside url whitelist checks at the moment, only checking for the exact name prefix is implemented for some reason, whitelists are not persisting this commit also fixes a very recent problem where setting any media texture parameters after the initial configuration would not work --- .../World/Media/Moap/MoapModule.cs | 71 +++++++++++++++++-- .../World/Permissions/PermissionsModule.cs | 30 ++++++-- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index ce4e921b85..3c546c48e3 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -91,6 +91,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void AddRegion(Scene scene) { m_scene = scene; + m_scene.RegisterModuleInterface(this); } public void RemoveRegion(Scene scene) {} @@ -156,20 +157,28 @@ namespace OpenSim.Region.CoreModules.Media.Moap public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { + MediaEntry me = null; + CheckFaceParam(part, face); List media = part.Shape.Media; if (null == media) { - return null; + me = null; } else - { + { + me = media[face]; + // TODO: Really need a proper copy constructor down in libopenmetaverse - MediaEntry me = media[face]; - return (null == me ? null : MediaEntry.FromOSD(me.GetOSD())); + if (me != null) + me = MediaEntry.FromOSD(me.GetOSD()); } + +// m_log.DebugFormat("[MOAP]: GetMediaEntry for {0} face {1} found {2}", part.Name, face, me); + + return me; } public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) @@ -295,6 +304,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (null == media) { + m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name); part.Shape.Media = new List(omu.FaceMedia); } else @@ -309,7 +319,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap for (int i = 0; i < media.Count; i++) { if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) + { media[i] = omu.FaceMedia[i]; +// m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name); + } } } @@ -362,10 +375,31 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; m_log.DebugFormat( - "[MOAP]: Updating media entry for face {0} on prim {1} {2} to {3}", + "[MOAP]: Received request to update media entry for face {0} on prim {1} {2} to {3}", omn.Face, part.Name, part.UUID, omn.URL); + // If media has never been set for this prim, then just return. + if (null == part.Shape.Media) + return string.Empty; + MediaEntry me = part.Shape.Media[omn.Face]; + + // Do the same if media has not been set up for a specific face + if (null == me) + return string.Empty; + + if (me.EnableWhiteList) + { + if (!CheckUrlAgainstWhitelist(omn.URL, me.WhiteList)) + { + m_log.DebugFormat( + "[MOAP]: Blocking change of face {0} on prim {1} {2} to {3} since it's not on the enabled whitelist", + omn.Face, part.Name, part.UUID, omn.URL); + + return string.Empty; + } + } + me.CurrentURL = omn.URL; UpdateMediaUrl(part); @@ -413,5 +447,32 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); } + + /// + /// Check the given url against the given whitelist. + /// + /// + /// + /// true if the url matches an entry on the whitelist, false otherwise + protected bool CheckUrlAgainstWhitelist(string url, string[] whitelist) + { + foreach (string rawWlUrl in whitelist) + { + string wlUrl = rawWlUrl; + + if (!wlUrl.StartsWith("http://")) + wlUrl = "http://" + wlUrl; + + m_log.DebugFormat("[MOAP]: Checking whitelist URL {0}", wlUrl); + + if (url.StartsWith(wlUrl)) + { + m_log.DebugFormat("[MOAP]: Whitelist url {0} matches requested url {1}", wlUrl, url); + return true; + } + } + + return false; + } } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 3a690afc73..7f6f851520 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -178,7 +178,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions string permissionModules = myConfig.GetString("permissionmodules", "DefaultPermissionsModule"); - List modules=new List(permissionModules.Split(',')); + List modules = new List(permissionModules.Split(',')); if (!modules.Contains("DefaultPermissionsModule")) return; @@ -399,6 +399,10 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_log.Warn("[PERMISSIONS]: Groups module not found, group permissions will not work"); m_moapModule = m_scene.RequestModuleInterface(); + + // This log line will be commented out when no longer required for debugging + if (m_moapModule == null) + m_log.Warn("[PERMISSIONS]: Media on a prim module not found, media on a prim permissions will not work"); } public void Close() @@ -1901,7 +1905,11 @@ namespace OpenSim.Region.CoreModules.World.Permissions } private bool CanControlPrimMedia(UUID agentID, UUID primID, int face) - { + { +// m_log.DebugFormat( +// "[PERMISSONS]: Performing CanControlPrimMedia check with agentID {0}, primID {1}, face {2}", +// agentID, primID, face); + if (null == m_moapModule) return false; @@ -1909,17 +1917,25 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (null == part) return false; - MediaEntry me = m_moapModule.GetMediaEntry(part, face); + MediaEntry me = m_moapModule.GetMediaEntry(part, face); // If there is no existing media entry then it can be controlled (in this context, created). if (null == me) return true; + m_log.DebugFormat( + "[PERMISSIONS]: Checking CanControlPrimMedia for {0} on {1} face {2} with control permissions {3}", + agentID, primID, face, me.ControlPermissions); + return GenericPrimMediaPermission(part, agentID, me.ControlPermissions); } private bool CanInteractWithPrimMedia(UUID agentID, UUID primID, int face) { +// m_log.DebugFormat( +// "[PERMISSONS]: Performing CanInteractWithPrimMedia check with agentID {0}, primID {1}, face {2}", +// agentID, primID, face); + if (null == m_moapModule) return false; @@ -1933,13 +1949,17 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (null == me) return true; + m_log.DebugFormat( + "[PERMISSIONS]: Checking CanInteractWithPrimMedia for {0} on {1} face {2} with interact permissions {3}", + agentID, primID, face, me.InteractPermissions); + return GenericPrimMediaPermission(part, agentID, me.InteractPermissions); } private bool GenericPrimMediaPermission(SceneObjectPart part, UUID agentID, MediaPermission perms) { - if (IsAdministrator(agentID)) - return true; +// if (IsAdministrator(agentID)) +// return true; if ((perms & MediaPermission.Anyone) == MediaPermission.Anyone) return true; From fc76ce0f466c7dfa2328c08e590c86460b068140 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 14 Jul 2010 23:48:24 +0100 Subject: [PATCH 070/120] fix bug where prim persistence would fail if media had never been set --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index 51f4ceff27..7acbd22105 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -1919,14 +1919,17 @@ namespace OpenSim.Data.SQLite row["Texture"] = s.TextureEntry; row["ExtraParams"] = s.ExtraParams; - OSDArray meArray = new OSDArray(); - foreach (MediaEntry me in s.Media) + if (null != s.Media) { - OSD osd = (null == me ? new OSD() : me.GetOSD()); - meArray.Add(osd); + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in s.Media) + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } + + row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } - - row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } /// From dce7307aa20f49276139708077e329835829d8c2 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 00:15:23 +0100 Subject: [PATCH 071/120] properly expose prim media LSL functions to scripts scripts using these functions should now compile but I don't know how well the methods themselves work yet llSetPrimMedia(), at least, appears to have problems when a current url is set for a face that doesn't yet have a texture --- .../CoreModules/World/Media/Moap/MoapModule.cs | 2 +- .../ScriptEngine/Shared/Api/Interface/ILSL_Api.cs | 3 +++ .../ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 3c546c48e3..4bbac6e3a1 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -186,7 +186,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap CheckFaceParam(part, face); if (null == part.Shape.Media) - part.Shape.Media = new List(part.GetNumberOfSides()); + part.Shape.Media = new List(new MediaEntry[part.GetNumberOfSides()]); part.Shape.Media[face] = me; UpdateMediaUrl(part); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs index cba46a36c7..561e3b3213 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs @@ -62,6 +62,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces void llBreakLink(int linknum); LSL_Integer llCeil(double f); void llClearCameraParams(); + LSL_Integer llClearPrimMedia(LSL_Integer face); void llCloseRemoteDataChannel(string channel); LSL_Float llCloud(LSL_Vector offset); void llCollisionFilter(string name, string id, int accept); @@ -162,6 +163,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces LSL_List llGetParcelPrimOwners(LSL_Vector pos); LSL_Integer llGetPermissions(); LSL_Key llGetPermissionsKey(); + LSL_List llGetPrimMediaParams(int face, LSL_List rules); LSL_Vector llGetPos(); LSL_List llGetPrimitiveParams(LSL_List rules); LSL_Integer llGetRegionAgentCount(); @@ -332,6 +334,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces void llSetParcelMusicURL(string url); void llSetPayPrice(int price, LSL_List quick_pay_buttons); void llSetPos(LSL_Vector pos); + LSL_Integer llSetPrimMediaParams(int face, LSL_List rules); void llSetPrimitiveParams(LSL_List rules); void llSetLinkPrimitiveParamsFast(int linknum, LSL_List rules); void llSetPrimURL(string url); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs index 3339995f66..451163fe99 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs @@ -1832,5 +1832,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase { return m_LSL_Functions.llXorBase64StringsCorrect(str1, str2); } + + public LSL_List llGetPrimMediaParams(int face, LSL_List rules) + { + return m_LSL_Functions.llGetPrimMediaParams(face, rules); + } + + public LSL_Integer llSetPrimMediaParams(int face, LSL_List rules) + { + return m_LSL_Functions.llSetPrimMediaParams(face, rules); + } + + public LSL_Integer llClearPrimMedia(LSL_Integer face) + { + return m_LSL_Functions.llClearPrimMedia(face); + } } } From 0edabffb7d6213db041ba3d01fee157dabcbcda5 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 21:14:55 +0100 Subject: [PATCH 072/120] Implement * end wildcard for whitelist urls --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 4bbac6e3a1..c24e6d55ef 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -460,6 +460,10 @@ namespace OpenSim.Region.CoreModules.Media.Moap { string wlUrl = rawWlUrl; + // Deal with a line-ending wildcard + if (wlUrl.EndsWith("*")) + wlUrl = wlUrl.Remove(wlUrl.Length - 1); + if (!wlUrl.StartsWith("http://")) wlUrl = "http://" + wlUrl; @@ -467,7 +471,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (url.StartsWith(wlUrl)) { - m_log.DebugFormat("[MOAP]: Whitelist url {0} matches requested url {1}", wlUrl, url); + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, url); return true; } } From 664cbe2357cdb05202c01f1575f1e9f08273904e Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 21:40:44 +0100 Subject: [PATCH 073/120] refactor: simplify current whitelist url checking by using System.Uri --- .../CoreModules/World/Media/Moap/MoapModule.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index c24e6d55ef..c8e72ca895 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -451,11 +451,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// /// Check the given url against the given whitelist. /// - /// + /// /// /// true if the url matches an entry on the whitelist, false otherwise - protected bool CheckUrlAgainstWhitelist(string url, string[] whitelist) + protected bool CheckUrlAgainstWhitelist(string rawUrl, string[] whitelist) { + Uri url = new Uri(rawUrl); + foreach (string rawWlUrl in whitelist) { string wlUrl = rawWlUrl; @@ -464,14 +466,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap if (wlUrl.EndsWith("*")) wlUrl = wlUrl.Remove(wlUrl.Length - 1); - if (!wlUrl.StartsWith("http://")) - wlUrl = "http://" + wlUrl; - m_log.DebugFormat("[MOAP]: Checking whitelist URL {0}", wlUrl); + + string urlToMatch = url.Authority + url.AbsolutePath; - if (url.StartsWith(wlUrl)) + if (urlToMatch.StartsWith(wlUrl)) { - m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, url); + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, urlToMatch); return true; } } From cd985ab71b489d63d9f1033b5159f207ab614b18 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 21:51:57 +0100 Subject: [PATCH 074/120] Handle checking of line starting "*" wildcard for whitelist patterns A line starting * can only be applied to the domain, not the path --- .../World/Media/Moap/MoapModule.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index c8e72ca895..cbe9af2864 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -458,22 +458,36 @@ namespace OpenSim.Region.CoreModules.Media.Moap { Uri url = new Uri(rawUrl); - foreach (string rawWlUrl in whitelist) + foreach (string origWlUrl in whitelist) { - string wlUrl = rawWlUrl; + string wlUrl = origWlUrl; // Deal with a line-ending wildcard if (wlUrl.EndsWith("*")) wlUrl = wlUrl.Remove(wlUrl.Length - 1); - m_log.DebugFormat("[MOAP]: Checking whitelist URL {0}", wlUrl); + m_log.DebugFormat("[MOAP]: Checking whitelist URL pattern {0}", origWlUrl); - string urlToMatch = url.Authority + url.AbsolutePath; - - if (urlToMatch.StartsWith(wlUrl)) + // Handle a line starting wildcard slightly differently since this can only match the domain, not the path + if (wlUrl.StartsWith("*")) { - m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", wlUrl, urlToMatch); - return true; + wlUrl = wlUrl.Substring(1); + + if (url.Host.Contains(wlUrl)) + { + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); + return true; + } + } + else + { + string urlToMatch = url.Authority + url.AbsolutePath; + + if (urlToMatch.StartsWith(wlUrl)) + { + m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); + return true; + } } } From f872a2af116e5e9cdf80efd2313818200b204a04 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 15 Jul 2010 23:28:36 +0100 Subject: [PATCH 075/120] add missing regionstore migration file for new fields. D'oh! this should enable persistence now --- OpenSim/Data/SQLite/Resources/020_RegionStore.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 OpenSim/Data/SQLite/Resources/020_RegionStore.sql diff --git a/OpenSim/Data/SQLite/Resources/020_RegionStore.sql b/OpenSim/Data/SQLite/Resources/020_RegionStore.sql new file mode 100644 index 0000000000..39cb752979 --- /dev/null +++ b/OpenSim/Data/SQLite/Resources/020_RegionStore.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE prims ADD COLUMN MediaURL varchar(255); +ALTER TABLE primshapes ADD COLUMN Media TEXT; + +COMMIT; \ No newline at end of file From 60c52ac0c44a395e3bd3b3ca761ad71ba10906c2 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 21 Jul 2010 14:25:21 +0100 Subject: [PATCH 076/120] start adding user ids to the media urls --- .../World/Media/Moap/MoapModule.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index cbe9af2864..6755df70d6 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -189,7 +189,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap part.Shape.Media = new List(new MediaEntry[part.GetNumberOfSides()]); part.Shape.Media[face] = me; - UpdateMediaUrl(part); + UpdateMediaUrl(part, UUID.Zero); part.ScheduleFullUpdate(); part.TriggerScriptChangedEvent(Changed.MEDIA); } @@ -300,6 +300,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap return string.Empty; } + UUID agentId = default(UUID); + + lock (m_omCapUsers) + agentId = m_omCapUsers[path]; + List media = part.Shape.Media; if (null == media) @@ -310,12 +315,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap else { // We need to go through the media textures one at a time to make sure that we have permission - // to change them - UUID agentId = default(UUID); - - lock (m_omCapUsers) - agentId = m_omCapUsers[path]; - + // to change them for (int i = 0; i < media.Count; i++) { if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) @@ -326,7 +326,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } - UpdateMediaUrl(part); + UpdateMediaUrl(part, agentId); // Arguably, we could avoid sending a full update to the avatar that just changed the texture. part.ScheduleFullUpdate(); @@ -402,13 +402,13 @@ namespace OpenSim.Region.CoreModules.Media.Moap me.CurrentURL = omn.URL; - UpdateMediaUrl(part); + UpdateMediaUrl(part, agentId); part.ScheduleFullUpdate(); part.TriggerScriptChangedEvent(Changed.MEDIA); - return string.Empty; + return OSDParser.SerializeLLSDXmlString(new OSD()); } /// @@ -431,12 +431,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// Update the media url of the given part /// /// - protected void UpdateMediaUrl(SceneObjectPart part) + /// + /// The id to attach to this update. Normally, this is the user that changed the + /// texture + /// + protected void UpdateMediaUrl(SceneObjectPart part, UUID updateId) { if (null == part.MediaUrl) { // TODO: We can't set the last changer until we start tracking which cap we give to which agent id - part.MediaUrl = "x-mv:0000000000/" + UUID.Zero; + part.MediaUrl = "x-mv:0000000000/" + updateId; } else { From b06308e1d30022f1a240fdc7ca875cc7277b10ea Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 21 Jul 2010 17:12:43 +0100 Subject: [PATCH 077/120] Properly set TextureEntry.MediaFlags when a media texture is set Media flags is cleared via a direct TextureEntry update from the client. If the clearing leaves no media textures on the prim, then a CAP ObjectMediaUpdate is not received. If there are still media textures present then one is received. This change fixes drag-and-drop on Windows (and Mac?) clients. It may also fix problems with clearing and then subsequently setting new media textures. --- .../World/Media/Moap/MoapModule.cs | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 6755df70d6..4818546f9c 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -311,19 +311,59 @@ namespace OpenSim.Region.CoreModules.Media.Moap { m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name); part.Shape.Media = new List(omu.FaceMedia); + + for (int i = 0; i < omu.FaceMedia.Length; i++) + { + if (omu.FaceMedia[i] != null) + { + // FIXME: Race condition here since some other texture entry manipulator may overwrite/get + // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry + // directly. + Primitive.TextureEntry te = part.Shape.Textures; + Primitive.TextureEntryFace face = te.CreateFace((uint)i); + face.MediaFlags = true; + part.Shape.Textures = te; + m_log.DebugFormat( + "[MOAP]: Media flags for face {0} is {1}", + i, part.Shape.Textures.FaceTextures[i].MediaFlags); + } + } } else - { + { // We need to go through the media textures one at a time to make sure that we have permission // to change them + + // FIXME: Race condition here since some other texture entry manipulator may overwrite/get + // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry + // directly. + Primitive.TextureEntry te = part.Shape.Textures; + for (int i = 0; i < media.Count; i++) - { + { if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) - { + { media[i] = omu.FaceMedia[i]; + + // When a face is cleared this is done by setting the MediaFlags in the TextureEntry via a normal + // texture update, so we don't need to worry about clearing MediaFlags here. + if (null == media[i]) + continue; + + Primitive.TextureEntryFace face = te.CreateFace((uint)i); + face.MediaFlags = true; + + m_log.DebugFormat( + "[MOAP]: Media flags for face {0} is {1}", + i, face.MediaFlags); // m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name); } } + + part.Shape.Textures = te; + +// for (int i2 = 0; i2 < part.Shape.Textures.FaceTextures.Length; i2++) +// m_log.DebugFormat("[MOAP]: FaceTexture[{0}] is {1}", i2, part.Shape.Textures.FaceTextures[i2]); } UpdateMediaUrl(part, agentId); From d764c95d09c2141e1d4459dd608e95fb1f9d7546 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 21 Jul 2010 19:32:05 +0100 Subject: [PATCH 078/120] also add avatar id to an updated media url - not just new ones --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 4818546f9c..0130ff95fa 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -486,7 +486,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap { string rawVersion = part.MediaUrl.Substring(5, 10); int version = int.Parse(rawVersion); - part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, UUID.Zero); + part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, updateId); } m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); From afdbeba4e46f631b320b75bd304197959e650c2e Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 19:56:55 +0100 Subject: [PATCH 079/120] Put a wrapper around the media texture region serialization THIS WILL BREAK EXISTING MEDIA TEXTURE PERSISTENCE. Please delete your existing sqlite databases if you are experimenting with this branch. This wrapper will make it easier to maintain compatibility if the media texture data evolves. This will also make it easier to store non-sl media texture data. --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 58 ++++++++++++++++++------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index 7acbd22105..b5644195da 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -31,6 +31,7 @@ using System.Data; using System.Drawing; using System.IO; using System.Reflection; +using System.Xml; using log4net; using Mono.Data.Sqlite; using OpenMetaverse; @@ -1862,17 +1863,26 @@ namespace OpenSim.Data.SQLite if (!(row["Media"] is System.DBNull)) { - string rawMeArray = (string)row["Media"]; - OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(rawMeArray); - - List mediaEntries = new List(); - foreach (OSD osdMe in osdMeArray) + using (StringReader sr = new StringReader((string)row["Media"])) { - MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); - mediaEntries.Add(me); + using (XmlTextReader xtr = new XmlTextReader(sr)) + { + xtr.ReadStartElement("osmedia"); + + OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); + + List mediaEntries = new List(); + foreach (OSD osdMe in osdMeArray) + { + MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); + mediaEntries.Add(me); + } + + s.Media = mediaEntries; + + xtr.ReadEndElement(); + } } - - s.Media = mediaEntries; } return s; @@ -1921,14 +1931,32 @@ namespace OpenSim.Data.SQLite if (null != s.Media) { - OSDArray meArray = new OSDArray(); - foreach (MediaEntry me in s.Media) + using (StringWriter sw = new StringWriter()) { - OSD osd = (null == me ? new OSD() : me.GetOSD()); - meArray.Add(osd); + using (XmlTextWriter xtw = new XmlTextWriter(sw)) + { + xtw.WriteStartElement("osmedia"); + xtw.WriteAttributeString("type", "sl"); + xtw.WriteAttributeString("major_version", "0"); + xtw.WriteAttributeString("minor_version", "1"); + + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in s.Media) + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } + + xtw.WriteStartElement("osdata"); + xtw.WriteRaw(OSDParser.SerializeLLSDXmlString(meArray)); + xtw.WriteEndElement(); + + xtw.WriteEndElement(); + + xtw.Flush(); + row["Media"] = sw.ToString(); + } } - - row["Media"] = OSDParser.SerializeLLSDXmlString(meArray); } } From 586ae0f6a07358f8367c4f916bff9fd688a43aa3 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 20:13:26 +0100 Subject: [PATCH 080/120] Add EventManager.OnSceneObjectLoaded() for future use. This is fired immediately after a scene object is loaded from storage. --- .../World/Media/Moap/MoapModule.cs | 19 +++++++---- .../Region/Framework/Scenes/EventManager.cs | 32 +++++++++++++++++-- OpenSim/Region/Framework/Scenes/Scene.cs | 4 ++- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 0130ff95fa..2771492f6d 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -98,17 +98,19 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void RegionLoaded(Scene scene) { - m_scene.EventManager.OnRegisterCaps += RegisterCaps; - m_scene.EventManager.OnDeregisterCaps += DeregisterCaps; + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; + m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; + m_scene.EventManager.OnSceneObjectLoaded += OnSceneObjectLoaded; } public void Close() { - m_scene.EventManager.OnRegisterCaps -= RegisterCaps; - m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps; + m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; + m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; + m_scene.EventManager.OnSceneObjectLoaded -= OnSceneObjectLoaded; } - public void RegisterCaps(UUID agentID, Caps caps) + public void OnRegisterCaps(UUID agentID, Caps caps) { m_log.DebugFormat( "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); @@ -138,7 +140,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } - public void DeregisterCaps(UUID agentID, Caps caps) + public void OnDeregisterCaps(UUID agentID, Caps caps) { lock (m_omCapUsers) { @@ -155,6 +157,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } + public void OnSceneObjectLoaded(SceneObjectGroup sog) + { + m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", sog.Name, sog.UUID); + } + public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { MediaEntry me = null; diff --git a/OpenSim/Region/Framework/Scenes/EventManager.cs b/OpenSim/Region/Framework/Scenes/EventManager.cs index 9db2e4135b..0b1f593df5 100644 --- a/OpenSim/Region/Framework/Scenes/EventManager.cs +++ b/OpenSim/Region/Framework/Scenes/EventManager.cs @@ -331,9 +331,16 @@ namespace OpenSim.Region.Framework.Scenes /// the avatarID is UUID.Zero (I know, this doesn't make much sense but now it's historical). public delegate void Attach(uint localID, UUID itemID, UUID avatarID); public event Attach OnAttach; + + public delegate void SceneObjectDelegate(SceneObjectGroup so); + + /// + /// Called immediately after an object is loaded from storage. + /// + public event SceneObjectDelegate OnSceneObjectLoaded; public delegate void RegionUp(GridRegion region); - public event RegionUp OnRegionUp; + public event RegionUp OnRegionUp; public class MoneyTransferArgs : EventArgs { @@ -2013,5 +2020,26 @@ namespace OpenSim.Region.Framework.Scenes } } } + + public void TriggerOnSceneObjectLoaded(SceneObjectGroup so) + { + SceneObjectDelegate handler = OnSceneObjectLoaded; + if (handler != null) + { + foreach (SceneObjectDelegate d in handler.GetInvocationList()) + { + try + { + d(so); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerOnSceneObjectLoaded failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 9141d447ef..e8dce080e2 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -1887,9 +1887,11 @@ namespace OpenSim.Region.Framework.Scenes foreach (SceneObjectGroup group in PrimsFromDB) { + EventManager.TriggerOnSceneObjectLoaded(group); + if (group.RootPart == null) { - m_log.ErrorFormat("[SCENE] Found a SceneObjectGroup with m_rootPart == null and {0} children", + m_log.ErrorFormat("[SCENE]: Found a SceneObjectGroup with m_rootPart == null and {0} children", group.Children == null ? 0 : group.Children.Count); } From b51b2efdc8059548e1da7ac13ee858673b71592f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 20:36:28 +0100 Subject: [PATCH 081/120] Add EventManager.OnSceneObjectPreSave() for future use. This is triggered immediately before a copy of the group is persisted to storage --- .../World/Media/Moap/MoapModule.cs | 11 +++++- .../Region/Framework/Scenes/EventManager.cs | 39 +++++++++++++++++-- .../Framework/Scenes/SceneObjectGroup.cs | 1 + 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 2771492f6d..263ee579e7 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -101,6 +101,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded += OnSceneObjectLoaded; + m_scene.EventManager.OnSceneObjectPreSave += OnSceneObjectPreSave; } public void Close() @@ -108,6 +109,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded -= OnSceneObjectLoaded; + m_scene.EventManager.OnSceneObjectPreSave -= OnSceneObjectPreSave; } public void OnRegisterCaps(UUID agentID, Caps caps) @@ -157,11 +159,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap } } - public void OnSceneObjectLoaded(SceneObjectGroup sog) + public void OnSceneObjectLoaded(SceneObjectGroup so) { - m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", sog.Name, sog.UUID); + m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", so.Name, so.UUID); } + public void OnSceneObjectPreSave(SceneObjectGroup persistingSo, SceneObjectGroup originalSo) + { + m_log.DebugFormat("[MOAP]: OnSceneObjectPreSave fired for {0} {1}", persistingSo.Name, persistingSo.UUID); + } + public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { MediaEntry me = null; diff --git a/OpenSim/Region/Framework/Scenes/EventManager.cs b/OpenSim/Region/Framework/Scenes/EventManager.cs index 0b1f593df5..3b8d7276da 100644 --- a/OpenSim/Region/Framework/Scenes/EventManager.cs +++ b/OpenSim/Region/Framework/Scenes/EventManager.cs @@ -330,14 +330,26 @@ namespace OpenSim.Region.Framework.Scenes /// If the object is being attached, then the avatarID will be present. If the object is being detached then /// the avatarID is UUID.Zero (I know, this doesn't make much sense but now it's historical). public delegate void Attach(uint localID, UUID itemID, UUID avatarID); - public event Attach OnAttach; - - public delegate void SceneObjectDelegate(SceneObjectGroup so); + public event Attach OnAttach; /// /// Called immediately after an object is loaded from storage. /// public event SceneObjectDelegate OnSceneObjectLoaded; + public delegate void SceneObjectDelegate(SceneObjectGroup so); + + /// + /// Called immediately before an object is saved to storage. + /// + /// + /// The scene object being persisted. + /// This is actually a copy of the original scene object so changes made here will be saved to storage but will not be kept in memory. + /// + /// + /// The original scene object being persisted. Changes here will stay in memory but will not be saved to storage on this save. + /// + public event SceneObjectPreSaveDelegate OnSceneObjectPreSave; + public delegate void SceneObjectPreSaveDelegate(SceneObjectGroup persistingSo, SceneObjectGroup originalSo); public delegate void RegionUp(GridRegion region); public event RegionUp OnRegionUp; @@ -2040,6 +2052,27 @@ namespace OpenSim.Region.Framework.Scenes } } } + } + + public void TriggerOnSceneObjectPreSave(SceneObjectGroup persistingSo, SceneObjectGroup originalSo) + { + SceneObjectPreSaveDelegate handler = OnSceneObjectPreSave; + if (handler != null) + { + foreach (SceneObjectPreSaveDelegate d in handler.GetInvocationList()) + { + try + { + d(persistingSo, originalSo); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerOnSceneObjectPreSave failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } } } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 17275d0d19..c2f9117220 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -1479,6 +1479,7 @@ namespace OpenSim.Region.Framework.Scenes backup_group.RootPart.ParticleSystem = RootPart.ParticleSystem; HasGroupChanged = false; + m_scene.EventManager.TriggerOnSceneObjectPreSave(backup_group, this); datastore.StoreObject(backup_group, m_scene.RegionInfo.RegionID); backup_group.ForEachPart(delegate(SceneObjectPart part) From 412fed975fc0b28c3af111be89a1bcb4aaa05a9b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 21:09:54 +0100 Subject: [PATCH 082/120] relocate serialization code from SQLiteRegionData to MoapModule using load and save events. This is better modularity. It also allows MoapModule to be replaced with some other media module that may behave completely differently in the future. Remaining non-modularity: PrimitiveBaseShape needs explicit Media and MediaRaw fields. MediaRaw is required in order to shuttle the pre-serialization data back and forth from the database layer. The database also needs to know about MediaRaw though not about Media. IMO, it would be extremely nice to remove these hard codings but this is a bridge too far at the present time. --- OpenSim/Data/SQLite/SQLiteRegionData.cs | 55 +-------------- OpenSim/Framework/PrimitiveBaseShape.cs | 6 ++ .../World/Media/Moap/MoapModule.cs | 70 ++++++++++++++++++- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/OpenSim/Data/SQLite/SQLiteRegionData.cs b/OpenSim/Data/SQLite/SQLiteRegionData.cs index b5644195da..f63d35e3bd 100644 --- a/OpenSim/Data/SQLite/SQLiteRegionData.cs +++ b/OpenSim/Data/SQLite/SQLiteRegionData.cs @@ -31,7 +31,6 @@ using System.Data; using System.Drawing; using System.IO; using System.Reflection; -using System.Xml; using log4net; using Mono.Data.Sqlite; using OpenMetaverse; @@ -1862,28 +1861,7 @@ namespace OpenSim.Data.SQLite s.ExtraParams = (byte[]) row["ExtraParams"]; if (!(row["Media"] is System.DBNull)) - { - using (StringReader sr = new StringReader((string)row["Media"])) - { - using (XmlTextReader xtr = new XmlTextReader(sr)) - { - xtr.ReadStartElement("osmedia"); - - OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); - - List mediaEntries = new List(); - foreach (OSD osdMe in osdMeArray) - { - MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); - mediaEntries.Add(me); - } - - s.Media = mediaEntries; - - xtr.ReadEndElement(); - } - } - } + s.MediaRaw = (string)row["Media"]; return s; } @@ -1928,36 +1906,7 @@ namespace OpenSim.Data.SQLite row["Texture"] = s.TextureEntry; row["ExtraParams"] = s.ExtraParams; - - if (null != s.Media) - { - using (StringWriter sw = new StringWriter()) - { - using (XmlTextWriter xtw = new XmlTextWriter(sw)) - { - xtw.WriteStartElement("osmedia"); - xtw.WriteAttributeString("type", "sl"); - xtw.WriteAttributeString("major_version", "0"); - xtw.WriteAttributeString("minor_version", "1"); - - OSDArray meArray = new OSDArray(); - foreach (MediaEntry me in s.Media) - { - OSD osd = (null == me ? new OSD() : me.GetOSD()); - meArray.Add(osd); - } - - xtw.WriteStartElement("osdata"); - xtw.WriteRaw(OSDParser.SerializeLLSDXmlString(meArray)); - xtw.WriteEndElement(); - - xtw.WriteEndElement(); - - xtw.Flush(); - row["Media"] = sw.ToString(); - } - } - } + row["Media"] = s.MediaRaw; } /// diff --git a/OpenSim/Framework/PrimitiveBaseShape.cs b/OpenSim/Framework/PrimitiveBaseShape.cs index 85638ca243..03ddb3394e 100644 --- a/OpenSim/Framework/PrimitiveBaseShape.cs +++ b/OpenSim/Framework/PrimitiveBaseShape.cs @@ -173,6 +173,12 @@ namespace OpenSim.Framework } } + /// + /// Raw media data suitable for serialization operations. This should only ever be used by an IMoapModule. + /// + [XmlIgnore] + public string MediaRaw { get; set; } + /// /// Entries to store media textures on each face /// diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 263ee579e7..0e03318643 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -32,6 +32,7 @@ using System.Collections.Specialized; using System.Reflection; using System.IO; using System.Web; +using System.Xml; using log4net; using Mono.Addins; using Nini.Config; @@ -46,6 +47,7 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Media.Moap @@ -162,12 +164,76 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void OnSceneObjectLoaded(SceneObjectGroup so) { m_log.DebugFormat("[MOAP]: OnSceneObjectLoaded fired for {0} {1}", so.Name, so.UUID); + + so.ForEachPart(OnSceneObjectPartLoaded); } public void OnSceneObjectPreSave(SceneObjectGroup persistingSo, SceneObjectGroup originalSo) { - m_log.DebugFormat("[MOAP]: OnSceneObjectPreSave fired for {0} {1}", persistingSo.Name, persistingSo.UUID); - } + m_log.DebugFormat("[MOAP]: OnSceneObjectPreSave fired for {0} {1}", persistingSo.Name, persistingSo.UUID); + + persistingSo.ForEachPart(OnSceneObjectPartPreSave); + } + + protected void OnSceneObjectPartLoaded(SceneObjectPart part) + { + if (null == part.Shape.MediaRaw) + return; + + using (StringReader sr = new StringReader(part.Shape.MediaRaw)) + { + using (XmlTextReader xtr = new XmlTextReader(sr)) + { + xtr.ReadStartElement("osmedia"); + + OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); + + List mediaEntries = new List(); + foreach (OSD osdMe in osdMeArray) + { + MediaEntry me = (osdMe is OSDMap ? MediaEntry.FromOSD(osdMe) : new MediaEntry()); + mediaEntries.Add(me); + } + + xtr.ReadEndElement(); + + part.Shape.Media = mediaEntries; + } + } + } + + protected void OnSceneObjectPartPreSave(SceneObjectPart part) + { + if (null == part.Shape.Media) + return; + + using (StringWriter sw = new StringWriter()) + { + using (XmlTextWriter xtw = new XmlTextWriter(sw)) + { + xtw.WriteStartElement("osmedia"); + xtw.WriteAttributeString("type", "sl"); + xtw.WriteAttributeString("major_version", "0"); + xtw.WriteAttributeString("minor_version", "1"); + + OSDArray meArray = new OSDArray(); + foreach (MediaEntry me in part.Shape.Media) + { + OSD osd = (null == me ? new OSD() : me.GetOSD()); + meArray.Add(osd); + } + + xtw.WriteStartElement("osdata"); + xtw.WriteRaw(OSDParser.SerializeLLSDXmlString(meArray)); + xtw.WriteEndElement(); + + xtw.WriteEndElement(); + + xtw.Flush(); + part.Shape.MediaRaw = sw.ToString(); + } + } + } public MediaEntry GetMediaEntry(SceneObjectPart part, int face) { From 4d23749241eb002c3815aa18789e8c3ffd44bfc1 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 21:41:39 +0100 Subject: [PATCH 083/120] provide config option for media on a prim --- .../World/Media/Moap/MoapModule.cs | 21 +++++++++++++++++-- .../World/Permissions/PermissionsModule.cs | 4 ++-- bin/OpenSim.ini.example | 5 +++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 0e03318643..7afeeec014 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -60,6 +60,11 @@ namespace OpenSim.Region.CoreModules.Media.Moap public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + /// + /// Is this module enabled? + /// + protected bool m_isEnabled = true; + /// /// The scene to which this module is attached /// @@ -85,13 +90,19 @@ namespace OpenSim.Region.CoreModules.Media.Moap /// protected Dictionary m_omuCapUrls = new Dictionary(); - public void Initialise(IConfigSource config) + public void Initialise(IConfigSource configSource) { - // TODO: Add config switches to enable/disable this module + IConfig config = configSource.Configs["MediaOnAPrim"]; + + if (config != null && !config.GetBoolean("Enabled", false)) + m_isEnabled = false; } public void AddRegion(Scene scene) { + if (!m_isEnabled) + return; + m_scene = scene; m_scene.RegisterModuleInterface(this); } @@ -100,6 +111,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void RegionLoaded(Scene scene) { + if (!m_isEnabled) + return; + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded += OnSceneObjectLoaded; @@ -108,6 +122,9 @@ namespace OpenSim.Region.CoreModules.Media.Moap public void Close() { + if (!m_isEnabled) + return; + m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; m_scene.EventManager.OnSceneObjectLoaded -= OnSceneObjectLoaded; diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index 7f6f851520..982ac523a4 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -401,8 +401,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions m_moapModule = m_scene.RequestModuleInterface(); // This log line will be commented out when no longer required for debugging - if (m_moapModule == null) - m_log.Warn("[PERMISSIONS]: Media on a prim module not found, media on a prim permissions will not work"); +// if (m_moapModule == null) +// m_log.Warn("[PERMISSIONS]: Media on a prim module not found, media on a prim permissions will not work"); } public void Close() diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 5dcc601481..54c013a769 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -1245,6 +1245,11 @@ ; enabled=false +[MediaOnAPrim] + ; Enable media on a prim facilities + Enabled = true; + + ;; ;; These are defaults that are overwritten below in [Architecture]. ;; These defaults allow OpenSim to work out of the box with From 849fc0483f02984abf03994967168d6c38174732 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 23:19:31 +0100 Subject: [PATCH 084/120] add mysql support for media on a prim --- OpenSim/Data/MySQL/MySQLLegacyRegionData.cs | 8 ++++++-- OpenSim/Data/MySQL/Resources/RegionStore.migrations | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs b/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs index bfeae12385..f17e8aebe2 100644 --- a/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs +++ b/OpenSim/Data/MySQL/MySQLLegacyRegionData.cs @@ -222,7 +222,7 @@ namespace OpenSim.Data.MySQL "PathTaperX, PathTaperY, PathTwist, " + "PathTwistBegin, ProfileBegin, ProfileEnd, " + "ProfileCurve, ProfileHollow, Texture, " + - "ExtraParams, State) values (?UUID, " + + "ExtraParams, State, Media) values (?UUID, " + "?Shape, ?ScaleX, ?ScaleY, ?ScaleZ, " + "?PCode, ?PathBegin, ?PathEnd, " + "?PathScaleX, ?PathScaleY, " + @@ -233,7 +233,7 @@ namespace OpenSim.Data.MySQL "?PathTwistBegin, ?ProfileBegin, " + "?ProfileEnd, ?ProfileCurve, " + "?ProfileHollow, ?Texture, ?ExtraParams, " + - "?State)"; + "?State, ?Media)"; FillShapeCommand(cmd, prim); @@ -1700,6 +1700,9 @@ namespace OpenSim.Data.MySQL s.ExtraParams = (byte[])row["ExtraParams"]; s.State = (byte)(int)row["State"]; + + if (!(row["Media"] is System.DBNull)) + s.MediaRaw = (string)row["Media"]; return s; } @@ -1743,6 +1746,7 @@ namespace OpenSim.Data.MySQL cmd.Parameters.AddWithValue("Texture", s.TextureEntry); cmd.Parameters.AddWithValue("ExtraParams", s.ExtraParams); cmd.Parameters.AddWithValue("State", s.State); + cmd.Parameters.AddWithValue("Media", s.MediaRaw); } public void StorePrimInventory(UUID primID, ICollection items) diff --git a/OpenSim/Data/MySQL/Resources/RegionStore.migrations b/OpenSim/Data/MySQL/Resources/RegionStore.migrations index 3f644f903f..13697043f1 100644 --- a/OpenSim/Data/MySQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MySQL/Resources/RegionStore.migrations @@ -1,4 +1,4 @@ - + :VERSION 1 #--------------------- BEGIN; @@ -800,3 +800,10 @@ BEGIN; ALTER TABLE `regionwindlight` CHANGE COLUMN `cloud_scroll_x` `cloud_scroll_x` FLOAT(4,2) NOT NULL DEFAULT '0.20' AFTER `cloud_detail_density`, CHANGE COLUMN `cloud_scroll_y` `cloud_scroll_y` FLOAT(4,2) NOT NULL DEFAULT '0.01' AFTER `cloud_scroll_x_lock`; COMMIT; +:VERSION 35 #--------------------- +-- Added post 0.7 + +BEGIN; +ALTER TABLE prims ADD COLUMN MediaURL varchar(255); +ALTER TABLE primshapes ADD COLUMN Media TEXT; +COMMIT; \ No newline at end of file From 109ddd1bd5fcabf3155e579fe6eb15e07869b9b8 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 26 Jul 2010 23:26:22 +0100 Subject: [PATCH 085/120] add mssql support for media on a prim compiles but not tested. please test and correct if necessary! --- OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs | 10 +++++++--- OpenSim/Data/MSSQL/Resources/RegionStore.migrations | 9 ++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs b/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs index d6cb91f27f..e61b4d9259 100644 --- a/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs +++ b/OpenSim/Data/MSSQL/MSSQLLegacyRegionData.cs @@ -385,7 +385,7 @@ IF EXISTS (SELECT UUID FROM primshapes WHERE UUID = @UUID) PathSkew = @PathSkew, PathCurve = @PathCurve, PathRadiusOffset = @PathRadiusOffset, PathRevolutions = @PathRevolutions, PathTaperX = @PathTaperX, PathTaperY = @PathTaperY, PathTwist = @PathTwist, PathTwistBegin = @PathTwistBegin, ProfileBegin = @ProfileBegin, ProfileEnd = @ProfileEnd, ProfileCurve = @ProfileCurve, ProfileHollow = @ProfileHollow, - Texture = @Texture, ExtraParams = @ExtraParams, State = @State + Texture = @Texture, ExtraParams = @ExtraParams, State = @State, Media = @Media WHERE UUID = @UUID END ELSE @@ -394,11 +394,11 @@ ELSE primshapes ( UUID, Shape, ScaleX, ScaleY, ScaleZ, PCode, PathBegin, PathEnd, PathScaleX, PathScaleY, PathShearX, PathShearY, PathSkew, PathCurve, PathRadiusOffset, PathRevolutions, PathTaperX, PathTaperY, PathTwist, PathTwistBegin, ProfileBegin, - ProfileEnd, ProfileCurve, ProfileHollow, Texture, ExtraParams, State + ProfileEnd, ProfileCurve, ProfileHollow, Texture, ExtraParams, State, Media ) VALUES ( @UUID, @Shape, @ScaleX, @ScaleY, @ScaleZ, @PCode, @PathBegin, @PathEnd, @PathScaleX, @PathScaleY, @PathShearX, @PathShearY, @PathSkew, @PathCurve, @PathRadiusOffset, @PathRevolutions, @PathTaperX, @PathTaperY, @PathTwist, @PathTwistBegin, @ProfileBegin, - @ProfileEnd, @ProfileCurve, @ProfileHollow, @Texture, @ExtraParams, @State + @ProfileEnd, @ProfileCurve, @ProfileHollow, @Texture, @ExtraParams, @State, @Media ) END"; @@ -1180,6 +1180,9 @@ VALUES { } + if (!(shapeRow["Media"] is System.DBNull)) + baseShape.MediaRaw = (string)shapeRow["Media"]; + return baseShape; } @@ -1557,6 +1560,7 @@ VALUES parameters.Add(_Database.CreateParameter("Texture", s.TextureEntry)); parameters.Add(_Database.CreateParameter("ExtraParams", s.ExtraParams)); parameters.Add(_Database.CreateParameter("State", s.State)); + parameters.Add(_Database.CreateParameter("Media", s.MediaRaw)); return parameters.ToArray(); } diff --git a/OpenSim/Data/MSSQL/Resources/RegionStore.migrations b/OpenSim/Data/MSSQL/Resources/RegionStore.migrations index e912d646a0..e2e8cbb117 100644 --- a/OpenSim/Data/MSSQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MSSQL/Resources/RegionStore.migrations @@ -1,4 +1,4 @@ - + :VERSION 1 CREATE TABLE [dbo].[prims]( @@ -925,5 +925,12 @@ ALTER TABLE regionsettings ADD loaded_creation_datetime int NOT NULL default 0 COMMIT +:VERSION 24 +-- Added post 0.7 +BEGIN TRANSACTION +ALTER TABLE prims ADD COLUMN MediaURL varchar(255) +ALTER TABLE primshapes ADD COLUMN Media TEXT + +COMMIT \ No newline at end of file From ac542a907bb9612a0580019d645a16697fc1c3b4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 27 Jul 2010 18:59:05 +0100 Subject: [PATCH 086/120] Make MoapModule ignore non-sl media texture data --- .../CoreModules/World/Media/Moap/MoapModule.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 7afeeec014..81e4449b40 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -200,8 +200,16 @@ namespace OpenSim.Region.CoreModules.Media.Moap using (StringReader sr = new StringReader(part.Shape.MediaRaw)) { using (XmlTextReader xtr = new XmlTextReader(sr)) - { - xtr.ReadStartElement("osmedia"); + { + xtr.MoveToContent(); + + string type = xtr.GetAttribute("type"); + //m_log.DebugFormat("[MOAP]: Loaded media texture entry with type {0}", type); + + if (type != "sl") + return; + + xtr.ReadStartElement("osmedia"); OSDArray osdMeArray = (OSDArray)OSDParser.DeserializeLLSDXml(xtr.ReadInnerXml()); From 30ac67fb3d10c0cdeb82bb995b579986a4fed528 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 27 Jul 2010 20:15:43 +0100 Subject: [PATCH 087/120] make MoapModule ignore possible future media texture data that it can't handle --- OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 81e4449b40..3a86167930 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -60,6 +60,8 @@ namespace OpenSim.Region.CoreModules.Media.Moap public string Name { get { return "MoapModule"; } } public Type ReplaceableInterface { get { return null; } } + public const string MEDIA_TEXTURE_TYPE = "sl"; + /// /// Is this module enabled? /// @@ -206,7 +208,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap string type = xtr.GetAttribute("type"); //m_log.DebugFormat("[MOAP]: Loaded media texture entry with type {0}", type); - if (type != "sl") + if (type != MEDIA_TEXTURE_TYPE) return; xtr.ReadStartElement("osmedia"); @@ -237,7 +239,7 @@ namespace OpenSim.Region.CoreModules.Media.Moap using (XmlTextWriter xtw = new XmlTextWriter(sw)) { xtw.WriteStartElement("osmedia"); - xtw.WriteAttributeString("type", "sl"); + xtw.WriteAttributeString("type", MEDIA_TEXTURE_TYPE); xtw.WriteAttributeString("major_version", "0"); xtw.WriteAttributeString("minor_version", "1"); From 1996eb93b99a566a30a8ed447b3740697c18fc4d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 27 Jul 2010 21:52:50 +0100 Subject: [PATCH 088/120] Update OpenMetaverse libraries to r3287 + r3294 (removal of OpenMetaverse.Http.dll dependency) + r3378 (treat MediaPermission as a bitfield) As far as I can determine, r3287 + r3294 patch was the previous update to the OpenMetaverse libraries This change just adds r3378 to overcome problems storing media textures with certain permission combinations in inventory This is a limited change in order to isolate moap from any other possible libomv update issues An update to the forthcoming libomv 0.8.3 is expected in due course to replace this This commit also deletes OpenMetaverse.Utilities.* as it's unused (on the advice of jhurliman). --- bin/OpenMetaverse.StructuredData.XML | 666 +- bin/OpenMetaverse.StructuredData.dll | Bin 102400 -> 102400 bytes bin/OpenMetaverse.Utilities.XML | 98 - bin/OpenMetaverse.Utilities.dll | Bin 49152 -> 0 bytes bin/OpenMetaverse.XML | 47265 +++++++++++++------------ bin/OpenMetaverse.dll | Bin 1691648 -> 1691648 bytes bin/OpenMetaverse.dll.config | 14 +- bin/OpenMetaverseTypes.XML | 3809 +- bin/OpenMetaverseTypes.dll | Bin 106496 -> 106496 bytes 9 files changed, 27547 insertions(+), 24305 deletions(-) delete mode 100644 bin/OpenMetaverse.Utilities.XML delete mode 100644 bin/OpenMetaverse.Utilities.dll diff --git a/bin/OpenMetaverse.StructuredData.XML b/bin/OpenMetaverse.StructuredData.XML index 374bc25e38..cce365625e 100644 --- a/bin/OpenMetaverse.StructuredData.XML +++ b/bin/OpenMetaverse.StructuredData.XML @@ -1,333 +1,333 @@ - - - - OpenMetaverse.StructuredData - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Uses reflection to create an SDMap from all of the SD - serializable types in an object - - Class or struct containing serializable types - An SDMap holding the serialized values from the - container object - - - - Uses reflection to deserialize member variables in an object from - an SDMap - - Reference to an object to fill with deserialized - values - Serialized values to put in the target - object - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + OpenMetaverse.StructuredData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uses reflection to create an SDMap from all of the SD + serializable types in an object + + Class or struct containing serializable types + An SDMap holding the serialized values from the + container object + + + + Uses reflection to deserialize member variables in an object from + an SDMap + + Reference to an object to fill with deserialized + values + Serialized values to put in the target + object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/OpenMetaverse.StructuredData.dll b/bin/OpenMetaverse.StructuredData.dll index f4992a21ea02c6faeb35d2306f2fb4c99c1d7a63..76db137d0c53aef840d571ae9ec69e3e43d1db32 100644 GIT binary patch literal 102400 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 A)c^nh literal 102400 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 diff --git a/bin/OpenMetaverse.Utilities.XML b/bin/OpenMetaverse.Utilities.XML deleted file mode 100644 index 3fdf0044c0..0000000000 --- a/bin/OpenMetaverse.Utilities.XML +++ /dev/null @@ -1,98 +0,0 @@ - - - - OpenMetaverse.Utilities - - - - Amount of time to wait for the voice daemon to respond. - The value needs to stay relatively high because some of the calls - require the voice daemon to make remote queries before replying - - - - Does not appear to be working - - - - - - - Returns the new user ID or throws an exception containing the error code - The error codes can be found here: https://wiki.secondlife.com/wiki/RegAPIError - - New user account to create - The UUID of the new user account - - - - See https://secure-web6.secondlife.com/developers/third_party_reg/#service_create_user or - https://wiki.secondlife.com/wiki/RegAPIDoc for description - - - - - - - - - - - - - - - - - - - - - - Aims at the specified position, enters mouselook, presses and - releases the left mouse button, and leaves mouselook - - - Target to shoot at - - - - - Enters mouselook, presses and releases the left mouse button, and leaves mouselook - - - - - - A psuedo-realistic chat function that uses the typing sound and - animation, types at three characters per second, and randomly - pauses. This function will block until the message has been sent - - A reference to the client that will chat - The chat message to send - - - - A psuedo-realistic chat function that uses the typing sound and - animation, types at a given rate, and randomly pauses. This - function will block until the message has been sent - - A reference to the client that will chat - The chat message to send - The chat type (usually Normal, Whisper or Shout) - Characters per second rate for chatting - - - Unknown voice service level - - - Spatialized local chat - - - Remote multi-party chat - - - One-to-one and small group chat - - - diff --git a/bin/OpenMetaverse.Utilities.dll b/bin/OpenMetaverse.Utilities.dll deleted file mode 100644 index 04248beaea234f3700495c1e0d61e691bc71e0c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49152 zcmeHw3w&Hvwf8<}X3k6|$uvou$*XOqv?*ztq;Hy(7Mmt%8%UeBNlSUuFiocI(8-)| zX3`QuFjcH5R0Z(?_FmpsQBV;gCUXQ)@-fOSD_S*YQhArp6SNMbwe!Tzk7a=}}PkOCqcycfXcBJ~ji1TT^p zbZS0vuXgZkyS3+qJ}+Mj3GrS~Vv_o+PpI-kj2AP#NcNj3)PMQXgaDmhKC%Aoj4%$M z=<0)Ijze!3l^zmeHyL95>v6+V>WSV7Wgag1^|!8>h72% zU({%2tUi>vZx5kBGB@MxdacKFHCl<32|@1b^I($Pr|@>Y)(f%0g-#czEii3?X$wqS zVA=xH7MQlcv<0RuFl~Wp3rt(!|04^0o6gmE(b-fN0nxSzXU+XW;1vB=y=E@;iCw6P z`o+;qu@K>~K9Yy?xjr%v&@f9t(recUVU(^YceBiy;4I9{0<1Ii5geZVQL_|SeNeAm z1^GJ36XF!xIP-P3amM&I4q_ZS#^H8X0H&ps4;ao#9 zxQ3U33mm&ffNBuVH6(*;co~=xy9VY;A0?b?NCwyNG9tMem^*!xaIPU4T*J$lk*k5Z z)JF;D8j`^^ybPLNWh-a8xrStL4KIVHTh+)-KUr5YxQ3U3SYp?Jb@WlfxedwS8eRrs zj9mjBr;ifOH6(*;co~RCo*IO64avyWa2SYLo*IO64avyWa2N;_o*IO64avyWa2QyM zo*IO64avyWa2T{?m35sZ%rzu~Yj_#7gxxg==NgictKl%P9_eL@hdQ)X|Ned&r0Uv-%* zuUoOsS6yzGm-!qej@ly4N5(=+?@m@1tq2*>3}!l9cLgTdhG$#vKDt{Q|phxnnF7Et5u9oR4|AVW1ud7UxBU^FJX2^UKb#s#GdJI*^O&8~L z8tjE2rPf~aPsmicC2y{Wy5?_0%jZdVPjTpJ3i~1 zjm$=T=4TKX!UaM931c$~83dWIc{V^W6jdEM2iORrK-k2Z50}+d9m8_f3iG1YMTQu> zHLuKfYhJl^IhQK}x8_w^N4Z?PmBd}H8m+TF1CEz;NVnR9nEew5S~a+RYY2o|%(+nq z?k6P|y-?g|9WI7SzV};vBFcMw?+MV@yTXQ*AIf=GzVfc%c<(Z-OCT0-`H5kD2^61) z@}HpiJ(P3uY=1DUuer)Ub(It0oJ`J_3|{CHe94&7m;M*jjQ1rUV$FFNE4|#YrrG74 z)|>Nb@lgcfQTt?s>7pr}#AUk*CN_gT)(~%&&3M{5%iYc^sOfIU70#w=?7qT@jnRJI zJ4stj?zYa4c-lI7(0mcXZp%60l*e^gwdIR05BRG4mlavp%n*atw*WHRV5H03hV=~i zhY)$413p&d?F!;I+4isR!^+~<-G^sU#cdAWLqg(xutV6pj_NZA;G8S*J|?%WD@(*+ z