InventoryArchiveWriteRequest.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.IO.Compression;
  31. using System.Reflection;
  32. using System.Xml;
  33. using log4net;
  34. using OpenMetaverse;
  35. using OpenSim.Framework;
  36. using OpenSim.Framework.Serialization;
  37. using OpenSim.Framework.Serialization.External;
  38. using OpenSim.Region.CoreModules.World.Archiver;
  39. using OpenSim.Region.Framework.Scenes;
  40. using OpenSim.Services.Interfaces;
  41. using Ionic.Zlib;
  42. using GZipStream = Ionic.Zlib.GZipStream;
  43. using CompressionMode = Ionic.Zlib.CompressionMode;
  44. namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver
  45. {
  46. public class InventoryArchiveWriteRequest
  47. {
  48. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  49. /// <summary>
  50. /// Determine whether this archive will save assets. Default is true.
  51. /// </summary>
  52. public bool SaveAssets { get; set; }
  53. /// <value>
  54. /// Used to select all inventory nodes in a folder but not the folder itself
  55. /// </value>
  56. private const string STAR_WILDCARD = "*";
  57. private InventoryArchiverModule m_module;
  58. private UserAccount m_userInfo;
  59. private string m_invPath;
  60. protected TarArchiveWriter m_archiveWriter;
  61. protected UuidGatherer m_assetGatherer;
  62. /// <value>
  63. /// We only use this to request modules
  64. /// </value>
  65. protected Scene m_scene;
  66. /// <value>
  67. /// ID of this request
  68. /// </value>
  69. protected Guid m_id;
  70. /// <value>
  71. /// Used to collect the uuids of the assets that we need to save into the archive
  72. /// </value>
  73. protected Dictionary<UUID, AssetType> m_assetUuids = new Dictionary<UUID, AssetType>();
  74. /// <value>
  75. /// Used to collect the uuids of the users that we need to save into the archive
  76. /// </value>
  77. protected Dictionary<UUID, int> m_userUuids = new Dictionary<UUID, int>();
  78. /// <value>
  79. /// The stream to which the inventory archive will be saved.
  80. /// </value>
  81. private Stream m_saveStream;
  82. /// <summary>
  83. /// Constructor
  84. /// </summary>
  85. public InventoryArchiveWriteRequest(
  86. Guid id, InventoryArchiverModule module, Scene scene,
  87. UserAccount userInfo, string invPath, string savePath)
  88. : this(
  89. id,
  90. module,
  91. scene,
  92. userInfo,
  93. invPath,
  94. new GZipStream(new FileStream(savePath, FileMode.Create), CompressionMode.Compress, CompressionLevel.BestCompression))
  95. {
  96. }
  97. /// <summary>
  98. /// Constructor
  99. /// </summary>
  100. public InventoryArchiveWriteRequest(
  101. Guid id, InventoryArchiverModule module, Scene scene,
  102. UserAccount userInfo, string invPath, Stream saveStream)
  103. {
  104. m_id = id;
  105. m_module = module;
  106. m_scene = scene;
  107. m_userInfo = userInfo;
  108. m_invPath = invPath;
  109. m_saveStream = saveStream;
  110. m_assetGatherer = new UuidGatherer(m_scene.AssetService);
  111. SaveAssets = true;
  112. }
  113. protected void ReceivedAllAssets(ICollection<UUID> assetsFoundUuids, ICollection<UUID> assetsNotFoundUuids)
  114. {
  115. Exception reportedException = null;
  116. bool succeeded = true;
  117. try
  118. {
  119. m_archiveWriter.Close();
  120. }
  121. catch (Exception e)
  122. {
  123. reportedException = e;
  124. succeeded = false;
  125. }
  126. finally
  127. {
  128. m_saveStream.Close();
  129. }
  130. m_module.TriggerInventoryArchiveSaved(
  131. m_id, succeeded, m_userInfo, m_invPath, m_saveStream, reportedException);
  132. }
  133. protected void SaveInvItem(InventoryItemBase inventoryItem, string path, Dictionary<string, object> options, IUserAccountService userAccountService)
  134. {
  135. if (options.ContainsKey("exclude"))
  136. {
  137. if (((List<String>)options["exclude"]).Contains(inventoryItem.Name) ||
  138. ((List<String>)options["exclude"]).Contains(inventoryItem.ID.ToString()))
  139. {
  140. if (options.ContainsKey("verbose"))
  141. {
  142. m_log.InfoFormat(
  143. "[INVENTORY ARCHIVER]: Skipping inventory item {0} {1} at {2}",
  144. inventoryItem.Name, inventoryItem.ID, path);
  145. }
  146. return;
  147. }
  148. }
  149. if (options.ContainsKey("verbose"))
  150. m_log.InfoFormat(
  151. "[INVENTORY ARCHIVER]: Saving item {0} {1} with asset {2}",
  152. inventoryItem.ID, inventoryItem.Name, inventoryItem.AssetID);
  153. string filename = path + CreateArchiveItemName(inventoryItem);
  154. // Record the creator of this item for user record purposes (which might go away soon)
  155. m_userUuids[inventoryItem.CreatorIdAsUuid] = 1;
  156. string serialization = UserInventoryItemSerializer.Serialize(inventoryItem, options, userAccountService);
  157. m_archiveWriter.WriteFile(filename, serialization);
  158. AssetType itemAssetType = (AssetType)inventoryItem.AssetType;
  159. // Don't chase down link asset items as they actually point to their target item IDs rather than an asset
  160. if (SaveAssets && itemAssetType != AssetType.Link && itemAssetType != AssetType.LinkFolder)
  161. m_assetGatherer.GatherAssetUuids(inventoryItem.AssetID, (AssetType)inventoryItem.AssetType, m_assetUuids);
  162. }
  163. /// <summary>
  164. /// Save an inventory folder
  165. /// </summary>
  166. /// <param name="inventoryFolder">The inventory folder to save</param>
  167. /// <param name="path">The path to which the folder should be saved</param>
  168. /// <param name="saveThisFolderItself">If true, save this folder itself. If false, only saves contents</param>
  169. /// <param name="options"></param>
  170. /// <param name="userAccountService"></param>
  171. protected void SaveInvFolder(
  172. InventoryFolderBase inventoryFolder, string path, bool saveThisFolderItself,
  173. Dictionary<string, object> options, IUserAccountService userAccountService)
  174. {
  175. if (options.ContainsKey("excludefolders"))
  176. {
  177. if (((List<String>)options["excludefolders"]).Contains(inventoryFolder.Name) ||
  178. ((List<String>)options["excludefolders"]).Contains(inventoryFolder.ID.ToString()))
  179. {
  180. if (options.ContainsKey("verbose"))
  181. {
  182. m_log.InfoFormat(
  183. "[INVENTORY ARCHIVER]: Skipping folder {0} at {1}",
  184. inventoryFolder.Name, path);
  185. }
  186. return;
  187. }
  188. }
  189. if (options.ContainsKey("verbose"))
  190. m_log.InfoFormat("[INVENTORY ARCHIVER]: Saving folder {0}", inventoryFolder.Name);
  191. if (saveThisFolderItself)
  192. {
  193. path += CreateArchiveFolderName(inventoryFolder);
  194. // We need to make sure that we record empty folders
  195. m_archiveWriter.WriteDir(path);
  196. }
  197. InventoryCollection contents
  198. = m_scene.InventoryService.GetFolderContent(inventoryFolder.Owner, inventoryFolder.ID);
  199. foreach (InventoryFolderBase childFolder in contents.Folders)
  200. {
  201. SaveInvFolder(childFolder, path, true, options, userAccountService);
  202. }
  203. foreach (InventoryItemBase item in contents.Items)
  204. {
  205. SaveInvItem(item, path, options, userAccountService);
  206. }
  207. }
  208. /// <summary>
  209. /// Execute the inventory write request
  210. /// </summary>
  211. public void Execute(Dictionary<string, object> options, IUserAccountService userAccountService)
  212. {
  213. if (options.ContainsKey("noassets") && (bool)options["noassets"])
  214. SaveAssets = false;
  215. try
  216. {
  217. InventoryFolderBase inventoryFolder = null;
  218. InventoryItemBase inventoryItem = null;
  219. InventoryFolderBase rootFolder = m_scene.InventoryService.GetRootFolder(m_userInfo.PrincipalID);
  220. bool saveFolderContentsOnly = false;
  221. // Eliminate double slashes and any leading / on the path.
  222. string[] components
  223. = m_invPath.Split(
  224. new string[] { InventoryFolderImpl.PATH_DELIMITER }, StringSplitOptions.RemoveEmptyEntries);
  225. int maxComponentIndex = components.Length - 1;
  226. // If the path terminates with a STAR then later on we want to archive all nodes in the folder but not the
  227. // folder itself. This may get more sophisicated later on
  228. if (maxComponentIndex >= 0 && components[maxComponentIndex] == STAR_WILDCARD)
  229. {
  230. saveFolderContentsOnly = true;
  231. maxComponentIndex--;
  232. }
  233. m_invPath = String.Empty;
  234. for (int i = 0; i <= maxComponentIndex; i++)
  235. {
  236. m_invPath += components[i] + InventoryFolderImpl.PATH_DELIMITER;
  237. }
  238. // Annoyingly Split actually returns the original string if the input string consists only of delimiters
  239. // Therefore if we still start with a / after the split, then we need the root folder
  240. if (m_invPath.Length == 0)
  241. {
  242. inventoryFolder = rootFolder;
  243. }
  244. else
  245. {
  246. m_invPath = m_invPath.Remove(m_invPath.LastIndexOf(InventoryFolderImpl.PATH_DELIMITER));
  247. List<InventoryFolderBase> candidateFolders
  248. = InventoryArchiveUtils.FindFolderByPath(m_scene.InventoryService, rootFolder, m_invPath);
  249. if (candidateFolders.Count > 0)
  250. inventoryFolder = candidateFolders[0];
  251. }
  252. // The path may point to an item instead
  253. if (inventoryFolder == null)
  254. inventoryItem = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, rootFolder, m_invPath);
  255. if (null == inventoryFolder && null == inventoryItem)
  256. {
  257. // We couldn't find the path indicated
  258. string errorMessage = string.Format("Aborted save. Could not find inventory path {0}", m_invPath);
  259. Exception e = new InventoryArchiverException(errorMessage);
  260. m_module.TriggerInventoryArchiveSaved(m_id, false, m_userInfo, m_invPath, m_saveStream, e);
  261. throw e;
  262. }
  263. m_archiveWriter = new TarArchiveWriter(m_saveStream);
  264. m_log.InfoFormat("[INVENTORY ARCHIVER]: Adding control file to archive.");
  265. // Write out control file. This has to be done first so that subsequent loaders will see this file first
  266. // XXX: I know this is a weak way of doing it since external non-OAR aware tar executables will not do this
  267. // not sure how to fix this though, short of going with a completely different file format.
  268. m_archiveWriter.WriteFile(ArchiveConstants.CONTROL_FILE_PATH, CreateControlFile(options));
  269. if (inventoryFolder != null)
  270. {
  271. m_log.DebugFormat(
  272. "[INVENTORY ARCHIVER]: Found folder {0} {1} at {2}",
  273. inventoryFolder.Name,
  274. inventoryFolder.ID,
  275. m_invPath == String.Empty ? InventoryFolderImpl.PATH_DELIMITER : m_invPath);
  276. //recurse through all dirs getting dirs and files
  277. SaveInvFolder(inventoryFolder, ArchiveConstants.INVENTORY_PATH, !saveFolderContentsOnly, options, userAccountService);
  278. }
  279. else if (inventoryItem != null)
  280. {
  281. m_log.DebugFormat(
  282. "[INVENTORY ARCHIVER]: Found item {0} {1} at {2}",
  283. inventoryItem.Name, inventoryItem.ID, m_invPath);
  284. SaveInvItem(inventoryItem, ArchiveConstants.INVENTORY_PATH, options, userAccountService);
  285. }
  286. // Don't put all this profile information into the archive right now.
  287. //SaveUsers();
  288. if (SaveAssets)
  289. {
  290. m_log.DebugFormat("[INVENTORY ARCHIVER]: Saving {0} assets for items", m_assetUuids.Count);
  291. new AssetsRequest(
  292. new AssetsArchiver(m_archiveWriter),
  293. m_assetUuids, m_scene.AssetService,
  294. m_scene.UserAccountService, m_scene.RegionInfo.ScopeID,
  295. options, ReceivedAllAssets).Execute();
  296. }
  297. else
  298. {
  299. m_log.DebugFormat("[INVENTORY ARCHIVER]: Not saving assets since --noassets was specified");
  300. ReceivedAllAssets(new List<UUID>(), new List<UUID>());
  301. }
  302. }
  303. catch (Exception)
  304. {
  305. m_saveStream.Close();
  306. throw;
  307. }
  308. }
  309. /// <summary>
  310. /// Save information for the users that we've collected.
  311. /// </summary>
  312. protected void SaveUsers()
  313. {
  314. m_log.InfoFormat("[INVENTORY ARCHIVER]: Saving user information for {0} users", m_userUuids.Count);
  315. foreach (UUID creatorId in m_userUuids.Keys)
  316. {
  317. // Record the creator of this item
  318. UserAccount creator = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, creatorId);
  319. if (creator != null)
  320. {
  321. m_archiveWriter.WriteFile(
  322. ArchiveConstants.USERS_PATH + creator.FirstName + " " + creator.LastName + ".xml",
  323. UserProfileSerializer.Serialize(creator.PrincipalID, creator.FirstName, creator.LastName));
  324. }
  325. else
  326. {
  327. m_log.WarnFormat("[INVENTORY ARCHIVER]: Failed to get creator profile for {0}", creatorId);
  328. }
  329. }
  330. }
  331. /// <summary>
  332. /// Create the archive name for a particular folder.
  333. /// </summary>
  334. ///
  335. /// These names are prepended with an inventory folder's UUID so that more than one folder can have the
  336. /// same name
  337. ///
  338. /// <param name="folder"></param>
  339. /// <returns></returns>
  340. public static string CreateArchiveFolderName(InventoryFolderBase folder)
  341. {
  342. return CreateArchiveFolderName(folder.Name, folder.ID);
  343. }
  344. /// <summary>
  345. /// Create the archive name for a particular item.
  346. /// </summary>
  347. ///
  348. /// These names are prepended with an inventory item's UUID so that more than one item can have the
  349. /// same name
  350. ///
  351. /// <param name="item"></param>
  352. /// <returns></returns>
  353. public static string CreateArchiveItemName(InventoryItemBase item)
  354. {
  355. return CreateArchiveItemName(item.Name, item.ID);
  356. }
  357. /// <summary>
  358. /// Create an archive folder name given its constituent components
  359. /// </summary>
  360. /// <param name="name"></param>
  361. /// <param name="id"></param>
  362. /// <returns></returns>
  363. public static string CreateArchiveFolderName(string name, UUID id)
  364. {
  365. return string.Format(
  366. "{0}{1}{2}/",
  367. InventoryArchiveUtils.EscapeArchivePath(name),
  368. ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR,
  369. id);
  370. }
  371. /// <summary>
  372. /// Create an archive item name given its constituent components
  373. /// </summary>
  374. /// <param name="name"></param>
  375. /// <param name="id"></param>
  376. /// <returns></returns>
  377. public static string CreateArchiveItemName(string name, UUID id)
  378. {
  379. return string.Format(
  380. "{0}{1}{2}.xml",
  381. InventoryArchiveUtils.EscapeArchivePath(name),
  382. ArchiveConstants.INVENTORY_NODE_NAME_COMPONENT_SEPARATOR,
  383. id);
  384. }
  385. /// <summary>
  386. /// Create the control file for the archive
  387. /// </summary>
  388. /// <param name="options"></param>
  389. /// <returns></returns>
  390. public string CreateControlFile(Dictionary<string, object> options)
  391. {
  392. int majorVersion, minorVersion;
  393. if (options.ContainsKey("home"))
  394. {
  395. majorVersion = 1;
  396. minorVersion = 2;
  397. }
  398. else
  399. {
  400. majorVersion = 0;
  401. minorVersion = 3;
  402. }
  403. m_log.InfoFormat("[INVENTORY ARCHIVER]: Creating version {0}.{1} IAR", majorVersion, minorVersion);
  404. StringWriter sw = new StringWriter();
  405. XmlTextWriter xtw = new XmlTextWriter(sw);
  406. xtw.Formatting = Formatting.Indented;
  407. xtw.WriteStartDocument();
  408. xtw.WriteStartElement("archive");
  409. xtw.WriteAttributeString("major_version", majorVersion.ToString());
  410. xtw.WriteAttributeString("minor_version", minorVersion.ToString());
  411. xtw.WriteElementString("assets_included", SaveAssets.ToString());
  412. xtw.WriteEndElement();
  413. xtw.Flush();
  414. xtw.Close();
  415. String s = sw.ToString();
  416. sw.Close();
  417. return s;
  418. }
  419. }
  420. }