aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Grid
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Grid')
-rw-r--r--OpenSim/Grid/InventoryServer/InventoryManager.cs131
-rw-r--r--OpenSim/Grid/InventoryServer/InventoryService.cs136
-rw-r--r--OpenSim/Grid/InventoryServer/Main.cs113
-rw-r--r--OpenSim/Grid/UserServer/Main.cs11
-rw-r--r--OpenSim/Grid/UserServer/UserLoginService.cs39
5 files changed, 80 insertions, 350 deletions
diff --git a/OpenSim/Grid/InventoryServer/InventoryManager.cs b/OpenSim/Grid/InventoryServer/InventoryManager.cs
index a46f359..016b8bb 100644
--- a/OpenSim/Grid/InventoryServer/InventoryManager.cs
+++ b/OpenSim/Grid/InventoryServer/InventoryManager.cs
@@ -26,34 +26,37 @@
26* 26*
27*/ 27*/
28using System; 28using System;
29using System.Text;
30using System.Reflection;
31using System.Collections; 29using System.Collections;
32using System.Collections.Generic; 30using System.Collections.Generic;
31using System.Text;
32using OpenGrid.Framework.Data;
33using libsecondlife; 33using libsecondlife;
34using System.Reflection;
34 35
35using System.Xml; 36using System.Xml;
36using OpenSim.Framework.Console; 37using Nwc.XmlRpc;
38using OpenSim.Framework.Sims;
39using OpenSim.Framework.Inventory;
37using OpenSim.Framework.Utilities; 40using OpenSim.Framework.Utilities;
38using OpenSim.Framework.Data;
39using InventoryCategory = OpenSim.Framework.Data.InventoryCategory;
40 41
41namespace OpenSim.Grid.InventoryServer 42using System.Security.Cryptography;
43
44namespace OpenGridServices.InventoryServer
42{ 45{
43 class InventoryManager : IInventoryData 46 class InventoryManager
44 { 47 {
45 IInventoryData _databasePlugin; 48 Dictionary<string, IInventoryData> _plugins = new Dictionary<string, IInventoryData>();
46 49
47 /// <summary> 50 /// <summary>
48 /// Adds a new inventory server plugin - user servers will be requested in the order they were loaded. 51 /// Adds a new inventory server plugin - user servers will be requested in the order they were loaded.
49 /// </summary> 52 /// </summary>
50 /// <param name="FileName">The filename to the inventory server plugin DLL</param> 53 /// <param name="FileName">The filename to the inventory server plugin DLL</param>
51 public void AddDatabasePlugin(string FileName) 54 public void AddPlugin(string FileName)
52 { 55 {
53 MainLog.Instance.Verbose(OpenInventory_Main.MainLogName, "Invenstorage: Attempting to load " + FileName); 56 OpenSim.Framework.Console.MainConsole.Instance.Verbose( "Invenstorage: Attempting to load " + FileName);
54 Assembly pluginAssembly = Assembly.LoadFrom(FileName); 57 Assembly pluginAssembly = Assembly.LoadFrom(FileName);
55 58
56 MainLog.Instance.Verbose(OpenInventory_Main.MainLogName, "Invenstorage: Found " + pluginAssembly.GetTypes().Length + " interfaces."); 59 OpenSim.Framework.Console.MainConsole.Instance.Verbose( "Invenstorage: Found " + pluginAssembly.GetTypes().Length + " interfaces.");
57 foreach (Type pluginType in pluginAssembly.GetTypes()) 60 foreach (Type pluginType in pluginAssembly.GetTypes())
58 { 61 {
59 if (!pluginType.IsAbstract) 62 if (!pluginType.IsAbstract)
@@ -64,9 +67,8 @@ namespace OpenSim.Grid.InventoryServer
64 { 67 {
65 IInventoryData plug = (IInventoryData)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString())); 68 IInventoryData plug = (IInventoryData)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));
66 plug.Initialise(); 69 plug.Initialise();
67 _databasePlugin = plug; 70 this._plugins.Add(plug.getName(), plug);
68 MainLog.Instance.Verbose(OpenInventory_Main.MainLogName, "Invenstorage: Added IInventoryData Interface"); 71 OpenSim.Framework.Console.MainConsole.Instance.Verbose( "Invenstorage: Added IUserData Interface");
69 break;
70 } 72 }
71 73
72 typeInterface = null; 74 typeInterface = null;
@@ -78,87 +80,46 @@ namespace OpenSim.Grid.InventoryServer
78 80
79 public List<InventoryFolderBase> getRootFolders(LLUUID user) 81 public List<InventoryFolderBase> getRootFolders(LLUUID user)
80 { 82 {
83 foreach (KeyValuePair<string, IInventoryData> kvp in _plugins)
84 {
85 try
86 {
87 return kvp.Value.getUserRootFolders(user);
88 }
89 catch (Exception e)
90 {
91 OpenSim.Framework.Console.MainConsole.Instance.Notice("Unable to get root folders via " + kvp.Key + " (" + e.ToString() + ")");
92 }
93 }
81 return null; 94 return null;
82 } 95 }
83 96
84 #region IInventoryData Members 97 public XmlRpcResponse XmlRpcInventoryRequest(XmlRpcRequest request)
85
86
87 public List<InventoryItemBase> getInventoryInFolder(LLUUID folderID)
88 {
89 return _databasePlugin.getInventoryInFolder(folderID);
90 }
91
92 public List<InventoryFolderBase> getUserRootFolders(LLUUID user)
93 {
94 return _databasePlugin.getUserRootFolders(user);
95 }
96
97 public List<InventoryFolderBase> getInventoryFolders(LLUUID parentID)
98 {
99 return _databasePlugin.getInventoryFolders(parentID);
100 }
101
102 public InventoryItemBase getInventoryItem(LLUUID item)
103 {
104 throw new Exception("The method or operation is not implemented.");
105 }
106
107 public InventoryFolderBase getInventoryFolder(LLUUID folder)
108 {
109 return _databasePlugin.getInventoryFolder(folder);
110 }
111
112 public void addInventoryItem(InventoryItemBase item)
113 { 98 {
114 _databasePlugin.addInventoryItem(item); 99 XmlRpcResponse response = new XmlRpcResponse();
115 } 100 Hashtable requestData = (Hashtable)request.Params[0];
116
117 public void updateInventoryItem(InventoryItemBase item)
118 {
119 throw new Exception("The method or operation is not implemented.");
120 }
121
122 public void deleteInventoryItem(InventoryItemBase item)
123 {
124 throw new Exception("The method or operation is not implemented.");
125 }
126 101
127 public void addInventoryFolder(InventoryFolderBase folder) 102 Hashtable responseData = new Hashtable();
128 {
129 _databasePlugin.addInventoryFolder(folder);
130 }
131 103
132 public void updateInventoryFolder(InventoryFolderBase folder) 104 // Stuff happens here
133 {
134 throw new Exception("The method or operation is not implemented.");
135 }
136
137 public void Initialise()
138 {
139 throw new Exception("The method or operation is not implemented.");
140 }
141 105
142 public void Close() 106 if (requestData.ContainsKey("Access-type"))
143 { 107 {
144 throw new Exception("The method or operation is not implemented."); 108 if (requestData["access-type"] == "rootfolders")
145 } 109 {
110// responseData["rootfolders"] =
111 }
112 }
113 else
114 {
115 responseData["error"] = "No access-type specified.";
116 }
146 117
147 public string getName()
148 {
149 throw new Exception("The method or operation is not implemented.");
150 }
151 118
152 public string getVersion() 119 // Stuff stops happening here
153 {
154 throw new Exception("The method or operation is not implemented.");
155 }
156 120
157 public void deleteInventoryCategory(InventoryCategory inventoryCategory) 121 response.Value = responseData;
158 { 122 return response;
159 _databasePlugin.deleteInventoryCategory(inventoryCategory);
160 } 123 }
161
162 #endregion
163 } 124 }
164} 125}
diff --git a/OpenSim/Grid/InventoryServer/InventoryService.cs b/OpenSim/Grid/InventoryServer/InventoryService.cs
deleted file mode 100644
index cbc0558..0000000
--- a/OpenSim/Grid/InventoryServer/InventoryService.cs
+++ /dev/null
@@ -1,136 +0,0 @@
1using System;
2using System.Runtime.Remoting;
3using System.Runtime.Remoting.Channels;
4using System.Runtime.Remoting.Channels.Tcp;
5using System.Runtime.Serialization.Formatters;
6using System.Collections;
7using System.Collections.Generic;
8
9
10using libsecondlife;
11using OpenSim.Framework.Data;
12using OpenSim.Framework.Communications;
13using OpenSim.Framework.Configuration;
14using InventoryCategory = OpenSim.Framework.Data.InventoryCategory;
15
16namespace OpenSim.Grid.InventoryServer
17{
18 class InventoryServiceSingleton : OpenSim.Framework.Communications.InventoryServiceBase
19 {
20 static InventoryManager _inventoryManager;
21
22 static public InventoryManager InventoryManager
23 {
24 set { _inventoryManager = value; }
25 get { return _inventoryManager; }
26 }
27
28#region Singleton Pattern
29 static InventoryServiceSingleton instance=null;
30
31 InventoryServiceSingleton()
32 {
33 }
34
35 public static InventoryServiceSingleton Instance
36 {
37 get
38 {
39 if (instance==null)
40 {
41 instance = new InventoryServiceSingleton();
42 }
43 return instance;
44 }
45 }
46#endregion
47
48#region IInventoryServices Members
49
50 public override void RequestInventoryForUser(LLUUID userID, InventoryFolderInfo folderCallBack, InventoryItemInfo itemCallBack)
51 {
52 List<InventoryFolderBase> folders = this.RequestFirstLevelFolders(userID);
53 InventoryFolderBase rootFolder = null;
54
55 //need to make sure we send root folder first
56 foreach (InventoryFolderBase folder in folders)
57 {
58 if (folder.parentID == libsecondlife.LLUUID.Zero)
59 {
60 rootFolder = folder;
61 folderCallBack(userID, folder);
62 }
63 }
64
65 if (rootFolder != null)
66 {
67 foreach (InventoryFolderBase folder in folders)
68 {
69 if (folder.folderID != rootFolder.folderID)
70 {
71 folderCallBack(userID, folder);
72
73 List<InventoryItemBase> items = this.RequestFolderItems(folder.folderID);
74 foreach (InventoryItemBase item in items)
75 {
76 itemCallBack(userID, item);
77 }
78 }
79 }
80 }
81
82 }
83
84 public override void AddNewInventoryFolder(LLUUID userID, InventoryFolderBase folder)
85 {
86 _inventoryManager.addInventoryFolder(folder);
87 }
88
89 public override void AddNewInventoryItem(LLUUID userID, InventoryItemBase item)
90 {
91 throw new Exception("Not implemented exception");
92 }
93
94 public override void DeleteInventoryItem(LLUUID userID, InventoryItemBase item)
95 {
96 throw new Exception("Not implemented exception");
97 }
98
99 public List<InventoryItemBase> RequestFolderItems(LLUUID folderID)
100 {
101 return _inventoryManager.getInventoryInFolder(folderID);
102 }
103
104
105#endregion
106 }
107
108 class InventoryService
109 {
110 InventoryServiceSingleton _inventoryServiceMethods;
111 public InventoryService(InventoryManager inventoryManager, InventoryConfig cfg)
112 {
113 // we only need to register the tcp channel once, and we don't know which other modules use remoting
114 if (ChannelServices.GetChannel("tcp") == null)
115 {
116 // Creating a custom formatter for a TcpChannel sink chain.
117 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
118 serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
119
120 IDictionary props = new Hashtable();
121 props["port"] = cfg.RemotingPort;
122 props["typeFilterLevel"] = TypeFilterLevel.Full;
123
124 // Pass the properties for the port setting and the server provider in the server chain argument. (Client remains null here.)
125 ChannelServices.RegisterChannel(new TcpChannel(props, null, serverProvider), true);
126 }
127
128 // Register the object
129 RemotingConfiguration.RegisterWellKnownServiceType(typeof(InventoryServiceSingleton), "Inventory", WellKnownObjectMode.Singleton);
130
131 _inventoryServiceMethods = InventoryServiceSingleton.Instance;
132 InventoryServiceSingleton.InventoryManager = inventoryManager;
133 }
134
135 }
136}
diff --git a/OpenSim/Grid/InventoryServer/Main.cs b/OpenSim/Grid/InventoryServer/Main.cs
index 392c741..97addb0 100644
--- a/OpenSim/Grid/InventoryServer/Main.cs
+++ b/OpenSim/Grid/InventoryServer/Main.cs
@@ -31,130 +31,49 @@ using System.Collections.Generic;
31using System.Reflection; 31using System.Reflection;
32using System.IO; 32using System.IO;
33using System.Text; 33using System.Text;
34using System.Xml;
35
36using libsecondlife; 34using libsecondlife;
35using OpenSim.Framework.User;
36using OpenSim.Framework.Sims;
37using OpenSim.Framework.Inventory;
37using OpenSim.Framework.Interfaces; 38using OpenSim.Framework.Interfaces;
38using OpenSim.Framework.Console; 39using OpenSim.Framework.Console;
40using OpenSim.Servers;
39using OpenSim.Framework.Utilities; 41using OpenSim.Framework.Utilities;
40using OpenSim.Framework.Configuration;
41using OpenSim.Framework.Data;
42using InventoryCategory = OpenSim.Framework.Data.InventoryCategory;
43 42
44namespace OpenSim.Grid.InventoryServer 43namespace OpenGridServices.InventoryServer
45{ 44{
46 45 public class OpenInventory_Main : BaseServer, conscmd_callback
47 public class OpenInventory_Main : conscmd_callback
48 { 46 {
47 ConsoleBase m_console;
48 InventoryManager m_inventoryManager;
49 49
50 public const string MainLogName = "INVENTORY";
51
52 private InventoryConfig m_config;
53 LogBase m_console;
54 InventoryManager m_inventoryManager; ///connection the database backend
55 InventoryService m_inventoryService; ///Remoting interface, where messages arrive
56 [STAThread]
57 public static void Main(string[] args) 50 public static void Main(string[] args)
58 { 51 {
59 Console.WriteLine("Launching InventoryServer...");
60
61 OpenInventory_Main inventoryServer = new OpenInventory_Main();
62
63 inventoryServer.Startup();
64
65// inventoryServer.RunCmd("load", new string[] { "library", "inventory_library.xml" });
66// inventoryServer.RunCmd("load", new string[] { "default", "inventory_default.xml" });
67
68 inventoryServer.Work();
69 } 52 }
70 53
71 public OpenInventory_Main() 54 public OpenInventory_Main()
72 { 55 {
73 56 m_console = new ConsoleBase("opengrid-inventory-console.log", "OpenInventory", this, false);
74 if (!Directory.Exists(Util.logDir())) 57 MainConsole.Instance = m_console;
75 {
76 Directory.CreateDirectory(Util.logDir());
77 }
78
79 m_console = new LogBase(Path.Combine(Util.logDir(), "opensim-inventory-console.log"), "OpenInventory", this, false);
80 MainLog.Instance = m_console;
81 }
82
83 private void Work()
84 {
85 m_console.Notice(OpenInventory_Main.MainLogName, "Enter help for a list of commands\n");
86
87 while (true)
88 {
89 m_console.MainLogPrompt();
90 }
91 } 58 }
92 59
93 public void Startup() 60 public void Startup()
94 { 61 {
95 MainLog.Instance.Verbose(OpenInventory_Main.MainLogName, "Initialising inventory manager..."); 62 MainConsole.Instance.Notice("Initialising inventory manager...");
96
97 m_config = new InventoryConfig(OpenInventory_Main.MainLogName, (Path.Combine(Util.configDir(), "InventoryServer_Config.xml")));
98
99 // instantiate the manager, responsible for doing the actual storage
100 m_inventoryManager = new InventoryManager(); 63 m_inventoryManager = new InventoryManager();
101 m_inventoryManager.AddDatabasePlugin(m_config.DatabaseProvider);
102
103 m_inventoryService = new InventoryService(m_inventoryManager, m_config);
104 64
105 // Dig out the embedded version number of this assembly 65 MainConsole.Instance.Notice("Starting HTTP server");
106 Assembly assembly = Assembly.GetExecutingAssembly(); 66 BaseHttpServer httpServer = new BaseHttpServer(8004);
107 string serverExeName = assembly.ManifestModule.Name;
108 Version serverExeVersion = AssemblyName.GetAssemblyName(serverExeName).Version;
109 67
110 m_console.Status(OpenInventory_Main.MainLogName, "Inventoryserver {0}.{1}.{2}.{3} - Startup complete", serverExeVersion.Major, serverExeVersion.Minor, serverExeVersion.Revision, serverExeVersion.Build); 68 httpServer.AddXmlRPCHandler("rootfolders", m_inventoryManager.XmlRpcInventoryRequest);
111 } 69 //httpServer.AddRestHandler("GET","/rootfolders/",Rest
112
113
114 public void Load(string[] cmdparams)
115 {
116 string cmd = cmdparams[0];
117 string fileName = cmdparams[1];
118
119 if (cmdparams.Length != 2)
120 {
121 cmd = "help";
122 }
123
124 switch (cmd)
125 {
126 case "library":
127 InventoryServiceSingleton.Instance.loadInventoryFromXmlFile(InventoryCategory.Library, fileName);
128 break;
129 case "default":
130 InventoryServiceSingleton.Instance.loadInventoryFromXmlFile(InventoryCategory.Default, fileName);
131 break;
132 case "user":
133 InventoryServiceSingleton.Instance.loadInventoryFromXmlFile(InventoryCategory.User, fileName);
134 break;
135 case "help":
136 m_console.Notice("load library <filename>, load library inventory, shared between all users");
137 m_console.Notice("load default <filename>, load template inventory, used when creating a new user inventory");
138 m_console.Notice("load user <first> <last>, load inventory for a specific users, warning this will reset the contents of the inventory");
139 break;
140 }
141 } 70 }
142 71
143 public void RunCmd(string cmd, string[] cmdparams) 72 public void RunCmd(string cmd, string[] cmdparams)
144 { 73 {
145 switch (cmd) 74 switch (cmd)
146 { 75 {
147 case "help":
148 m_console.Notice("load - load verious inventories, use \"load help\", to see a list of commands");
149 m_console.Notice("shutdown - shutdown the grid (USE CAUTION!)");
150 m_console.Notice("quit - shutdown the grid (USE CAUTION!)");
151 break;
152 case "load":
153 Load(cmdparams);
154 break;
155 case "quit":
156 case "shutdown": 76 case "shutdown":
157 MainLog.Instance.Verbose(OpenInventory_Main.MainLogName, "Shutting down inventory server");
158 m_console.Close(); 77 m_console.Close();
159 Environment.Exit(0); 78 Environment.Exit(0);
160 break; 79 break;
diff --git a/OpenSim/Grid/UserServer/Main.cs b/OpenSim/Grid/UserServer/Main.cs
index 28635dd..c2822b6 100644
--- a/OpenSim/Grid/UserServer/Main.cs
+++ b/OpenSim/Grid/UserServer/Main.cs
@@ -36,8 +36,6 @@ using OpenSim.Framework.Interfaces;
36using OpenSim.Framework.Servers; 36using OpenSim.Framework.Servers;
37using OpenSim.Framework.Utilities; 37using OpenSim.Framework.Utilities;
38using OpenSim.Framework.Configuration; 38using OpenSim.Framework.Configuration;
39using OpenSim.Framework.Communications;
40using OpenSim.Region.Communications.OGS1;
41 39
42namespace OpenSim.Grid.UserServer 40namespace OpenSim.Grid.UserServer
43{ 41{
@@ -50,8 +48,6 @@ namespace OpenSim.Grid.UserServer
50 public UserManager m_userManager; 48 public UserManager m_userManager;
51 public UserLoginService m_loginService; 49 public UserLoginService m_loginService;
52 50
53 public IInventoryServices m_inventoryService;
54
55 LogBase m_console; 51 LogBase m_console;
56 52
57 [STAThread] 53 [STAThread]
@@ -94,11 +90,7 @@ namespace OpenSim.Grid.UserServer
94 m_userManager._config = Cfg; 90 m_userManager._config = Cfg;
95 m_userManager.AddPlugin(Cfg.DatabaseProvider); 91 m_userManager.AddPlugin(Cfg.DatabaseProvider);
96 92
97 // prepare connection to the inventory server 93 m_loginService = new UserLoginService(m_userManager, Cfg, Cfg.DefaultStartupMsg);
98 m_inventoryService = new OGS1InventoryService(Cfg.InventoryServerName, Cfg.InventoryServerPort, null);
99
100
101 m_loginService = new UserLoginService(m_userManager, m_inventoryService, Cfg, Cfg.DefaultStartupMsg);
102 94
103 MainLog.Instance.Verbose("Main.cs:Startup() - Starting HTTP process"); 95 MainLog.Instance.Verbose("Main.cs:Startup() - Starting HTTP process");
104 BaseHttpServer httpServer = new BaseHttpServer((int)Cfg.HttpPort); 96 BaseHttpServer httpServer = new BaseHttpServer((int)Cfg.HttpPort);
@@ -111,7 +103,6 @@ namespace OpenSim.Grid.UserServer
111 httpServer.AddStreamHandler( new RestStreamHandler("DELETE", "/usersessions/", m_userManager.RestDeleteUserSessionMethod )); 103 httpServer.AddStreamHandler( new RestStreamHandler("DELETE", "/usersessions/", m_userManager.RestDeleteUserSessionMethod ));
112 104
113 httpServer.Start(); 105 httpServer.Start();
114
115 m_console.Status("SERVER", "Userserver 0.3 - Startup complete"); 106 m_console.Status("SERVER", "Userserver 0.3 - Startup complete");
116 } 107 }
117 108
diff --git a/OpenSim/Grid/UserServer/UserLoginService.cs b/OpenSim/Grid/UserServer/UserLoginService.cs
index f0140a5..95192e3 100644
--- a/OpenSim/Grid/UserServer/UserLoginService.cs
+++ b/OpenSim/Grid/UserServer/UserLoginService.cs
@@ -6,7 +6,6 @@ using OpenSim.Framework.Data;
6using OpenSim.Framework.UserManagement; 6using OpenSim.Framework.UserManagement;
7using OpenSim.Framework.Utilities; 7using OpenSim.Framework.Utilities;
8using OpenSim.Framework.Configuration; 8using OpenSim.Framework.Configuration;
9using OpenSim.Framework.Communications;
10 9
11namespace OpenSim.Grid.UserServer 10namespace OpenSim.Grid.UserServer
12{ 11{
@@ -14,8 +13,8 @@ namespace OpenSim.Grid.UserServer
14 { 13 {
15 public UserConfig m_config; 14 public UserConfig m_config;
16 15
17 public UserLoginService(UserManagerBase userManager, IInventoryServices inventoryServer, UserConfig config, string welcomeMess) 16 public UserLoginService(UserManagerBase userManager, UserConfig config, string welcomeMess)
18 : base(userManager, inventoryServer, welcomeMess) 17 : base(userManager, welcomeMess)
19 { 18 {
20 m_config = config; 19 m_config = config;
21 } 20 }
@@ -72,29 +71,25 @@ namespace OpenSim.Grid.UserServer
72 theUser.currentAgent.currentHandle = SimInfo.regionHandle; 71 theUser.currentAgent.currentHandle = SimInfo.regionHandle;
73 72
74 System.Console.WriteLine("Informing region --> " + SimInfo.httpServerURI); 73 System.Console.WriteLine("Informing region --> " + SimInfo.httpServerURI);
75 // Send 74 // Send
76 try 75 try
77 { 76 {
78 XmlRpcRequest GridReq = new XmlRpcRequest("expect_user", SendParams); 77 XmlRpcRequest GridReq = new XmlRpcRequest("expect_user", SendParams);
79 XmlRpcResponse GridResp = GridReq.Send(SimInfo.httpServerURI, 6000); 78 XmlRpcResponse GridResp = GridReq.Send(SimInfo.httpServerURI, 6000);
80 } 79 }
81 catch( WebException e ) 80 catch( WebException e )
82 { 81 {
83 switch( e.Status ) 82 switch( e.Status )
84 { 83 {
85 case WebExceptionStatus.Timeout: 84 case WebExceptionStatus.Timeout:
86 //TODO: Send him to nearby or default region instead 85 //TODO: Send him to nearby or default region instead
87 break; 86 break;
88 87
89 default: 88 default:
90 throw; 89 throw;
91 } 90 }
92 } 91 }
93 } 92 }
94 } 93 }
95} 94}
96 95
97
98
99
100