clockwerk-opensim-stable – Blame information for rev 1

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