OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
InventoryArchiveWriteRequest.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.IO.Compression;
32 using System.Reflection;
33 using System.Xml;
34 using log4net;
35 using OpenMetaverse;
36 using OpenSim.Framework;
37 using OpenSim.Framework.Monitoring;
38 using OpenSim.Framework.Serialization;
39 using OpenSim.Framework.Serialization.External;
40 using OpenSim.Region.CoreModules.World.Archiver;
41 using OpenSim.Region.Framework.Scenes;
42 using OpenSim.Services.Interfaces;
43 using Ionic.Zlib;
48 
49 namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver
50 {
52  {
53  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54 
58  public bool SaveAssets { get; set; }
59 
64  public string FilterContent { get; set; }
65 
69  public int CountItems { get; set; }
70 
74  public int CountFiltered { get; set; }
75 
79  private const string STAR_WILDCARD = "*";
80 
81  private InventoryArchiverModule m_module;
82  private UserAccount m_userInfo;
83  private string m_invPath;
86 
90  protected Scene m_scene;
91 
95  protected UUID m_id;
96 
100  protected Dictionary<UUID, int> m_userUuids = new Dictionary<UUID, int>();
101 
105  private Stream m_saveStream;
106 
111  UUID id, InventoryArchiverModule module, Scene scene,
112  UserAccount userInfo, string invPath, string savePath)
113  : this(
114  id,
115  module,
116  scene,
117  userInfo,
118  invPath,
119  new GZipStream(new FileStream(savePath, FileMode.Create), CompressionMode.Compress, CompressionLevel.BestCompression))
120  {
121  }
122 
127  UUID id, InventoryArchiverModule module, Scene scene,
128  UserAccount userInfo, string invPath, Stream saveStream)
129  {
130  m_id = id;
131  m_module = module;
132  m_scene = scene;
133  m_userInfo = userInfo;
134  m_invPath = invPath;
135  m_saveStream = saveStream;
136  m_assetGatherer = new UuidGatherer(m_scene.AssetService);
137 
138  SaveAssets = true;
139  FilterContent = null;
140  }
141 
142  protected void ReceivedAllAssets(ICollection<UUID> assetsFoundUuids, ICollection<UUID> assetsNotFoundUuids, bool timedOut)
143  {
144  Exception reportedException = null;
145  bool succeeded = true;
146 
147  try
148  {
149  m_archiveWriter.Close();
150  }
151  catch (Exception e)
152  {
153  reportedException = e;
154  succeeded = false;
155  }
156  finally
157  {
158  m_saveStream.Close();
159  }
160 
161  if (timedOut)
162  {
163  succeeded = false;
164  reportedException = new Exception("Loading assets timed out");
165  }
166 
167  m_module.TriggerInventoryArchiveSaved(
168  m_id, succeeded, m_userInfo, m_invPath, m_saveStream, reportedException, CountItems, CountFiltered);
169  }
170 
171  protected void SaveInvItem(InventoryItemBase inventoryItem, string path, Dictionary<string, object> options, IUserAccountService userAccountService)
172  {
173  if (options.ContainsKey("exclude"))
174  {
175  if (((List<String>)options["exclude"]).Contains(inventoryItem.Name) ||
176  ((List<String>)options["exclude"]).Contains(inventoryItem.ID.ToString()))
177  {
178  if (options.ContainsKey("verbose"))
179  {
180  m_log.InfoFormat(
181  "[INVENTORY ARCHIVER]: Skipping inventory item {0} {1} at {2}",
182  inventoryItem.Name, inventoryItem.ID, path);
183  }
184 
185  CountFiltered++;
186 
187  return;
188  }
189  }
190 
191  // Check For Permissions Filter Flags
192  if (!CanUserArchiveObject(m_userInfo.PrincipalID, inventoryItem))
193  {
194  m_log.InfoFormat(
195  "[INVENTORY ARCHIVER]: Insufficient permissions, skipping inventory item {0} {1} at {2}",
196  inventoryItem.Name, inventoryItem.ID, path);
197 
198  // Count Items Excluded
199  CountFiltered++;
200 
201  return;
202  }
203 
204  if (options.ContainsKey("verbose"))
205  m_log.InfoFormat(
206  "[INVENTORY ARCHIVER]: Saving item {0} {1} (asset UUID {2})",
207  inventoryItem.ID, inventoryItem.Name, inventoryItem.AssetID);
208 
209  string filename = path + CreateArchiveItemName(inventoryItem);
210 
211  // Record the creator of this item for user record purposes (which might go away soon)
212  m_userUuids[inventoryItem.CreatorIdAsUuid] = 1;
213 
214  string serialization = UserInventoryItemSerializer.Serialize(inventoryItem, options, userAccountService);
215  m_archiveWriter.WriteFile(filename, serialization);
216 
217  AssetType itemAssetType = (AssetType)inventoryItem.AssetType;
218 
219  // Count inventory items (different to asset count)
220  CountItems++;
221 
222  // Don't chase down link asset items as they actually point to their target item IDs rather than an asset
223  if (SaveAssets && itemAssetType != AssetType.Link && itemAssetType != AssetType.LinkFolder)
224  m_assetGatherer.AddForInspection(inventoryItem.AssetID);
225  }
226 
235  protected void SaveInvFolder(
236  InventoryFolderBase inventoryFolder, string path, bool saveThisFolderItself,
237  Dictionary<string, object> options, IUserAccountService userAccountService)
238  {
239  if (options.ContainsKey("excludefolders"))
240  {
241  if (((List<String>)options["excludefolders"]).Contains(inventoryFolder.Name) ||
242  ((List<String>)options["excludefolders"]).Contains(inventoryFolder.ID.ToString()))
243  {
244  if (options.ContainsKey("verbose"))
245  {
246  m_log.InfoFormat(
247  "[INVENTORY ARCHIVER]: Skipping folder {0} at {1}",
248  inventoryFolder.Name, path);
249  }
250  return;
251  }
252  }
253 
254  if (options.ContainsKey("verbose"))
255  m_log.InfoFormat("[INVENTORY ARCHIVER]: Saving folder {0}", inventoryFolder.Name);
256 
257  if (saveThisFolderItself)
258  {
259  path += CreateArchiveFolderName(inventoryFolder);
260 
261  // We need to make sure that we record empty folders
262  m_archiveWriter.WriteDir(path);
263  }
264 
265  InventoryCollection contents
266  = m_scene.InventoryService.GetFolderContent(inventoryFolder.Owner, inventoryFolder.ID);
267 
268  foreach (InventoryFolderBase childFolder in contents.Folders)
269  {
270  SaveInvFolder(childFolder, path, true, options, userAccountService);
271  }
272 
273  foreach (InventoryItemBase item in contents.Items)
274  {
275  SaveInvItem(item, path, options, userAccountService);
276  }
277  }
278 
285  private bool CanUserArchiveObject(UUID UserID, InventoryItemBase InvItem)
286  {
287  if (FilterContent == null)
288  return true;// Default To Allow Export
289 
290  bool permitted = true;
291 
292  bool canCopy = (InvItem.CurrentPermissions & (uint)PermissionMask.Copy) != 0;
293  bool canTransfer = (InvItem.CurrentPermissions & (uint)PermissionMask.Transfer) != 0;
294  bool canMod = (InvItem.CurrentPermissions & (uint)PermissionMask.Modify) != 0;
295 
296  if (FilterContent.Contains("C") && !canCopy)
297  permitted = false;
298 
299  if (FilterContent.Contains("T") && !canTransfer)
300  permitted = false;
301 
302  if (FilterContent.Contains("M") && !canMod)
303  permitted = false;
304 
305  return permitted;
306  }
307 
311  public void Execute(Dictionary<string, object> options, IUserAccountService userAccountService)
312  {
313  if (options.ContainsKey("noassets") && (bool)options["noassets"])
314  SaveAssets = false;
315 
316  // Set Permission filter if flag is set
317  if (options.ContainsKey("checkPermissions"))
318  {
319  Object temp;
320  if (options.TryGetValue("checkPermissions", out temp))
321  FilterContent = temp.ToString().ToUpper();
322  }
323 
324  try
325  {
326  InventoryFolderBase inventoryFolder = null;
327  InventoryItemBase inventoryItem = null;
328  InventoryFolderBase rootFolder = m_scene.InventoryService.GetRootFolder(m_userInfo.PrincipalID);
329 
330  bool saveFolderContentsOnly = false;
331 
332  // Eliminate double slashes and any leading / on the path.
333  string[] components
334  = m_invPath.Split(
335  new string[] { InventoryFolderImpl.PATH_DELIMITER }, StringSplitOptions.RemoveEmptyEntries);
336 
337  int maxComponentIndex = components.Length - 1;
338 
339  // If the path terminates with a STAR then later on we want to archive all nodes in the folder but not the
340  // folder itself. This may get more sophisicated later on
341  if (maxComponentIndex >= 0 && components[maxComponentIndex] == STAR_WILDCARD)
342  {
343  saveFolderContentsOnly = true;
344  maxComponentIndex--;
345  }
346  else if (maxComponentIndex == -1)
347  {
348  // If the user has just specified "/", then don't save the root "My Inventory" folder. This is
349  // more intuitive then requiring the user to specify "/*" for this.
350  saveFolderContentsOnly = true;
351  }
352 
353  m_invPath = String.Empty;
354  for (int i = 0; i <= maxComponentIndex; i++)
355  {
356  m_invPath += components[i] + InventoryFolderImpl.PATH_DELIMITER;
357  }
358 
359  // Annoyingly Split actually returns the original string if the input string consists only of delimiters
360  // Therefore if we still start with a / after the split, then we need the root folder
361  if (m_invPath.Length == 0)
362  {
363  inventoryFolder = rootFolder;
364  }
365  else
366  {
367  m_invPath = m_invPath.Remove(m_invPath.LastIndexOf(InventoryFolderImpl.PATH_DELIMITER));
368  List<InventoryFolderBase> candidateFolders
369  = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, rootFolder, m_invPath);
370  if (candidateFolders.Count > 0)
371  inventoryFolder = candidateFolders[0];
372  }
373 
374  // The path may point to an item instead
375  if (inventoryFolder == null)
376  inventoryItem = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, rootFolder, m_invPath);
377 
378  if (null == inventoryFolder && null == inventoryItem)
379  {
380  // We couldn't find the path indicated
381  string errorMessage = string.Format("Aborted save. Could not find inventory path {0}", m_invPath);
382  Exception e = new InventoryArchiverException(errorMessage);
383  m_module.TriggerInventoryArchiveSaved(m_id, false, m_userInfo, m_invPath, m_saveStream, e, 0, 0);
384  throw e;
385  }
386 
387  m_archiveWriter = new TarArchiveWriter(m_saveStream);
388 
389  m_log.InfoFormat("[INVENTORY ARCHIVER]: Adding control file to archive.");
390 
391  // Write out control file. This has to be done first so that subsequent loaders will see this file first
392  // XXX: I know this is a weak way of doing it since external non-OAR aware tar executables will not do this
393  // not sure how to fix this though, short of going with a completely different file format.
394  m_archiveWriter.WriteFile(ArchiveConstants.CONTROL_FILE_PATH, CreateControlFile(options));
395 
396  if (inventoryFolder != null)
397  {
398  m_log.DebugFormat(
399  "[INVENTORY ARCHIVER]: Found folder {0} {1} at {2}",
400  inventoryFolder.Name,
401  inventoryFolder.ID,
402  m_invPath == String.Empty ? InventoryFolderImpl.PATH_DELIMITER : m_invPath);
403 
404  //recurse through all dirs getting dirs and files
405  SaveInvFolder(inventoryFolder, ArchiveConstants.INVENTORY_PATH, !saveFolderContentsOnly, options, userAccountService);
406  }
407  else if (inventoryItem != null)
408  {
409  m_log.DebugFormat(
410  "[INVENTORY ARCHIVER]: Found item {0} {1} at {2}",
411  inventoryItem.Name, inventoryItem.ID, m_invPath);
412 
413  SaveInvItem(inventoryItem, ArchiveConstants.INVENTORY_PATH, options, userAccountService);
414  }
415 
416  // Don't put all this profile information into the archive right now.
417  //SaveUsers();
418 
419  if (SaveAssets)
420  {
421  m_assetGatherer.GatherAll();
422 
423  m_log.DebugFormat(
424  "[INVENTORY ARCHIVER]: Saving {0} assets for items", m_assetGatherer.GatheredUuids.Count);
425 
426  AssetsRequest ar
427  = new AssetsRequest(
428  new AssetsArchiver(m_archiveWriter),
429  m_assetGatherer.GatheredUuids, m_scene.AssetService,
430  m_scene.UserAccountService, m_scene.RegionInfo.ScopeID,
431  options, ReceivedAllAssets);
432 
433  WorkManager.RunInThread(o => ar.Execute(), null, string.Format("AssetsRequest ({0})", m_scene.Name));
434  }
435  else
436  {
437  m_log.DebugFormat("[INVENTORY ARCHIVER]: Not saving assets since --noassets was specified");
438 
439  ReceivedAllAssets(new List<UUID>(), new List<UUID>(), false);
440  }
441  }
442  catch (Exception)
443  {
444  m_saveStream.Close();
445  throw;
446  }
447  }
448 
452  protected void SaveUsers()
453  {
454  m_log.InfoFormat("[INVENTORY ARCHIVER]: Saving user information for {0} users", m_userUuids.Count);
455 
456  foreach (UUID creatorId in m_userUuids.Keys)
457  {
458  // Record the creator of this item
459  UserAccount creator = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, creatorId);
460 
461  if (creator != null)
462  {
463  m_archiveWriter.WriteFile(
464  ArchiveConstants.USERS_PATH + creator.FirstName + " " + creator.LastName + ".xml",
465  UserProfileSerializer.Serialize(creator.PrincipalID, creator.FirstName, creator.LastName));
466  }
467  else
468  {
469  m_log.WarnFormat("[INVENTORY ARCHIVER]: Failed to get creator profile for {0}", creatorId);
470  }
471  }
472  }
473 
483  public static string CreateArchiveFolderName(InventoryFolderBase folder)
484  {
485  return CreateArchiveFolderName(folder.Name, folder.ID);
486  }
487 
497  public static string CreateArchiveItemName(InventoryItemBase item)
498  {
499  return CreateArchiveItemName(item.Name, item.ID);
500  }
501 
508  public static string CreateArchiveFolderName(string name, UUID id)
509  {
510  return string.Format(
511  "{0}{1}{2}/",
512  InventoryArchiveUtils.EscapeArchivePath(name),
514  id);
515  }
516 
523  public static string CreateArchiveItemName(string name, UUID id)
524  {
525  return string.Format(
526  "{0}{1}{2}.xml",
527  InventoryArchiveUtils.EscapeArchivePath(name),
529  id);
530  }
531 
537  public string CreateControlFile(Dictionary<string, object> options)
538  {
539  int majorVersion, minorVersion;
540 
541  if (options.ContainsKey("home"))
542  {
543  majorVersion = 1;
544  minorVersion = 2;
545  }
546  else
547  {
548  majorVersion = 0;
549  minorVersion = 3;
550  }
551 
552  m_log.InfoFormat("[INVENTORY ARCHIVER]: Creating version {0}.{1} IAR", majorVersion, minorVersion);
553 
554  StringWriter sw = new StringWriter();
555  XmlTextWriter xtw = new XmlTextWriter(sw);
556  xtw.Formatting = Formatting.Indented;
557  xtw.WriteStartDocument();
558  xtw.WriteStartElement("archive");
559  xtw.WriteAttributeString("major_version", majorVersion.ToString());
560  xtw.WriteAttributeString("minor_version", minorVersion.ToString());
561 
562  xtw.WriteElementString("assets_included", SaveAssets.ToString());
563 
564  xtw.WriteEndElement();
565 
566  xtw.Flush();
567  xtw.Close();
568 
569  String s = sw.ToString();
570  sw.Close();
571 
572  return s;
573  }
574  }
575 }
void SaveInvItem(InventoryItemBase inventoryItem, string path, Dictionary< string, object > options, IUserAccountService userAccountService)
Gather uuids for a given entity.
Definition: UuidGatherer.cs:54
void Execute(Dictionary< string, object > options, IUserAccountService userAccountService)
Execute the inventory write request
Constants for the archiving module
InventoryArchiveWriteRequest(UUID id, InventoryArchiverModule module, Scene scene, UserAccount userInfo, string invPath, Stream saveStream)
Constructor
Ionic.Zlib.GZipStream GZipStream
Temporary code to produce a tar archive in tar v7 format
static string CreateArchiveFolderName(InventoryFolderBase folder)
Create the archive name for a particular folder.
OpenSim.Framework.PermissionMask PermissionMask
Encapsulate the asynchronous requests for the assets required for an archive operation ...
This module loads and saves OpenSimulator inventory archives
static string CreateArchiveItemName(InventoryItemBase item)
Create the archive name for a particular item.
Inventory Item - contains all the properties associated with an individual inventory piece...
string CreateControlFile(Dictionary< string, object > options)
Create the control file for the archive
UUID ID
A UUID containing the ID for the inventory node itself
InventoryArchiveWriteRequest(UUID id, InventoryArchiverModule module, Scene scene, UserAccount userInfo, string invPath, string savePath)
Constructor
Ionic.Zlib.CompressionLevel CompressionLevel
static string CreateArchiveFolderName(string name, UUID id)
Create an archive folder name given its constituent components
virtual string Name
The name of the node (64 characters or less)
Ionic.Zlib.CompressionMode CompressionMode
static string CreateArchiveItemName(string name, UUID id)
Create an archive item name given its constituent components
void ReceivedAllAssets(ICollection< UUID > assetsFoundUuids, ICollection< UUID > assetsNotFoundUuids, bool timedOut)
void SaveInvFolder(InventoryFolderBase inventoryFolder, string path, bool saveThisFolderItself, Dictionary< string, object > options, IUserAccountService userAccountService)
Save an inventory folder
OpenSim.Framework.Serialization.TarArchiveWriter TarArchiveWriter
Used to serialize a whole inventory for transfer over the network.