aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/Framework/Caps
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/Framework/Caps
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/Framework/Caps')
-rw-r--r--OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs419
1 files changed, 366 insertions, 53 deletions
diff --git a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs
index 8329af0..817ef85 100644
--- a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs
+++ b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs
@@ -28,6 +28,7 @@
28using System; 28using System;
29using System.Collections; 29using System.Collections;
30using System.Collections.Generic; 30using System.Collections.Generic;
31using System.Linq;
31using System.Reflection; 32using System.Reflection;
32using System.Text; 33using System.Text;
33using log4net; 34using log4net;
@@ -37,6 +38,7 @@ using OpenMetaverse;
37using OpenSim.Framework; 38using OpenSim.Framework;
38using OpenSim.Framework.Console; 39using OpenSim.Framework.Console;
39using OpenSim.Framework.Servers; 40using OpenSim.Framework.Servers;
41using OpenSim.Framework.Servers.HttpServer;
40using OpenSim.Region.Framework.Interfaces; 42using OpenSim.Region.Framework.Interfaces;
41using OpenSim.Region.Framework.Scenes; 43using OpenSim.Region.Framework.Scenes;
42using Caps=OpenSim.Framework.Capabilities.Caps; 44using Caps=OpenSim.Framework.Capabilities.Caps;
@@ -57,8 +59,9 @@ namespace OpenSim.Region.CoreModules.Framework
57 /// </summary> 59 /// </summary>
58 protected Dictionary<UUID, Caps> m_capsObjects = new Dictionary<UUID, Caps>(); 60 protected Dictionary<UUID, Caps> m_capsObjects = new Dictionary<UUID, Caps>();
59 61
60 protected Dictionary<UUID, string> capsPaths = new Dictionary<UUID, string>(); 62 protected Dictionary<UUID, string> m_capsPaths = new Dictionary<UUID, string>();
61 protected Dictionary<UUID, Dictionary<ulong, string>> childrenSeeds 63
64 protected Dictionary<UUID, Dictionary<ulong, string>> m_childrenSeeds
62 = new Dictionary<UUID, Dictionary<ulong, string>>(); 65 = new Dictionary<UUID, Dictionary<ulong, string>>();
63 66
64 public void Initialise(IConfigSource source) 67 public void Initialise(IConfigSource source)
@@ -70,9 +73,24 @@ namespace OpenSim.Region.CoreModules.Framework
70 m_scene = scene; 73 m_scene = scene;
71 m_scene.RegisterModuleInterface<ICapabilitiesModule>(this); 74 m_scene.RegisterModuleInterface<ICapabilitiesModule>(this);
72 75
73 MainConsole.Instance.Commands.AddCommand("Comms", false, "show caps", 76 MainConsole.Instance.Commands.AddCommand(
74 "show caps", 77 "Comms", false, "show caps list",
75 "Shows all registered capabilities for users", HandleShowCapsCommand); 78 "show caps list",
79 "Shows list of registered capabilities for users.", HandleShowCapsListCommand);
80
81 MainConsole.Instance.Commands.AddCommand(
82 "Comms", false, "show caps stats by user",
83 "show caps stats by user [<first-name> <last-name>]",
84 "Shows statistics on capabilities use by user.",
85 "If a user name is given, then prints a detailed breakdown of caps use ordered by number of requests received.",
86 HandleShowCapsStatsByUserCommand);
87
88 MainConsole.Instance.Commands.AddCommand(
89 "Comms", false, "show caps stats by cap",
90 "show caps stats by cap [<cap-name>]",
91 "Shows statistics on capabilities use by capability.",
92 "If a capability name is given, then prints a detailed breakdown of use by each user.",
93 HandleShowCapsStatsByCapCommand);
76 } 94 }
77 95
78 public void RegionLoaded(Scene scene) 96 public void RegionLoaded(Scene scene)
@@ -105,35 +123,43 @@ namespace OpenSim.Region.CoreModules.Framework
105 if (m_scene.RegionInfo.EstateSettings.IsBanned(agentId)) 123 if (m_scene.RegionInfo.EstateSettings.IsBanned(agentId))
106 return; 124 return;
107 125
126 Caps caps;
108 String capsObjectPath = GetCapsPath(agentId); 127 String capsObjectPath = GetCapsPath(agentId);
109 128
110 if (m_capsObjects.ContainsKey(agentId)) 129 lock (m_capsObjects)
111 { 130 {
112 Caps oldCaps = m_capsObjects[agentId]; 131 if (m_capsObjects.ContainsKey(agentId))
113 132 {
114 m_log.DebugFormat( 133 Caps oldCaps = m_capsObjects[agentId];
115 "[CAPS]: Recreating caps for agent {0}. Old caps path {1}, new caps path {2}. ", 134
116 agentId, oldCaps.CapsObjectPath, capsObjectPath); 135 //m_log.WarnFormat(
117 // This should not happen. The caller code is confused. We need to fix that. 136 // "[CAPS]: Recreating caps for agent {0} in region {1}. Old caps path {2}, new caps path {3}. ",
118 // CAPs can never be reregistered, or the client will be confused. 137 // agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath);
119 // Hence this return here. 138 }
120 //return;
121 }
122 139
123 Caps caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, 140// m_log.DebugFormat(
124 (MainServer.Instance == null) ? 0: MainServer.Instance.Port, 141// "[CAPS]: Adding capabilities for agent {0} in {1} with path {2}",
125 capsObjectPath, agentId, m_scene.RegionInfo.RegionName); 142// agentId, m_scene.RegionInfo.RegionName, capsObjectPath);
126 143
127 m_capsObjects[agentId] = caps; 144 caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName,
145 (MainServer.Instance == null) ? 0: MainServer.Instance.Port,
146 capsObjectPath, agentId, m_scene.RegionInfo.RegionName);
147
148 m_capsObjects[agentId] = caps;
149 }
128 150
129 m_scene.EventManager.TriggerOnRegisterCaps(agentId, caps); 151 m_scene.EventManager.TriggerOnRegisterCaps(agentId, caps);
130 } 152 }
131 153
132 public void RemoveCaps(UUID agentId) 154 public void RemoveCaps(UUID agentId)
133 { 155 {
134 if (childrenSeeds.ContainsKey(agentId)) 156 m_log.DebugFormat("[CAPS]: Remove caps for agent {0} in region {1}", agentId, m_scene.RegionInfo.RegionName);
157 lock (m_childrenSeeds)
135 { 158 {
136 childrenSeeds.Remove(agentId); 159 if (m_childrenSeeds.ContainsKey(agentId))
160 {
161 m_childrenSeeds.Remove(agentId);
162 }
137 } 163 }
138 164
139 lock (m_capsObjects) 165 lock (m_capsObjects)
@@ -168,16 +194,22 @@ namespace OpenSim.Region.CoreModules.Framework
168 194
169 public void SetAgentCapsSeeds(AgentCircuitData agent) 195 public void SetAgentCapsSeeds(AgentCircuitData agent)
170 { 196 {
171 capsPaths[agent.AgentID] = agent.CapsPath; 197 lock (m_capsPaths)
172 childrenSeeds[agent.AgentID] 198 m_capsPaths[agent.AgentID] = agent.CapsPath;
173 = ((agent.ChildrenCapSeeds == null) ? new Dictionary<ulong, string>() : agent.ChildrenCapSeeds); 199
200 lock (m_childrenSeeds)
201 m_childrenSeeds[agent.AgentID]
202 = ((agent.ChildrenCapSeeds == null) ? new Dictionary<ulong, string>() : agent.ChildrenCapSeeds);
174 } 203 }
175 204
176 public string GetCapsPath(UUID agentId) 205 public string GetCapsPath(UUID agentId)
177 { 206 {
178 if (capsPaths.ContainsKey(agentId)) 207 lock (m_capsPaths)
179 { 208 {
180 return capsPaths[agentId]; 209 if (m_capsPaths.ContainsKey(agentId))
210 {
211 return m_capsPaths[agentId];
212 }
181 } 213 }
182 214
183 return null; 215 return null;
@@ -186,17 +218,24 @@ namespace OpenSim.Region.CoreModules.Framework
186 public Dictionary<ulong, string> GetChildrenSeeds(UUID agentID) 218 public Dictionary<ulong, string> GetChildrenSeeds(UUID agentID)
187 { 219 {
188 Dictionary<ulong, string> seeds = null; 220 Dictionary<ulong, string> seeds = null;
189 if (childrenSeeds.TryGetValue(agentID, out seeds)) 221
190 return seeds; 222 lock (m_childrenSeeds)
223 if (m_childrenSeeds.TryGetValue(agentID, out seeds))
224 return seeds;
225
191 return new Dictionary<ulong, string>(); 226 return new Dictionary<ulong, string>();
192 } 227 }
193 228
194 public void DropChildSeed(UUID agentID, ulong handle) 229 public void DropChildSeed(UUID agentID, ulong handle)
195 { 230 {
196 Dictionary<ulong, string> seeds; 231 Dictionary<ulong, string> seeds;
197 if (childrenSeeds.TryGetValue(agentID, out seeds)) 232
233 lock (m_childrenSeeds)
198 { 234 {
199 seeds.Remove(handle); 235 if (m_childrenSeeds.TryGetValue(agentID, out seeds))
236 {
237 seeds.Remove(handle);
238 }
200 } 239 }
201 } 240 }
202 241
@@ -204,53 +243,327 @@ namespace OpenSim.Region.CoreModules.Framework
204 { 243 {
205 Dictionary<ulong, string> seeds; 244 Dictionary<ulong, string> seeds;
206 string returnval; 245 string returnval;
207 if (childrenSeeds.TryGetValue(agentID, out seeds)) 246
247 lock (m_childrenSeeds)
208 { 248 {
209 if (seeds.TryGetValue(handle, out returnval)) 249 if (m_childrenSeeds.TryGetValue(agentID, out seeds))
210 return returnval; 250 {
251 if (seeds.TryGetValue(handle, out returnval))
252 return returnval;
253 }
211 } 254 }
255
212 return null; 256 return null;
213 } 257 }
214 258
215 public void SetChildrenSeed(UUID agentID, Dictionary<ulong, string> seeds) 259 public void SetChildrenSeed(UUID agentID, Dictionary<ulong, string> seeds)
216 { 260 {
217 //m_log.DebugFormat(" !!! Setting child seeds in {0} to {1}", m_scene.RegionInfo.RegionName, seeds.Count); 261 //m_log.DebugFormat(" !!! Setting child seeds in {0} to {1}", m_scene.RegionInfo.RegionName, seeds.Count);
218 childrenSeeds[agentID] = seeds; 262
263 lock (m_childrenSeeds)
264 m_childrenSeeds[agentID] = seeds;
219 } 265 }
220 266
221 public void DumpChildrenSeeds(UUID agentID) 267 public void DumpChildrenSeeds(UUID agentID)
222 { 268 {
223 m_log.Info("================ ChildrenSeed "+m_scene.RegionInfo.RegionName+" ================"); 269 m_log.Info("================ ChildrenSeed "+m_scene.RegionInfo.RegionName+" ================");
224 foreach (KeyValuePair<ulong, string> kvp in childrenSeeds[agentID]) 270
271 lock (m_childrenSeeds)
272 {
273 foreach (KeyValuePair<ulong, string> kvp in m_childrenSeeds[agentID])
274 {
275 uint x, y;
276 Util.RegionHandleToRegionLoc(kvp.Key, out x, out y);
277 m_log.Info(" >> "+x+", "+y+": "+kvp.Value);
278 }
279 }
280 }
281
282 private void HandleShowCapsListCommand(string module, string[] cmdParams)
283 {
284 if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene)
285 return;
286
287 StringBuilder capsReport = new StringBuilder();
288 capsReport.AppendFormat("Region {0}:\n", m_scene.RegionInfo.RegionName);
289
290 lock (m_capsObjects)
225 { 291 {
226 uint x, y; 292 foreach (KeyValuePair<UUID, Caps> kvp in m_capsObjects)
227 Utils.LongToUInts(kvp.Key, out x, out y); 293 {
228 x = x / Constants.RegionSize; 294 capsReport.AppendFormat("** User {0}:\n", kvp.Key);
229 y = y / Constants.RegionSize; 295 Caps caps = kvp.Value;
230 m_log.Info(" >> "+x+", "+y+": "+kvp.Value); 296
297 for (IDictionaryEnumerator kvp2 = caps.CapsHandlers.GetCapsDetails(false, null).GetEnumerator(); kvp2.MoveNext(); )
298 {
299 Uri uri = new Uri(kvp2.Value.ToString());
300 capsReport.AppendFormat(m_showCapsCommandFormat, kvp2.Key, uri.PathAndQuery);
301 }
302
303 foreach (KeyValuePair<string, PollServiceEventArgs> kvp2 in caps.GetPollHandlers())
304 capsReport.AppendFormat(m_showCapsCommandFormat, kvp2.Key, kvp2.Value.Url);
305
306 foreach (KeyValuePair<string, string> kvp3 in caps.ExternalCapsHandlers)
307 capsReport.AppendFormat(m_showCapsCommandFormat, kvp3.Key, kvp3.Value);
308 }
231 } 309 }
310
311 MainConsole.Instance.Output(capsReport.ToString());
232 } 312 }
233 313
234 private void HandleShowCapsCommand(string module, string[] cmdparams) 314 private void HandleShowCapsStatsByCapCommand(string module, string[] cmdParams)
235 { 315 {
236 StringBuilder caps = new StringBuilder(); 316 if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene)
237 caps.AppendFormat("Region {0}:\n", m_scene.RegionInfo.RegionName); 317 return;
318
319 if (cmdParams.Length != 5 && cmdParams.Length != 6)
320 {
321 MainConsole.Instance.Output("Usage: show caps stats by cap [<cap-name>]");
322 return;
323 }
324
325 StringBuilder sb = new StringBuilder();
326 sb.AppendFormat("Region {0}:\n", m_scene.Name);
327
328 if (cmdParams.Length == 5)
329 {
330 BuildSummaryStatsByCapReport(sb);
331 }
332 else if (cmdParams.Length == 6)
333 {
334 BuildDetailedStatsByCapReport(sb, cmdParams[5]);
335 }
336
337 MainConsole.Instance.Output(sb.ToString());
338 }
339
340 private void BuildDetailedStatsByCapReport(StringBuilder sb, string capName)
341 {
342 sb.AppendFormat("Capability name {0}\n", capName);
343
344 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
345 cdt.AddColumn("User Name", 34);
346 cdt.AddColumn("Req Received", 12);
347 cdt.AddColumn("Req Handled", 12);
348 cdt.Indent = 2;
349
350 Dictionary<string, int> receivedStats = new Dictionary<string, int>();
351 Dictionary<string, int> handledStats = new Dictionary<string, int>();
352
353 m_scene.ForEachScenePresence(
354 sp =>
355 {
356 Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
238 357
239 foreach (KeyValuePair<UUID, Caps> kvp in m_capsObjects) 358 if (caps == null)
359 return;
360
361 Dictionary<string, IRequestHandler> capsHandlers = caps.CapsHandlers.GetCapsHandlers();
362
363 IRequestHandler reqHandler;
364 if (capsHandlers.TryGetValue(capName, out reqHandler))
365 {
366 receivedStats[sp.Name] = reqHandler.RequestsReceived;
367 handledStats[sp.Name] = reqHandler.RequestsHandled;
368 }
369 else
370 {
371 PollServiceEventArgs pollHandler = null;
372 if (caps.TryGetPollHandler(capName, out pollHandler))
373 {
374 receivedStats[sp.Name] = pollHandler.RequestsReceived;
375 handledStats[sp.Name] = pollHandler.RequestsHandled;
376 }
377 }
378 }
379 );
380
381 foreach (KeyValuePair<string, int> kvp in receivedStats.OrderByDescending(kp => kp.Value))
240 { 382 {
241 caps.AppendFormat("** User {0}:\n", kvp.Key); 383 cdt.AddRow(kvp.Key, kvp.Value, handledStats[kvp.Key]);
384 }
385
386 sb.Append(cdt.ToString());
387 }
388
389 private void BuildSummaryStatsByCapReport(StringBuilder sb)
390 {
391 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
392 cdt.AddColumn("Name", 34);
393 cdt.AddColumn("Req Received", 12);
394 cdt.AddColumn("Req Handled", 12);
395 cdt.Indent = 2;
396
397 Dictionary<string, int> receivedStats = new Dictionary<string, int>();
398 Dictionary<string, int> handledStats = new Dictionary<string, int>();
242 399
243 for (IDictionaryEnumerator kvp2 = kvp.Value.CapsHandlers.GetCapsDetails(false).GetEnumerator(); kvp2.MoveNext(); ) 400 m_scene.ForEachScenePresence(
401 sp =>
244 { 402 {
245 Uri uri = new Uri(kvp2.Value.ToString()); 403 Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
246 caps.AppendFormat(m_showCapsCommandFormat, kvp2.Key, uri.PathAndQuery); 404
405 if (caps == null)
406 return;
407
408 foreach (IRequestHandler reqHandler in caps.CapsHandlers.GetCapsHandlers().Values)
409 {
410 string reqName = reqHandler.Name ?? "";
411
412 if (!receivedStats.ContainsKey(reqName))
413 {
414 receivedStats[reqName] = reqHandler.RequestsReceived;
415 handledStats[reqName] = reqHandler.RequestsHandled;
416 }
417 else
418 {
419 receivedStats[reqName] += reqHandler.RequestsReceived;
420 handledStats[reqName] += reqHandler.RequestsHandled;
421 }
422 }
423
424 foreach (KeyValuePair<string, PollServiceEventArgs> kvp in caps.GetPollHandlers())
425 {
426 string name = kvp.Key;
427 PollServiceEventArgs pollHandler = kvp.Value;
428
429 if (!receivedStats.ContainsKey(name))
430 {
431 receivedStats[name] = pollHandler.RequestsReceived;
432 handledStats[name] = pollHandler.RequestsHandled;
433 }
434 else
435 {
436 receivedStats[name] += pollHandler.RequestsReceived;
437 handledStats[name] += pollHandler.RequestsHandled;
438 }
439 }
247 } 440 }
441 );
442
443 foreach (KeyValuePair<string, int> kvp in receivedStats.OrderByDescending(kp => kp.Value))
444 cdt.AddRow(kvp.Key, kvp.Value, handledStats[kvp.Key]);
445
446 sb.Append(cdt.ToString());
447 }
448
449 private void HandleShowCapsStatsByUserCommand(string module, string[] cmdParams)
450 {
451 if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene)
452 return;
453
454 if (cmdParams.Length != 5 && cmdParams.Length != 7)
455 {
456 MainConsole.Instance.Output("Usage: show caps stats by user [<first-name> <last-name>]");
457 return;
458 }
459
460 StringBuilder sb = new StringBuilder();
461 sb.AppendFormat("Region {0}:\n", m_scene.Name);
462
463 if (cmdParams.Length == 5)
464 {
465 BuildSummaryStatsByUserReport(sb);
466 }
467 else if (cmdParams.Length == 7)
468 {
469 string firstName = cmdParams[5];
470 string lastName = cmdParams[6];
471
472 ScenePresence sp = m_scene.GetScenePresence(firstName, lastName);
473
474 if (sp == null)
475 return;
248 476
249 foreach (KeyValuePair<string, string> kvp3 in kvp.Value.ExternalCapsHandlers) 477 BuildDetailedStatsByUserReport(sb, sp);
250 caps.AppendFormat(m_showCapsCommandFormat, kvp3.Key, kvp3.Value);
251 } 478 }
252 479
253 MainConsole.Instance.Output(caps.ToString()); 480 MainConsole.Instance.Output(sb.ToString());
481 }
482
483 private void BuildDetailedStatsByUserReport(StringBuilder sb, ScenePresence sp)
484 {
485 sb.AppendFormat("Avatar name {0}, type {1}\n", sp.Name, sp.IsChildAgent ? "child" : "root");
486
487 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
488 cdt.AddColumn("Cap Name", 34);
489 cdt.AddColumn("Req Received", 12);
490 cdt.AddColumn("Req Handled", 12);
491 cdt.Indent = 2;
492
493 Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
494
495 if (caps == null)
496 return;
497
498 List<CapTableRow> capRows = new List<CapTableRow>();
499
500 foreach (IRequestHandler reqHandler in caps.CapsHandlers.GetCapsHandlers().Values)
501 capRows.Add(new CapTableRow(reqHandler.Name, reqHandler.RequestsReceived, reqHandler.RequestsHandled));
502
503 foreach (KeyValuePair<string, PollServiceEventArgs> kvp in caps.GetPollHandlers())
504 capRows.Add(new CapTableRow(kvp.Key, kvp.Value.RequestsReceived, kvp.Value.RequestsHandled));
505
506 foreach (CapTableRow ctr in capRows.OrderByDescending(ctr => ctr.RequestsReceived))
507 cdt.AddRow(ctr.Name, ctr.RequestsReceived, ctr.RequestsHandled);
508
509 sb.Append(cdt.ToString());
510 }
511
512 private void BuildSummaryStatsByUserReport(StringBuilder sb)
513 {
514 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
515 cdt.AddColumn("Name", 32);
516 cdt.AddColumn("Type", 5);
517 cdt.AddColumn("Req Received", 12);
518 cdt.AddColumn("Req Handled", 12);
519 cdt.Indent = 2;
520
521 m_scene.ForEachScenePresence(
522 sp =>
523 {
524 Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
525
526 if (caps == null)
527 return;
528
529 Dictionary<string, IRequestHandler> capsHandlers = caps.CapsHandlers.GetCapsHandlers();
530
531 int totalRequestsReceived = 0;
532 int totalRequestsHandled = 0;
533
534 foreach (IRequestHandler reqHandler in capsHandlers.Values)
535 {
536 totalRequestsReceived += reqHandler.RequestsReceived;
537 totalRequestsHandled += reqHandler.RequestsHandled;
538 }
539
540 Dictionary<string, PollServiceEventArgs> capsPollHandlers = caps.GetPollHandlers();
541
542 foreach (PollServiceEventArgs handler in capsPollHandlers.Values)
543 {
544 totalRequestsReceived += handler.RequestsReceived;
545 totalRequestsHandled += handler.RequestsHandled;
546 }
547
548 cdt.AddRow(sp.Name, sp.IsChildAgent ? "child" : "root", totalRequestsReceived, totalRequestsHandled);
549 }
550 );
551
552 sb.Append(cdt.ToString());
553 }
554
555 private class CapTableRow
556 {
557 public string Name { get; set; }
558 public int RequestsReceived { get; set; }
559 public int RequestsHandled { get; set; }
560
561 public CapTableRow(string name, int requestsReceived, int requestsHandled)
562 {
563 Name = name;
564 RequestsReceived = requestsReceived;
565 RequestsHandled = requestsHandled;
566 }
254 } 567 }
255 } 568 }
256} 569} \ No newline at end of file