diff options
Diffstat (limited to '')
12 files changed, 200 insertions, 8 deletions
diff --git a/OpenSim/Data/IPresenceData.cs b/OpenSim/Data/IPresenceData.cs index 4086245..b026995 100644 --- a/OpenSim/Data/IPresenceData.cs +++ b/OpenSim/Data/IPresenceData.cs | |||
@@ -49,6 +49,7 @@ namespace OpenSim.Data | |||
49 | bool Store(PresenceData data); | 49 | bool Store(PresenceData data); |
50 | 50 | ||
51 | PresenceData Get(UUID sessionID); | 51 | PresenceData Get(UUID sessionID); |
52 | PresenceData GetByUser(UUID userID); | ||
52 | void LogoutRegionAgents(UUID regionID); | 53 | void LogoutRegionAgents(UUID regionID); |
53 | bool ReportAgent(UUID sessionID, UUID regionID); | 54 | bool ReportAgent(UUID sessionID, UUID regionID); |
54 | PresenceData[] Get(string field, string data); | 55 | PresenceData[] Get(string field, string data); |
diff --git a/OpenSim/Data/MySQL/MySQLPresenceData.cs b/OpenSim/Data/MySQL/MySQLPresenceData.cs index 70aca5f..23d9683 100644 --- a/OpenSim/Data/MySQL/MySQLPresenceData.cs +++ b/OpenSim/Data/MySQL/MySQLPresenceData.cs | |||
@@ -52,8 +52,17 @@ namespace OpenSim.Data.MySQL | |||
52 | 52 | ||
53 | public PresenceData Get(UUID sessionID) | 53 | public PresenceData Get(UUID sessionID) |
54 | { | 54 | { |
55 | PresenceData[] ret = Get("SessionID", | 55 | PresenceData[] ret = Get("SessionID", sessionID.ToString()); |
56 | sessionID.ToString()); | 56 | |
57 | if (ret.Length == 0) | ||
58 | return null; | ||
59 | |||
60 | return ret[0]; | ||
61 | } | ||
62 | |||
63 | public PresenceData GetByUser(UUID userID) | ||
64 | { | ||
65 | PresenceData[] ret = Get("UserID", userID.ToString()); | ||
57 | 66 | ||
58 | if (ret.Length == 0) | 67 | if (ret.Length == 0) |
59 | return null; | 68 | return null; |
@@ -110,4 +119,4 @@ namespace OpenSim.Data.MySQL | |||
110 | return true; | 119 | return true; |
111 | } | 120 | } |
112 | } | 121 | } |
113 | } \ No newline at end of file | 122 | } |
diff --git a/OpenSim/Data/Null/NullPresenceData.cs b/OpenSim/Data/Null/NullPresenceData.cs index 8c442c9..ce1404e 100644 --- a/OpenSim/Data/Null/NullPresenceData.cs +++ b/OpenSim/Data/Null/NullPresenceData.cs | |||
@@ -79,6 +79,19 @@ namespace OpenSim.Data.Null | |||
79 | return null; | 79 | return null; |
80 | } | 80 | } |
81 | 81 | ||
82 | public PresenceData GetByUser(UUID userID) | ||
83 | { | ||
84 | if (Instance != this) | ||
85 | return Instance.GetByUser(userID); | ||
86 | |||
87 | if (m_presenceData.ContainsKey(userID)) | ||
88 | { | ||
89 | return m_presenceData[userID]; | ||
90 | } | ||
91 | |||
92 | return null; | ||
93 | } | ||
94 | |||
82 | public void LogoutRegionAgents(UUID regionID) | 95 | public void LogoutRegionAgents(UUID regionID) |
83 | { | 96 | { |
84 | if (Instance != this) | 97 | if (Instance != this) |
diff --git a/OpenSim/Data/PGSQL/PGSQLPresenceData.cs b/OpenSim/Data/PGSQL/PGSQLPresenceData.cs index ebbe8d3..51154af 100644 --- a/OpenSim/Data/PGSQL/PGSQLPresenceData.cs +++ b/OpenSim/Data/PGSQL/PGSQLPresenceData.cs | |||
@@ -60,6 +60,16 @@ namespace OpenSim.Data.PGSQL | |||
60 | return ret[0]; | 60 | return ret[0]; |
61 | } | 61 | } |
62 | 62 | ||
63 | public PresenceData GetByUser(UUID userID) | ||
64 | { | ||
65 | PresenceData[] ret = Get("UserID", userID.ToString()); | ||
66 | |||
67 | if (ret.Length == 0) | ||
68 | return null; | ||
69 | |||
70 | return ret[0]; | ||
71 | } | ||
72 | |||
63 | public void LogoutRegionAgents(UUID regionID) | 73 | public void LogoutRegionAgents(UUID regionID) |
64 | { | 74 | { |
65 | using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString)) | 75 | using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString)) |
diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/BasePresenceServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/BasePresenceServiceConnector.cs index fdbe10a..19a31d4 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/BasePresenceServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/BasePresenceServiceConnector.cs | |||
@@ -123,6 +123,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Presence | |||
123 | return m_PresenceService.GetAgent(sessionID); | 123 | return m_PresenceService.GetAgent(sessionID); |
124 | } | 124 | } |
125 | 125 | ||
126 | public PresenceInfo GetAgentByUser(UUID userID) | ||
127 | { | ||
128 | return m_PresenceService.GetAgentByUser(userID); | ||
129 | } | ||
130 | |||
126 | public PresenceInfo[] GetAgents(string[] userIDs) | 131 | public PresenceInfo[] GetAgents(string[] userIDs) |
127 | { | 132 | { |
128 | // Don't bother potentially making a useless network call if we not going to ask for any users anyway. | 133 | // Don't bother potentially making a useless network call if we not going to ask for any users anyway. |
@@ -134,4 +139,4 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Presence | |||
134 | 139 | ||
135 | #endregion | 140 | #endregion |
136 | } | 141 | } |
137 | } \ No newline at end of file | 142 | } |
diff --git a/OpenSim/Region/Framework/Interfaces/IPresenceModule.cs b/OpenSim/Region/Framework/Interfaces/IPresenceModule.cs index fb5933c..398611c 100644 --- a/OpenSim/Region/Framework/Interfaces/IPresenceModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IPresenceModule.cs | |||
@@ -33,11 +33,19 @@ namespace OpenSim.Region.Framework.Interfaces | |||
33 | { | 33 | { |
34 | public string UserID; | 34 | public string UserID; |
35 | public UUID RegionID; | 35 | public UUID RegionID; |
36 | public UUID SessionID; | ||
36 | 37 | ||
38 | public PresenceInfo(string userID, UUID regionID, UUID sessionID) | ||
39 | { | ||
40 | UserID = userID; | ||
41 | RegionID = regionID; | ||
42 | SessionID = sessionID; | ||
43 | } | ||
37 | public PresenceInfo(string userID, UUID regionID) | 44 | public PresenceInfo(string userID, UUID regionID) |
38 | { | 45 | { |
39 | UserID = userID; | 46 | UserID = userID; |
40 | RegionID = regionID; | 47 | RegionID = regionID; |
48 | SessionID = UUID.Zero; | ||
41 | } | 49 | } |
42 | } | 50 | } |
43 | 51 | ||
diff --git a/OpenSim/Services/Connectors/Presence/PresenceServicesConnector.cs b/OpenSim/Services/Connectors/Presence/PresenceServicesConnector.cs index 89d64ca..04a0c5e 100644 --- a/OpenSim/Services/Connectors/Presence/PresenceServicesConnector.cs +++ b/OpenSim/Services/Connectors/Presence/PresenceServicesConnector.cs | |||
@@ -329,6 +329,63 @@ namespace OpenSim.Services.Connectors | |||
329 | return pinfo; | 329 | return pinfo; |
330 | } | 330 | } |
331 | 331 | ||
332 | public PresenceInfo GetAgentByUser(UUID userID) | ||
333 | { | ||
334 | Dictionary<string, object> sendData = new Dictionary<string, object>(); | ||
335 | //sendData["SCOPEID"] = scopeID.ToString(); | ||
336 | sendData["VERSIONMIN"] = ProtocolVersions.ClientProtocolVersionMin.ToString(); | ||
337 | sendData["VERSIONMAX"] = ProtocolVersions.ClientProtocolVersionMax.ToString(); | ||
338 | sendData["METHOD"] = "getagentbyuser"; | ||
339 | |||
340 | sendData["UserID"] = userID.ToString(); | ||
341 | |||
342 | string reply = string.Empty; | ||
343 | string reqString = ServerUtils.BuildQueryString(sendData); | ||
344 | string uri = m_ServerURI + "/presence"; | ||
345 | // m_log.DebugFormat("[PRESENCE CONNECTOR]: queryString = {0}", reqString); | ||
346 | try | ||
347 | { | ||
348 | reply = SynchronousRestFormsRequester.MakeRequest("POST", | ||
349 | uri, | ||
350 | reqString, | ||
351 | m_Auth); | ||
352 | if (reply == null || (reply != null && reply == string.Empty)) | ||
353 | { | ||
354 | m_log.DebugFormat("[PRESENCE CONNECTOR]: GetAgentByUser received null or empty reply"); | ||
355 | return null; | ||
356 | } | ||
357 | } | ||
358 | catch (Exception e) | ||
359 | { | ||
360 | m_log.DebugFormat("[PRESENCE CONNECTOR]: Exception when contacting presence server at {0}: {1}", uri, e.Message); | ||
361 | return null; | ||
362 | } | ||
363 | |||
364 | Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply); | ||
365 | PresenceInfo pinfo = null; | ||
366 | |||
367 | if ((replyData != null) && replyData.ContainsKey("result") && (replyData["result"] != null)) | ||
368 | { | ||
369 | if (replyData["result"] is Dictionary<string, object>) | ||
370 | { | ||
371 | pinfo = new PresenceInfo((Dictionary<string, object>)replyData["result"]); | ||
372 | } | ||
373 | else | ||
374 | { | ||
375 | if (replyData["result"].ToString() == "null") | ||
376 | return null; | ||
377 | |||
378 | m_log.DebugFormat("[PRESENCE CONNECTOR]: Invalid reply (result not dictionary) received from presence server when querying for userID {0}", userID.ToString()); | ||
379 | } | ||
380 | } | ||
381 | else | ||
382 | { | ||
383 | m_log.DebugFormat("[PRESENCE CONNECTOR]: Invalid reply received from presence server when querying for userID {0}", userID.ToString()); | ||
384 | } | ||
385 | |||
386 | return pinfo; | ||
387 | } | ||
388 | |||
332 | public PresenceInfo[] GetAgents(string[] userIDs) | 389 | public PresenceInfo[] GetAgents(string[] userIDs) |
333 | { | 390 | { |
334 | Dictionary<string, object> sendData = new Dictionary<string, object>(); | 391 | Dictionary<string, object> sendData = new Dictionary<string, object>(); |
diff --git a/OpenSim/Services/Connectors/SimianGrid/SimianPresenceServiceConnector.cs b/OpenSim/Services/Connectors/SimianGrid/SimianPresenceServiceConnector.cs index 08efefb..2a34379 100644 --- a/OpenSim/Services/Connectors/SimianGrid/SimianPresenceServiceConnector.cs +++ b/OpenSim/Services/Connectors/SimianGrid/SimianPresenceServiceConnector.cs | |||
@@ -222,6 +222,18 @@ namespace OpenSim.Services.Connectors.SimianGrid | |||
222 | return ResponseToPresenceInfo(sessionResponse); | 222 | return ResponseToPresenceInfo(sessionResponse); |
223 | } | 223 | } |
224 | 224 | ||
225 | public PresenceInfo GetAgentByUser(UUID userID) | ||
226 | { | ||
227 | OSDMap userResponse = GetUserData(userID); | ||
228 | if (userResponse == null) | ||
229 | { | ||
230 | m_log.WarnFormat("[SIMIAN PRESENCE CONNECTOR]: Failed to retrieve user data for {0}: {1}",userID.ToString(),userResponse["Message"].AsString()); | ||
231 | return null; | ||
232 | } | ||
233 | |||
234 | return ResponseToPresenceInfo(userResponse); | ||
235 | } | ||
236 | |||
225 | public PresenceInfo[] GetAgents(string[] userIDs) | 237 | public PresenceInfo[] GetAgents(string[] userIDs) |
226 | { | 238 | { |
227 | List<PresenceInfo> presences = new List<PresenceInfo>(); | 239 | List<PresenceInfo> presences = new List<PresenceInfo>(); |
diff --git a/OpenSim/Services/HypergridService/GatekeeperService.cs b/OpenSim/Services/HypergridService/GatekeeperService.cs index 8ad9df8..4c134db 100644 --- a/OpenSim/Services/HypergridService/GatekeeperService.cs +++ b/OpenSim/Services/HypergridService/GatekeeperService.cs | |||
@@ -411,13 +411,39 @@ namespace OpenSim.Services.HypergridService | |||
411 | 411 | ||
412 | if(!m_allowDuplicatePresences) | 412 | if(!m_allowDuplicatePresences) |
413 | { | 413 | { |
414 | //// TODO - Should also check Presence.UserID if RegionID == UUID.Zero, they are a ghost. Maybe. | ||
415 | //// NOTE - this is a person hypergridding in, or returning home, or just logging in and it's duplicating effort in OpenSim/Services/LLLoginService/LLLoginService.cs. | ||
414 | if(guinfo != null && guinfo.Online && guinfo.LastRegionID != UUID.Zero) | 416 | if(guinfo != null && guinfo.Online && guinfo.LastRegionID != UUID.Zero) |
415 | { | 417 | { |
416 | if(SendAgentGodKillToRegion(UUID.Zero, agentID, guinfo)) | 418 | // Also check Presence.UserID if RegionID == UUID.Zero, they are a ghost. |
419 | // Ghosting might be caused by failure to call PresenceService.LogoutAgent() on logout / crash / failed login. | ||
420 | // Might also need to double check if they are out hypergridding. | ||
421 | |||
422 | bool success = false; | ||
423 | if (m_PresenceService != null) | ||
424 | { | ||
425 | PresenceInfo pi = m_PresenceService.GetAgentByUser(account.PrincipalID); | ||
426 | if (null != pi) | ||
427 | { | ||
428 | Dictionary<string, object> pid = pi.ToKeyValuePairs(); | ||
429 | if ((pid["RegionID"].ToString() == UUID.Zero.ToString()) && (0 != String.Compare(pid["SessionID"].ToString(), aCircuit.SessionID.ToString()))) | ||
430 | { | ||
431 | m_log.WarnFormat("[GATEKEEPER SERVICE]: Exorcising ghost avatar {0}, session {1}, new session {2}.", pid["UserID"], pid["SessionID"], aCircuit.SessionID); | ||
432 | m_log.WarnFormat("[GATEKEEPER SERVICE]: NOT REALLY."); | ||
433 | //// Don't do this until after more checking. | ||
434 | //// success = m_PresenceService.LogoutAgent(new UUID(pid["SessionID"].ToString())); | ||
435 | //// if (success) | ||
436 | //// m_log.WarnFormat("[GATEKEEPER SERVICE]: Ghost avatar exorcised {0}, session {1}, new session {2}.", pid["UserID"], pid["SessionID"], aCircuit.SessionID); | ||
437 | //// else | ||
438 | //// m_log.ErrorFormat("[GATEKEEPER SERVICE]: Ghost avatar not exorcised {0}, session {1}, new session {2}!", pid["UserID"], pid["SessionID"], aCircuit.SessionID); | ||
439 | } | ||
440 | } | ||
441 | } | ||
442 | if ((!success) && SendAgentGodKillToRegion(UUID.Zero, agentID, guinfo)) | ||
417 | { | 443 | { |
418 | if(account != null) | 444 | if(account != null) |
419 | m_log.InfoFormat( | 445 | m_log.InfoFormat( |
420 | "[GATEKEEPER SERVICE]: Login failed for {0} {1}, reason: already logged in", | 446 | "[GATEKEEPER SERVICE]: Login failed for {0} {1}, reason: already logged in. This may be bogus if a ghost avatar was exorcised above.", |
421 | account.FirstName, account.LastName); | 447 | account.FirstName, account.LastName); |
422 | reason = "You appear to be already logged in on the destination grid " + | 448 | reason = "You appear to be already logged in on the destination grid " + |
423 | "Please wait a a minute or two and retry. " + | 449 | "Please wait a a minute or two and retry. " + |
diff --git a/OpenSim/Services/Interfaces/IPresenceService.cs b/OpenSim/Services/Interfaces/IPresenceService.cs index 90f9842..b235f1e 100644 --- a/OpenSim/Services/Interfaces/IPresenceService.cs +++ b/OpenSim/Services/Interfaces/IPresenceService.cs | |||
@@ -36,6 +36,7 @@ namespace OpenSim.Services.Interfaces | |||
36 | { | 36 | { |
37 | public string UserID; | 37 | public string UserID; |
38 | public UUID RegionID; | 38 | public UUID RegionID; |
39 | public UUID SessionID; | ||
39 | 40 | ||
40 | public PresenceInfo() | 41 | public PresenceInfo() |
41 | { | 42 | { |
@@ -47,6 +48,8 @@ namespace OpenSim.Services.Interfaces | |||
47 | UserID = kvp["UserID"].ToString(); | 48 | UserID = kvp["UserID"].ToString(); |
48 | if (kvp.ContainsKey("RegionID")) | 49 | if (kvp.ContainsKey("RegionID")) |
49 | UUID.TryParse(kvp["RegionID"].ToString(), out RegionID); | 50 | UUID.TryParse(kvp["RegionID"].ToString(), out RegionID); |
51 | if (kvp.ContainsKey("SessionID")) | ||
52 | UUID.TryParse(kvp["SessionID"].ToString(), out SessionID); | ||
50 | } | 53 | } |
51 | 54 | ||
52 | public Dictionary<string, object> ToKeyValuePairs() | 55 | public Dictionary<string, object> ToKeyValuePairs() |
@@ -54,6 +57,7 @@ namespace OpenSim.Services.Interfaces | |||
54 | Dictionary<string, object> result = new Dictionary<string, object>(); | 57 | Dictionary<string, object> result = new Dictionary<string, object>(); |
55 | result["UserID"] = UserID; | 58 | result["UserID"] = UserID; |
56 | result["RegionID"] = RegionID.ToString(); | 59 | result["RegionID"] = RegionID.ToString(); |
60 | result["SessionID"] = SessionID.ToString(); | ||
57 | 61 | ||
58 | return result; | 62 | return result; |
59 | } | 63 | } |
@@ -100,10 +104,17 @@ namespace OpenSim.Services.Interfaces | |||
100 | PresenceInfo GetAgent(UUID sessionID); | 104 | PresenceInfo GetAgent(UUID sessionID); |
101 | 105 | ||
102 | /// <summary> | 106 | /// <summary> |
107 | /// Get session information for a given session ID. | ||
108 | /// </summary> | ||
109 | /// <returns></returns> | ||
110 | /// <param name='sessionID'></param> | ||
111 | PresenceInfo GetAgentByUser(UUID userID); | ||
112 | |||
113 | /// <summary> | ||
103 | /// Get session information for a collection of users. | 114 | /// Get session information for a collection of users. |
104 | /// </summary> | 115 | /// </summary> |
105 | /// <returns>Session information for the users.</returns> | 116 | /// <returns>Session information for the users.</returns> |
106 | /// <param name='userIDs'></param> | 117 | /// <param name='userIDs'></param> |
107 | PresenceInfo[] GetAgents(string[] userIDs); | 118 | PresenceInfo[] GetAgents(string[] userIDs); |
108 | } | 119 | } |
109 | } \ No newline at end of file | 120 | } |
diff --git a/OpenSim/Services/LLLoginService/LLLoginService.cs b/OpenSim/Services/LLLoginService/LLLoginService.cs index e7816c2..7aa18b1 100644 --- a/OpenSim/Services/LLLoginService/LLLoginService.cs +++ b/OpenSim/Services/LLLoginService/LLLoginService.cs | |||
@@ -412,7 +412,30 @@ namespace OpenSim.Services.LLLoginService | |||
412 | { | 412 | { |
413 | if(guinfo != null && guinfo.Online && guinfo.LastRegionID != UUID.Zero) | 413 | if(guinfo != null && guinfo.Online && guinfo.LastRegionID != UUID.Zero) |
414 | { | 414 | { |
415 | if(SendAgentGodKillToRegion(scopeID, account.PrincipalID, guinfo)) | 415 | // Also check Presence.UserID if RegionID == UUID.Zero, they are a ghost. |
416 | // Ghosting might be caused by failure to call PresenceService.LogoutAgent() on logout / crash / failed login. | ||
417 | // Might also need to double check if they are out hypergridding. | ||
418 | |||
419 | success = false; | ||
420 | if (m_PresenceService != null) | ||
421 | { | ||
422 | PresenceInfo pi = m_PresenceService.GetAgentByUser(account.PrincipalID); | ||
423 | if (null != pi) | ||
424 | { | ||
425 | Dictionary<string, object> pid = pi.ToKeyValuePairs(); | ||
426 | if (pid["RegionID"].ToString() == UUID.Zero.ToString()) | ||
427 | { | ||
428 | m_log.WarnFormat("[LLOGIN SERVICE]: Exorcising ghost avatar {0} {1}, session {2}, new session {3}.", firstName, lastName, pid["SessionID"], session); | ||
429 | success = m_PresenceService.LogoutAgent(new UUID(pid["SessionID"].ToString())); | ||
430 | if (success) | ||
431 | m_log.WarnFormat("[LLOGIN SERVICE]: Ghost avatar exorcised {0} {1}, session {2}, new session {3}.", firstName, lastName, pid["SessionID"], session); | ||
432 | else | ||
433 | m_log.ErrorFormat("[LLOGIN SERVICE]: Ghost avatar not exorcised {0} {1}, session {2}, new session {3}!", firstName, lastName, pid["SessionID"], session); | ||
434 | } | ||
435 | } | ||
436 | } | ||
437 | |||
438 | if ((!success) && SendAgentGodKillToRegion(scopeID, account.PrincipalID, guinfo)) | ||
416 | { | 439 | { |
417 | m_log.InfoFormat( | 440 | m_log.InfoFormat( |
418 | "[LLOGIN SERVICE]: Login failed for {0} {1}, reason: already logged in", | 441 | "[LLOGIN SERVICE]: Login failed for {0} {1}, reason: already logged in", |
diff --git a/OpenSim/Services/PresenceService/PresenceService.cs b/OpenSim/Services/PresenceService/PresenceService.cs index 1539d5b..db87b83 100644 --- a/OpenSim/Services/PresenceService/PresenceService.cs +++ b/OpenSim/Services/PresenceService/PresenceService.cs | |||
@@ -151,6 +151,22 @@ namespace OpenSim.Services.PresenceService | |||
151 | 151 | ||
152 | ret.UserID = data.UserID; | 152 | ret.UserID = data.UserID; |
153 | ret.RegionID = data.RegionID; | 153 | ret.RegionID = data.RegionID; |
154 | ret.SessionID = data.SessionID; | ||
155 | |||
156 | return ret; | ||
157 | } | ||
158 | |||
159 | public PresenceInfo GetAgentByUser(UUID userID) | ||
160 | { | ||
161 | PresenceInfo ret = new PresenceInfo(); | ||
162 | |||
163 | PresenceData data = m_Database.GetByUser(userID); | ||
164 | if (data == null) | ||
165 | return null; | ||
166 | |||
167 | ret.UserID = data.UserID; | ||
168 | ret.RegionID = data.RegionID; | ||
169 | ret.SessionID = data.SessionID; | ||
154 | 170 | ||
155 | return ret; | 171 | return ret; |
156 | } | 172 | } |
@@ -169,6 +185,7 @@ namespace OpenSim.Services.PresenceService | |||
169 | 185 | ||
170 | ret.UserID = d.UserID; | 186 | ret.UserID = d.UserID; |
171 | ret.RegionID = d.RegionID; | 187 | ret.RegionID = d.RegionID; |
188 | ret.SessionID = d.SessionID; | ||
172 | 189 | ||
173 | info.Add(ret); | 190 | info.Add(ret); |
174 | } | 191 | } |