From 0e09b4a08beddb3b0239a3f088ab9a230b8f3979 Mon Sep 17 00:00:00 2001 From: Mike Mazur Date: Mon, 16 Feb 2009 02:24:57 +0000 Subject: Adding - NewAssetServer code - NewAssetServer addin manifest - example AssetServer.ini file --- .../Grid/NewAssetServer/Extensions/AuthorizeAll.cs | 78 ++ .../NewAssetServer/Extensions/BrowseFrontend.cs | 125 ++++ .../Grid/NewAssetServer/Extensions/DBConnString.cs | 78 ++ .../Extensions/NullAuthentication.cs | 68 ++ .../Grid/NewAssetServer/Extensions/NullMetrics.cs | 124 ++++ .../NewAssetServer/Extensions/OpenSimFrontend.cs | 215 ++++++ .../Extensions/OpenSimInventoryFrontend.cs | 636 ++++++++++++++++ .../Extensions/OpenSimMySQLInventory.cs | 804 +++++++++++++++++++++ .../Extensions/OpenSimMySQLStorage.cs | 311 ++++++++ .../NewAssetServer/Extensions/ReferenceFrontend.cs | 239 ++++++ .../NewAssetServer/Extensions/SimpleInventory.cs | 602 +++++++++++++++ .../NewAssetServer/Extensions/SimpleStorage.cs | 260 +++++++ .../Grid/NewAssetServer/Extensions/SimpleUtils.cs | 44 ++ 13 files changed, 3584 insertions(+) create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/AuthorizeAll.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/BrowseFrontend.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/DBConnString.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/NullAuthentication.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/NullMetrics.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/OpenSimFrontend.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/OpenSimInventoryFrontend.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLInventory.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLStorage.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/ReferenceFrontend.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/SimpleInventory.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/SimpleStorage.cs create mode 100644 OpenSim/Grid/NewAssetServer/Extensions/SimpleUtils.cs (limited to 'OpenSim/Grid/NewAssetServer/Extensions') diff --git a/OpenSim/Grid/NewAssetServer/Extensions/AuthorizeAll.cs b/OpenSim/Grid/NewAssetServer/Extensions/AuthorizeAll.cs new file mode 100644 index 0000000..f112c5e --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/AuthorizeAll.cs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * 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 ExtensionLoader; +using OpenMetaverse; + +namespace AssetServer.Extensions +{ + public class AuthorizeAll : IExtension, IAuthorizationProvider + { + AssetServer server; + + public AuthorizeAll() + { + } + + public void Start(AssetServer server) + { + this.server = server; + } + + public void Stop() + { + } + + public bool IsMetadataAuthorized(UUID authToken, UUID assetID) + { + return true; + } + + public bool IsDataAuthorized(UUID authToken, UUID assetID) + { + return true; + } + + public bool IsCreateAuthorized(UUID authToken) + { + return true; + } + + public bool IsInventoryReadAuthorized(UUID authToken, Uri owner) + { + return true; + } + + public bool IsInventoryWriteAuthorized(UUID authToken, Uri owner) + { + return true; + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/BrowseFrontend.cs b/OpenSim/Grid/NewAssetServer/Extensions/BrowseFrontend.cs new file mode 100644 index 0000000..9f42722 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/BrowseFrontend.cs @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Net; +using System.Text; +using System.Web; +using ExtensionLoader; +using OpenMetaverse; +using HttpServer; + +namespace AssetServer.Extensions +{ + public class BrowseFrontend : IExtension + { + AssetServer server; + + public BrowseFrontend() + { + } + + public void Start(AssetServer server) + { + this.server = server; + + // Request for / or /?... + server.HttpServer.AddHandler("get", null, @"(^/$)|(^/\?.*)", BrowseRequestHandler); + } + + public void Stop() + { + } + + bool BrowseRequestHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + const int ASSETS_PER_PAGE = 25; + const string HEADER = "Asset Server"; + const string TABLE_HEADER = + ""; + const string TABLE_FOOTER = "
NameDescriptionTypeIDTemporarySHA-1
"; + const string FOOTER = ""; + + UUID authToken = Utils.GetAuthToken(request); + + StringBuilder html = new StringBuilder(); + int start = 0; + uint page = 0; + + if (!String.IsNullOrEmpty(request.Uri.Query)) + { + NameValueCollection query = HttpUtility.ParseQueryString(request.Uri.Query); + if (!String.IsNullOrEmpty(query["page"]) && UInt32.TryParse(query["page"], out page)) + start = (int)page * ASSETS_PER_PAGE; + } + + html.AppendLine(HEADER); + + html.AppendLine("

"); + if (page > 0) + html.AppendFormat("< Previous Page | ", request.Uri.AbsolutePath, page - 1); + html.AppendFormat("Next Page >", request.Uri.AbsolutePath, page + 1); + html.AppendLine("

"); + + html.AppendLine(TABLE_HEADER); + + server.StorageProvider.ForEach( + delegate(Metadata data) + { + if (server.AuthorizationProvider.IsMetadataAuthorized(authToken, data.ID)) + { + html.AppendLine(String.Format( + "{0}{1}{2}{3}{4}{5}", + data.Name, data.Description, data.ContentType, data.ID, data.Temporary, + BitConverter.ToString(data.SHA1).Replace("-", String.Empty))); + } + else + { + html.AppendLine(String.Format( + "[Protected Asset]  {0}{1} ", + data.ID, data.Temporary)); + } + }, start, ASSETS_PER_PAGE + ); + + html.AppendLine(TABLE_FOOTER); + + html.AppendLine(FOOTER); + + byte[] responseData = System.Text.Encoding.UTF8.GetBytes(html.ToString()); + + response.Status = HttpStatusCode.OK; + response.Body.Write(responseData, 0, responseData.Length); + response.Body.Flush(); + return true; + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/DBConnString.cs b/OpenSim/Grid/NewAssetServer/Extensions/DBConnString.cs new file mode 100644 index 0000000..3c5f971 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/DBConnString.cs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Xml; +using ExtensionLoader.Config; +using MySql.Data.MySqlClient; + +namespace AssetServer.Extensions +{ + public static class DBConnString + { + private static string connectionString; + + /// + /// Parses the MySQL connection string out of either the asset server + /// .ini or a OpenSim-style .xml file and caches the result for future + /// requests + /// + public static string GetConnectionString(IniConfigSource configFile) + { + if (connectionString == null) + { + // Try parsing from the ini file + try + { + // Load the extension list (and ordering) from our config file + IConfig extensionConfig = configFile.Configs["MySQL"]; + connectionString = extensionConfig.GetString("database_connect", null); + } + catch (Exception) { } + + if (connectionString != null) + { + // Force MySQL's broken connection pooling off + MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder(connectionString); + builder.Pooling = false; + if (String.IsNullOrEmpty(builder.Database)) + Logger.Log.Error("No database selected in the connectionString: " + connectionString); + connectionString = builder.ToString(); + } + else + { + Logger.Log.Error("Database connection string is missing, check that the database_connect line is " + + "correct and uncommented in AssetServer.ini"); + } + } + + return connectionString; + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/NullAuthentication.cs b/OpenSim/Grid/NewAssetServer/Extensions/NullAuthentication.cs new file mode 100644 index 0000000..9d38bf4 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/NullAuthentication.cs @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * 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 ExtensionLoader; +using OpenMetaverse; + +namespace AssetServer.Extensions +{ + public class NullAuthentication : IExtension, IAuthenticationProvider + { + AssetServer server; + + public NullAuthentication() + { + } + + public void Start(AssetServer server) + { + this.server = server; + } + + public void Stop() + { + } + + public void AddIdentifier(UUID authToken, Uri identifier) + { + } + + public bool RemoveIdentifier(UUID authToken) + { + return true; + } + + public bool TryGetIdentifier(UUID authToken, out Uri identifier) + { + identifier = null; + return true; + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/NullMetrics.cs b/OpenSim/Grid/NewAssetServer/Extensions/NullMetrics.cs new file mode 100644 index 0000000..84657c4 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/NullMetrics.cs @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * 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 ExtensionLoader; +using OpenMetaverse; + +namespace AssetServer.Extensions +{ + public class NullMetrics : IExtension, IMetricsProvider + { + AssetServer server; + + public NullMetrics() + { + } + + public void Start(AssetServer server) + { + this.server = server; + } + + public void Stop() + { + } + + public void LogAssetMetadataFetch(string extension, BackendResponse response, UUID assetID, DateTime time) + { + Logger.Log.DebugFormat("[{0}] AssetMetadataFetch(): AssetID: {1}, Response: {2}", extension, assetID, response); + } + + public void LogAssetDataFetch(string extension, BackendResponse response, UUID assetID, int dataSize, DateTime time) + { + Logger.Log.DebugFormat("[{0}] AssetDataFetch(): AssetID: {1}, DataSize: {2}, Response: {3}", extension, assetID, + dataSize, response); + } + + public void LogAssetCreate(string extension, BackendResponse response, UUID assetID, int dataSize, DateTime time) + { + Logger.Log.DebugFormat("[{0}] AssetCreate(): AssetID: {1}, DataSize: {2}, Response: {3}", extension, assetID, + dataSize, response); + } + + public void LogInventoryFetch(string extension, BackendResponse response, Uri owner, UUID objID, bool folder, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryFetch(): ObjID: {1}, Folder: {2}, OwnerID: {3}, Response: {4}", extension, + objID, folder, owner, response); + } + + public void LogInventoryFetchFolderContents(string extension, BackendResponse response, Uri owner, UUID folderID, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryFetchFolderContents(): FolderID: {1}, OwnerID: {2}, Response: {3}", extension, + folderID, owner, response); + } + + public void LogInventoryFetchFolderList(string extension, BackendResponse response, Uri owner, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryFetchFolderList(): OwnerID: {1}, Response: {2}", extension, + owner, response); + } + + public void LogInventoryFetchInventory(string extension, BackendResponse response, Uri owner, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryFetchInventory(): OwnerID: {1}, Response: {2}", extension, + owner, response); + } + + public void LogInventoryFetchActiveGestures(string extension, BackendResponse response, Uri owner, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryFetchActiveGestures(): OwnerID: {1}, Response: {2}", extension, + owner, response); + } + + public void LogInventoryCreate(string extension, BackendResponse response, Uri owner, bool folder, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryCreate(): OwnerID: {1}, Response: {2}", extension, + owner, response); + } + + public void LogInventoryCreateInventory(string extension, BackendResponse response, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryCreateInventory(): Response: {1}", extension, + response); + } + + public void LogInventoryDelete(string extension, BackendResponse response, Uri owner, UUID objID, bool folder, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryDelete(): OwnerID: {1}, Folder: {2}, Response: {3}", extension, + owner, folder, response); + } + + public void LogInventoryPurgeFolder(string extension, BackendResponse response, Uri owner, UUID folderID, DateTime time) + { + Logger.Log.DebugFormat("[{0}] InventoryPurgeFolder(): OwnerID: {1}, FolderID: {2}, Response: {3}", extension, + owner, response); + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/OpenSimFrontend.cs b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimFrontend.cs new file mode 100644 index 0000000..7a645b3 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimFrontend.cs @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.IO; +using System.Xml; +using ExtensionLoader; +using OpenMetaverse; +using HttpServer; + +namespace AssetServer.Extensions +{ + public class OpenSimFrontend : IExtension + { + AssetServer server; + + public OpenSimFrontend() + { + } + + public void Start(AssetServer server) + { + this.server = server; + + // Asset request + server.HttpServer.AddHandler("get", null, @"^/assets/", AssetRequestHandler); + + // Asset creation + server.HttpServer.AddHandler("post", null, @"^/assets/", AssetPostHandler); + } + + public void Stop() + { + } + + bool AssetRequestHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID assetID; + // Split the URL up to get the asset ID out + string[] rawUrl = request.Uri.PathAndQuery.Split('/'); + + if (rawUrl.Length >= 3 && rawUrl[2].Length >= 36 && UUID.TryParse(rawUrl[2].Substring(0, 36), out assetID)) + { + Metadata metadata; + byte[] assetData; + BackendResponse dataResponse; + + if ((dataResponse = server.StorageProvider.TryFetchDataMetadata(assetID, out metadata, out assetData)) == BackendResponse.Success) + { + MemoryStream stream = new MemoryStream(); + + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + XmlWriter writer = XmlWriter.Create(stream, settings); + + writer.WriteStartDocument(); + writer.WriteStartElement("AssetBase"); + writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); + writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); + writer.WriteStartElement("FullID"); + writer.WriteStartElement("Guid"); + writer.WriteString(assetID.ToString()); + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteStartElement("ID"); + writer.WriteString(assetID.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("Data"); + writer.WriteBase64(assetData, 0, assetData.Length); + writer.WriteEndElement(); + writer.WriteStartElement("Type"); + writer.WriteValue(Utils.ContentTypeToSLAssetType(metadata.ContentType)); + writer.WriteEndElement(); + writer.WriteStartElement("Name"); + writer.WriteString(metadata.Name); + writer.WriteEndElement(); + writer.WriteStartElement("Description"); + writer.WriteString(metadata.Description); + writer.WriteEndElement(); + writer.WriteStartElement("Local"); + writer.WriteValue(false); + writer.WriteEndElement(); + writer.WriteStartElement("Temporary"); + writer.WriteValue(metadata.Temporary); + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteEndDocument(); + + writer.Flush(); + byte[] buffer = stream.GetBuffer(); + + response.Status = HttpStatusCode.OK; + response.ContentType = "application/xml"; + response.ContentLength = stream.Length; + response.Body.Write(buffer, 0, (int)stream.Length); + response.Body.Flush(); + } + else + { + Logger.Log.WarnFormat("Failed to fetch asset data or metadata for {0}: {1}", assetID, dataResponse); + response.Status = HttpStatusCode.NotFound; + } + } + else + { + Logger.Log.Warn("Unrecognized OpenSim asset request: " + request.Uri.PathAndQuery); + } + + return true; + } + + bool AssetPostHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + byte[] assetData = null; + Metadata metadata = new Metadata(); + + Logger.Log.Debug("Handling OpenSim asset upload"); + + try + { + using (XmlReader reader = XmlReader.Create(request.Body)) + { + reader.MoveToContent(); + reader.ReadStartElement("AssetBase"); + + reader.ReadStartElement("FullID"); + UUID.TryParse(reader.ReadElementContentAsString("Guid", String.Empty), out metadata.ID); + reader.ReadEndElement(); + reader.ReadStartElement("ID"); + reader.Skip(); + reader.ReadEndElement(); + + // HACK: Broken on Mono. https://bugzilla.novell.com/show_bug.cgi?id=464229 + //int readBytes = 0; + //byte[] buffer = new byte[1024]; + //MemoryStream stream = new MemoryStream(); + //BinaryWriter writer = new BinaryWriter(stream); + //while ((readBytes = reader.ReadElementContentAsBase64(buffer, 0, buffer.Length)) > 0) + // writer.Write(buffer, 0, readBytes); + //writer.Flush(); + //assetData = stream.GetBuffer(); + //Array.Resize(ref assetData, (int)stream.Length); + + assetData = Convert.FromBase64String(reader.ReadElementContentAsString()); + + int type; + Int32.TryParse(reader.ReadElementContentAsString("Type", String.Empty), out type); + metadata.ContentType = Utils.SLAssetTypeToContentType(type); + metadata.Name = reader.ReadElementContentAsString("Name", String.Empty); + metadata.Description = reader.ReadElementContentAsString("Description", String.Empty); + Boolean.TryParse(reader.ReadElementContentAsString("Local", String.Empty), out metadata.Temporary); + Boolean.TryParse(reader.ReadElementContentAsString("Temporary", String.Empty), out metadata.Temporary); + + reader.ReadEndElement(); + } + + if (assetData != null && assetData.Length > 0) + { + metadata.SHA1 = OpenMetaverse.Utils.SHA1(assetData); + metadata.CreationDate = DateTime.Now; + + BackendResponse storageResponse = server.StorageProvider.TryCreateAsset(metadata, assetData); + + if (storageResponse == BackendResponse.Success) + response.Status = HttpStatusCode.Created; + else if (storageResponse == BackendResponse.NotFound) + response.Status = HttpStatusCode.NotFound; + else + response.Status = HttpStatusCode.InternalServerError; + } + else + { + Logger.Log.Warn("AssetPostHandler called with no asset data"); + response.Status = HttpStatusCode.BadRequest; + } + } + catch (Exception ex) + { + Logger.Log.Warn("Failed to parse POST data (expecting AssetBase): " + ex.Message); + response.Status = HttpStatusCode.BadRequest; + } + + Logger.Log.Debug("Finished handling OpenSim asset upload, Status: " + response.Status.ToString()); + return true; + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/OpenSimInventoryFrontend.cs b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimInventoryFrontend.cs new file mode 100644 index 0000000..a559f19 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimInventoryFrontend.cs @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.IO; +using System.Xml; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using HttpServer; + +namespace AssetServer.Extensions +{ + public class OpenSimInventoryFrontend : IExtension + { + AssetServer server; + Utils.InventoryItemSerializer itemSerializer = new Utils.InventoryItemSerializer(); + Utils.InventoryFolderSerializer folderSerializer = new Utils.InventoryFolderSerializer(); + Utils.InventoryCollectionSerializer collectionSerializer = new Utils.InventoryCollectionSerializer(); + + public OpenSimInventoryFrontend() + { + } + + public void Start(AssetServer server) + { + this.server = server; + + server.HttpServer.AddHandler("post", null, @"^/GetInventory/", GetInventoryHandler); + server.HttpServer.AddHandler("post", null, @"^/CreateInventory/", CreateInventoryHandler); + server.HttpServer.AddHandler("post", null, @"^/NewFolder/", NewFolderHandler); + server.HttpServer.AddHandler("post", null, @"^/UpdateFolder/", UpdateFolderHandler); + server.HttpServer.AddHandler("post", null, @"^/MoveFolder/", MoveFolderHandler); + server.HttpServer.AddHandler("post", null, @"^/PurgeFolder/", PurgeFolderHandler); + server.HttpServer.AddHandler("post", null, @"^/NewItem/", NewItemHandler); + server.HttpServer.AddHandler("post", null, @"^/DeleteItem/", DeleteItemHandler); + server.HttpServer.AddHandler("post", null, @"^/RootFolders/", RootFoldersHandler); + server.HttpServer.AddHandler("post", null, @"^/ActiveGestures/", ActiveGesturesHandler); + } + + public void Stop() + { + } + + bool GetInventoryHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID sessionID, agentID; + UUID ownerID = DeserializeUUID(request.Body, out agentID, out sessionID); + + if (ownerID != UUID.Zero) + { + Logger.Log.Warn("GetInventory is not scalable on some inventory backends, avoid calling it wherever possible"); + + Uri owner = Utils.GetOpenSimUri(ownerID); + InventoryCollection inventory; + BackendResponse storageResponse = server.InventoryProvider.TryFetchInventory(owner, out inventory); + + if (storageResponse == BackendResponse.Success) + { + collectionSerializer.Serialize(response.Body, inventory); + response.Body.Flush(); + } + else if (storageResponse == BackendResponse.NotFound) + { + // Return an empty inventory set to mimic OpenSim.Grid.InventoryServer.exe + inventory = new InventoryCollection(); + inventory.UserID = ownerID; + inventory.Folders = new Dictionary(); + inventory.Items = new Dictionary(); + collectionSerializer.Serialize(response.Body, inventory); + response.Body.Flush(); + } + else + { + response.Status = HttpStatusCode.InternalServerError; + } + } + else + { + response.Status = HttpStatusCode.BadRequest; + } + + return true; + } + + bool CreateInventoryHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID ownerID = DeserializeUUID(request.Body); + + if (ownerID != UUID.Zero) + { + Uri owner = Utils.GetOpenSimUri(ownerID); + Logger.Log.DebugFormat("Created URI {0} for inventory creation", owner); + + InventoryFolder rootFolder = new InventoryFolder("My Inventory", ownerID, UUID.Zero, (short)AssetType.Folder); + BackendResponse storageResponse = server.InventoryProvider.TryCreateInventory(owner, rootFolder); + if (storageResponse == BackendResponse.Success) + { + CreateFolder("Animations", ownerID, rootFolder.ID, AssetType.Animation); + CreateFolder("Body Parts", ownerID, rootFolder.ID, AssetType.Bodypart); + CreateFolder("Calling Cards", ownerID, rootFolder.ID, AssetType.CallingCard); + CreateFolder("Clothing", ownerID, rootFolder.ID, AssetType.Clothing); + CreateFolder("Gestures", ownerID, rootFolder.ID, AssetType.Gesture); + CreateFolder("Landmarks", ownerID, rootFolder.ID, AssetType.Landmark); + CreateFolder("Lost and Found", ownerID, rootFolder.ID, AssetType.LostAndFoundFolder); + CreateFolder("Notecards", ownerID, rootFolder.ID, AssetType.Notecard); + CreateFolder("Objects", ownerID, rootFolder.ID, AssetType.Object); + CreateFolder("Photo Album", ownerID, rootFolder.ID, AssetType.SnapshotFolder); + CreateFolder("Scripts", ownerID, rootFolder.ID, AssetType.LSLText); + CreateFolder("Sounds", ownerID, rootFolder.ID, AssetType.Sound); + CreateFolder("Textures", ownerID, rootFolder.ID, AssetType.Texture); + CreateFolder("Trash", ownerID, rootFolder.ID, AssetType.TrashFolder); + + SerializeBool(response.Body, true); + return true; + } + } + + SerializeBool(response.Body, false); + return true; + } + + bool NewFolderHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID agentID, sessionID; + InventoryFolder folder = DeserializeFolder(request.Body, out agentID, out sessionID); + + if (folder != null) + { + Uri owner = Utils.GetOpenSimUri(folder.Owner); + + // Some calls that are moving or updating a folder instead of creating a new one + // will pass in an InventoryFolder without the name set. If this is the case we + // need to look up the name first + if (String.IsNullOrEmpty(folder.Name)) + { + InventoryFolder oldFolder; + if (server.InventoryProvider.TryFetchFolder(owner, folder.ID, out oldFolder) == BackendResponse.Success) + folder.Name = oldFolder.Name; + } + + BackendResponse storageResponse = server.InventoryProvider.TryCreateFolder(owner, folder); + + if (storageResponse == BackendResponse.Success) + { + SerializeBool(response.Body, true); + return true; + } + } + + SerializeBool(response.Body, false); + return true; + } + + bool UpdateFolderHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + return NewFolderHandler(client, request, response); + } + + bool MoveFolderHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + return NewFolderHandler(client, request, response); + } + + bool PurgeFolderHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID agentID, sessionID; + InventoryFolder folder = DeserializeFolder(request.Body, out agentID, out sessionID); + + if (folder != null) + { + Uri owner = Utils.GetOpenSimUri(folder.Owner); + BackendResponse storageResponse = server.InventoryProvider.TryPurgeFolder(owner, folder.ID); + + if (storageResponse == BackendResponse.Success) + { + SerializeBool(response.Body, true); + return true; + } + } + + SerializeBool(response.Body, false); + return true; + } + + bool NewItemHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID agentID, sessionID; + InventoryItem item = DeserializeItem(request.Body, out agentID, out sessionID); + + if (item != null) + { + Uri owner = Utils.GetOpenSimUri(agentID); + BackendResponse storageResponse = server.InventoryProvider.TryCreateItem(owner, item); + + if (storageResponse == BackendResponse.Success) + { + SerializeBool(response.Body, true); + return true; + } + } + + SerializeBool(response.Body, false); + return true; + } + + bool DeleteItemHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID agentID, sessionID; + InventoryItem item = DeserializeItem(request.Body, out agentID, out sessionID); + + if (item != null) + { + Uri owner = Utils.GetOpenSimUri(item.Owner); + BackendResponse storageResponse = server.InventoryProvider.TryDeleteItem(owner, item.ID); + + if (storageResponse == BackendResponse.Success) + { + SerializeBool(response.Body, true); + return true; + } + } + + SerializeBool(response.Body, false); + return true; + } + + bool RootFoldersHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID ownerID = DeserializeUUID(request.Body); + + if (ownerID != UUID.Zero) + { + Uri owner = Utils.GetOpenSimUri(ownerID); + List skeleton; + BackendResponse storageResponse = server.InventoryProvider.TryFetchFolderList(owner, out skeleton); + + if (storageResponse == BackendResponse.Success) + { + SerializeFolderList(response.Body, skeleton); + } + else if (storageResponse == BackendResponse.NotFound) + { + // Return an empty set of inventory so the requester knows that + // an inventory needs to be created for this agent + SerializeFolderList(response.Body, new List(0)); + } + else + { + response.Status = HttpStatusCode.InternalServerError; + } + } + else + { + response.Status = HttpStatusCode.BadRequest; + } + + return true; + } + + bool ActiveGesturesHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID ownerID = DeserializeUUID(request.Body); + + if (ownerID != UUID.Zero) + { + Uri owner = Utils.GetOpenSimUri(ownerID); + List gestures; + BackendResponse storageResponse = server.InventoryProvider.TryFetchActiveGestures(owner, out gestures); + + if (storageResponse == BackendResponse.Success) + { + SerializeItemList(response.Body, gestures); + } + else if (storageResponse == BackendResponse.NotFound) + { + // Return an empty set of gestures to match OpenSim.Grid.InventoryServer.exe behavior + SerializeItemList(response.Body, new List(0)); + } + else + { + response.Status = HttpStatusCode.InternalServerError; + } + } + else + { + response.Status = HttpStatusCode.BadRequest; + } + + return true; + } + + BackendResponse CreateFolder(string name, UUID ownerID, UUID parentID, AssetType assetType) + { + InventoryFolder folder = new InventoryFolder(name, ownerID, parentID, (short)assetType); + Uri owner = Utils.GetOpenSimUri(ownerID); + return server.InventoryProvider.TryCreateFolder(owner, folder); + } + + UUID DeserializeUUID(Stream stream) + { + UUID id = UUID.Zero; + + try + { + using (XmlReader reader = XmlReader.Create(stream)) + { + reader.MoveToContent(); + UUID.TryParse(reader.ReadElementContentAsString("guid", String.Empty), out id); + } + } + catch (Exception ex) + { + Logger.Log.Warn("Failed to parse POST data (expecting guid): " + ex.Message); + } + + return id; + } + + UUID DeserializeUUID(Stream stream, out UUID agentID, out UUID sessionID) + { + UUID id; + + try + { + using (XmlReader reader = XmlReader.Create(stream)) + { + reader.MoveToContent(); + reader.ReadStartElement("RestSessionObjectOfGuid"); + UUID.TryParse(reader.ReadElementContentAsString("SessionID", String.Empty), out sessionID); + UUID.TryParse(reader.ReadElementContentAsString("AvatarID", String.Empty), out agentID); + UUID.TryParse(reader.ReadElementContentAsString("Body", String.Empty), out id); + reader.ReadEndElement(); + } + } + catch (Exception ex) + { + Logger.Log.Warn("Failed to parse GetInventory POST data: " + ex.Message); + agentID = UUID.Zero; + sessionID = UUID.Zero; + return UUID.Zero; + } + + return id; + } + + InventoryFolder DeserializeFolder(Stream stream, out UUID agentID, out UUID sessionID) + { + InventoryFolder folder = new InventoryFolder(); + + try + { + using (XmlReader reader = XmlReader.Create(stream)) + { + reader.MoveToContent(); + reader.ReadStartElement("RestSessionObjectOfInventoryFolderBase"); + UUID.TryParse(reader.ReadElementContentAsString("SessionID", String.Empty), out sessionID); + UUID.TryParse(reader.ReadElementContentAsString("AvatarID", String.Empty), out agentID); + reader.ReadStartElement("Body"); + if (reader.Name == "Name") + folder.Name = reader.ReadElementContentAsString("Name", String.Empty); + else + folder.Name = String.Empty; + ReadUUID(reader, "Owner", out folder.Owner); + ReadUUID(reader, "ParentID", out folder.ParentID); + ReadUUID(reader, "ID", out folder.ID); + Int16.TryParse(reader.ReadElementContentAsString("Type", String.Empty), out folder.Type); + UInt16.TryParse(reader.ReadElementContentAsString("Version", String.Empty), out folder.Version); + reader.ReadEndElement(); + reader.ReadEndElement(); + } + } + catch (Exception ex) + { + Logger.Log.Warn("Failed to parse POST data (expecting InventoryFolderBase): " + ex.Message); + agentID = UUID.Zero; + sessionID = UUID.Zero; + return null; + } + + return folder; + } + + InventoryItem DeserializeItem(Stream stream, out UUID agentID, out UUID sessionID) + { + InventoryItem item = new InventoryItem(); + + try + { + using (XmlReader reader = XmlReader.Create(stream)) + { + reader.MoveToContent(); + reader.ReadStartElement("RestSessionObjectOfInventoryItemBase"); + UUID.TryParse(reader.ReadElementContentAsString("SessionID", String.Empty), out sessionID); + UUID.TryParse(reader.ReadElementContentAsString("AvatarID", String.Empty), out agentID); + reader.ReadStartElement("Body"); + ReadUUID(reader, "ID", out item.ID); + Int32.TryParse(reader.ReadElementContentAsString("InvType", String.Empty), out item.InvType); + ReadUUID(reader, "Folder", out item.Folder); + ReadUUID(reader, "Owner", out item.Owner); + ReadUUID(reader, "Creator", out item.Creator); + item.Name = reader.ReadElementContentAsString("Name", String.Empty); + item.Description = reader.ReadElementContentAsString("Description", String.Empty); + UInt32.TryParse(reader.ReadElementContentAsString("NextPermissions", String.Empty), out item.NextPermissions); + UInt32.TryParse(reader.ReadElementContentAsString("CurrentPermissions", String.Empty), out item.CurrentPermissions); + UInt32.TryParse(reader.ReadElementContentAsString("BasePermissions", String.Empty), out item.BasePermissions); + UInt32.TryParse(reader.ReadElementContentAsString("EveryOnePermissions", String.Empty), out item.EveryOnePermissions); + UInt32.TryParse(reader.ReadElementContentAsString("GroupPermissions", String.Empty), out item.GroupPermissions); + Int32.TryParse(reader.ReadElementContentAsString("AssetType", String.Empty), out item.AssetType); + ReadUUID(reader, "AssetID", out item.AssetID); + ReadUUID(reader, "GroupID", out item.GroupID); + Boolean.TryParse(reader.ReadElementContentAsString("GroupOwned", String.Empty), out item.GroupOwned); + Int32.TryParse(reader.ReadElementContentAsString("SalePrice", String.Empty), out item.SalePrice); + Byte.TryParse(reader.ReadElementContentAsString("SaleType", String.Empty), out item.SaleType); + UInt32.TryParse(reader.ReadElementContentAsString("Flags", String.Empty), out item.Flags); + Int32.TryParse(reader.ReadElementContentAsString("CreationDate", String.Empty), out item.CreationDate); + reader.ReadEndElement(); + reader.ReadEndElement(); + } + } + catch (Exception ex) + { + Logger.Log.Warn("Failed to parse POST data (expecting InventoryItemBase): " + ex.Message); + agentID = UUID.Zero; + sessionID = UUID.Zero; + return null; + } + + return item; + } + + void SerializeBool(Stream stream, bool value) + { + using (XmlWriter writer = XmlWriter.Create(stream)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("boolean"); + writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); + writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); + writer.WriteString(value.ToString().ToLower()); + writer.WriteEndElement(); + writer.WriteEndDocument(); + writer.Flush(); + } + + stream.Flush(); + } + + void SerializeFolderList(Stream stream, List folders) + { + using (XmlWriter writer = XmlWriter.Create(stream)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("ArrayOfInventoryFolderBase"); + writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); + writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); + + if (folders != null) + { + foreach (InventoryFolder folder in folders) + { + writer.WriteStartElement("InventoryFolderBase"); + writer.WriteElementString("Name", folder.Name); + WriteUUID(writer, "Owner", folder.Owner); + WriteUUID(writer, "ParentID", folder.ParentID); + WriteUUID(writer, "ID", folder.ID); + writer.WriteElementString("Type", XmlConvert.ToString(folder.Type)); + writer.WriteElementString("Version", XmlConvert.ToString(folder.Version)); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + + writer.Flush(); + } + + stream.Flush(); + } + + void SerializeItemList(Stream stream, List items) + { + using (XmlWriter writer = XmlWriter.Create(stream)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("ArrayOfInventoryItemBase"); + writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); + writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); + + if (items != null) + { + foreach (InventoryItem item in items) + { + writer.WriteStartElement("InventoryItemBase"); + WriteUUID(writer, "ID", item.ID); + writer.WriteElementString("InvType", XmlConvert.ToString(item.InvType)); + WriteUUID(writer, "Folder", item.Folder); + WriteUUID(writer, "Owner", item.Owner); + WriteUUID(writer, "Creator", item.Creator); + writer.WriteElementString("Name", item.Name); + writer.WriteElementString("Description", item.Description); + writer.WriteElementString("NextPermissions", XmlConvert.ToString(item.NextPermissions)); + writer.WriteElementString("CurrentPermissions", XmlConvert.ToString(item.CurrentPermissions)); + writer.WriteElementString("BasePermissions", XmlConvert.ToString(item.BasePermissions)); + writer.WriteElementString("EveryOnePermissions", XmlConvert.ToString(item.EveryOnePermissions)); + writer.WriteElementString("GroupPermissions", XmlConvert.ToString(item.GroupPermissions)); + writer.WriteElementString("AssetType", XmlConvert.ToString(item.AssetType)); + WriteUUID(writer, "AssetID", item.AssetID); + WriteUUID(writer, "GroupID", item.GroupID); + writer.WriteElementString("GroupOwned", XmlConvert.ToString(item.GroupOwned)); + writer.WriteElementString("SalePrice", XmlConvert.ToString(item.SalePrice)); + writer.WriteElementString("SaleType", XmlConvert.ToString(item.SaleType)); + writer.WriteElementString("Flags", XmlConvert.ToString(item.Flags)); + writer.WriteElementString("CreationDate", XmlConvert.ToString(item.CreationDate)); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + + writer.Flush(); + } + + stream.Flush(); + } + + void WriteUUID(XmlWriter writer, string name, UUID id) + { + writer.WriteStartElement(name); + writer.WriteElementString("Guid", XmlConvert.ToString(id.Guid)); + writer.WriteEndElement(); + } + + void ReadUUID(XmlReader reader, string name, out UUID id) + { + reader.ReadStartElement(name); + UUID.TryParse(reader.ReadElementContentAsString("Guid", String.Empty), out id); + reader.ReadEndElement(); + } + } + + #region OpenSim AssetType + + /// + /// The different types of grid assets + /// + public enum AssetType : sbyte + { + /// Unknown asset type + Unknown = -1, + /// Texture asset, stores in JPEG2000 J2C stream format + Texture = 0, + /// Sound asset + Sound = 1, + /// Calling card for another avatar + CallingCard = 2, + /// Link to a location in world + Landmark = 3, + // Legacy script asset, you should never see one of these + //[Obsolete] + //Script = 4, + /// Collection of textures and parameters that can be + /// worn by an avatar + Clothing = 5, + /// Primitive that can contain textures, sounds, + /// scripts and more + Object = 6, + /// Notecard asset + Notecard = 7, + /// Holds a collection of inventory items + Folder = 8, + /// Root inventory folder + RootFolder = 9, + /// Linden scripting language script + LSLText = 10, + /// LSO bytecode for a script + LSLBytecode = 11, + /// Uncompressed TGA texture + TextureTGA = 12, + /// Collection of textures and shape parameters that can + /// be worn + Bodypart = 13, + /// Trash folder + TrashFolder = 14, + /// Snapshot folder + SnapshotFolder = 15, + /// Lost and found folder + LostAndFoundFolder = 16, + /// Uncompressed sound + SoundWAV = 17, + /// Uncompressed TGA non-square image, not to be used as a + /// texture + ImageTGA = 18, + /// Compressed JPEG non-square image, not to be used as a + /// texture + ImageJPEG = 19, + /// Animation + Animation = 20, + /// Sequence of animations, sounds, chat, and pauses + Gesture = 21, + /// Simstate file + Simstate = 22, + } + + #endregion OpenSim AssetType +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLInventory.cs b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLInventory.cs new file mode 100644 index 0000000..1b5facd --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLInventory.cs @@ -0,0 +1,804 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Data; +using MySql.Data.MySqlClient; +using ExtensionLoader; +using ExtensionLoader.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace AssetServer.Extensions +{ + public class OpenSimMySQLInventory : IExtension, IInventoryProvider + { + const string EXTENSION_NAME = "OpenSimMySQLInventory"; // Used in metrics reporting + + AssetServer server; + + public OpenSimMySQLInventory() + { + } + + #region Required Interfaces + + public void Start(AssetServer server) + { + this.server = server; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + Logger.Log.Info("Connected to MySQL inventory backend: " + dbConnection.ServerVersion); + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL inventory backend failed: " + ex.Message); + } + } + } + + public void Stop() + { + } + + public BackendResponse TryFetchItem(Uri owner, UUID itemID, out InventoryItem item) + { + item = null; + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT assetID,assetType,inventoryName,inventoryDescription,inventoryNextPermissions," + + "inventoryCurrentPermissions,invType,creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,salePrice,saleType," + + "creationDate,groupID,groupOwned,flags,avatarID,parentFolderID,inventoryGroupPermissions FROM inventoryitems WHERE inventoryID='{0}'", + itemID.ToString()); + reader = command.ExecuteReader(); + + if (reader.Read()) + { + item = new InventoryItem(); + item.ID = itemID; + item.AssetID = UUID.Parse(reader.GetString(0)); + item.AssetType = reader.GetInt32(1); + item.Name = reader.GetString(2); + item.Description = reader.GetString(3); + item.NextPermissions = (uint)reader.GetInt32(4); + item.CurrentPermissions = (uint)reader.GetInt32(5); + item.InvType = reader.GetInt32(6); + item.Creator = UUID.Parse(reader.GetString(7)); + item.BasePermissions = (uint)reader.GetInt32(8); + item.EveryOnePermissions = (uint)reader.GetInt32(9); + item.SalePrice = reader.GetInt32(10); + item.SaleType = reader.GetByte(11); + item.CreationDate = reader.GetInt32(12); + item.GroupID = UUID.Parse(reader.GetString(13)); + item.GroupOwned = reader.GetBoolean(14); + item.Flags = (uint)reader.GetInt32(15); + item.Owner = UUID.Parse(reader.GetString(16)); + item.Folder = UUID.Parse(reader.GetString(17)); + item.GroupPermissions = (uint)reader.GetInt32(18); + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogInventoryFetch(EXTENSION_NAME, ret, owner, itemID, false, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchFolder(Uri owner, UUID folderID, out InventoryFolder folder) + { + folder = null; + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT folderName,type,version,agentID,parentFolderID FROM inventoryfolders WHERE folderID='{0}'", + folderID.ToString()); + reader = command.ExecuteReader(); + + if (reader.Read()) + { + folder = new InventoryFolder(); + folder.Children = null; // This call only returns data for the folder itself, no children data + folder.ID = folderID; + folder.Name = reader.GetString(0); + folder.Type = reader.GetInt16(1); + folder.Version = (ushort)reader.GetInt16(2); + folder.Owner = UUID.Parse(reader.GetString(3)); + folder.ParentID = UUID.Parse(reader.GetString(4)); + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogInventoryFetch(EXTENSION_NAME, ret, owner, folderID, true, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchFolderContents(Uri owner, UUID folderID, out InventoryCollection contents) + { + contents = null; + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + contents = new InventoryCollection(); + + #region Folder retrieval + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT folderName,type,version,agentID,folderID FROM inventoryfolders WHERE parentFolderID='{0}'", + folderID.ToString()); + reader = command.ExecuteReader(); + + contents.Folders = new Dictionary(); + + while (reader.Read()) + { + InventoryFolder folder = new InventoryFolder(); + folder.ParentID = folderID; + folder.Children = null; // This call doesn't do recursion + folder.Name = reader.GetString(0); + folder.Type = reader.GetInt16(1); + folder.Version = (ushort)reader.GetInt16(2); + folder.Owner = UUID.Parse(reader.GetString(3)); + folder.ID = UUID.Parse(reader.GetString(4)); + + contents.Folders.Add(folder.ID, folder); + contents.UserID = folder.Owner; + } + + reader.Close(); + + #endregion Folder retrieval + + #region Item retrieval + + command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT assetID,assetType,inventoryName,inventoryDescription,inventoryNextPermissions," + + "inventoryCurrentPermissions,invType,creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,salePrice,saleType," + + "creationDate,groupID,groupOwned,flags,avatarID,inventoryID,inventoryGroupPermissions FROM inventoryitems WHERE parentFolderID='{0}'", + folderID.ToString()); + reader = command.ExecuteReader(); + + contents.Items = new Dictionary(); + + while (reader.Read()) + { + InventoryItem item = new InventoryItem(); + item.Folder = folderID; + item.AssetID = UUID.Parse(reader.GetString(0)); + item.AssetType = reader.GetInt32(1); + item.Name = reader.GetString(2); + item.Description = reader.GetString(3); + item.NextPermissions = (uint)reader.GetInt32(4); + item.CurrentPermissions = (uint)reader.GetInt32(5); + item.InvType = reader.GetInt32(6); + item.Creator = UUID.Parse(reader.GetString(7)); + item.BasePermissions = (uint)reader.GetInt32(8); + item.EveryOnePermissions = (uint)reader.GetInt32(9); + item.SalePrice = reader.GetInt32(10); + item.SaleType = reader.GetByte(11); + item.CreationDate = reader.GetInt32(12); + item.GroupID = UUID.Parse(reader.GetString(13)); + item.GroupOwned = reader.GetBoolean(14); + item.Flags = (uint)reader.GetInt32(15); + item.Owner = UUID.Parse(reader.GetString(16)); + item.ID = UUID.Parse(reader.GetString(17)); + item.GroupPermissions = (uint)reader.GetInt32(18); + + contents.Items.Add(item.ID, item); + contents.UserID = item.Owner; + } + + #endregion Item retrieval + + ret = BackendResponse.Success; + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogInventoryFetchFolderContents(EXTENSION_NAME, ret, owner, folderID, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchFolderList(Uri owner, out List folders) + { + folders = null; + BackendResponse ret; + UUID ownerID; + + if (Utils.TryGetOpenSimUUID(owner, out ownerID)) + { + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + folders = new List(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT folderName,type,version,folderID,parentFolderID FROM inventoryfolders WHERE agentID='{0}'", + ownerID.ToString()); + reader = command.ExecuteReader(); + + while (reader.Read()) + { + InventoryFolder folder = new InventoryFolder(); + folder.Owner = ownerID; + folder.Children = null; // This call does not create a folder hierarchy + folder.Name = reader.GetString(0); + folder.Type = reader.GetInt16(1); + folder.Version = (ushort)reader.GetInt16(2); + folder.ID = UUID.Parse(reader.GetString(3)); + folder.ParentID = UUID.Parse(reader.GetString(4)); + + folders.Add(folder); + } + + ret = BackendResponse.Success; + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryFetchFolderList(EXTENSION_NAME, ret, owner, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchInventory(Uri owner, out InventoryCollection inventory) + { + inventory = null; + BackendResponse ret; + List folders; + UUID ownerID; + + ret = TryFetchFolderList(owner, out folders); + + if (ret == BackendResponse.Success) + { + // Add the retrieved folders to the inventory collection + inventory = new InventoryCollection(); + inventory.Folders = new Dictionary(folders.Count); + foreach (InventoryFolder folder in folders) + inventory.Folders[folder.ID] = folder; + + // Fetch inventory items + if (Utils.TryGetOpenSimUUID(owner, out ownerID)) + { + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT assetID,assetType,inventoryName,inventoryDescription,inventoryNextPermissions," + + "inventoryCurrentPermissions,invType,creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,salePrice,saleType," + + "creationDate,groupID,groupOwned,flags,inventoryID,parentFolderID,inventoryGroupPermissions FROM inventoryitems WHERE " + + "avatarID='{0}'", ownerID.ToString()); + reader = command.ExecuteReader(); + + inventory.UserID = ownerID; + inventory.Items = new Dictionary(); + + while (reader.Read()) + { + InventoryItem item = new InventoryItem(); + item.Owner = ownerID; + item.AssetID = UUID.Parse(reader.GetString(0)); + item.AssetType = reader.GetInt32(1); + item.Name = reader.GetString(2); + item.Description = reader.GetString(3); + item.NextPermissions = (uint)reader.GetInt32(4); + item.CurrentPermissions = (uint)reader.GetInt32(5); + item.InvType = reader.GetInt32(6); + item.Creator = UUID.Parse(reader.GetString(7)); + item.BasePermissions = (uint)reader.GetInt32(8); + item.EveryOnePermissions = (uint)reader.GetInt32(9); + item.SalePrice = reader.GetInt32(10); + item.SaleType = reader.GetByte(11); + item.CreationDate = reader.GetInt32(12); + item.GroupID = UUID.Parse(reader.GetString(13)); + item.GroupOwned = reader.GetBoolean(14); + item.Flags = (uint)reader.GetInt32(15); + item.ID = UUID.Parse(reader.GetString(16)); + item.Folder = UUID.Parse(reader.GetString(17)); + item.GroupPermissions = (uint)reader.GetInt32(18); + + inventory.Items.Add(item.ID, item); + } + + ret = BackendResponse.Success; + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + } + else + { + ret = BackendResponse.NotFound; + } + } + + server.MetricsProvider.LogInventoryFetchInventory(EXTENSION_NAME, ret, owner, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchActiveGestures(Uri owner, out List gestures) + { + gestures = null; + BackendResponse ret; + UUID ownerID; + + if (Utils.TryGetOpenSimUUID(owner, out ownerID)) + { + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + MySqlCommand command = new MySqlCommand("SELECT assetID,inventoryName,inventoryDescription,inventoryNextPermissions," + + "inventoryCurrentPermissions,invType,creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,salePrice,saleType," + + "creationDate,groupID,groupOwned,inventoryID,parentFolderID,inventoryGroupPermissions FROM inventoryitems WHERE " + + "avatarId=?uuid AND assetType=?type AND flags=1", dbConnection); + command.Parameters.AddWithValue("?uuid", ownerID.ToString()); + command.Parameters.AddWithValue("?type", (int)AssetType.Gesture); + reader = command.ExecuteReader(); + + while (reader.Read()) + { + InventoryItem item = new InventoryItem(); + item.Owner = ownerID; + item.AssetType = (int)AssetType.Gesture; + item.Flags = (uint)1; + item.AssetID = UUID.Parse(reader.GetString(0)); + item.Name = reader.GetString(1); + item.Description = reader.GetString(2); + item.NextPermissions = (uint)reader.GetInt32(3); + item.CurrentPermissions = (uint)reader.GetInt32(4); + item.InvType = reader.GetInt32(5); + item.Creator = UUID.Parse(reader.GetString(6)); + item.BasePermissions = (uint)reader.GetInt32(7); + item.EveryOnePermissions = (uint)reader.GetInt32(8); + item.SalePrice = reader.GetInt32(9); + item.SaleType = reader.GetByte(10); + item.CreationDate = reader.GetInt32(11); + item.GroupID = UUID.Parse(reader.GetString(12)); + item.GroupOwned = reader.GetBoolean(13); + item.ID = UUID.Parse(reader.GetString(14)); + item.Folder = UUID.Parse(reader.GetString(15)); + item.GroupPermissions = (uint)reader.GetInt32(16); + + gestures.Add(item); + } + + ret = BackendResponse.Success; + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryFetchActiveGestures(EXTENSION_NAME, ret, owner, DateTime.Now); + return ret; + } + + public BackendResponse TryCreateItem(Uri owner, InventoryItem item) + { + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + + MySqlCommand command = new MySqlCommand( + "REPLACE INTO inventoryitems (assetID,assetType,inventoryName,inventoryDescription,inventoryNextPermissions," + + "inventoryCurrentPermissions,invType,creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,salePrice,saleType," + + "creationDate,groupID,groupOwned,flags,inventoryID,avatarID,parentFolderID,inventoryGroupPermissions) VALUES " + + + "(?assetID,?assetType,?inventoryName,?inventoryDescription,?inventoryNextPermissions,?inventoryCurrentPermissions,?invType," + + "?creatorID,?inventoryBasePermissions,?inventoryEveryOnePermissions,?salePrice,?saleType,?creationDate,?groupID,?groupOwned," + + "?flags,?inventoryID,?avatarID,?parentFolderID,?inventoryGroupPermissions)", dbConnection); + + command.Parameters.AddWithValue("?assetID", item.AssetID.ToString()); + command.Parameters.AddWithValue("?assetType", item.AssetType); + command.Parameters.AddWithValue("?inventoryName", item.Name); + command.Parameters.AddWithValue("?inventoryDescription", item.Description); + command.Parameters.AddWithValue("?inventoryNextPermissions", item.NextPermissions); + command.Parameters.AddWithValue("?inventoryCurrentPermissions", item.CurrentPermissions); + command.Parameters.AddWithValue("?invType", item.InvType); + command.Parameters.AddWithValue("?creatorID", item.Creator.ToString()); + command.Parameters.AddWithValue("?inventoryBasePermissions", item.BasePermissions); + command.Parameters.AddWithValue("?inventoryEveryOnePermissions", item.EveryOnePermissions); + command.Parameters.AddWithValue("?salePrice", item.SalePrice); + command.Parameters.AddWithValue("?saleType", item.SaleType); + command.Parameters.AddWithValue("?creationDate", item.CreationDate); + command.Parameters.AddWithValue("?groupID", item.GroupID.ToString()); + command.Parameters.AddWithValue("?groupOwned", item.GroupOwned); + command.Parameters.AddWithValue("?flags", item.Flags); + command.Parameters.AddWithValue("?inventoryID", item.ID); + command.Parameters.AddWithValue("?avatarID", item.Owner); + command.Parameters.AddWithValue("?parentFolderID", item.Folder); + command.Parameters.AddWithValue("?inventoryGroupPermissions", item.GroupPermissions); + + int rowsAffected = command.ExecuteNonQuery(); + if (rowsAffected == 1) + { + ret = BackendResponse.Success; + } + else if (rowsAffected == 2) + { + Logger.Log.Info("Replaced inventory item " + item.ID.ToString()); + ret = BackendResponse.Success; + } + else + { + Logger.Log.ErrorFormat("MySQL REPLACE query affected {0} rows", rowsAffected); + ret = BackendResponse.Failure; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogInventoryCreate(EXTENSION_NAME, ret, owner, false, DateTime.Now); + return ret; + } + + public BackendResponse TryCreateFolder(Uri owner, InventoryFolder folder) + { + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + + MySqlCommand command = new MySqlCommand( + "REPLACE INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) VALUES " + + "(?folderName,?type,?version,?folderID,?agentID,?parentFolderID)", dbConnection); + + command.Parameters.AddWithValue("?folderName", folder.Name); + command.Parameters.AddWithValue("?type", folder.Type); + command.Parameters.AddWithValue("?version", folder.Version); + command.Parameters.AddWithValue("?folderID", folder.ID); + command.Parameters.AddWithValue("?agentID", folder.Owner); + command.Parameters.AddWithValue("?parentFolderID", folder.ParentID); + + int rowsAffected = command.ExecuteNonQuery(); + if (rowsAffected == 1) + { + ret = BackendResponse.Success; + } + else if (rowsAffected == 2) + { + Logger.Log.Info("Replaced inventory folder " + folder.ID.ToString()); + ret = BackendResponse.Success; + } + else + { + Logger.Log.ErrorFormat("MySQL REPLACE query affected {0} rows", rowsAffected); + ret = BackendResponse.Failure; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogInventoryCreate(EXTENSION_NAME, ret, owner, true, DateTime.Now); + return ret; + } + + public BackendResponse TryCreateInventory(Uri owner, InventoryFolder rootFolder) + { + return TryCreateFolder(owner, rootFolder); + } + + public BackendResponse TryDeleteItem(Uri owner, UUID itemID) + { + BackendResponse ret; + UUID ownerID; + + if (Utils.TryGetOpenSimUUID(owner, out ownerID)) + { + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + + MySqlCommand command = new MySqlCommand( + "DELETE FROM inventoryitems WHERE inventoryID=?inventoryID AND avatarID=?avatarID", dbConnection); + + command.Parameters.AddWithValue("?inventoryID", itemID.ToString()); + command.Parameters.AddWithValue("?avatarID", ownerID.ToString()); + + int rowsAffected = command.ExecuteNonQuery(); + if (rowsAffected == 1) + { + ret = BackendResponse.Success; + } + else + { + Logger.Log.ErrorFormat("MySQL DELETE query affected {0} rows", rowsAffected); + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryDelete(EXTENSION_NAME, ret, owner, itemID, false, DateTime.Now); + return ret; + } + + public BackendResponse TryDeleteFolder(Uri owner, UUID folderID) + { + BackendResponse ret; + UUID ownerID; + + if (Utils.TryGetOpenSimUUID(owner, out ownerID)) + { + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + + MySqlCommand command = new MySqlCommand( + "DELETE FROM inventoryfolders WHERE folderID=?folderID AND agentID=?agentID", dbConnection); + + command.Parameters.AddWithValue("?folderID", folderID.ToString()); + command.Parameters.AddWithValue("?agentID", ownerID.ToString()); + + int rowsAffected = command.ExecuteNonQuery(); + if (rowsAffected == 1) + { + ret = BackendResponse.Success; + } + else + { + Logger.Log.ErrorFormat("MySQL DELETE query affected {0} rows", rowsAffected); + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryDelete(EXTENSION_NAME, ret, owner, folderID, true, DateTime.Now); + return ret; + } + + public BackendResponse TryPurgeFolder(Uri owner, UUID folderID) + { + BackendResponse ret; + UUID ownerID; + + if (Utils.TryGetOpenSimUUID(owner, out ownerID)) + { + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + + #region Delete items + + MySqlCommand command = new MySqlCommand( + "DELETE FROM inventoryitems WHERE parentFolderID=?parentFolderID AND avatarID=?avatarID", dbConnection); + + command.Parameters.AddWithValue("?parentFolderID", folderID.ToString()); + command.Parameters.AddWithValue("?avatarID", ownerID.ToString()); + + int rowsAffected = command.ExecuteNonQuery(); + + #endregion Delete items + + #region Delete folders + + command = new MySqlCommand( + "DELETE FROM inventoryfolders WHERE parentFolderID=?parentFolderID AND agentID=?agentID", dbConnection); + + command.Parameters.AddWithValue("?parentFolderID", folderID.ToString()); + command.Parameters.AddWithValue("?agentID", ownerID.ToString()); + + rowsAffected += command.ExecuteNonQuery(); + + #endregion Delete folders + + Logger.Log.DebugFormat("Deleted {0} inventory objects from MySQL in a folder purge", rowsAffected); + + ret = BackendResponse.Success; + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryPurgeFolder(EXTENSION_NAME, ret, owner, folderID, DateTime.Now); + return ret; + } + + public int ForEach(Action action, int start, int count) + { + int rowCount = 0; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + MySqlDataReader reader; + + try + { + dbConnection.Open(); + + MySqlCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT name,description,assetType,temporary,data,id FROM assets LIMIT {0}, {1}", + start, count); + reader = command.ExecuteReader(); + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + return 0; + } + + while (reader.Read()) + { + Metadata metadata = new Metadata(); + metadata.CreationDate = OpenMetaverse.Utils.Epoch; + metadata.Description = reader.GetString(1); + metadata.ID = UUID.Parse(reader.GetString(5)); + metadata.Name = reader.GetString(0); + metadata.SHA1 = OpenMetaverse.Utils.SHA1((byte[])reader.GetValue(4)); + metadata.Temporary = reader.GetBoolean(3); + metadata.ContentType = Utils.SLAssetTypeToContentType(reader.GetInt32(2)); + + action(metadata); + ++rowCount; + } + + reader.Close(); + } + + return rowCount; + } + + #endregion Required Interfaces + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLStorage.cs b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLStorage.cs new file mode 100644 index 0000000..34f1ef0 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/OpenSimMySQLStorage.cs @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Data; +using MySql.Data.MySqlClient; +using ExtensionLoader; +using ExtensionLoader.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace AssetServer.Extensions +{ + public class OpenSimMySQLStorage : IExtension, IStorageProvider + { + const string EXTENSION_NAME = "OpenSimMySQLStorage"; // Used in metrics reporting + + AssetServer server; + + public OpenSimMySQLStorage() + { + } + + #region Required Interfaces + + public void Start(AssetServer server) + { + this.server = server; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + Logger.Log.Info("Connected to MySQL storage backend: " + dbConnection.ServerVersion); + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL storage backend failed: " + ex.Message); + } + } + } + + public void Stop() + { + } + + public BackendResponse TryFetchMetadata(UUID assetID, out Metadata metadata) + { + metadata = null; + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT name,description,assetType,temporary FROM assets WHERE id='{0}'", assetID.ToString()); + reader = command.ExecuteReader(); + + if (reader.Read()) + { + metadata = new Metadata(); + metadata.CreationDate = OpenMetaverse.Utils.Epoch; + metadata.SHA1 = null; + metadata.ID = assetID; + metadata.Name = reader.GetString(0); + metadata.Description = reader.GetString(1); + metadata.ContentType = Utils.SLAssetTypeToContentType(reader.GetInt32(2)); + metadata.Temporary = reader.GetBoolean(3); + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogAssetMetadataFetch(EXTENSION_NAME, ret, assetID, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchData(UUID assetID, out byte[] assetData) + { + assetData = null; + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT data FROM assets WHERE id='{0}'", assetID.ToString()); + reader = command.ExecuteReader(); + + if (reader.Read()) + { + assetData = (byte[])reader.GetValue(0); + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogAssetDataFetch(EXTENSION_NAME, ret, assetID, (assetData != null ? assetData.Length : 0), DateTime.Now); + return ret; + } + + public BackendResponse TryFetchDataMetadata(UUID assetID, out Metadata metadata, out byte[] assetData) + { + metadata = null; + assetData = null; + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + IDataReader reader; + + try + { + dbConnection.Open(); + + IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT name,description,assetType,temporary,data FROM assets WHERE id='{0}'", assetID.ToString()); + reader = command.ExecuteReader(); + + if (reader.Read()) + { + metadata = new Metadata(); + metadata.CreationDate = OpenMetaverse.Utils.Epoch; + metadata.SHA1 = null; + metadata.ID = assetID; + metadata.Name = reader.GetString(0); + metadata.Description = reader.GetString(1); + metadata.ContentType = Utils.SLAssetTypeToContentType(reader.GetInt32(2)); + metadata.Temporary = reader.GetBoolean(3); + + assetData = (byte[])reader.GetValue(4); + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogAssetMetadataFetch(EXTENSION_NAME, ret, assetID, DateTime.Now); + server.MetricsProvider.LogAssetDataFetch(EXTENSION_NAME, ret, assetID, (assetData != null ? assetData.Length : 0), DateTime.Now); + return ret; + } + + public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData, out UUID assetID) + { + assetID = metadata.ID = UUID.Random(); + return TryCreateAsset(metadata, assetData); + } + + public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData) + { + BackendResponse ret; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + try + { + dbConnection.Open(); + + MySqlCommand command = new MySqlCommand( + "REPLACE INTO assets (name,description,assetType,local,temporary,data,id) VALUES " + + "(?name,?description,?assetType,?local,?temporary,?data,?id)", dbConnection); + + command.Parameters.AddWithValue("?name", metadata.Name); + command.Parameters.AddWithValue("?description", metadata.Description); + command.Parameters.AddWithValue("?assetType", Utils.ContentTypeToSLAssetType(metadata.ContentType)); + command.Parameters.AddWithValue("?local", 0); + command.Parameters.AddWithValue("?temporary", metadata.Temporary); + command.Parameters.AddWithValue("?data", assetData); + command.Parameters.AddWithValue("?id", metadata.ID.ToString()); + + int rowsAffected = command.ExecuteNonQuery(); + if (rowsAffected == 1) + { + ret = BackendResponse.Success; + } + else if (rowsAffected == 2) + { + Logger.Log.Info("Replaced asset " + metadata.ID.ToString()); + ret = BackendResponse.Success; + } + else + { + Logger.Log.ErrorFormat("MySQL REPLACE query affected {0} rows", rowsAffected); + ret = BackendResponse.Failure; + } + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogAssetCreate(EXTENSION_NAME, ret, metadata.ID, assetData.Length, DateTime.Now); + return ret; + } + + public int ForEach(Action action, int start, int count) + { + int rowCount = 0; + + using (MySqlConnection dbConnection = new MySqlConnection(DBConnString.GetConnectionString(server.ConfigFile))) + { + MySqlDataReader reader; + + try + { + dbConnection.Open(); + + MySqlCommand command = dbConnection.CreateCommand(); + command.CommandText = String.Format("SELECT name,description,assetType,temporary,data,id FROM assets LIMIT {0}, {1}", + start, count); + reader = command.ExecuteReader(); + } + catch (MySqlException ex) + { + Logger.Log.Error("Connection to MySQL backend failed: " + ex.Message); + return 0; + } + + while (reader.Read()) + { + Metadata metadata = new Metadata(); + metadata.CreationDate = OpenMetaverse.Utils.Epoch; + metadata.Description = reader.GetString(1); + metadata.ID = UUID.Parse(reader.GetString(5)); + metadata.Name = reader.GetString(0); + metadata.SHA1 = OpenMetaverse.Utils.SHA1((byte[])reader.GetValue(4)); + metadata.Temporary = reader.GetBoolean(3); + metadata.ContentType = Utils.SLAssetTypeToContentType(reader.GetInt32(2)); + + action(metadata); + ++rowCount; + } + + reader.Close(); + } + + return rowCount; + } + + #endregion Required Interfaces + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/ReferenceFrontend.cs b/OpenSim/Grid/NewAssetServer/Extensions/ReferenceFrontend.cs new file mode 100644 index 0000000..133f87c --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/ReferenceFrontend.cs @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Xml; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using HttpServer; + +namespace AssetServer.Extensions +{ + public class ReferenceFrontend : IExtension + { + AssetServer server; + + public ReferenceFrontend() + { + } + + public void Start(AssetServer server) + { + this.server = server; + + // Asset metadata request + server.HttpServer.AddHandler("get", null, @"^/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/metadata", + MetadataRequestHandler); + + // Asset data request + server.HttpServer.AddHandler("get", null, @"^/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/data", + DataRequestHandler); + + // Asset creation + server.HttpServer.AddHandler("post", null, "^/createasset", CreateRequestHandler); + } + + public void Stop() + { + } + + bool MetadataRequestHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID assetID; + // Split the URL up into an AssetID and a method + string[] rawUrl = request.Uri.PathAndQuery.Split('/'); + + if (rawUrl.Length >= 3 && UUID.TryParse(rawUrl[1], out assetID)) + { + UUID authToken = Utils.GetAuthToken(request); + + if (server.AuthorizationProvider.IsMetadataAuthorized(authToken, assetID)) + { + Metadata metadata; + BackendResponse storageResponse = server.StorageProvider.TryFetchMetadata(assetID, out metadata); + + if (storageResponse == BackendResponse.Success) + { + // If the asset data location wasn't specified in the metadata, specify it + // manually here by pointing back to this asset server + if (!metadata.Methods.ContainsKey("data")) + { + metadata.Methods["data"] = new Uri(String.Format("{0}://{1}/{2}/data", + request.Uri.Scheme, request.Uri.Authority, assetID)); + } + + byte[] serializedData = metadata.SerializeToBytes(); + + response.Status = HttpStatusCode.OK; + response.ContentType = "application/json"; + response.ContentLength = serializedData.Length; + response.Body.Write(serializedData, 0, serializedData.Length); + + } + else if (storageResponse == BackendResponse.NotFound) + { + Logger.Log.Warn("Could not find metadata for asset " + assetID.ToString()); + response.Status = HttpStatusCode.NotFound; + } + else + { + response.Status = HttpStatusCode.InternalServerError; + } + } + else + { + response.Status = HttpStatusCode.Forbidden; + } + + return true; + } + + response.Status = HttpStatusCode.NotFound; + return true; + } + + bool DataRequestHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID assetID; + // Split the URL up into an AssetID and a method + string[] rawUrl = request.Uri.PathAndQuery.Split('/'); + + if (rawUrl.Length >= 3 && UUID.TryParse(rawUrl[1], out assetID)) + { + UUID authToken = Utils.GetAuthToken(request); + + if (server.AuthorizationProvider.IsDataAuthorized(authToken, assetID)) + { + byte[] assetData; + BackendResponse storageResponse = server.StorageProvider.TryFetchData(assetID, out assetData); + + if (storageResponse == BackendResponse.Success) + { + response.Status = HttpStatusCode.OK; + response.Status = HttpStatusCode.OK; + response.ContentType = "application/octet-stream"; + response.AddHeader("Content-Disposition", "attachment; filename=" + assetID.ToString()); + response.ContentLength = assetData.Length; + response.Body.Write(assetData, 0, assetData.Length); + } + else if (storageResponse == BackendResponse.NotFound) + { + response.Status = HttpStatusCode.NotFound; + } + else + { + response.Status = HttpStatusCode.InternalServerError; + } + } + else + { + response.Status = HttpStatusCode.Forbidden; + } + + return true; + } + + response.Status = HttpStatusCode.BadRequest; + return true; + } + + bool CreateRequestHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response) + { + UUID authToken = Utils.GetAuthToken(request); + + if (server.AuthorizationProvider.IsCreateAuthorized(authToken)) + { + try + { + OSD osdata = OSDParser.DeserializeJson(request.Body); + + if (osdata.Type == OSDType.Map) + { + OSDMap map = (OSDMap)osdata; + Metadata metadata = new Metadata(); + metadata.Deserialize(map); + + byte[] assetData = map["data"].AsBinary(); + + if (assetData != null && assetData.Length > 0) + { + BackendResponse storageResponse; + + if (metadata.ID != UUID.Zero) + storageResponse = server.StorageProvider.TryCreateAsset(metadata, assetData); + else + storageResponse = server.StorageProvider.TryCreateAsset(metadata, assetData, out metadata.ID); + + if (storageResponse == BackendResponse.Success) + { + response.Status = HttpStatusCode.Created; + OSDMap responseMap = new OSDMap(1); + responseMap["id"] = OSD.FromUUID(metadata.ID); + LitJson.JsonData jsonData = OSDParser.SerializeJson(responseMap); + byte[] responseData = System.Text.Encoding.UTF8.GetBytes(jsonData.ToJson()); + response.Body.Write(responseData, 0, responseData.Length); + response.Body.Flush(); + } + else if (storageResponse == BackendResponse.NotFound) + { + response.Status = HttpStatusCode.NotFound; + } + else + { + response.Status = HttpStatusCode.InternalServerError; + } + } + else + { + response.Status = HttpStatusCode.BadRequest; + } + } + else + { + response.Status = HttpStatusCode.BadRequest; + } + } + catch (Exception ex) + { + response.Status = HttpStatusCode.InternalServerError; + response.Reason = ex.Message; + } + } + else + { + response.Status = HttpStatusCode.Forbidden; + } + + return true; + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/SimpleInventory.cs b/OpenSim/Grid/NewAssetServer/Extensions/SimpleInventory.cs new file mode 100644 index 0000000..782b6b4 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/SimpleInventory.cs @@ -0,0 +1,602 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.IO; +using System.Text; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace AssetServer.Extensions +{ + public class SimpleInventory : IExtension, IInventoryProvider + { + const string EXTENSION_NAME = "SimpleInventory"; // Used for metrics reporting + const string DEFAULT_INVENTORY_DIR = "SimpleInventory"; + + AssetServer server; + Dictionary inventories = new Dictionary(); + Dictionary> activeGestures = new Dictionary>(); + Utils.InventoryItemSerializer itemSerializer = new Utils.InventoryItemSerializer(); + Utils.InventoryFolderSerializer folderSerializer = new Utils.InventoryFolderSerializer(); + + public SimpleInventory() + { + } + + #region Required Interfaces + + public void Start(AssetServer server) + { + this.server = server; + + LoadFiles(DEFAULT_INVENTORY_DIR); + + Logger.Log.InfoFormat("Initialized the inventory index with data for {0} avatars", + inventories.Count); + } + + public void Stop() + { + } + + public BackendResponse TryFetchItem(Uri owner, UUID itemID, out InventoryItem item) + { + item = null; + BackendResponse ret; + + InventoryCollection collection; + if (inventories.TryGetValue(owner, out collection) && collection.Items.TryGetValue(itemID, out item)) + ret = BackendResponse.Success; + else + ret = BackendResponse.NotFound; + + server.MetricsProvider.LogInventoryFetch(EXTENSION_NAME, ret, owner, itemID, false, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchFolder(Uri owner, UUID folderID, out InventoryFolder folder) + { + folder = null; + BackendResponse ret; + + InventoryCollection collection; + if (inventories.TryGetValue(owner, out collection) && collection.Folders.TryGetValue(folderID, out folder)) + ret = BackendResponse.Success; + else + ret = BackendResponse.NotFound; + + server.MetricsProvider.LogInventoryFetch(EXTENSION_NAME, ret, owner, folderID, true, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchFolderContents(Uri owner, UUID folderID, out InventoryCollection contents) + { + contents = null; + BackendResponse ret; + + InventoryCollection collection; + InventoryFolder folder; + + if (inventories.TryGetValue(owner, out collection) && collection.Folders.TryGetValue(folderID, out folder)) + { + contents = new InventoryCollection(); + contents.UserID = collection.UserID; + contents.Folders = new Dictionary(); + contents.Items = new Dictionary(); + + foreach (InventoryBase invBase in folder.Children.Values) + { + if (invBase is InventoryItem) + { + InventoryItem invItem = invBase as InventoryItem; + contents.Items.Add(invItem.ID, invItem); + } + else + { + InventoryFolder invFolder = invBase as InventoryFolder; + contents.Folders.Add(invFolder.ID, invFolder); + } + } + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryFetchFolderContents(EXTENSION_NAME, ret, owner, folderID, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchFolderList(Uri owner, out List folders) + { + folders = null; + BackendResponse ret; + + InventoryCollection collection; + if (inventories.TryGetValue(owner, out collection)) + { + folders = new List(collection.Folders.Values); + return BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryFetchFolderList(EXTENSION_NAME, ret, owner, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchInventory(Uri owner, out InventoryCollection inventory) + { + inventory = null; + BackendResponse ret; + + if (inventories.TryGetValue(owner, out inventory)) + ret = BackendResponse.Success; + else + ret = BackendResponse.NotFound; + + server.MetricsProvider.LogInventoryFetchInventory(EXTENSION_NAME, ret, owner, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchActiveGestures(Uri owner, out List gestures) + { + gestures = null; + BackendResponse ret; + + if (activeGestures.TryGetValue(owner, out gestures)) + ret = BackendResponse.Success; + else + ret = BackendResponse.NotFound; + + server.MetricsProvider.LogInventoryFetchActiveGestures(EXTENSION_NAME, ret, owner, DateTime.Now); + return ret; + } + + public BackendResponse TryCreateItem(Uri owner, InventoryItem item) + { + BackendResponse ret; + + InventoryCollection collection; + if (inventories.TryGetValue(owner, out collection)) + { + // Delete this item first if it already exists + InventoryItem oldItem; + if (collection.Items.TryGetValue(item.ID, out oldItem)) + TryDeleteItem(owner, item.ID); + + try + { + // Create the file + SaveItem(item); + + // Add the item to the collection + lock (collection) collection.Items[item.ID] = item; + + // Add the item to its parent folder + InventoryFolder parent; + if (collection.Folders.TryGetValue(item.Folder, out parent)) + lock (parent.Children) parent.Children.Add(item.ID, item); + + // Add active gestures to our list + if (item.InvType == (int)InventoryType.Gesture && item.Flags == 1) + { + lock (activeGestures) + activeGestures[owner].Add(item); + } + + ret = BackendResponse.Success; + } + catch (Exception ex) + { + Logger.Log.Error(ex.Message); + ret = BackendResponse.Failure; + } + } + else + { + return BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryCreate(EXTENSION_NAME, ret, owner, false, DateTime.Now); + return ret; + } + + public BackendResponse TryCreateFolder(Uri owner, InventoryFolder folder) + { + BackendResponse ret; + + InventoryCollection collection; + if (inventories.TryGetValue(owner, out collection)) + { + // Delete this folder first if it already exists + InventoryFolder oldFolder; + if (collection.Folders.TryGetValue(folder.ID, out oldFolder)) + TryDeleteFolder(owner, folder.ID); + + try + { + // Create the file + SaveFolder(folder); + + // Add the folder to the collection + lock (collection) collection.Folders[folder.ID] = folder; + + // Add the folder to its parent folder + InventoryFolder parent; + if (collection.Folders.TryGetValue(folder.ParentID, out parent)) + lock (parent.Children) parent.Children.Add(folder.ID, folder); + + ret = BackendResponse.Success; + } + catch (Exception ex) + { + Logger.Log.Error(ex.Message); + ret = BackendResponse.Failure; + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryCreate(EXTENSION_NAME, ret, owner, true, DateTime.Now); + return ret; + } + + public BackendResponse TryCreateInventory(Uri owner, InventoryFolder rootFolder) + { + BackendResponse ret; + + lock (inventories) + { + if (!inventories.ContainsKey(owner)) + { + InventoryCollection collection = new InventoryCollection(); + collection.UserID = rootFolder.Owner; + collection.Folders = new Dictionary(); + collection.Folders.Add(rootFolder.ID, rootFolder); + collection.Items = new Dictionary(); + + inventories.Add(owner, collection); + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.Failure; + } + } + + if (ret == BackendResponse.Success) + { + string path = Path.Combine(DEFAULT_INVENTORY_DIR, rootFolder.Owner.ToString()); + try + { + // Create the directory for this agent + Directory.CreateDirectory(path); + + // Create an index.txt containing the UUID and URI for this agent + string[] index = new string[] { rootFolder.Owner.ToString(), owner.ToString() }; + File.WriteAllLines(Path.Combine(path, "index.txt"), index); + + // Create the root folder file + SaveFolder(rootFolder); + } + catch (Exception ex) + { + Logger.Log.Error(ex.Message); + ret = BackendResponse.Failure; + } + } + + server.MetricsProvider.LogInventoryCreateInventory(EXTENSION_NAME, ret, DateTime.Now); + return ret; + } + + public BackendResponse TryDeleteItem(Uri owner, UUID itemID) + { + BackendResponse ret; + + InventoryCollection collection; + InventoryItem item; + if (inventories.TryGetValue(owner, out collection) && collection.Items.TryGetValue(itemID, out item)) + { + // Remove the item from its parent folder + InventoryFolder parent; + if (collection.Folders.TryGetValue(item.Folder, out parent)) + lock (parent.Children) parent.Children.Remove(itemID); + + // Remove the item from the collection + lock (collection) collection.Items.Remove(itemID); + + // Remove from the active gestures list if applicable + if (item.InvType == (int)InventoryType.Gesture) + { + lock (activeGestures) + { + for (int i = 0; i < activeGestures[owner].Count; i++) + { + if (activeGestures[owner][i].ID == itemID) + { + activeGestures[owner].RemoveAt(i); + break; + } + } + } + } + + // Delete the file. We don't know exactly what the file name is, + // so search for it + string path = PathFromURI(owner); + string[] matches = Directory.GetFiles(path, String.Format("*{0}.item", itemID), SearchOption.TopDirectoryOnly); + foreach (string match in matches) + { + try { File.Delete(match); } + catch (Exception ex) { Logger.Log.ErrorFormat("Failed to delete file {0}: {1}", match, ex.Message); } + } + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryDelete(EXTENSION_NAME, ret, owner, itemID, false, DateTime.Now); + return ret; + } + + public BackendResponse TryDeleteFolder(Uri owner, UUID folderID) + { + BackendResponse ret; + + InventoryCollection collection; + InventoryFolder folder; + if (inventories.TryGetValue(owner, out collection) && collection.Folders.TryGetValue(folderID, out folder)) + { + // Remove the folder from its parent folder + InventoryFolder parent; + if (collection.Folders.TryGetValue(folder.ParentID, out parent)) + lock (parent.Children) parent.Children.Remove(folderID); + + // Remove the folder from the collection + lock (collection) collection.Items.Remove(folderID); + + // Delete the folder file. We don't know exactly what the file name is, + // so search for it + string path = PathFromURI(owner); + string[] matches = Directory.GetFiles(path, String.Format("*{0}.folder", folderID), SearchOption.TopDirectoryOnly); + foreach (string match in matches) + { + try { File.Delete(match); } + catch (Exception ex) { Logger.Log.ErrorFormat("Failed to delete folder file {0}: {1}", match, ex.Message); } + } + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryDelete(EXTENSION_NAME, ret, owner, folderID, true, DateTime.Now); + return ret; + } + + public BackendResponse TryPurgeFolder(Uri owner, UUID folderID) + { + BackendResponse ret; + + InventoryCollection collection; + InventoryFolder folder; + if (inventories.TryGetValue(owner, out collection) && collection.Folders.TryGetValue(folderID, out folder)) + { + // Delete all of the folder children + foreach (InventoryBase obj in new List(folder.Children.Values)) + { + if (obj is InventoryItem) + { + TryDeleteItem(owner, (obj as InventoryItem).ID); + } + else + { + InventoryFolder childFolder = obj as InventoryFolder; + TryPurgeFolder(owner, childFolder.ID); + TryDeleteFolder(owner, childFolder.ID); + } + } + + ret = BackendResponse.Success; + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogInventoryPurgeFolder(EXTENSION_NAME, ret, owner, folderID, DateTime.Now); + return ret; + } + + #endregion Required Interfaces + + void SaveItem(InventoryItem item) + { + string filename = String.Format("{0}-{1}.item", SanitizeFilename(item.Name), item.ID); + + string path = Path.Combine(DEFAULT_INVENTORY_DIR, item.Owner.ToString()); + path = Path.Combine(path, filename); + + using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write)) + { + itemSerializer.Serialize(stream, item); + stream.Flush(); + } + } + + void SaveFolder(InventoryFolder folder) + { + string filename = String.Format("{0}-{1}.folder", SanitizeFilename(folder.Name), folder.ID); + + string path = Path.Combine(DEFAULT_INVENTORY_DIR, folder.Owner.ToString()); + path = Path.Combine(path, filename); + + using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write)) + { + folderSerializer.Serialize(stream, folder); + stream.Flush(); + } + } + + string SanitizeFilename(string filename) + { + string output = filename; + + if (output.Length > 64) + output = output.Substring(0, 64); + + foreach (char i in Path.GetInvalidFileNameChars()) + output = output.Replace(i, '_'); + + return output; + } + + static string PathFromURI(Uri uri) + { + byte[] hash = OpenMetaverse.Utils.SHA1(Encoding.UTF8.GetBytes(uri.ToString())); + StringBuilder digest = new StringBuilder(40); + + // Convert the hash to a hex string + foreach (byte b in hash) + digest.AppendFormat(OpenMetaverse.Utils.EnUsCulture, "{0:x2}", b); + + return Path.Combine(DEFAULT_INVENTORY_DIR, digest.ToString()); + } + + void LoadFiles(string folder) + { + // Try to create the directory if it doesn't already exist + if (!Directory.Exists(folder)) + { + try { Directory.CreateDirectory(folder); } + catch (Exception ex) + { + Logger.Log.Warn(ex.Message); + return; + } + } + + try + { + string[] agentFolders = Directory.GetDirectories(DEFAULT_INVENTORY_DIR); + + for (int i = 0; i < agentFolders.Length; i++) + { + string foldername = agentFolders[i]; + string indexPath = Path.Combine(foldername, "index.txt"); + UUID ownerID = UUID.Zero; + Uri owner = null; + + try + { + string[] index = File.ReadAllLines(indexPath); + ownerID = UUID.Parse(index[0]); + owner = new Uri(index[1]); + } + catch (Exception ex) + { + Logger.Log.WarnFormat("Failed loading the index file {0}: {1}", indexPath, ex.Message); + } + + if (ownerID != UUID.Zero && owner != null) + { + // Initialize the active gestures list for this agent + activeGestures.Add(owner, new List()); + + InventoryCollection collection = new InventoryCollection(); + collection.UserID = ownerID; + + // Load all of the folders for this agent + string[] folders = Directory.GetFiles(foldername, "*.folder", SearchOption.TopDirectoryOnly); + collection.Folders = new Dictionary(folders.Length); + + for (int j = 0; j < folders.Length; j++) + { + InventoryFolder invFolder = (InventoryFolder)folderSerializer.Deserialize( + new FileStream(folders[j], FileMode.Open, FileAccess.Read)); + collection.Folders[invFolder.ID] = invFolder; + } + + // Iterate over the folders collection, adding children to their parents + foreach (InventoryFolder invFolder in collection.Folders.Values) + { + InventoryFolder parent; + if (collection.Folders.TryGetValue(invFolder.ParentID, out parent)) + parent.Children[invFolder.ID] = invFolder; + } + + // Load all of the items for this agent + string[] files = Directory.GetFiles(foldername, "*.item", SearchOption.TopDirectoryOnly); + collection.Items = new Dictionary(files.Length); + + for (int j = 0; j < files.Length; j++) + { + InventoryItem invItem = (InventoryItem)itemSerializer.Deserialize( + new FileStream(files[j], FileMode.Open, FileAccess.Read)); + collection.Items[invItem.ID] = invItem; + + // Add items to their parent folders + InventoryFolder parent; + if (collection.Folders.TryGetValue(invItem.Folder, out parent)) + parent.Children[invItem.ID] = invItem; + + // Add active gestures to our list + if (invItem.InvType == (int)InventoryType.Gesture && invItem.Flags != 0) + activeGestures[owner].Add(invItem); + } + + inventories.Add(owner, collection); + } + } + } + catch (Exception ex) + { + Logger.Log.ErrorFormat("Failed loading inventory from {0}: {1}", folder, ex.Message); + } + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/SimpleStorage.cs b/OpenSim/Grid/NewAssetServer/Extensions/SimpleStorage.cs new file mode 100644 index 0000000..1c0fe33 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/SimpleStorage.cs @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2008 Intel Corporation + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -- Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * -- 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 Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.IO; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace AssetServer.Extensions +{ + public class SimpleStorage : IExtension, IStorageProvider + { + const string EXTENSION_NAME = ""; // Used in metrics reporting + const string DEFAULT_DATA_DIR = "SimpleAssets"; + const string TEMP_DATA_DIR = "SimpleAssetsTemp"; + + AssetServer server; + Dictionary metadataStorage; + Dictionary filenames; + + public SimpleStorage() + { + } + + #region Required Interfaces + + public void Start(AssetServer server) + { + this.server = server; + metadataStorage = new Dictionary(); + filenames = new Dictionary(); + + LoadFiles(DEFAULT_DATA_DIR, false); + LoadFiles(TEMP_DATA_DIR, true); + + Logger.Log.InfoFormat("Initialized the store index with metadata for {0} assets", + metadataStorage.Count); + } + + public void Stop() + { + WipeTemporary(); + } + + public BackendResponse TryFetchMetadata(UUID assetID, out Metadata metadata) + { + metadata = null; + BackendResponse ret; + + if (metadataStorage.TryGetValue(assetID, out metadata)) + ret = BackendResponse.Success; + else + ret = BackendResponse.NotFound; + + server.MetricsProvider.LogAssetMetadataFetch(EXTENSION_NAME, ret, assetID, DateTime.Now); + return ret; + } + + public BackendResponse TryFetchData(UUID assetID, out byte[] assetData) + { + assetData = null; + string filename; + BackendResponse ret; + + if (filenames.TryGetValue(assetID, out filename)) + { + try + { + assetData = File.ReadAllBytes(filename); + ret = BackendResponse.Success; + } + catch (Exception ex) + { + Logger.Log.ErrorFormat("Failed reading data for asset {0} from {1}: {2}", assetID, filename, ex.Message); + ret = BackendResponse.Failure; + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogAssetDataFetch(EXTENSION_NAME, ret, assetID, (assetData != null ? assetData.Length : 0), DateTime.Now); + return ret; + } + + public BackendResponse TryFetchDataMetadata(UUID assetID, out Metadata metadata, out byte[] assetData) + { + metadata = null; + assetData = null; + string filename; + BackendResponse ret; + + if (metadataStorage.TryGetValue(assetID, out metadata) && + filenames.TryGetValue(assetID, out filename)) + { + try + { + assetData = File.ReadAllBytes(filename); + ret = BackendResponse.Success; + } + catch (Exception ex) + { + Logger.Log.ErrorFormat("Failed reading data for asset {0} from {1}: {2}", assetID, filename, ex.Message); + ret = BackendResponse.Failure; + } + } + else + { + ret = BackendResponse.NotFound; + } + + server.MetricsProvider.LogAssetMetadataFetch(EXTENSION_NAME, ret, assetID, DateTime.Now); + server.MetricsProvider.LogAssetDataFetch(EXTENSION_NAME, ret, assetID, (assetData != null ? assetData.Length : 0), DateTime.Now); + return ret; + } + + public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData, out UUID assetID) + { + assetID = metadata.ID = UUID.Random(); + return TryCreateAsset(metadata, assetData); + } + + public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData) + { + BackendResponse ret; + + string path; + string filename = String.Format("{0}.{1}", metadata.ID, Utils.ContentTypeToExtension(metadata.ContentType)); + + if (metadata.Temporary) + path = Path.Combine(TEMP_DATA_DIR, filename); + else + path = Path.Combine(DEFAULT_DATA_DIR, filename); + + try + { + File.WriteAllBytes(path, assetData); + lock (filenames) filenames[metadata.ID] = path; + + // Set the creation date to right now + metadata.CreationDate = DateTime.Now; + + lock (metadataStorage) + metadataStorage[metadata.ID] = metadata; + + ret = BackendResponse.Success; + } + catch (Exception ex) + { + Logger.Log.ErrorFormat("Failed writing data for asset {0} to {1}: {2}", metadata.ID, filename, ex.Message); + ret = BackendResponse.Failure; + } + + server.MetricsProvider.LogAssetCreate(EXTENSION_NAME, ret, metadata.ID, assetData.Length, DateTime.Now); + return ret; + } + + public int ForEach(Action action, int start, int count) + { + int rowCount = 0; + + lock (metadataStorage) + { + foreach (Metadata metadata in metadataStorage.Values) + { + action(metadata); + ++rowCount; + } + } + + return rowCount; + } + + #endregion Required Interfaces + + public void WipeTemporary() + { + if (Directory.Exists(TEMP_DATA_DIR)) + { + try { Directory.Delete(TEMP_DATA_DIR); } + catch (Exception ex) { Logger.Log.Error(ex.Message); } + } + } + + void LoadFiles(string folder, bool temporary) + { + // Try to create the directory if it doesn't already exist + if (!Directory.Exists(folder)) + { + try { Directory.CreateDirectory(folder); } + catch (Exception ex) + { + Logger.Log.Warn(ex.Message); + return; + } + } + + lock (metadataStorage) + { + try + { + string[] assets = Directory.GetFiles(folder); + + for (int i = 0; i < assets.Length; i++) + { + string filename = assets[i]; + byte[] data = File.ReadAllBytes(filename); + + Metadata metadata = new Metadata(); + metadata.CreationDate = File.GetCreationTime(filename); + metadata.Description = String.Empty; + metadata.ID = SimpleUtils.ParseUUIDFromFilename(filename); + metadata.Name = SimpleUtils.ParseNameFromFilename(filename); + metadata.SHA1 = OpenMetaverse.Utils.SHA1(data); + metadata.Temporary = false; + metadata.ContentType = Utils.ExtensionToContentType(Path.GetExtension(filename).TrimStart('.')); + + // Store the loaded data + metadataStorage[metadata.ID] = metadata; + filenames[metadata.ID] = filename; + } + } + catch (Exception ex) + { + Logger.Log.Warn(ex.Message); + } + } + } + } +} diff --git a/OpenSim/Grid/NewAssetServer/Extensions/SimpleUtils.cs b/OpenSim/Grid/NewAssetServer/Extensions/SimpleUtils.cs new file mode 100644 index 0000000..6642f90 --- /dev/null +++ b/OpenSim/Grid/NewAssetServer/Extensions/SimpleUtils.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using OpenMetaverse; + +namespace AssetServer.Extensions +{ + public static class SimpleUtils + { + public static string ParseNameFromFilename(string filename) + { + filename = Path.GetFileName(filename); + + int dot = filename.LastIndexOf('.'); + int firstDash = filename.IndexOf('-'); + + if (dot - 37 > 0 && firstDash > 0) + return filename.Substring(0, firstDash); + else + return String.Empty; + } + + public static UUID ParseUUIDFromFilename(string filename) + { + int dot = filename.LastIndexOf('.'); + + if (dot > 35) + { + // Grab the last 36 characters of the filename + string uuidString = filename.Substring(dot - 36, 36); + UUID uuid; + UUID.TryParse(uuidString, out uuid); + return uuid; + } + else + { + UUID uuid; + if (UUID.TryParse(Path.GetFileName(filename), out uuid)) + return uuid; + else + return UUID.Zero; + } + } + } +} -- cgit v1.1