opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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.Text.RegularExpressions;
34 using System.Threading;
35 using System.Xml;
36 using log4net;
37 using OpenMetaverse;
38 using OpenSim.Framework;
39 using OpenSim.Framework.Serialization;
40 using OpenSim.Region.CoreModules.World.Terrain;
41 using OpenSim.Region.Framework.Interfaces;
42 using OpenSim.Region.Framework.Scenes;
43 using Ionic.Zlib;
44 using GZipStream = Ionic.Zlib.GZipStream;
45 using CompressionMode = Ionic.Zlib.CompressionMode;
46 using OpenSim.Framework.Serialization.External;
47 using PermissionMask = OpenSim.Framework.PermissionMask;
48  
49 namespace OpenSim.Region.CoreModules.World.Archiver
50 {
51 /// <summary>
52 /// Prepare to write out an archive.
53 /// </summary>
54 public class ArchiveWriteRequest
55 {
56 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
57  
58 /// <summary>
59 /// The minimum major version of OAR that we can write.
60 /// </summary>
61 public static int MIN_MAJOR_VERSION = 0;
62  
63 /// <summary>
64 /// The maximum major version of OAR that we can write.
65 /// </summary>
66 public static int MAX_MAJOR_VERSION = 1;
67  
68 /// <summary>
69 /// Whether we're saving a multi-region archive.
70 /// </summary>
71 public bool MultiRegionFormat { get; set; }
72  
73 /// <summary>
74 /// Determine whether this archive will save assets. Default is true.
75 /// </summary>
76 public bool SaveAssets { get; set; }
77  
78 /// <summary>
79 /// Determines which objects will be included in the archive, according to their permissions.
80 /// Default is null, meaning no permission checks.
81 /// </summary>
82 public string CheckPermissions { get; set; }
83  
84 protected Scene m_rootScene;
85 protected Stream m_saveStream;
86 protected TarArchiveWriter m_archiveWriter;
87 protected Guid m_requestId;
88 protected Dictionary<string, object> m_options;
89  
90 /// <summary>
91 /// Constructor
92 /// </summary>
93 /// <param name="module">Calling module</param>
94 /// <param name="savePath">The path to which to save data.</param>
95 /// <param name="requestId">The id associated with this request</param>
96 /// <exception cref="System.IO.IOException">
97 /// If there was a problem opening a stream for the file specified by the savePath
98 /// </exception>
99 public ArchiveWriteRequest(Scene scene, string savePath, Guid requestId) : this(scene, requestId)
100 {
101 try
102 {
103 m_saveStream = new GZipStream(new FileStream(savePath, FileMode.Create), CompressionMode.Compress, CompressionLevel.BestCompression);
104 }
105 catch (EntryPointNotFoundException e)
106 {
107 m_log.ErrorFormat(
108 "[ARCHIVER]: Mismatch between Mono and zlib1g library version when trying to create compression stream."
109 + "If you've manually installed Mono, have you appropriately updated zlib1g as well?");
110 m_log.ErrorFormat("{0} {1}", e.Message, e.StackTrace);
111 }
112 }
113  
114 /// <summary>
115 /// Constructor.
116 /// </summary>
117 /// <param name="scene">The root scene to archive</param>
118 /// <param name="saveStream">The stream to which to save data.</param>
119 /// <param name="requestId">The id associated with this request</param>
120 public ArchiveWriteRequest(Scene scene, Stream saveStream, Guid requestId) : this(scene, requestId)
121 {
122 m_saveStream = saveStream;
123 }
124  
125 protected ArchiveWriteRequest(Scene scene, Guid requestId)
126 {
127 m_rootScene = scene;
128 m_requestId = requestId;
129 m_archiveWriter = null;
130  
131 MultiRegionFormat = false;
132 SaveAssets = true;
133 CheckPermissions = null;
134 }
135  
136 /// <summary>
137 /// Archive the region requested.
138 /// </summary>
139 /// <exception cref="System.IO.IOException">if there was an io problem with creating the file</exception>
140 public void ArchiveRegion(Dictionary<string, object> options)
141 {
142 m_options = options;
143  
144 if (options.ContainsKey("all") && (bool)options["all"])
145 MultiRegionFormat = true;
146  
147 if (options.ContainsKey("noassets") && (bool)options["noassets"])
148 SaveAssets = false;
149  
150 Object temp;
151 if (options.TryGetValue("checkPermissions", out temp))
152 CheckPermissions = (string)temp;
153  
154  
155 // Find the regions to archive
156 ArchiveScenesGroup scenesGroup = new ArchiveScenesGroup();
157 if (MultiRegionFormat)
158 {
159 m_log.InfoFormat("[ARCHIVER]: Saving {0} regions", SceneManager.Instance.Scenes.Count);
160 SceneManager.Instance.ForEachScene(delegate(Scene scene)
161 {
162 scenesGroup.AddScene(scene);
163 });
164 }
165 else
166 {
167 scenesGroup.AddScene(m_rootScene);
168 }
169 scenesGroup.CalcSceneLocations();
170  
171 m_archiveWriter = new TarArchiveWriter(m_saveStream);
172  
173 try
174 {
175 // Write out control file. It should be first so that it will be found ASAP when loading the file.
176 m_archiveWriter.WriteFile(ArchiveConstants.CONTROL_FILE_PATH, CreateControlFile(scenesGroup));
177 m_log.InfoFormat("[ARCHIVER]: Added control file to archive.");
178  
179 // Archive the regions
180  
181 Dictionary<UUID, sbyte> assetUuids = new Dictionary<UUID, sbyte>();
182  
183 scenesGroup.ForEachScene(delegate(Scene scene)
184 {
185 string regionDir = MultiRegionFormat ? scenesGroup.GetRegionDir(scene.RegionInfo.RegionID) : "";
186 ArchiveOneRegion(scene, regionDir, assetUuids);
187 });
188  
189 // Archive the assets
190  
191 if (SaveAssets)
192 {
193 m_log.DebugFormat("[ARCHIVER]: Saving {0} assets", assetUuids.Count);
194  
195 // Asynchronously request all the assets required to perform this archive operation
196 AssetsRequest ar
197 = new AssetsRequest(
198 new AssetsArchiver(m_archiveWriter), assetUuids,
199 m_rootScene.AssetService, m_rootScene.UserAccountService,
200 m_rootScene.RegionInfo.ScopeID, options, ReceivedAllAssets);
201  
202 Util.FireAndForget(o => ar.Execute());
203  
204 // CloseArchive() will be called from ReceivedAllAssets()
205 }
206 else
207 {
208 m_log.DebugFormat("[ARCHIVER]: Not saving assets since --noassets was specified");
209 CloseArchive(string.Empty);
210 }
211 }
212 catch (Exception e)
213 {
214 CloseArchive(e.Message);
215 throw;
216 }
217 }
218  
219 private void ArchiveOneRegion(Scene scene, string regionDir, Dictionary<UUID, sbyte> assetUuids)
220 {
221 m_log.InfoFormat("[ARCHIVER]: Writing region {0}", scene.RegionInfo.RegionName);
222  
223 EntityBase[] entities = scene.GetEntities();
224 List<SceneObjectGroup> sceneObjects = new List<SceneObjectGroup>();
225  
226 int numObjectsSkippedPermissions = 0;
227  
228 // Filter entities so that we only have scene objects.
229 // FIXME: Would be nicer to have this as a proper list in SceneGraph, since lots of methods
230 // end up having to do this
231 IPermissionsModule permissionsModule = scene.RequestModuleInterface<IPermissionsModule>();
232 foreach (EntityBase entity in entities)
233 {
234 if (entity is SceneObjectGroup)
235 {
236 SceneObjectGroup sceneObject = (SceneObjectGroup)entity;
237  
238 if (!sceneObject.IsDeleted && !sceneObject.IsAttachment)
239 {
240 if (!CanUserArchiveObject(scene.RegionInfo.EstateSettings.EstateOwner, sceneObject, CheckPermissions, permissionsModule))
241 {
242 // The user isn't allowed to copy/transfer this object, so it will not be included in the OAR.
243 ++numObjectsSkippedPermissions;
244 }
245 else
246 {
247 sceneObjects.Add(sceneObject);
248 }
249 }
250 }
251 }
252  
253 if (SaveAssets)
254 {
255 UuidGatherer assetGatherer = new UuidGatherer(scene.AssetService);
256 int prevAssets = assetUuids.Count;
257  
258 foreach (SceneObjectGroup sceneObject in sceneObjects)
259 {
260 assetGatherer.GatherAssetUuids(sceneObject, assetUuids);
261 }
262  
263 m_log.DebugFormat(
264 "[ARCHIVER]: {0} scene objects to serialize requiring save of {1} assets",
265 sceneObjects.Count, assetUuids.Count - prevAssets);
266 }
267  
268 if (numObjectsSkippedPermissions > 0)
269 {
270 m_log.DebugFormat(
271 "[ARCHIVER]: {0} scene objects skipped due to lack of permissions",
272 numObjectsSkippedPermissions);
273 }
274  
275 // Make sure that we also request terrain texture assets
276 RegionSettings regionSettings = scene.RegionInfo.RegionSettings;
277  
278 if (regionSettings.TerrainTexture1 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_1)
279 assetUuids[regionSettings.TerrainTexture1] = (sbyte)AssetType.Texture;
280  
281 if (regionSettings.TerrainTexture2 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_2)
282 assetUuids[regionSettings.TerrainTexture2] = (sbyte)AssetType.Texture;
283  
284 if (regionSettings.TerrainTexture3 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_3)
285 assetUuids[regionSettings.TerrainTexture3] = (sbyte)AssetType.Texture;
286  
287 if (regionSettings.TerrainTexture4 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_4)
288 assetUuids[regionSettings.TerrainTexture4] = (sbyte)AssetType.Texture;
289  
290 Save(scene, sceneObjects, regionDir);
291 }
292  
293 /// <summary>
294 /// Checks whether the user has permission to export an object group to an OAR.
295 /// </summary>
296 /// <param name="user">The user</param>
297 /// <param name="objGroup">The object group</param>
298 /// <param name="checkPermissions">Which permissions to check: "C" = Copy, "T" = Transfer</param>
299 /// <param name="permissionsModule">The scene's permissions module</param>
300 /// <returns>Whether the user is allowed to export the object to an OAR</returns>
301 private bool CanUserArchiveObject(UUID user, SceneObjectGroup objGroup, string checkPermissions, IPermissionsModule permissionsModule)
302 {
303 if (checkPermissions == null)
304 return true;
305  
306 if (permissionsModule == null)
307 return true; // this shouldn't happen
308  
309 // Check whether the user is permitted to export all of the parts in the SOG. If any
310 // part can't be exported then the entire SOG can't be exported.
311  
312 bool permitted = true;
313 //int primNumber = 1;
314  
315 foreach (SceneObjectPart obj in objGroup.Parts)
316 {
317 uint perm;
318 PermissionClass permissionClass = permissionsModule.GetPermissionClass(user, obj);
319 switch (permissionClass)
320 {
321 case PermissionClass.Owner:
322 perm = obj.BaseMask;
323 break;
324 case PermissionClass.Group:
325 perm = obj.GroupMask | obj.EveryoneMask;
326 break;
327 case PermissionClass.Everyone:
328 default:
329 perm = obj.EveryoneMask;
330 break;
331 }
332  
333 bool canCopy = (perm & (uint)PermissionMask.Copy) != 0;
334 bool canTransfer = (perm & (uint)PermissionMask.Transfer) != 0;
335  
336 // Special case: if Everyone can copy the object then this implies it can also be
337 // Transferred.
338 // However, if the user is the Owner then we don't check EveryoneMask, because it seems that the mask
339 // always (incorrectly) includes the Copy bit set in this case. But that's a mistake: the viewer
340 // does NOT show that the object has Everyone-Copy permissions, and doesn't allow it to be copied.
341 if (permissionClass != PermissionClass.Owner)
342 canTransfer |= (obj.EveryoneMask & (uint)PermissionMask.Copy) != 0;
343  
344 bool partPermitted = true;
345 if (checkPermissions.Contains("C") && !canCopy)
346 partPermitted = false;
347 if (checkPermissions.Contains("T") && !canTransfer)
348 partPermitted = false;
349  
350 // If the user is the Creator of the object then it can always be included in the OAR
351 bool creator = (obj.CreatorID.Guid == user.Guid);
352 if (creator)
353 partPermitted = true;
354  
355 //string name = (objGroup.PrimCount == 1) ? objGroup.Name : string.Format("{0} ({1}/{2})", obj.Name, primNumber, objGroup.PrimCount);
356 //m_log.DebugFormat("[ARCHIVER]: Object permissions: {0}: Base={1:X4}, Owner={2:X4}, Everyone={3:X4}, permissionClass={4}, checkPermissions={5}, canCopy={6}, canTransfer={7}, creator={8}, permitted={9}",
357 // name, obj.BaseMask, obj.OwnerMask, obj.EveryoneMask,
358 // permissionClass, checkPermissions, canCopy, canTransfer, creator, partPermitted);
359  
360 if (!partPermitted)
361 {
362 permitted = false;
363 break;
364 }
365  
366 //++primNumber;
367 }
368  
369 return permitted;
370 }
371  
372 /// <summary>
373 /// Create the control file.
374 /// </summary>
375 /// <returns></returns>
376 public string CreateControlFile(ArchiveScenesGroup scenesGroup)
377 {
378 int majorVersion;
379 int minorVersion;
380  
381 if (MultiRegionFormat)
382 {
383 majorVersion = MAX_MAJOR_VERSION;
384 minorVersion = 0;
385 }
386 else
387 {
388 // To support older versions of OpenSim, we continue to create single-region OARs
389 // using the old file format. In the future this format will be discontinued.
390 majorVersion = 0;
391 minorVersion = 8;
392 }
393 //
394 // if (m_options.ContainsKey("version"))
395 // {
396 // string[] parts = m_options["version"].ToString().Split('.');
397 // if (parts.Length >= 1)
398 // {
399 // majorVersion = Int32.Parse(parts[0]);
400 //
401 // if (parts.Length >= 2)
402 // minorVersion = Int32.Parse(parts[1]);
403 // }
404 // }
405 //
406 // if (majorVersion < MIN_MAJOR_VERSION || majorVersion > MAX_MAJOR_VERSION)
407 // {
408 // throw new Exception(
409 // string.Format(
410 // "OAR version number for save must be between {0} and {1}",
411 // MIN_MAJOR_VERSION, MAX_MAJOR_VERSION));
412 // }
413 // else if (majorVersion == MAX_MAJOR_VERSION)
414 // {
415 // // Force 1.0
416 // minorVersion = 0;
417 // }
418 // else if (majorVersion == MIN_MAJOR_VERSION)
419 // {
420 // // Force 0.4
421 // minorVersion = 4;
422 // }
423  
424 m_log.InfoFormat("[ARCHIVER]: Creating version {0}.{1} OAR", majorVersion, minorVersion);
425 if (majorVersion == 1)
426 {
427 m_log.WarnFormat("[ARCHIVER]: Please be aware that version 1.0 OARs are not compatible with OpenSim versions prior to 0.7.4. Do not use the --all option if you want to produce a compatible OAR");
428 }
429  
430 String s;
431  
432 using (StringWriter sw = new StringWriter())
433 {
434 using (XmlTextWriter xtw = new XmlTextWriter(sw))
435 {
436 xtw.Formatting = Formatting.Indented;
437 xtw.WriteStartDocument();
438 xtw.WriteStartElement("archive");
439 xtw.WriteAttributeString("major_version", majorVersion.ToString());
440 xtw.WriteAttributeString("minor_version", minorVersion.ToString());
441  
442 xtw.WriteStartElement("creation_info");
443 DateTime now = DateTime.UtcNow;
444 TimeSpan t = now - new DateTime(1970, 1, 1);
445 xtw.WriteElementString("datetime", ((int)t.TotalSeconds).ToString());
446 if (!MultiRegionFormat)
447 xtw.WriteElementString("id", m_rootScene.RegionInfo.RegionID.ToString());
448 xtw.WriteEndElement();
449  
450 xtw.WriteElementString("assets_included", SaveAssets.ToString());
451  
452 if (MultiRegionFormat)
453 {
454 WriteRegionsManifest(scenesGroup, xtw);
455 }
456 else
457 {
458 xtw.WriteStartElement("region_info");
459 WriteRegionInfo(m_rootScene, xtw);
460 xtw.WriteEndElement();
461 }
462  
463 xtw.WriteEndElement();
464  
465 xtw.Flush();
466 }
467  
468 s = sw.ToString();
469 }
470  
471 return s;
472 }
473  
474 /// <summary>
475 /// Writes the list of regions included in a multi-region OAR.
476 /// </summary>
477 private static void WriteRegionsManifest(ArchiveScenesGroup scenesGroup, XmlTextWriter xtw)
478 {
479 xtw.WriteStartElement("regions");
480  
481 // Write the regions in order: rows from South to North, then regions from West to East.
482 // The list of regions can have "holes"; we write empty elements in their position.
483  
484 for (uint y = (uint)scenesGroup.Rect.Top; y < scenesGroup.Rect.Bottom; ++y)
485 {
486 SortedDictionary<uint, Scene> row;
487 if (scenesGroup.Regions.TryGetValue(y, out row))
488 {
489 xtw.WriteStartElement("row");
490  
491 for (uint x = (uint)scenesGroup.Rect.Left; x < scenesGroup.Rect.Right; ++x)
492 {
493 Scene scene;
494 if (row.TryGetValue(x, out scene))
495 {
496 xtw.WriteStartElement("region");
497 xtw.WriteElementString("id", scene.RegionInfo.RegionID.ToString());
498 xtw.WriteElementString("dir", scenesGroup.GetRegionDir(scene.RegionInfo.RegionID));
499 WriteRegionInfo(scene, xtw);
500 xtw.WriteEndElement();
501 }
502 else
503 {
504 // Write a placeholder for a missing region
505 xtw.WriteElementString("region", "");
506 }
507 }
508  
509 xtw.WriteEndElement();
510 }
511 else
512 {
513 // Write a placeholder for a missing row
514 xtw.WriteElementString("row", "");
515 }
516 }
517  
518 xtw.WriteEndElement(); // "regions"
519 }
520  
521 protected static void WriteRegionInfo(Scene scene, XmlTextWriter xtw)
522 {
523 bool isMegaregion;
524 Vector2 size;
525  
526 IRegionCombinerModule rcMod = scene.RequestModuleInterface<IRegionCombinerModule>();
527  
528 if (rcMod != null)
529 isMegaregion = rcMod.IsRootForMegaregion(scene.RegionInfo.RegionID);
530 else
531 isMegaregion = false;
532  
533 if (isMegaregion)
534 size = rcMod.GetSizeOfMegaregion(scene.RegionInfo.RegionID);
535 else
536 size = new Vector2((float)scene.RegionInfo.RegionSizeX, (float)scene.RegionInfo.RegionSizeY);
537  
538 xtw.WriteElementString("is_megaregion", isMegaregion.ToString());
539 xtw.WriteElementString("size_in_meters", string.Format("{0},{1}", size.X, size.Y));
540 }
541  
542 protected void Save(Scene scene, List<SceneObjectGroup> sceneObjects, string regionDir)
543 {
544 if (regionDir != string.Empty)
545 regionDir = ArchiveConstants.REGIONS_PATH + regionDir + "/";
546  
547 m_log.InfoFormat("[ARCHIVER]: Adding region settings to archive.");
548  
549 // Write out region settings
550 string settingsPath = String.Format("{0}{1}{2}.xml",
551 regionDir, ArchiveConstants.SETTINGS_PATH, scene.RegionInfo.RegionName);
552 m_archiveWriter.WriteFile(settingsPath, RegionSettingsSerializer.Serialize(scene.RegionInfo.RegionSettings));
553  
554 m_log.InfoFormat("[ARCHIVER]: Adding parcel settings to archive.");
555  
556 // Write out land data (aka parcel) settings
557 List<ILandObject> landObjects = scene.LandChannel.AllParcels();
558 foreach (ILandObject lo in landObjects)
559 {
560 LandData landData = lo.LandData;
561 string landDataPath
562 = String.Format("{0}{1}", regionDir, ArchiveConstants.CreateOarLandDataPath(landData));
563 m_archiveWriter.WriteFile(landDataPath, LandDataSerializer.Serialize(landData, m_options));
564 }
565  
566 m_log.InfoFormat("[ARCHIVER]: Adding terrain information to archive.");
567  
568 // Write out terrain
569 string terrainPath = String.Format("{0}{1}{2}.r32",
570 regionDir, ArchiveConstants.TERRAINS_PATH, scene.RegionInfo.RegionName);
571  
572 MemoryStream ms = new MemoryStream();
573 scene.RequestModuleInterface<ITerrainModule>().SaveToStream(terrainPath, ms);
574 m_archiveWriter.WriteFile(terrainPath, ms.ToArray());
575 ms.Close();
576  
577 m_log.InfoFormat("[ARCHIVER]: Adding scene objects to archive.");
578  
579 // Write out scene object metadata
580 IRegionSerialiserModule serializer = scene.RequestModuleInterface<IRegionSerialiserModule>();
581 foreach (SceneObjectGroup sceneObject in sceneObjects)
582 {
583 //m_log.DebugFormat("[ARCHIVER]: Saving {0} {1}, {2}", entity.Name, entity.UUID, entity.GetType());
584  
585 string serializedObject = serializer.SerializeGroupToXml2(sceneObject, m_options);
586 string objectPath = string.Format("{0}{1}", regionDir, ArchiveHelpers.CreateObjectPath(sceneObject));
587 m_archiveWriter.WriteFile(objectPath, serializedObject);
588 }
589 }
590  
591 protected void ReceivedAllAssets(ICollection<UUID> assetsFoundUuids, ICollection<UUID> assetsNotFoundUuids, bool timedOut)
592 {
593 string errorMessage;
594  
595 if (timedOut)
596 {
597 errorMessage = "Loading assets timed out";
598 }
599 else
600 {
601 foreach (UUID uuid in assetsNotFoundUuids)
602 {
603 m_log.DebugFormat("[ARCHIVER]: Could not find asset {0}", uuid);
604 }
605  
606 // m_log.InfoFormat(
607 // "[ARCHIVER]: Received {0} of {1} assets requested",
608 // assetsFoundUuids.Count, assetsFoundUuids.Count + assetsNotFoundUuids.Count);
609  
610 errorMessage = String.Empty;
611 }
612  
613 CloseArchive(errorMessage);
614 }
615  
616 /// <summary>
617 /// Closes the archive and notifies that we're done.
618 /// </summary>
619 /// <param name="errorMessage">The error that occurred, or empty for success</param>
620 protected void CloseArchive(string errorMessage)
621 {
622 try
623 {
624 if (m_archiveWriter != null)
625 m_archiveWriter.Close();
626 m_saveStream.Close();
627 }
628 catch (Exception e)
629 {
630 m_log.Error(string.Format("[ARCHIVER]: Error closing archive: {0} ", e.Message), e);
631 if (errorMessage == string.Empty)
632 errorMessage = e.Message;
633 }
634  
635 m_log.InfoFormat("[ARCHIVER]: Finished writing out OAR for {0}", m_rootScene.RegionInfo.RegionName);
636  
637 m_rootScene.EventManager.TriggerOarFileSaved(m_requestId, errorMessage);
638 }
639 }
640 }