/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Reflection; using System.Threading; using System.Text; using System.Xml; using log4net; using OpenMetaverse; using OpenSim.Framework; using OpenSim.Framework.Communications; using OpenSim.Framework.Communications.Osp; using OpenSim.Framework.Serialization; using OpenSim.Framework.Serialization.External; using OpenSim.Region.CoreModules.World.Archiver; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { public class InventoryArchiveReadRequest { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// /// The maximum major version of archive that we can read. Minor versions shouldn't need a max number since version /// bumps here should be compatible. /// public static int MAX_MAJOR_VERSION = 0; protected TarArchiveReader archive; private UserAccount m_userInfo; private string m_invPath; /// /// We only use this to request modules /// protected Scene m_scene; /// /// The stream from which the inventory archive will be loaded. /// private Stream m_loadStream; public InventoryArchiveReadRequest( Scene scene, UserAccount userInfo, string invPath, string loadPath) : this( scene, userInfo, invPath, new GZipStream(ArchiveHelpers.GetStream(loadPath), CompressionMode.Decompress)) { } public InventoryArchiveReadRequest( Scene scene, UserAccount userInfo, string invPath, Stream loadStream) { m_scene = scene; m_userInfo = userInfo; m_invPath = invPath; m_loadStream = loadStream; } /// /// Execute the request /// /// /// A list of the inventory nodes loaded. If folders were loaded then only the root folders are /// returned /// public List Execute() { string filePath = "ERROR"; int successfulAssetRestores = 0; int failedAssetRestores = 0; int successfulItemRestores = 0; List loadedNodes = new List(); List folderCandidates = InventoryArchiveUtils.FindFolderByPath( m_scene.InventoryService, m_userInfo.PrincipalID, m_invPath); if (folderCandidates.Count == 0) { // Possibly provide an option later on to automatically create this folder if it does not exist m_log.ErrorFormat("[INVENTORY ARCHIVER]: Inventory path {0} does not exist", m_invPath); return loadedNodes; } InventoryFolderBase rootDestinationFolder = folderCandidates[0]; archive = new TarArchiveReader(m_loadStream); // In order to load identically named folders, we need to keep track of the folders that we have already // resolved Dictionary resolvedFolders = new Dictionary(); byte[] data; TarArchiveReader.TarEntryType entryType; try { while ((data = archive.ReadEntry(out filePath, out entryType)) != null) { if (filePath == ArchiveConstants.CONTROL_FILE_PATH) { LoadControlFile(filePath, data); } else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) { if (LoadAsset(filePath, data)) successfulAssetRestores++; else failedAssetRestores++; if ((successfulAssetRestores) % 50 == 0) m_log.DebugFormat( "[INVENTORY ARCHIVER]: Loaded {0} assets...", successfulAssetRestores); } else if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH)) { filePath = filePath.Substring(ArchiveConstants.INVENTORY_PATH.Length); // Trim off the file portion if we aren't already dealing with a directory path if (TarArchiveReader.TarEntryType.TYPE_DIRECTORY != entryType) filePath = filePath.Remove(filePath.LastIndexOf("/") + 1); InventoryFolderBase foundFolder = ReplicateArchivePathToUserInventory( filePath, rootDestinationFolder, resolvedFolders, loadedNodes); if (TarArchiveReader.TarEntryType.TYPE_DIRECTORY != entryType) { InventoryItemBase item = LoadItem(data, foundFolder); if (item != null) { successfulItemRestores++; // If we're loading an item directly into the given destination folder then we need to record // it separately from any loaded root folders if (rootDestinationFolder == foundFolder) loadedNodes.Add(item); } } } } } finally { archive.Close(); } m_log.DebugFormat( "[INVENTORY ARCHIVER]: Successfully loaded {0} assets with {1} failures", successfulAssetRestores, failedAssetRestores); m_log.InfoFormat("[INVENTORY ARCHIVER]: Successfully loaded {0} items", successfulItemRestores); return loadedNodes; } public void Close() { if (m_loadStream != null) m_loadStream.Close(); } /// /// Replicate the inventory paths in the archive to the user's inventory as necessary. /// /// The item archive path to replicate /// The root folder for the inventory load /// /// The folders that we have resolved so far for a given archive path. /// This method will add more folders if necessary /// /// /// Track the inventory nodes created. /// /// The last user inventory folder created or found for the archive path public InventoryFolderBase ReplicateArchivePathToUserInventory( string iarPath, InventoryFolderBase rootDestFolder, Dictionary resolvedFolders, List loadedNodes) { string iarPathExisting = iarPath; // m_log.DebugFormat( // "[INVENTORY ARCHIVER]: Loading folder {0} {1}", rootDestFolder.Name, rootDestFolder.ID); InventoryFolderBase destFolder = ResolveDestinationFolder(rootDestFolder, ref iarPathExisting, resolvedFolders); // m_log.DebugFormat( // "[INVENTORY ARCHIVER]: originalArchivePath [{0}], section already loaded [{1}]", // iarPath, iarPathExisting); string iarPathToCreate = iarPath.Substring(iarPathExisting.Length); CreateFoldersForPath(destFolder, iarPathExisting, iarPathToCreate, resolvedFolders, loadedNodes); return destFolder; } /// /// Resolve a destination folder /// /// /// We require here a root destination folder (usually the root of the user's inventory) and the archive /// path. We also pass in a list of previously resolved folders in case we've found this one previously. /// /// /// The item archive path to resolve. The portion of the path passed back is that /// which corresponds to the resolved desintation folder. /// /// The root folder for the inventory load /// /// /// The folders that we have resolved so far for a given archive path. /// /// /// The folder in the user's inventory that matches best the archive path given. If no such folder was found /// then the passed in root destination folder is returned. /// protected InventoryFolderBase ResolveDestinationFolder( InventoryFolderBase rootDestFolder, ref string archivePath, Dictionary resolvedFolders) { string originalArchivePath = archivePath; InventoryFolderBase destFolder = null; if (archivePath.Length > 0) { while (null == destFolder && archivePath.Length > 0) { // m_log.DebugFormat("[INVENTORY ARCHIVER]: Trying to resolve destination folder {0}", archivePath); if (resolvedFolders.ContainsKey(archivePath)) { // m_log.DebugFormat( // "[INVENTORY ARCHIVER]: Found previously created folder from archive path {0}", archivePath); destFolder = resolvedFolders[archivePath]; } else { // Don't include the last slash so find the penultimate one int penultimateSlashIndex = archivePath.LastIndexOf("/", archivePath.Length - 2); if (penultimateSlashIndex >= 0) { // Remove the last section of path so that we can see if we've already resolved the parent archivePath = archivePath.Remove(penultimateSlashIndex + 1); } else { // m_log.DebugFormat( // "[INVENTORY ARCHIVER]: Found no previously created folder for archive path {0}", // originalArchivePath); archivePath = string.Empty; destFolder = rootDestFolder; } } } } if (null == destFolder) destFolder = rootDestFolder; return destFolder; } /// /// Create a set of folders for the given path. /// /// /// The root folder from which the creation will take place. /// /// /// the part of the iar path that already exists /// /// /// The path to replicate in the user's inventory from iar /// /// /// The folders that we have resolved so far for a given archive path. /// /// /// Track the inventory nodes created. /// protected void CreateFoldersForPath( InventoryFolderBase destFolder, string iarPathExisting, string iarPathToReplicate, Dictionary resolvedFolders, List loadedNodes) { string[] rawDirsToCreate = iarPathToReplicate.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); int i = 0; while (i < rawDirsToCreate.Length) { // m_log.DebugFormat("[INVENTORY ARCHIVER]: Creating folder {0} from IAR", rawDirsToCreate[i]); int identicalNameIdentifierIndex = rawDirsToCreate[i].LastIndexOf( ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR); if (identicalNameIdentifierIndex < 0) { i++; continue; } string newFolderName = rawDirsToCreate[i].Remove(identicalNameIdentifierIndex); newFolderName = InventoryArchiveUtils.UnescapeArchivePath(newFolderName); UUID newFolderId = UUID.Random(); // Asset type has to be Unknown here rather than Folder, otherwise the created folder can't be // deleted once the client has relogged. // The root folder appears to be labelled AssetType.Folder (shows up as "Category" in the client) // even though there is a AssetType.RootCategory destFolder = new InventoryFolderBase( newFolderId, newFolderName, m_userInfo.PrincipalID, (short)AssetType.Unknown, destFolder.ID, 1); m_scene.InventoryService.AddFolder(destFolder); // Record that we have now created this folder iarPathExisting += rawDirsToCreate[i] + "/"; m_log.DebugFormat("[INVENTORY ARCHIVER]: Created folder {0} from IAR", iarPathExisting); resolvedFolders[iarPathExisting] = destFolder; if (0 == i) loadedNodes.Add(destFolder); i++; } } /// /// Load an item from the archive /// /// The archive path for the item /// The raw item data /// The root destination folder for loaded items /// All the inventory nodes (items and folders) loaded so far protected InventoryItemBase LoadItem(byte[] data, InventoryFolderBase loadFolder) { InventoryItemBase item = UserInventoryItemSerializer.Deserialize(data); // Don't use the item ID that's in the file item.ID = UUID.Random(); UUID ospResolvedId = OspResolver.ResolveOspa(item.CreatorId, m_scene.UserAccountService); if (UUID.Zero != ospResolvedId) { item.CreatorIdAsUuid = ospResolvedId; // XXX: For now, don't preserve the OSPA in the creator id (which actually gets persisted to the // database). Instead, replace with the UUID that we found. item.CreatorId = ospResolvedId.ToString(); } else { item.CreatorIdAsUuid = m_userInfo.PrincipalID; } item.Owner = m_userInfo.PrincipalID; // Reset folder ID to the one in which we want to load it item.Folder = loadFolder.ID; //m_userInfo.AddItem(item); m_scene.InventoryService.AddItem(item); return item; } /// /// Load an asset /// /// /// /// true if asset was successfully loaded, false otherwise private bool LoadAsset(string assetPath, byte[] data) { //IRegionSerialiser serialiser = scene.RequestModuleInterface(); // Right now we're nastily obtaining the UUID from the filename string filename = assetPath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); int i = filename.LastIndexOf(ArchiveConstants.ASSET_EXTENSION_SEPARATOR); if (i == -1) { m_log.ErrorFormat( "[INVENTORY ARCHIVER]: Could not find extension information in asset path {0} since it's missing the separator {1}. Skipping", assetPath, ArchiveConstants.ASSET_EXTENSION_SEPARATOR); return false; } string extension = filename.Substring(i); string uuid = filename.Remove(filename.Length - extension.Length); if (ArchiveConstants.EXTENSION_TO_ASSET_TYPE.ContainsKey(extension)) { sbyte assetType = ArchiveConstants.EXTENSION_TO_ASSET_TYPE[extension]; if (assetType == (sbyte)AssetType.Unknown) m_log.WarnFormat("[INVENTORY ARCHIVER]: Importing {0} byte asset {1} with unknown type", data.Length, uuid); //m_log.DebugFormat("[INVENTORY ARCHIVER]: Importing asset {0}, type {1}", uuid, assetType); AssetBase asset = new AssetBase(new UUID(uuid), "RandomName", assetType, UUID.Zero.ToString()); asset.Data = data; m_scene.AssetService.Store(asset); return true; } else { m_log.ErrorFormat( "[INVENTORY ARCHIVER]: Tried to dearchive data with path {0} with an unknown type extension {1}", assetPath, extension); return false; } } /// /// Load control file /// /// /// protected void LoadControlFile(string path, byte[] data) { int majorVersion = -1; int minorVersion = -1; string version = "ERROR"; NameTable nt = new NameTable(); XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt); XmlParserContext context = new XmlParserContext(null, nsmgr, null, XmlSpace.None); XmlTextReader xtr = new XmlTextReader(Encoding.ASCII.GetString(data), XmlNodeType.Document, context); while (xtr.Read()) { if (xtr.NodeType == XmlNodeType.Element) { if (xtr.Name.ToString() == "archive") { majorVersion = int.Parse(xtr["major_version"]); minorVersion = int.Parse(xtr["minor_version"]); version = string.Format("{0}.{1}", majorVersion, minorVersion); } } } if (majorVersion > MAX_MAJOR_VERSION) { throw new Exception( string.Format( "The IAR you are trying to load has major version number of {0} but this version of OpenSim can only load IARs with major version number {1} and below", majorVersion, MAX_MAJOR_VERSION)); } m_log.InfoFormat("[INVENTORY ARCHIVER]: Loading IAR with version {0}", version); } } }