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