aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/Avatar/UserProfiles
diff options
context:
space:
mode:
authorDavid Walter Seikel2016-11-03 21:44:39 +1000
committerDavid Walter Seikel2016-11-03 21:44:39 +1000
commit134f86e8d5c414409631b25b8c6f0ee45fbd8631 (patch)
tree216b89d3fb89acfb81be1e440c25c41ab09fa96d /OpenSim/Region/CoreModules/Avatar/UserProfiles
parentMore changing to production grid. Double oops. (diff)
downloadopensim-SC_OLD-134f86e8d5c414409631b25b8c6f0ee45fbd8631.zip
opensim-SC_OLD-134f86e8d5c414409631b25b8c6f0ee45fbd8631.tar.gz
opensim-SC_OLD-134f86e8d5c414409631b25b8c6f0ee45fbd8631.tar.bz2
opensim-SC_OLD-134f86e8d5c414409631b25b8c6f0ee45fbd8631.tar.xz
Initial update to OpenSim 0.8.2.1 source code.
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/UserProfiles')
-rw-r--r--OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs1406
1 files changed, 1406 insertions, 0 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs b/OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs
new file mode 100644
index 0000000..c20369c
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs
@@ -0,0 +1,1406 @@
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
28using System;
29using System.IO;
30using System.Text;
31using System.Collections;
32using System.Collections.Generic;
33using System.Globalization;
34using System.Net;
35using System.Net.Sockets;
36using System.Reflection;
37using System.Xml;
38using OpenMetaverse;
39using OpenMetaverse.StructuredData;
40using log4net;
41using Nini.Config;
42using Nwc.XmlRpc;
43using OpenSim.Framework;
44using OpenSim.Region.Framework.Interfaces;
45using OpenSim.Region.Framework.Scenes;
46using OpenSim.Services.Interfaces;
47using Mono.Addins;
48using OpenSim.Services.Connectors.Hypergrid;
49using OpenSim.Framework.Servers.HttpServer;
50using OpenSim.Services.UserProfilesService;
51using GridRegion = OpenSim.Services.Interfaces.GridRegion;
52using Microsoft.CSharp;
53
54namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
55{
56 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UserProfilesModule")]
57 public class UserProfileModule : IProfileModule, INonSharedRegionModule
58 {
59 /// <summary>
60 /// Logging
61 /// </summary>
62 static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
63
64 // The pair of Dictionaries are used to handle the switching of classified ads
65 // by maintaining a cache of classified id to creator id mappings and an interest
66 // count. The entries are removed when the interest count reaches 0.
67 Dictionary<UUID, UUID> m_classifiedCache = new Dictionary<UUID, UUID>();
68 Dictionary<UUID, int> m_classifiedInterest = new Dictionary<UUID, int>();
69
70 private JsonRpcRequestManager rpc = new JsonRpcRequestManager();
71
72 public Scene Scene
73 {
74 get; private set;
75 }
76
77 /// <summary>
78 /// Gets or sets the ConfigSource.
79 /// </summary>
80 /// <value>
81 /// The configuration
82 /// </value>
83 public IConfigSource Config
84 {
85 get;
86 set;
87 }
88
89 /// <summary>
90 /// Gets or sets the URI to the profile server.
91 /// </summary>
92 /// <value>
93 /// The profile server URI.
94 /// </value>
95 public string ProfileServerUri
96 {
97 get;
98 set;
99 }
100
101 IProfileModule ProfileModule
102 {
103 get; set;
104 }
105
106 IUserManagement UserManagementModule
107 {
108 get; set;
109 }
110
111 /// <summary>
112 /// Gets or sets a value indicating whether this
113 /// <see cref="OpenSim.Region.Coremodules.UserProfiles.UserProfileModule"/> is enabled.
114 /// </summary>
115 /// <value>
116 /// <c>true</c> if enabled; otherwise, <c>false</c>.
117 /// </value>
118 public bool Enabled
119 {
120 get;
121 set;
122 }
123
124 public string MyGatekeeper
125 {
126 get; private set;
127 }
128
129
130 #region IRegionModuleBase implementation
131 /// <summary>
132 /// This is called to initialize the region module. For shared modules, this is called exactly once, after
133 /// creating the single (shared) instance. For non-shared modules, this is called once on each instance, after
134 /// the instace for the region has been created.
135 /// </summary>
136 /// <param name='source'>
137 /// Source.
138 /// </param>
139 public void Initialise(IConfigSource source)
140 {
141 Config = source;
142 ReplaceableInterface = typeof(IProfileModule);
143
144 IConfig profileConfig = Config.Configs["UserProfiles"];
145
146 if (profileConfig == null)
147 {
148 m_log.Debug("[PROFILES]: UserProfiles disabled, no configuration");
149 Enabled = false;
150 return;
151 }
152
153 // If we find ProfileURL then we configure for FULL support
154 // else we setup for BASIC support
155 ProfileServerUri = profileConfig.GetString("ProfileServiceURL", "");
156 if (ProfileServerUri == "")
157 {
158 Enabled = false;
159 return;
160 }
161
162 m_log.Debug("[PROFILES]: Full Profiles Enabled");
163 ReplaceableInterface = null;
164 Enabled = true;
165
166 MyGatekeeper = Util.GetConfigVarFromSections<string>(source, "GatekeeperURI",
167 new string[] { "Startup", "Hypergrid", "UserProfiles" }, String.Empty);
168 }
169
170 /// <summary>
171 /// Adds the region.
172 /// </summary>
173 /// <param name='scene'>
174 /// Scene.
175 /// </param>
176 public void AddRegion(Scene scene)
177 {
178 if(!Enabled)
179 return;
180
181 Scene = scene;
182 Scene.RegisterModuleInterface<IProfileModule>(this);
183 Scene.EventManager.OnNewClient += OnNewClient;
184 Scene.EventManager.OnMakeRootAgent += HandleOnMakeRootAgent;
185
186 UserManagementModule = Scene.RequestModuleInterface<IUserManagement>();
187 }
188
189 void HandleOnMakeRootAgent (ScenePresence obj)
190 {
191 if(obj.PresenceType == PresenceType.Npc)
192 return;
193
194 Util.FireAndForget(delegate
195 {
196 GetImageAssets(((IScenePresence)obj).UUID);
197 }, null, "UserProfileModule.GetImageAssets");
198 }
199
200 /// <summary>
201 /// Removes the region.
202 /// </summary>
203 /// <param name='scene'>
204 /// Scene.
205 /// </param>
206 public void RemoveRegion(Scene scene)
207 {
208 if(!Enabled)
209 return;
210 }
211
212 /// <summary>
213 /// This will be called once for every scene loaded. In a shared module this will be multiple times in one
214 /// instance, while a nonshared module instance will only be called once. This method is called after AddRegion
215 /// has been called in all modules for that scene, providing an opportunity to request another module's
216 /// interface, or hook an event from another module.
217 /// </summary>
218 /// <param name='scene'>
219 /// Scene.
220 /// </param>
221 public void RegionLoaded(Scene scene)
222 {
223 if(!Enabled)
224 return;
225 }
226
227 /// <summary>
228 /// If this returns non-null, it is the type of an interface that this module intends to register. This will
229 /// cause the loader to defer loading of this module until all other modules have been loaded. If no other
230 /// module has registered the interface by then, this module will be activated, else it will remain inactive,
231 /// letting the other module take over. This should return non-null ONLY in modules that are intended to be
232 /// easily replaceable, e.g. stub implementations that the developer expects to be replaced by third party
233 /// provided modules.
234 /// </summary>
235 /// <value>
236 /// The replaceable interface.
237 /// </value>
238 public Type ReplaceableInterface
239 {
240 get; private set;
241 }
242
243 /// <summary>
244 /// Called as the instance is closed.
245 /// </summary>
246 public void Close()
247 {
248 }
249
250 /// <value>
251 /// The name of the module
252 /// </value>
253 /// <summary>
254 /// Gets the module name.
255 /// </summary>
256 public string Name
257 {
258 get { return "UserProfileModule"; }
259 }
260 #endregion IRegionModuleBase implementation
261
262 #region Region Event Handlers
263 /// <summary>
264 /// Raises the new client event.
265 /// </summary>
266 /// <param name='client'>
267 /// Client.
268 /// </param>
269 void OnNewClient(IClientAPI client)
270 {
271 //Profile
272 client.OnRequestAvatarProperties += RequestAvatarProperties;
273 client.OnUpdateAvatarProperties += AvatarPropertiesUpdate;
274 client.OnAvatarInterestUpdate += AvatarInterestsUpdate;
275
276 // Classifieds
277 client.AddGenericPacketHandler("avatarclassifiedsrequest", ClassifiedsRequest);
278 client.OnClassifiedInfoUpdate += ClassifiedInfoUpdate;
279 client.OnClassifiedInfoRequest += ClassifiedInfoRequest;
280 client.OnClassifiedDelete += ClassifiedDelete;
281
282 // Picks
283 client.AddGenericPacketHandler("avatarpicksrequest", PicksRequest);
284 client.AddGenericPacketHandler("pickinforequest", PickInfoRequest);
285 client.OnPickInfoUpdate += PickInfoUpdate;
286 client.OnPickDelete += PickDelete;
287
288 // Notes
289 client.AddGenericPacketHandler("avatarnotesrequest", NotesRequest);
290 client.OnAvatarNotesUpdate += NotesUpdate;
291
292 // Preferences
293 client.OnUserInfoRequest += UserPreferencesRequest;
294 client.OnUpdateUserInfo += UpdateUserPreferences;
295 }
296 #endregion Region Event Handlers
297
298 #region Classified
299 ///
300 /// <summary>
301 /// Handles the avatar classifieds request.
302 /// </summary>
303 /// <param name='sender'>
304 /// Sender.
305 /// </param>
306 /// <param name='method'>
307 /// Method.
308 /// </param>
309 /// <param name='args'>
310 /// Arguments.
311 /// </param>
312 public void ClassifiedsRequest(Object sender, string method, List<String> args)
313 {
314 if (!(sender is IClientAPI))
315 return;
316
317 IClientAPI remoteClient = (IClientAPI)sender;
318
319 UUID targetID;
320 UUID.TryParse(args[0], out targetID);
321
322 // Can't handle NPC yet...
323 ScenePresence p = FindPresence(targetID);
324
325 if (null != p)
326 {
327 if (p.PresenceType == PresenceType.Npc)
328 return;
329 }
330
331 string serverURI = string.Empty;
332 GetUserProfileServerURI(targetID, out serverURI);
333 UUID creatorId = UUID.Zero;
334 Dictionary<UUID, string> classifieds = new Dictionary<UUID, string>();
335
336 OSDMap parameters= new OSDMap();
337 UUID.TryParse(args[0], out creatorId);
338 parameters.Add("creatorId", OSD.FromUUID(creatorId));
339 OSD Params = (OSD)parameters;
340 if(!rpc.JsonRpcRequest(ref Params, "avatarclassifiedsrequest", serverURI, UUID.Random().ToString()))
341 {
342 remoteClient.SendAvatarClassifiedReply(new UUID(args[0]), classifieds);
343 return;
344 }
345
346 parameters = (OSDMap)Params;
347
348 OSDArray list = (OSDArray)parameters["result"];
349
350
351 foreach(OSD map in list)
352 {
353 OSDMap m = (OSDMap)map;
354 UUID cid = m["classifieduuid"].AsUUID();
355 string name = m["name"].AsString();
356
357 classifieds[cid] = name;
358
359 lock (m_classifiedCache)
360 {
361 if (!m_classifiedCache.ContainsKey(cid))
362 {
363 m_classifiedCache.Add(cid,creatorId);
364 m_classifiedInterest.Add(cid, 0);
365 }
366
367 m_classifiedInterest[cid]++;
368 }
369 }
370
371 remoteClient.SendAvatarClassifiedReply(new UUID(args[0]), classifieds);
372 }
373
374 public void ClassifiedInfoRequest(UUID queryClassifiedID, IClientAPI remoteClient)
375 {
376 UUID target = remoteClient.AgentId;
377 UserClassifiedAdd ad = new UserClassifiedAdd();
378 ad.ClassifiedId = queryClassifiedID;
379
380 lock (m_classifiedCache)
381 {
382 if (m_classifiedCache.ContainsKey(queryClassifiedID))
383 {
384 target = m_classifiedCache[queryClassifiedID];
385
386 m_classifiedInterest[queryClassifiedID] --;
387
388 if (m_classifiedInterest[queryClassifiedID] == 0)
389 {
390 m_classifiedInterest.Remove(queryClassifiedID);
391 m_classifiedCache.Remove(queryClassifiedID);
392 }
393 }
394 }
395
396 string serverURI = string.Empty;
397 GetUserProfileServerURI(target, out serverURI);
398
399 object Ad = (object)ad;
400 if(!rpc.JsonRpcRequest(ref Ad, "classifieds_info_query", serverURI, UUID.Random().ToString()))
401 {
402 remoteClient.SendAgentAlertMessage(
403 "Error getting classified info", false);
404 return;
405 }
406 ad = (UserClassifiedAdd) Ad;
407
408 if(ad.CreatorId == UUID.Zero)
409 return;
410
411 Vector3 globalPos = new Vector3();
412 Vector3.TryParse(ad.GlobalPos, out globalPos);
413
414 remoteClient.SendClassifiedInfoReply(ad.ClassifiedId, ad.CreatorId, (uint)ad.CreationDate, (uint)ad.ExpirationDate,
415 (uint)ad.Category, ad.Name, ad.Description, ad.ParcelId, (uint)ad.ParentEstate,
416 ad.SnapshotId, ad.SimName, globalPos, ad.ParcelName, ad.Flags, ad.Price);
417
418 }
419
420 /// <summary>
421 /// Classifieds info update.
422 /// </summary>
423 /// <param name='queryclassifiedID'>
424 /// Queryclassified I.
425 /// </param>
426 /// <param name='queryCategory'>
427 /// Query category.
428 /// </param>
429 /// <param name='queryName'>
430 /// Query name.
431 /// </param>
432 /// <param name='queryDescription'>
433 /// Query description.
434 /// </param>
435 /// <param name='queryParcelID'>
436 /// Query parcel I.
437 /// </param>
438 /// <param name='queryParentEstate'>
439 /// Query parent estate.
440 /// </param>
441 /// <param name='querySnapshotID'>
442 /// Query snapshot I.
443 /// </param>
444 /// <param name='queryGlobalPos'>
445 /// Query global position.
446 /// </param>
447 /// <param name='queryclassifiedFlags'>
448 /// Queryclassified flags.
449 /// </param>
450 /// <param name='queryclassifiedPrice'>
451 /// Queryclassified price.
452 /// </param>
453 /// <param name='remoteClient'>
454 /// Remote client.
455 /// </param>
456 public void ClassifiedInfoUpdate(UUID queryclassifiedID, uint queryCategory, string queryName, string queryDescription, UUID queryParcelID,
457 uint queryParentEstate, UUID querySnapshotID, Vector3 queryGlobalPos, byte queryclassifiedFlags,
458 int queryclassifiedPrice, IClientAPI remoteClient)
459 {
460 Scene s = (Scene)remoteClient.Scene;
461 IMoneyModule money = s.RequestModuleInterface<IMoneyModule>();
462
463 if (money != null)
464 {
465 if (!money.AmountCovered(remoteClient.AgentId, queryclassifiedPrice))
466 {
467 remoteClient.SendAgentAlertMessage("You do not have enough money to create requested classified.", false);
468 return;
469 }
470 money.ApplyCharge(remoteClient.AgentId, queryclassifiedPrice, MoneyTransactionType.ClassifiedCharge);
471 }
472
473 UserClassifiedAdd ad = new UserClassifiedAdd();
474
475 Vector3 pos = remoteClient.SceneAgent.AbsolutePosition;
476 ILandObject land = s.LandChannel.GetLandObject(pos.X, pos.Y);
477 ScenePresence p = FindPresence(remoteClient.AgentId);
478
479 string serverURI = string.Empty;
480 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
481
482 if (land == null)
483 {
484 ad.ParcelName = string.Empty;
485 }
486 else
487 {
488 ad.ParcelName = land.LandData.Name;
489 }
490
491 ad.CreatorId = remoteClient.AgentId;
492 ad.ClassifiedId = queryclassifiedID;
493 ad.Category = Convert.ToInt32(queryCategory);
494 ad.Name = queryName;
495 ad.Description = queryDescription;
496 ad.ParentEstate = Convert.ToInt32(queryParentEstate);
497 ad.SnapshotId = querySnapshotID;
498 ad.SimName = remoteClient.Scene.RegionInfo.RegionName;
499 ad.GlobalPos = queryGlobalPos.ToString ();
500 ad.Flags = queryclassifiedFlags;
501 ad.Price = queryclassifiedPrice;
502 ad.ParcelId = p.currentParcelUUID;
503
504 object Ad = ad;
505
506 OSD.SerializeMembers(Ad);
507
508 if(!rpc.JsonRpcRequest(ref Ad, "classified_update", serverURI, UUID.Random().ToString()))
509 {
510 remoteClient.SendAgentAlertMessage(
511 "Error updating classified", false);
512 return;
513 }
514 }
515
516 /// <summary>
517 /// Classifieds delete.
518 /// </summary>
519 /// <param name='queryClassifiedID'>
520 /// Query classified I.
521 /// </param>
522 /// <param name='remoteClient'>
523 /// Remote client.
524 /// </param>
525 public void ClassifiedDelete(UUID queryClassifiedID, IClientAPI remoteClient)
526 {
527 string serverURI = string.Empty;
528 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
529
530 UUID classifiedId;
531 OSDMap parameters= new OSDMap();
532 UUID.TryParse(queryClassifiedID.ToString(), out classifiedId);
533 parameters.Add("classifiedId", OSD.FromUUID(classifiedId));
534 OSD Params = (OSD)parameters;
535 if(!rpc.JsonRpcRequest(ref Params, "classified_delete", serverURI, UUID.Random().ToString()))
536 {
537 remoteClient.SendAgentAlertMessage(
538 "Error classified delete", false);
539 return;
540 }
541
542 parameters = (OSDMap)Params;
543 }
544 #endregion Classified
545
546 #region Picks
547 /// <summary>
548 /// Handles the avatar picks request.
549 /// </summary>
550 /// <param name='sender'>
551 /// Sender.
552 /// </param>
553 /// <param name='method'>
554 /// Method.
555 /// </param>
556 /// <param name='args'>
557 /// Arguments.
558 /// </param>
559 public void PicksRequest(Object sender, string method, List<String> args)
560 {
561 if (!(sender is IClientAPI))
562 return;
563
564 IClientAPI remoteClient = (IClientAPI)sender;
565
566 UUID targetId;
567 UUID.TryParse(args[0], out targetId);
568
569 // Can't handle NPC yet...
570 ScenePresence p = FindPresence(targetId);
571
572 if (null != p)
573 {
574 if (p.PresenceType == PresenceType.Npc)
575 return;
576 }
577
578 string serverURI = string.Empty;
579 GetUserProfileServerURI(targetId, out serverURI);
580
581 Dictionary<UUID, string> picks = new Dictionary<UUID, string>();
582
583 OSDMap parameters= new OSDMap();
584 parameters.Add("creatorId", OSD.FromUUID(targetId));
585 OSD Params = (OSD)parameters;
586 if(!rpc.JsonRpcRequest(ref Params, "avatarpicksrequest", serverURI, UUID.Random().ToString()))
587 {
588 remoteClient.SendAvatarPicksReply(new UUID(args[0]), picks);
589 return;
590 }
591
592 parameters = (OSDMap)Params;
593
594 OSDArray list = (OSDArray)parameters["result"];
595
596 foreach(OSD map in list)
597 {
598 OSDMap m = (OSDMap)map;
599 UUID cid = m["pickuuid"].AsUUID();
600 string name = m["name"].AsString();
601
602 m_log.DebugFormat("[PROFILES]: PicksRequest {0}", name);
603
604 picks[cid] = name;
605 }
606 remoteClient.SendAvatarPicksReply(new UUID(args[0]), picks);
607 }
608
609 /// <summary>
610 /// Handles the pick info request.
611 /// </summary>
612 /// <param name='sender'>
613 /// Sender.
614 /// </param>
615 /// <param name='method'>
616 /// Method.
617 /// </param>
618 /// <param name='args'>
619 /// Arguments.
620 /// </param>
621 public void PickInfoRequest(Object sender, string method, List<String> args)
622 {
623 if (!(sender is IClientAPI))
624 return;
625
626 UUID targetID;
627 UUID.TryParse (args [0], out targetID);
628 string serverURI = string.Empty;
629 GetUserProfileServerURI (targetID, out serverURI);
630
631 string theirGatekeeperURI;
632 GetUserGatekeeperURI (targetID, out theirGatekeeperURI);
633
634 IClientAPI remoteClient = (IClientAPI)sender;
635
636 UserProfilePick pick = new UserProfilePick ();
637 UUID.TryParse (args [0], out pick.CreatorId);
638 UUID.TryParse (args [1], out pick.PickId);
639
640
641 object Pick = (object)pick;
642 if (!rpc.JsonRpcRequest (ref Pick, "pickinforequest", serverURI, UUID.Random ().ToString ())) {
643 remoteClient.SendAgentAlertMessage (
644 "Error selecting pick", false);
645 return;
646 }
647 pick = (UserProfilePick)Pick;
648
649 Vector3 globalPos = new Vector3(Vector3.Zero);
650
651 // Smoke and mirrors
652 if (pick.Gatekeeper == MyGatekeeper)
653 {
654 Vector3.TryParse(pick.GlobalPos,out globalPos);
655 }
656 else
657 {
658 // Setup the illusion
659 string region = string.Format("{0} {1}",pick.Gatekeeper,pick.SimName);
660 GridRegion target = Scene.GridService.GetRegionByName(Scene.RegionInfo.ScopeID, region);
661
662 if(target == null)
663 {
664 // This is a dead or unreachable region
665 }
666 else
667 {
668 // Work our slight of hand
669 int x = target.RegionLocX;
670 int y = target.RegionLocY;
671
672 dynamic synthX = globalPos.X - (globalPos.X/Constants.RegionSize) * Constants.RegionSize;
673 synthX += x;
674 globalPos.X = synthX;
675
676 dynamic synthY = globalPos.Y - (globalPos.Y/Constants.RegionSize) * Constants.RegionSize;
677 synthY += y;
678 globalPos.Y = synthY;
679 }
680 }
681
682 m_log.DebugFormat("[PROFILES]: PickInfoRequest: {0} : {1}", pick.Name.ToString(), pick.SnapshotId.ToString());
683
684 // Pull the rabbit out of the hat
685 remoteClient.SendPickInfoReply(pick.PickId,pick.CreatorId,pick.TopPick,pick.ParcelId,pick.Name,
686 pick.Desc,pick.SnapshotId,pick.ParcelName,pick.OriginalName,pick.SimName,
687 globalPos,pick.SortOrder,pick.Enabled);
688 }
689
690 /// <summary>
691 /// Updates the userpicks
692 /// </summary>
693 /// <param name='remoteClient'>
694 /// Remote client.
695 /// </param>
696 /// <param name='pickID'>
697 /// Pick I.
698 /// </param>
699 /// <param name='creatorID'>
700 /// the creator of the pick
701 /// </param>
702 /// <param name='topPick'>
703 /// Top pick.
704 /// </param>
705 /// <param name='name'>
706 /// Name.
707 /// </param>
708 /// <param name='desc'>
709 /// Desc.
710 /// </param>
711 /// <param name='snapshotID'>
712 /// Snapshot I.
713 /// </param>
714 /// <param name='sortOrder'>
715 /// Sort order.
716 /// </param>
717 /// <param name='enabled'>
718 /// Enabled.
719 /// </param>
720 public void PickInfoUpdate(IClientAPI remoteClient, UUID pickID, UUID creatorID, bool topPick, string name, string desc, UUID snapshotID, int sortOrder, bool enabled)
721 {
722 //TODO: See how this works with NPC, May need to test
723 m_log.DebugFormat("[PROFILES]: Start PickInfoUpdate Name: {0} PickId: {1} SnapshotId: {2}", name, pickID.ToString(), snapshotID.ToString());
724
725 UserProfilePick pick = new UserProfilePick();
726 string serverURI = string.Empty;
727 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
728 ScenePresence p = FindPresence(remoteClient.AgentId);
729
730 Vector3 avaPos = p.AbsolutePosition;
731 // Getting the global position for the Avatar
732 Vector3 posGlobal = new Vector3(remoteClient.Scene.RegionInfo.WorldLocX + avaPos.X,
733 remoteClient.Scene.RegionInfo.WorldLocY + avaPos.Y,
734 avaPos.Z);
735
736 string landParcelName = "My Parcel";
737 UUID landParcelID = p.currentParcelUUID;
738
739 ILandObject land = p.Scene.LandChannel.GetLandObject(avaPos.X, avaPos.Y);
740
741 if (land != null)
742 {
743 // If land found, use parcel uuid from here because the value from SP will be blank if the avatar hasnt moved
744 landParcelName = land.LandData.Name;
745 landParcelID = land.LandData.GlobalID;
746 }
747 else
748 {
749 m_log.WarnFormat(
750 "[PROFILES]: PickInfoUpdate found no parcel info at {0},{1} in {2}",
751 avaPos.X, avaPos.Y, p.Scene.Name);
752 }
753
754
755 pick.PickId = pickID;
756 pick.CreatorId = creatorID;
757 pick.TopPick = topPick;
758 pick.Name = name;
759 pick.Desc = desc;
760 pick.ParcelId = landParcelID;
761 pick.SnapshotId = snapshotID;
762 pick.ParcelName = landParcelName;
763 pick.SimName = remoteClient.Scene.RegionInfo.RegionName;
764 pick.Gatekeeper = MyGatekeeper;
765 pick.GlobalPos = posGlobal.ToString();
766 pick.SortOrder = sortOrder;
767 pick.Enabled = enabled;
768
769 object Pick = (object)pick;
770 if(!rpc.JsonRpcRequest(ref Pick, "picks_update", serverURI, UUID.Random().ToString()))
771 {
772 remoteClient.SendAgentAlertMessage(
773 "Error updating pick", false);
774 return;
775 }
776
777 m_log.DebugFormat("[PROFILES]: Finish PickInfoUpdate {0} {1}", pick.Name, pick.PickId.ToString());
778 }
779
780 /// <summary>
781 /// Delete a Pick
782 /// </summary>
783 /// <param name='remoteClient'>
784 /// Remote client.
785 /// </param>
786 /// <param name='queryPickID'>
787 /// Query pick I.
788 /// </param>
789 public void PickDelete(IClientAPI remoteClient, UUID queryPickID)
790 {
791 string serverURI = string.Empty;
792 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
793
794 OSDMap parameters= new OSDMap();
795 parameters.Add("pickId", OSD.FromUUID(queryPickID));
796 OSD Params = (OSD)parameters;
797 if(!rpc.JsonRpcRequest(ref Params, "picks_delete", serverURI, UUID.Random().ToString()))
798 {
799 remoteClient.SendAgentAlertMessage(
800 "Error picks delete", false);
801 return;
802 }
803 }
804 #endregion Picks
805
806 #region Notes
807 /// <summary>
808 /// Handles the avatar notes request.
809 /// </summary>
810 /// <param name='sender'>
811 /// Sender.
812 /// </param>
813 /// <param name='method'>
814 /// Method.
815 /// </param>
816 /// <param name='args'>
817 /// Arguments.
818 /// </param>
819 public void NotesRequest(Object sender, string method, List<String> args)
820 {
821 UserProfileNotes note = new UserProfileNotes();
822
823 if (!(sender is IClientAPI))
824 return;
825
826 IClientAPI remoteClient = (IClientAPI)sender;
827 string serverURI = string.Empty;
828 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
829 note.UserId = remoteClient.AgentId;
830 UUID.TryParse(args[0], out note.TargetId);
831
832 object Note = (object)note;
833 if(!rpc.JsonRpcRequest(ref Note, "avatarnotesrequest", serverURI, UUID.Random().ToString()))
834 {
835 remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes);
836 return;
837 }
838 note = (UserProfileNotes) Note;
839
840 remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes);
841 }
842
843 /// <summary>
844 /// Avatars the notes update.
845 /// </summary>
846 /// <param name='remoteClient'>
847 /// Remote client.
848 /// </param>
849 /// <param name='queryTargetID'>
850 /// Query target I.
851 /// </param>
852 /// <param name='queryNotes'>
853 /// Query notes.
854 /// </param>
855 public void NotesUpdate(IClientAPI remoteClient, UUID queryTargetID, string queryNotes)
856 {
857 UserProfileNotes note = new UserProfileNotes();
858
859 note.UserId = remoteClient.AgentId;
860 note.TargetId = queryTargetID;
861 note.Notes = queryNotes;
862
863 string serverURI = string.Empty;
864 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
865
866 object Note = note;
867 if(!rpc.JsonRpcRequest(ref Note, "avatar_notes_update", serverURI, UUID.Random().ToString()))
868 {
869 remoteClient.SendAgentAlertMessage(
870 "Error updating note", false);
871 return;
872 }
873 }
874 #endregion Notes
875
876 #region User Preferences
877 /// <summary>
878 /// Updates the user preferences.
879 /// </summary>
880 /// <param name='imViaEmail'>
881 /// Im via email.
882 /// </param>
883 /// <param name='visible'>
884 /// Visible.
885 /// </param>
886 /// <param name='remoteClient'>
887 /// Remote client.
888 /// </param>
889 public void UpdateUserPreferences(bool imViaEmail, bool visible, IClientAPI remoteClient)
890 {
891 UserPreferences pref = new UserPreferences();
892
893 pref.UserId = remoteClient.AgentId;
894 pref.IMViaEmail = imViaEmail;
895 pref.Visible = visible;
896
897 string serverURI = string.Empty;
898 bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
899
900 object Pref = pref;
901 if(!rpc.JsonRpcRequest(ref Pref, "user_preferences_update", serverURI, UUID.Random().ToString()))
902 {
903 m_log.InfoFormat("[PROFILES]: UserPreferences update error");
904 remoteClient.SendAgentAlertMessage("Error updating preferences", false);
905 return;
906 }
907 }
908
909 /// <summary>
910 /// Users the preferences request.
911 /// </summary>
912 /// <param name='remoteClient'>
913 /// Remote client.
914 /// </param>
915 public void UserPreferencesRequest(IClientAPI remoteClient)
916 {
917 UserPreferences pref = new UserPreferences();
918
919 pref.UserId = remoteClient.AgentId;
920
921 string serverURI = string.Empty;
922 bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
923
924
925 object Pref = (object)pref;
926 if(!rpc.JsonRpcRequest(ref Pref, "user_preferences_request", serverURI, UUID.Random().ToString()))
927 {
928// m_log.InfoFormat("[PROFILES]: UserPreferences request error");
929// remoteClient.SendAgentAlertMessage("Error requesting preferences", false);
930 return;
931 }
932 pref = (UserPreferences) Pref;
933
934 remoteClient.SendUserInfoReply(pref.IMViaEmail, pref.Visible, pref.EMail);
935
936 }
937 #endregion User Preferences
938
939 #region Avatar Properties
940 /// <summary>
941 /// Update the avatars interests .
942 /// </summary>
943 /// <param name='remoteClient'>
944 /// Remote client.
945 /// </param>
946 /// <param name='wantmask'>
947 /// Wantmask.
948 /// </param>
949 /// <param name='wanttext'>
950 /// Wanttext.
951 /// </param>
952 /// <param name='skillsmask'>
953 /// Skillsmask.
954 /// </param>
955 /// <param name='skillstext'>
956 /// Skillstext.
957 /// </param>
958 /// <param name='languages'>
959 /// Languages.
960 /// </param>
961 public void AvatarInterestsUpdate(IClientAPI remoteClient, uint wantmask, string wanttext, uint skillsmask, string skillstext, string languages)
962 {
963 UserProfileProperties prop = new UserProfileProperties();
964
965 prop.UserId = remoteClient.AgentId;
966 prop.WantToMask = (int)wantmask;
967 prop.WantToText = wanttext;
968 prop.SkillsMask = (int)skillsmask;
969 prop.SkillsText = skillstext;
970 prop.Language = languages;
971
972 string serverURI = string.Empty;
973 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
974
975 object Param = prop;
976 if(!rpc.JsonRpcRequest(ref Param, "avatar_interests_update", serverURI, UUID.Random().ToString()))
977 {
978 remoteClient.SendAgentAlertMessage(
979 "Error updating interests", false);
980 return;
981 }
982 }
983
984 public void RequestAvatarProperties(IClientAPI remoteClient, UUID avatarID)
985 {
986 if (String.IsNullOrEmpty(avatarID.ToString()) || String.IsNullOrEmpty(remoteClient.AgentId.ToString()))
987 {
988 // Looking for a reason that some viewers are sending null Id's
989 m_log.DebugFormat("[PROFILES]: This should not happen remoteClient.AgentId {0} - avatarID {1}", remoteClient.AgentId, avatarID);
990 return;
991 }
992
993 // Can't handle NPC yet...
994 ScenePresence p = FindPresence(avatarID);
995
996 if (null != p)
997 {
998 if (p.PresenceType == PresenceType.Npc)
999 return;
1000 }
1001
1002 string serverURI = string.Empty;
1003 bool foreign = GetUserProfileServerURI(avatarID, out serverURI);
1004
1005 UserAccount account = null;
1006 Dictionary<string,object> userInfo;
1007
1008 if (!foreign)
1009 {
1010 account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, avatarID);
1011 }
1012 else
1013 {
1014 userInfo = new Dictionary<string, object>();
1015 }
1016
1017 Byte[] charterMember = new Byte[1];
1018 string born = String.Empty;
1019 uint flags = 0x00;
1020
1021 if (null != account)
1022 {
1023 if (account.UserTitle == "")
1024 {
1025 charterMember[0] = (Byte)((account.UserFlags & 0xf00) >> 8);
1026 }
1027 else
1028 {
1029 charterMember = Utils.StringToBytes(account.UserTitle);
1030 }
1031
1032 born = Util.ToDateTime(account.Created).ToString(
1033 "M/d/yyyy", CultureInfo.InvariantCulture);
1034 flags = (uint)(account.UserFlags & 0xff);
1035 }
1036 else
1037 {
1038 if (GetUserAccountData(avatarID, out userInfo) == true)
1039 {
1040 if ((string)userInfo["user_title"] == "")
1041 {
1042 charterMember[0] = (Byte)(((Byte)userInfo["user_flags"] & 0xf00) >> 8);
1043 }
1044 else
1045 {
1046 charterMember = Utils.StringToBytes((string)userInfo["user_title"]);
1047 }
1048
1049 int val_born = (int)userInfo["user_created"];
1050 born = Util.ToDateTime(val_born).ToString(
1051 "M/d/yyyy", CultureInfo.InvariantCulture);
1052
1053 // picky, picky
1054 int val_flags = (int)userInfo["user_flags"];
1055 flags = (uint)(val_flags & 0xff);
1056 }
1057 }
1058
1059 UserProfileProperties props = new UserProfileProperties();
1060 string result = string.Empty;
1061
1062 props.UserId = avatarID;
1063
1064 if (!GetProfileData(ref props, foreign, out result))
1065 {
1066// m_log.DebugFormat("Error getting profile for {0}: {1}", avatarID, result);
1067 return;
1068 }
1069
1070 remoteClient.SendAvatarProperties(props.UserId, props.AboutText, born, charterMember , props.FirstLifeText, flags,
1071 props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
1072
1073
1074 remoteClient.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText, (uint)props.SkillsMask,
1075 props.SkillsText, props.Language);
1076 }
1077
1078 /// <summary>
1079 /// Updates the avatar properties.
1080 /// </summary>
1081 /// <param name='remoteClient'>
1082 /// Remote client.
1083 /// </param>
1084 /// <param name='newProfile'>
1085 /// New profile.
1086 /// </param>
1087 public void AvatarPropertiesUpdate(IClientAPI remoteClient, UserProfileData newProfile)
1088 {
1089 if (remoteClient.AgentId == newProfile.ID)
1090 {
1091 UserProfileProperties prop = new UserProfileProperties();
1092
1093 prop.UserId = remoteClient.AgentId;
1094 prop.WebUrl = newProfile.ProfileUrl;
1095 prop.ImageId = newProfile.Image;
1096 prop.AboutText = newProfile.AboutText;
1097 prop.FirstLifeImageId = newProfile.FirstLifeImage;
1098 prop.FirstLifeText = newProfile.FirstLifeAboutText;
1099
1100 string serverURI = string.Empty;
1101 GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
1102
1103 object Prop = prop;
1104
1105 if(!rpc.JsonRpcRequest(ref Prop, "avatar_properties_update", serverURI, UUID.Random().ToString()))
1106 {
1107 remoteClient.SendAgentAlertMessage(
1108 "Error updating properties", false);
1109 return;
1110 }
1111
1112 RequestAvatarProperties(remoteClient, newProfile.ID);
1113 }
1114 }
1115
1116 /// <summary>
1117 /// Gets the profile data.
1118 /// </summary>
1119 /// <returns>
1120 /// The profile data.
1121 /// </returns>
1122 bool GetProfileData(ref UserProfileProperties properties, bool foreign, out string message)
1123 {
1124 // Can't handle NPC yet...
1125 ScenePresence p = FindPresence(properties.UserId);
1126
1127 if (null != p)
1128 {
1129 if (p.PresenceType == PresenceType.Npc)
1130 {
1131 message = "Id points to NPC";
1132 return false;
1133 }
1134 }
1135
1136 string serverURI = string.Empty;
1137 GetUserProfileServerURI(properties.UserId, out serverURI);
1138
1139 // This is checking a friend on the home grid
1140 // Not HG friend
1141 if (String.IsNullOrEmpty(serverURI))
1142 {
1143 message = "No Presence - foreign friend";
1144 return false;
1145 }
1146
1147 object Prop = (object)properties;
1148 if (!rpc.JsonRpcRequest(ref Prop, "avatar_properties_request", serverURI, UUID.Random().ToString()))
1149 {
1150 // If it's a foreign user then try again using OpenProfile, in case that's what the grid is using
1151 bool secondChanceSuccess = false;
1152 if (foreign)
1153 {
1154 try
1155 {
1156 OpenProfileClient client = new OpenProfileClient(serverURI);
1157 if (client.RequestAvatarPropertiesUsingOpenProfile(ref properties))
1158 secondChanceSuccess = true;
1159 }
1160 catch (Exception e)
1161 {
1162 m_log.Debug(
1163 string.Format(
1164 "[PROFILES]: Request using the OpenProfile API for user {0} to {1} failed",
1165 properties.UserId, serverURI),
1166 e);
1167
1168 // Allow the return 'message' to say "JsonRpcRequest" and not "OpenProfile", because
1169 // the most likely reason that OpenProfile failed is that the remote server
1170 // doesn't support OpenProfile, and that's not very interesting.
1171 }
1172 }
1173
1174 if (!secondChanceSuccess)
1175 {
1176 message = string.Format("JsonRpcRequest for user {0} to {1} failed", properties.UserId, serverURI);
1177 m_log.DebugFormat("[PROFILES]: {0}", message);
1178
1179 return false;
1180 }
1181 // else, continue below
1182 }
1183
1184 properties = (UserProfileProperties)Prop;
1185
1186 message = "Success";
1187 return true;
1188 }
1189 #endregion Avatar Properties
1190
1191 #region Utils
1192 bool GetImageAssets(UUID avatarId)
1193 {
1194 string profileServerURI = string.Empty;
1195 string assetServerURI = string.Empty;
1196
1197 bool foreign = GetUserProfileServerURI(avatarId, out profileServerURI);
1198
1199 if(!foreign)
1200 return true;
1201
1202 assetServerURI = UserManagementModule.GetUserServerURL(avatarId, "AssetServerURI");
1203
1204 if(string.IsNullOrEmpty(profileServerURI) || string.IsNullOrEmpty(assetServerURI))
1205 return false;
1206
1207 OSDMap parameters= new OSDMap();
1208 parameters.Add("avatarId", OSD.FromUUID(avatarId));
1209 OSD Params = (OSD)parameters;
1210 if(!rpc.JsonRpcRequest(ref Params, "image_assets_request", profileServerURI, UUID.Random().ToString()))
1211 {
1212 return false;
1213 }
1214
1215 parameters = (OSDMap)Params;
1216
1217 if (parameters.ContainsKey("result"))
1218 {
1219 OSDArray list = (OSDArray)parameters["result"];
1220
1221 foreach (OSD asset in list)
1222 {
1223 OSDString assetId = (OSDString)asset;
1224
1225 Scene.AssetService.Get(string.Format("{0}/{1}", assetServerURI, assetId.AsString()));
1226 }
1227 return true;
1228 }
1229 else
1230 {
1231 m_log.ErrorFormat("[PROFILES]: Problematic response for image_assets_request from {0}", profileServerURI);
1232 return false;
1233 }
1234 }
1235
1236 /// <summary>
1237 /// Gets the user account data.
1238 /// </summary>
1239 /// <returns>
1240 /// The user profile data.
1241 /// </returns>
1242 /// <param name='userID'>
1243 /// If set to <c>true</c> user I.
1244 /// </param>
1245 /// <param name='userInfo'>
1246 /// If set to <c>true</c> user info.
1247 /// </param>
1248 bool GetUserAccountData(UUID userID, out Dictionary<string, object> userInfo)
1249 {
1250 Dictionary<string,object> info = new Dictionary<string, object>();
1251
1252 if (UserManagementModule.IsLocalGridUser(userID))
1253 {
1254 // Is local
1255 IUserAccountService uas = Scene.UserAccountService;
1256 UserAccount account = uas.GetUserAccount(Scene.RegionInfo.ScopeID, userID);
1257
1258 info["user_flags"] = account.UserFlags;
1259 info["user_created"] = account.Created;
1260
1261 if (!String.IsNullOrEmpty(account.UserTitle))
1262 info["user_title"] = account.UserTitle;
1263 else
1264 info["user_title"] = "";
1265
1266 userInfo = info;
1267
1268 return false;
1269 }
1270 else
1271 {
1272 // Is Foreign
1273 string home_url = UserManagementModule.GetUserServerURL(userID, "HomeURI");
1274
1275 if (String.IsNullOrEmpty(home_url))
1276 {
1277 info["user_flags"] = 0;
1278 info["user_created"] = 0;
1279 info["user_title"] = "Unavailable";
1280
1281 userInfo = info;
1282 return true;
1283 }
1284
1285 UserAgentServiceConnector uConn = new UserAgentServiceConnector(home_url);
1286
1287 Dictionary<string, object> account;
1288 try
1289 {
1290 account = uConn.GetUserInfo(userID);
1291 }
1292 catch (Exception e)
1293 {
1294 m_log.Debug("[PROFILES]: GetUserInfo call failed ", e);
1295 account = new Dictionary<string, object>();
1296 }
1297
1298 if (account.Count > 0)
1299 {
1300 if (account.ContainsKey("user_flags"))
1301 info["user_flags"] = account["user_flags"];
1302 else
1303 info["user_flags"] = "";
1304
1305 if (account.ContainsKey("user_created"))
1306 info["user_created"] = account["user_created"];
1307 else
1308 info["user_created"] = "";
1309
1310 info["user_title"] = "HG Visitor";
1311 }
1312 else
1313 {
1314 info["user_flags"] = 0;
1315 info["user_created"] = 0;
1316 info["user_title"] = "HG Visitor";
1317 }
1318 userInfo = info;
1319 return true;
1320 }
1321 }
1322
1323 /// <summary>
1324 /// Gets the user gatekeeper server URI.
1325 /// </summary>
1326 /// <returns>
1327 /// The user gatekeeper server URI.
1328 /// </returns>
1329 /// <param name='userID'>
1330 /// If set to <c>true</c> user URI.
1331 /// </param>
1332 /// <param name='serverURI'>
1333 /// If set to <c>true</c> server URI.
1334 /// </param>
1335 bool GetUserGatekeeperURI(UUID userID, out string serverURI)
1336 {
1337 bool local;
1338 local = UserManagementModule.IsLocalGridUser(userID);
1339
1340 if (!local)
1341 {
1342 serverURI = UserManagementModule.GetUserServerURL(userID, "GatekeeperURI");
1343 // Is Foreign
1344 return true;
1345 }
1346 else
1347 {
1348 serverURI = MyGatekeeper;
1349 // Is local
1350 return false;
1351 }
1352 }
1353
1354 /// <summary>
1355 /// Gets the user profile server UR.
1356 /// </summary>
1357 /// <returns>
1358 /// The user profile server UR.
1359 /// </returns>
1360 /// <param name='userID'>
1361 /// If set to <c>true</c> user I.
1362 /// </param>
1363 /// <param name='serverURI'>
1364 /// If set to <c>true</c> server UR.
1365 /// </param>
1366 bool GetUserProfileServerURI(UUID userID, out string serverURI)
1367 {
1368 bool local;
1369 local = UserManagementModule.IsLocalGridUser(userID);
1370
1371 if (!local)
1372 {
1373 serverURI = UserManagementModule.GetUserServerURL(userID, "ProfileServerURI");
1374 // Is Foreign
1375 return true;
1376 }
1377 else
1378 {
1379 serverURI = ProfileServerUri;
1380 // Is local
1381 return false;
1382 }
1383 }
1384
1385 /// <summary>
1386 /// Finds the presence.
1387 /// </summary>
1388 /// <returns>
1389 /// The presence.
1390 /// </returns>
1391 /// <param name='clientID'>
1392 /// Client I.
1393 /// </param>
1394 ScenePresence FindPresence(UUID clientID)
1395 {
1396 ScenePresence p;
1397
1398 p = Scene.GetScenePresence(clientID);
1399 if (p != null && !p.IsChildAgent)
1400 return p;
1401
1402 return null;
1403 }
1404 #endregion Util
1405 }
1406}