diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | addon-modules/OpenSimProfile/GridCommon.ini.example | 5 | ||||
-rw-r--r-- | addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs | 982 | ||||
-rw-r--r-- | addon-modules/OpenSimProfile/Robust.HG.ini.example | 3 | ||||
-rw-r--r-- | addon-modules/OpenSimProfile/prebuild.xml | 40 | ||||
-rw-r--r-- | addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs | 753 | ||||
-rw-r--r-- | addon-modules/OpenSimSearch/README | 294 | ||||
-rw-r--r-- | addon-modules/OpenSimSearch/prebuild.xml | 39 |
8 files changed, 2116 insertions, 1 deletions
@@ -78,7 +78,6 @@ TAGS | |||
78 | Makefile.local | 78 | Makefile.local |
79 | bin/.version | 79 | bin/.version |
80 | compile.bat | 80 | compile.bat |
81 | addon-modules | ||
82 | OpenSim/Data/Tests/test-results/ | 81 | OpenSim/Data/Tests/test-results/ |
83 | OpenSim/Framework/Serialization/Tests/test-results/ | 82 | OpenSim/Framework/Serialization/Tests/test-results/ |
84 | OpenSim/Framework/Servers/Tests/test-results/ | 83 | OpenSim/Framework/Servers/Tests/test-results/ |
diff --git a/addon-modules/OpenSimProfile/GridCommon.ini.example b/addon-modules/OpenSimProfile/GridCommon.ini.example new file mode 100644 index 0000000..8afe23d --- /dev/null +++ b/addon-modules/OpenSimProfile/GridCommon.ini.example | |||
@@ -0,0 +1,5 @@ | |||
1 | Add the following to your own GridCommon.ini file to make the UserProfile service working | ||
2 | |||
3 | [Profile] | ||
4 | ; Change it to your own HTTP server to have the Profile server work | ||
5 | ; ProfileURL = http://192.168.0.1/profile.php | ||
diff --git a/addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs b/addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs new file mode 100644 index 0000000..775e8b1 --- /dev/null +++ b/addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs | |||
@@ -0,0 +1,982 @@ | |||
1 | using System; | ||
2 | using System.Collections; | ||
3 | using System.Collections.Generic; | ||
4 | using System.Globalization; | ||
5 | using System.Net; | ||
6 | using System.Net.Sockets; | ||
7 | using System.Reflection; | ||
8 | using System.Xml; | ||
9 | using OpenMetaverse; | ||
10 | using log4net; | ||
11 | using Nini.Config; | ||
12 | using Nwc.XmlRpc; | ||
13 | using OpenSim.Framework; | ||
14 | using OpenSim.Region.Framework.Interfaces; | ||
15 | using OpenSim.Region.Framework.Scenes; | ||
16 | using OpenSim.Services.Interfaces; | ||
17 | using Mono.Addins; | ||
18 | using OpenSim.Services.Connectors.Hypergrid; | ||
19 | |||
20 | [assembly: Addin("OpenProfileModule", "0.1")] | ||
21 | [assembly: AddinDependency("OpenSim", "0.5")] | ||
22 | |||
23 | namespace OpenSimProfile.Modules.OpenProfile | ||
24 | { | ||
25 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")] | ||
26 | public class OpenProfileModule : IProfileModule, ISharedRegionModule | ||
27 | { | ||
28 | // | ||
29 | // Log module | ||
30 | // | ||
31 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
32 | |||
33 | // | ||
34 | // Module vars | ||
35 | // | ||
36 | private IConfigSource m_Config; | ||
37 | private List<Scene> m_Scenes = new List<Scene>(); | ||
38 | private string m_ProfileServer = ""; | ||
39 | private bool m_Enabled = true; | ||
40 | |||
41 | IUserManagement m_uMan; | ||
42 | IUserManagement UserManagementModule | ||
43 | { | ||
44 | get | ||
45 | { | ||
46 | if (m_uMan == null) | ||
47 | m_uMan = m_Scenes[0].RequestModuleInterface<IUserManagement>(); | ||
48 | return m_uMan; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | #region IRegionModuleBase implementation | ||
53 | public void Initialise(IConfigSource source) | ||
54 | { | ||
55 | m_Config = source; | ||
56 | |||
57 | IConfig profileConfig = m_Config.Configs["Profile"]; | ||
58 | |||
59 | if (profileConfig == null) | ||
60 | { | ||
61 | m_Enabled = false; | ||
62 | return; | ||
63 | } | ||
64 | m_ProfileServer = profileConfig.GetString("ProfileURL", ""); | ||
65 | if (m_ProfileServer == "") | ||
66 | { | ||
67 | m_Enabled = false; | ||
68 | return; | ||
69 | } | ||
70 | else | ||
71 | { | ||
72 | m_log.Info("[PROFILE] OpenProfile module is activated"); | ||
73 | m_Enabled = true; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | public void AddRegion(Scene scene) | ||
78 | { | ||
79 | if (!m_Enabled) | ||
80 | return; | ||
81 | |||
82 | // Hook up events | ||
83 | scene.EventManager.OnNewClient += OnNewClient; | ||
84 | |||
85 | // Take ownership of the IProfileModule service | ||
86 | scene.RegisterModuleInterface<IProfileModule>(this); | ||
87 | |||
88 | // Add our scene to our list... | ||
89 | lock(m_Scenes) | ||
90 | { | ||
91 | m_Scenes.Add(scene); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | public void RemoveRegion(Scene scene) | ||
96 | { | ||
97 | if (!m_Enabled) | ||
98 | return; | ||
99 | |||
100 | scene.UnregisterModuleInterface<IProfileModule>(this); | ||
101 | m_Scenes.Remove(scene); | ||
102 | } | ||
103 | |||
104 | public void RegionLoaded(Scene scene) | ||
105 | { | ||
106 | } | ||
107 | |||
108 | public Type ReplaceableInterface | ||
109 | { | ||
110 | get { return null; } | ||
111 | } | ||
112 | |||
113 | public void PostInitialise() | ||
114 | { | ||
115 | } | ||
116 | |||
117 | public void Close() | ||
118 | { | ||
119 | } | ||
120 | |||
121 | public string Name | ||
122 | { | ||
123 | get { return "ProfileModule"; } | ||
124 | } | ||
125 | #endregion | ||
126 | |||
127 | private ScenePresence FindPresence(UUID clientID) | ||
128 | { | ||
129 | ScenePresence p; | ||
130 | |||
131 | foreach (Scene s in m_Scenes) | ||
132 | { | ||
133 | p = s.GetScenePresence(clientID); | ||
134 | if (p != null && !p.IsChildAgent) | ||
135 | return p; | ||
136 | } | ||
137 | return null; | ||
138 | } | ||
139 | |||
140 | /// New Client Event Handler | ||
141 | private void OnNewClient(IClientAPI client) | ||
142 | { | ||
143 | // Subscribe to messages | ||
144 | |||
145 | // Classifieds | ||
146 | client.AddGenericPacketHandler("avatarclassifiedsrequest", HandleAvatarClassifiedsRequest); | ||
147 | client.OnClassifiedInfoUpdate += ClassifiedInfoUpdate; | ||
148 | client.OnClassifiedDelete += ClassifiedDelete; | ||
149 | |||
150 | // Picks | ||
151 | client.AddGenericPacketHandler("avatarpicksrequest", HandleAvatarPicksRequest); | ||
152 | client.AddGenericPacketHandler("pickinforequest", HandlePickInfoRequest); | ||
153 | client.OnPickInfoUpdate += PickInfoUpdate; | ||
154 | client.OnPickDelete += PickDelete; | ||
155 | |||
156 | // Notes | ||
157 | client.AddGenericPacketHandler("avatarnotesrequest", HandleAvatarNotesRequest); | ||
158 | client.OnAvatarNotesUpdate += AvatarNotesUpdate; | ||
159 | |||
160 | //Profile | ||
161 | client.OnRequestAvatarProperties += RequestAvatarProperties; | ||
162 | client.OnUpdateAvatarProperties += UpdateAvatarProperties; | ||
163 | client.OnAvatarInterestUpdate += AvatarInterestsUpdate; | ||
164 | client.OnUserInfoRequest += UserPreferencesRequest; | ||
165 | client.OnUpdateUserInfo += UpdateUserPreferences; | ||
166 | } | ||
167 | |||
168 | // | ||
169 | // Make external XMLRPC request | ||
170 | // | ||
171 | private Hashtable GenericXMLRPCRequest(Hashtable ReqParams, string method, string server) | ||
172 | { | ||
173 | ArrayList SendParams = new ArrayList(); | ||
174 | SendParams.Add(ReqParams); | ||
175 | |||
176 | // Send Request | ||
177 | XmlRpcResponse Resp; | ||
178 | try | ||
179 | { | ||
180 | XmlRpcRequest Req = new XmlRpcRequest(method, SendParams); | ||
181 | Resp = Req.Send(server, 30000); | ||
182 | } | ||
183 | catch (WebException ex) | ||
184 | { | ||
185 | m_log.ErrorFormat("[PROFILE]: Unable to connect to Profile " + | ||
186 | "Server {0}. Exception {1}", m_ProfileServer, ex); | ||
187 | |||
188 | Hashtable ErrorHash = new Hashtable(); | ||
189 | ErrorHash["success"] = false; | ||
190 | ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. "; | ||
191 | ErrorHash["errorURI"] = ""; | ||
192 | |||
193 | return ErrorHash; | ||
194 | } | ||
195 | catch (SocketException ex) | ||
196 | { | ||
197 | m_log.ErrorFormat( | ||
198 | "[PROFILE]: Unable to connect to Profile Server {0}. Method {1}, params {2}. " + | ||
199 | "Exception {3}", m_ProfileServer, method, ReqParams, ex); | ||
200 | |||
201 | Hashtable ErrorHash = new Hashtable(); | ||
202 | ErrorHash["success"] = false; | ||
203 | ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. "; | ||
204 | ErrorHash["errorURI"] = ""; | ||
205 | |||
206 | return ErrorHash; | ||
207 | } | ||
208 | catch (XmlException ex) | ||
209 | { | ||
210 | m_log.ErrorFormat( | ||
211 | "[PROFILE]: Unable to connect to Profile Server {0}. Method {1}, params {2}. " + | ||
212 | "Exception {3}", m_ProfileServer, method, ReqParams.ToString(), ex); | ||
213 | Hashtable ErrorHash = new Hashtable(); | ||
214 | ErrorHash["success"] = false; | ||
215 | ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. "; | ||
216 | ErrorHash["errorURI"] = ""; | ||
217 | |||
218 | return ErrorHash; | ||
219 | } | ||
220 | if (Resp.IsFault) | ||
221 | { | ||
222 | Hashtable ErrorHash = new Hashtable(); | ||
223 | ErrorHash["success"] = false; | ||
224 | ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. "; | ||
225 | ErrorHash["errorURI"] = ""; | ||
226 | return ErrorHash; | ||
227 | } | ||
228 | Hashtable RespData = (Hashtable)Resp.Value; | ||
229 | |||
230 | return RespData; | ||
231 | } | ||
232 | |||
233 | // Classifieds Handler | ||
234 | public void HandleAvatarClassifiedsRequest(Object sender, string method, List<String> args) | ||
235 | { | ||
236 | if (!(sender is IClientAPI)) | ||
237 | return; | ||
238 | |||
239 | IClientAPI remoteClient = (IClientAPI)sender; | ||
240 | |||
241 | UUID targetID; | ||
242 | UUID.TryParse(args[0], out targetID); | ||
243 | |||
244 | // Can't handle NPC yet... | ||
245 | ScenePresence p = FindPresence(targetID); | ||
246 | |||
247 | if (null != p) | ||
248 | { | ||
249 | if (p.PresenceType == PresenceType.Npc) | ||
250 | return; | ||
251 | } | ||
252 | |||
253 | string serverURI = string.Empty; | ||
254 | bool foreign = GetUserProfileServerURI(targetID, out serverURI); | ||
255 | |||
256 | Hashtable ReqHash = new Hashtable(); | ||
257 | ReqHash["uuid"] = args[0]; | ||
258 | |||
259 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
260 | method, serverURI); | ||
261 | |||
262 | if (!Convert.ToBoolean(result["success"])) | ||
263 | { | ||
264 | remoteClient.SendAgentAlertMessage( | ||
265 | result["errorMessage"].ToString(), false); | ||
266 | return; | ||
267 | } | ||
268 | |||
269 | ArrayList dataArray = (ArrayList)result["data"]; | ||
270 | |||
271 | Dictionary<UUID, string> classifieds = new Dictionary<UUID, string>(); | ||
272 | |||
273 | foreach (Object o in dataArray) | ||
274 | { | ||
275 | Hashtable d = (Hashtable)o; | ||
276 | |||
277 | classifieds[new UUID(d["classifiedid"].ToString())] = d["name"].ToString(); | ||
278 | } | ||
279 | |||
280 | remoteClient.SendAvatarClassifiedReply(new UUID(args[0]), classifieds); | ||
281 | } | ||
282 | |||
283 | // Classifieds Update | ||
284 | public void ClassifiedInfoUpdate(UUID queryclassifiedID, uint queryCategory, string queryName, string queryDescription, UUID queryParcelID, | ||
285 | uint queryParentEstate, UUID querySnapshotID, Vector3 queryGlobalPos, byte queryclassifiedFlags, | ||
286 | int queryclassifiedPrice, IClientAPI remoteClient) | ||
287 | { | ||
288 | Hashtable ReqHash = new Hashtable(); | ||
289 | |||
290 | Scene s = (Scene) remoteClient.Scene; | ||
291 | Vector3 pos = remoteClient.SceneAgent.AbsolutePosition; | ||
292 | ILandObject land = s.LandChannel.GetLandObject(pos.X, pos.Y); | ||
293 | |||
294 | if (land == null) | ||
295 | ReqHash["parcelname"] = String.Empty; | ||
296 | else | ||
297 | ReqHash["parcelname"] = land.LandData.Name; | ||
298 | |||
299 | ReqHash["creatorUUID"] = remoteClient.AgentId.ToString(); | ||
300 | ReqHash["classifiedUUID"] = queryclassifiedID.ToString(); | ||
301 | ReqHash["category"] = queryCategory.ToString(); | ||
302 | ReqHash["name"] = queryName; | ||
303 | ReqHash["description"] = queryDescription; | ||
304 | ReqHash["parentestate"] = queryParentEstate.ToString(); | ||
305 | ReqHash["snapshotUUID"] = querySnapshotID.ToString(); | ||
306 | ReqHash["sim_name"] = remoteClient.Scene.RegionInfo.RegionName; | ||
307 | ReqHash["globalpos"] = queryGlobalPos.ToString(); | ||
308 | ReqHash["classifiedFlags"] = queryclassifiedFlags.ToString(); | ||
309 | ReqHash["classifiedPrice"] = queryclassifiedPrice.ToString(); | ||
310 | |||
311 | ScenePresence p = FindPresence(remoteClient.AgentId); | ||
312 | |||
313 | |||
314 | string serverURI = string.Empty; | ||
315 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
316 | |||
317 | Vector3 avaPos = p.AbsolutePosition; | ||
318 | |||
319 | // Getting the parceluuid for this parcel | ||
320 | ReqHash["parcelUUID"] = p.currentParcelUUID.ToString(); | ||
321 | |||
322 | // Getting the global position for the Avatar | ||
323 | Vector3 posGlobal = new Vector3(remoteClient.Scene.RegionInfo.RegionLocX * Constants.RegionSize + avaPos.X, | ||
324 | remoteClient.Scene.RegionInfo.RegionLocY * Constants.RegionSize + avaPos.Y, | ||
325 | avaPos.Z); | ||
326 | |||
327 | ReqHash["pos_global"] = posGlobal.ToString(); | ||
328 | |||
329 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
330 | "classified_update", serverURI); | ||
331 | |||
332 | if (!Convert.ToBoolean(result["success"])) | ||
333 | { | ||
334 | remoteClient.SendAgentAlertMessage( | ||
335 | result["errorMessage"].ToString(), false); | ||
336 | } | ||
337 | } | ||
338 | |||
339 | // Classifieds Delete | ||
340 | public void ClassifiedDelete(UUID queryClassifiedID, IClientAPI remoteClient) | ||
341 | { | ||
342 | Hashtable ReqHash = new Hashtable(); | ||
343 | |||
344 | string serverURI = string.Empty; | ||
345 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
346 | |||
347 | ReqHash["classifiedID"] = queryClassifiedID.ToString(); | ||
348 | |||
349 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
350 | "classified_delete", serverURI); | ||
351 | |||
352 | if (!Convert.ToBoolean(result["success"])) | ||
353 | { | ||
354 | remoteClient.SendAgentAlertMessage( | ||
355 | result["errorMessage"].ToString(), false); | ||
356 | } | ||
357 | } | ||
358 | |||
359 | // Picks Handler | ||
360 | public void HandleAvatarPicksRequest(Object sender, string method, List<String> args) | ||
361 | { | ||
362 | if (!(sender is IClientAPI)) | ||
363 | return; | ||
364 | |||
365 | IClientAPI remoteClient = (IClientAPI)sender; | ||
366 | |||
367 | UUID targetID; | ||
368 | UUID.TryParse(args[0], out targetID); | ||
369 | |||
370 | // Can't handle NPC yet... | ||
371 | ScenePresence p = FindPresence(targetID); | ||
372 | |||
373 | if (null != p) | ||
374 | { | ||
375 | if (p.PresenceType == PresenceType.Npc) | ||
376 | return; | ||
377 | } | ||
378 | |||
379 | string serverURI = string.Empty; | ||
380 | bool foreign = GetUserProfileServerURI(targetID, out serverURI); | ||
381 | |||
382 | Hashtable ReqHash = new Hashtable(); | ||
383 | ReqHash["uuid"] = args[0]; | ||
384 | |||
385 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
386 | method, serverURI); | ||
387 | |||
388 | if (!Convert.ToBoolean(result["success"])) | ||
389 | { | ||
390 | remoteClient.SendAgentAlertMessage( | ||
391 | result["errorMessage"].ToString(), false); | ||
392 | return; | ||
393 | } | ||
394 | |||
395 | ArrayList dataArray = (ArrayList)result["data"]; | ||
396 | |||
397 | Dictionary<UUID, string> picks = new Dictionary<UUID, string>(); | ||
398 | |||
399 | if (dataArray != null) | ||
400 | { | ||
401 | foreach (Object o in dataArray) | ||
402 | { | ||
403 | Hashtable d = (Hashtable)o; | ||
404 | |||
405 | picks[new UUID(d["pickid"].ToString())] = d["name"].ToString(); | ||
406 | } | ||
407 | } | ||
408 | |||
409 | remoteClient.SendAvatarPicksReply(new UUID(args[0]), picks); | ||
410 | } | ||
411 | |||
412 | // Picks Request | ||
413 | public void HandlePickInfoRequest(Object sender, string method, List<String> args) | ||
414 | { | ||
415 | if (!(sender is IClientAPI)) | ||
416 | return; | ||
417 | |||
418 | IClientAPI remoteClient = (IClientAPI)sender; | ||
419 | |||
420 | Hashtable ReqHash = new Hashtable(); | ||
421 | |||
422 | UUID targetID; | ||
423 | UUID.TryParse(args[0], out targetID); | ||
424 | |||
425 | string serverURI = string.Empty; | ||
426 | bool foreign = GetUserProfileServerURI(targetID, out serverURI); | ||
427 | |||
428 | ReqHash["avatar_id"] = args[0]; | ||
429 | ReqHash["pick_id"] = args[1]; | ||
430 | |||
431 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
432 | method, serverURI); | ||
433 | |||
434 | if (!Convert.ToBoolean(result["success"])) | ||
435 | { | ||
436 | remoteClient.SendAgentAlertMessage( | ||
437 | result["errorMessage"].ToString(), false); | ||
438 | return; | ||
439 | } | ||
440 | |||
441 | ArrayList dataArray = (ArrayList)result["data"]; | ||
442 | |||
443 | Hashtable d = (Hashtable)dataArray[0]; | ||
444 | |||
445 | Vector3 globalPos = new Vector3(); | ||
446 | Vector3.TryParse(d["posglobal"].ToString(), out globalPos); | ||
447 | |||
448 | if (d["description"] == null) | ||
449 | d["description"] = String.Empty; | ||
450 | |||
451 | remoteClient.SendPickInfoReply( | ||
452 | new UUID(d["pickuuid"].ToString()), | ||
453 | new UUID(d["creatoruuid"].ToString()), | ||
454 | Convert.ToBoolean(d["toppick"]), | ||
455 | new UUID(d["parceluuid"].ToString()), | ||
456 | d["name"].ToString(), | ||
457 | d["description"].ToString(), | ||
458 | new UUID(d["snapshotuuid"].ToString()), | ||
459 | d["user"].ToString(), | ||
460 | d["originalname"].ToString(), | ||
461 | d["simname"].ToString(), | ||
462 | globalPos, | ||
463 | Convert.ToInt32(d["sortorder"]), | ||
464 | Convert.ToBoolean(d["enabled"])); | ||
465 | } | ||
466 | |||
467 | // Picks Update | ||
468 | public void PickInfoUpdate(IClientAPI remoteClient, UUID pickID, UUID creatorID, bool topPick, string name, string desc, UUID snapshotID, int sortOrder, bool enabled) | ||
469 | { | ||
470 | Hashtable ReqHash = new Hashtable(); | ||
471 | |||
472 | ReqHash["agent_id"] = remoteClient.AgentId.ToString(); | ||
473 | ReqHash["pick_id"] = pickID.ToString(); | ||
474 | ReqHash["creator_id"] = creatorID.ToString(); | ||
475 | ReqHash["top_pick"] = topPick.ToString(); | ||
476 | ReqHash["name"] = name; | ||
477 | ReqHash["desc"] = desc; | ||
478 | ReqHash["snapshot_id"] = snapshotID.ToString(); | ||
479 | ReqHash["sort_order"] = sortOrder.ToString(); | ||
480 | ReqHash["enabled"] = enabled.ToString(); | ||
481 | ReqHash["sim_name"] = remoteClient.Scene.RegionInfo.RegionName; | ||
482 | |||
483 | ScenePresence p = FindPresence(remoteClient.AgentId); | ||
484 | |||
485 | Vector3 avaPos = p.AbsolutePosition; | ||
486 | |||
487 | // Getting the parceluuid for this parcel | ||
488 | ReqHash["parcel_uuid"] = p.currentParcelUUID.ToString(); | ||
489 | |||
490 | // Getting the global position for the Avatar | ||
491 | Vector3 posGlobal = new Vector3(remoteClient.Scene.RegionInfo.RegionLocX*Constants.RegionSize + avaPos.X, | ||
492 | remoteClient.Scene.RegionInfo.RegionLocY*Constants.RegionSize + avaPos.Y, | ||
493 | avaPos.Z); | ||
494 | |||
495 | ReqHash["pos_global"] = posGlobal.ToString(); | ||
496 | |||
497 | // Getting the owner of the parcel | ||
498 | ReqHash["user"] = ""; //FIXME: Get avatar/group who owns parcel | ||
499 | |||
500 | string serverURI = string.Empty; | ||
501 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
502 | |||
503 | // Do the request | ||
504 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
505 | "picks_update", serverURI); | ||
506 | |||
507 | if (!Convert.ToBoolean(result["success"])) | ||
508 | { | ||
509 | remoteClient.SendAgentAlertMessage( | ||
510 | result["errorMessage"].ToString(), false); | ||
511 | } | ||
512 | } | ||
513 | |||
514 | // Picks Delete | ||
515 | public void PickDelete(IClientAPI remoteClient, UUID queryPickID) | ||
516 | { | ||
517 | Hashtable ReqHash = new Hashtable(); | ||
518 | |||
519 | ReqHash["pick_id"] = queryPickID.ToString(); | ||
520 | |||
521 | string serverURI = string.Empty; | ||
522 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
523 | |||
524 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
525 | "picks_delete", serverURI); | ||
526 | |||
527 | if (!Convert.ToBoolean(result["success"])) | ||
528 | { | ||
529 | remoteClient.SendAgentAlertMessage( | ||
530 | result["errorMessage"].ToString(), false); | ||
531 | } | ||
532 | } | ||
533 | |||
534 | // Notes Handler | ||
535 | public void HandleAvatarNotesRequest(Object sender, string method, List<String> args) | ||
536 | { | ||
537 | string targetid; | ||
538 | string notes = ""; | ||
539 | |||
540 | if (!(sender is IClientAPI)) | ||
541 | return; | ||
542 | |||
543 | IClientAPI remoteClient = (IClientAPI)sender; | ||
544 | |||
545 | Hashtable ReqHash = new Hashtable(); | ||
546 | |||
547 | ReqHash["avatar_id"] = remoteClient.AgentId.ToString(); | ||
548 | ReqHash["uuid"] = args[0]; | ||
549 | |||
550 | string serverURI = string.Empty; | ||
551 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
552 | |||
553 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
554 | method, serverURI); | ||
555 | |||
556 | if (!Convert.ToBoolean(result["success"])) | ||
557 | { | ||
558 | remoteClient.SendAgentAlertMessage( | ||
559 | result["errorMessage"].ToString(), false); | ||
560 | return; | ||
561 | } | ||
562 | |||
563 | ArrayList dataArray = (ArrayList)result["data"]; | ||
564 | |||
565 | if (dataArray != null && dataArray[0] != null) | ||
566 | { | ||
567 | Hashtable d = (Hashtable)dataArray[0]; | ||
568 | |||
569 | targetid = d["targetid"].ToString(); | ||
570 | if (d["notes"] != null) | ||
571 | notes = d["notes"].ToString(); | ||
572 | |||
573 | remoteClient.SendAvatarNotesReply(new UUID(targetid), notes); | ||
574 | } | ||
575 | } | ||
576 | |||
577 | // Notes Update | ||
578 | public void AvatarNotesUpdate(IClientAPI remoteClient, UUID queryTargetID, string queryNotes) | ||
579 | { | ||
580 | Hashtable ReqHash = new Hashtable(); | ||
581 | |||
582 | ReqHash["avatar_id"] = remoteClient.AgentId.ToString(); | ||
583 | ReqHash["target_id"] = queryTargetID.ToString(); | ||
584 | ReqHash["notes"] = queryNotes; | ||
585 | |||
586 | string serverURI = string.Empty; | ||
587 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
588 | |||
589 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
590 | "avatar_notes_update", serverURI); | ||
591 | |||
592 | if (!Convert.ToBoolean(result["success"])) | ||
593 | { | ||
594 | remoteClient.SendAgentAlertMessage( | ||
595 | result["errorMessage"].ToString(), false); | ||
596 | } | ||
597 | } | ||
598 | |||
599 | // Standard Profile bits | ||
600 | public void AvatarInterestsUpdate(IClientAPI remoteClient, uint wantmask, string wanttext, uint skillsmask, string skillstext, string languages) | ||
601 | { | ||
602 | Hashtable ReqHash = new Hashtable(); | ||
603 | |||
604 | ReqHash["avatar_id"] = remoteClient.AgentId.ToString(); | ||
605 | ReqHash["wantmask"] = wantmask.ToString(); | ||
606 | ReqHash["wanttext"] = wanttext; | ||
607 | ReqHash["skillsmask"] = skillsmask.ToString(); | ||
608 | ReqHash["skillstext"] = skillstext; | ||
609 | ReqHash["languages"] = languages; | ||
610 | |||
611 | string serverURI = string.Empty; | ||
612 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
613 | |||
614 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
615 | "avatar_interests_update", serverURI); | ||
616 | |||
617 | if (!Convert.ToBoolean(result["success"])) | ||
618 | { | ||
619 | remoteClient.SendAgentAlertMessage( | ||
620 | result["errorMessage"].ToString(), false); | ||
621 | } | ||
622 | } | ||
623 | |||
624 | public void UserPreferencesRequest(IClientAPI remoteClient) | ||
625 | { | ||
626 | Hashtable ReqHash = new Hashtable(); | ||
627 | |||
628 | ReqHash["avatar_id"] = remoteClient.AgentId.ToString(); | ||
629 | |||
630 | string serverURI = string.Empty; | ||
631 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
632 | |||
633 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
634 | "user_preferences_request", serverURI); | ||
635 | |||
636 | if (!Convert.ToBoolean(result["success"])) | ||
637 | { | ||
638 | remoteClient.SendAgentAlertMessage( | ||
639 | result["errorMessage"].ToString(), false); | ||
640 | return; | ||
641 | } | ||
642 | |||
643 | ArrayList dataArray = (ArrayList)result["data"]; | ||
644 | |||
645 | if (dataArray != null && dataArray[0] != null) | ||
646 | { | ||
647 | Hashtable d = (Hashtable)dataArray[0]; | ||
648 | string mail = ""; | ||
649 | |||
650 | if (d["email"] != null) | ||
651 | mail = d["email"].ToString(); | ||
652 | |||
653 | remoteClient.SendUserInfoReply( | ||
654 | Convert.ToBoolean(d["imviaemail"]), | ||
655 | Convert.ToBoolean(d["visible"]), | ||
656 | mail); | ||
657 | } | ||
658 | } | ||
659 | |||
660 | public void UpdateUserPreferences(bool imViaEmail, bool visible, IClientAPI remoteClient) | ||
661 | { | ||
662 | Hashtable ReqHash = new Hashtable(); | ||
663 | |||
664 | ReqHash["avatar_id"] = remoteClient.AgentId.ToString(); | ||
665 | ReqHash["imViaEmail"] = imViaEmail.ToString(); | ||
666 | ReqHash["visible"] = visible.ToString(); | ||
667 | |||
668 | string serverURI = string.Empty; | ||
669 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
670 | |||
671 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
672 | "user_preferences_update", serverURI); | ||
673 | |||
674 | if (!Convert.ToBoolean(result["success"])) | ||
675 | { | ||
676 | remoteClient.SendAgentAlertMessage( | ||
677 | result["errorMessage"].ToString(), false); | ||
678 | } | ||
679 | } | ||
680 | |||
681 | // Profile data like the WebURL | ||
682 | private Hashtable GetProfileData(UUID userID) | ||
683 | { | ||
684 | Hashtable ReqHash = new Hashtable(); | ||
685 | |||
686 | // Can't handle NPC yet... | ||
687 | ScenePresence p = FindPresence(userID); | ||
688 | |||
689 | if (null != p) | ||
690 | { | ||
691 | if (p.PresenceType == PresenceType.Npc) | ||
692 | { | ||
693 | Hashtable npc =new Hashtable(); | ||
694 | npc["success"] = "false"; | ||
695 | npc["errorMessage"] = "Presence is NPC. "; | ||
696 | return npc; | ||
697 | } | ||
698 | } | ||
699 | |||
700 | ReqHash["avatar_id"] = userID.ToString(); | ||
701 | |||
702 | string serverURI = string.Empty; | ||
703 | bool foreign = GetUserProfileServerURI(userID, out serverURI); | ||
704 | |||
705 | // This is checking a friend on the home grid | ||
706 | // Not HG friend | ||
707 | if ( String.IsNullOrEmpty(serverURI)) | ||
708 | { | ||
709 | Hashtable nop =new Hashtable(); | ||
710 | nop["success"] = "false"; | ||
711 | nop["errorMessage"] = "No Presence - foreign friend"; | ||
712 | return nop; | ||
713 | |||
714 | } | ||
715 | |||
716 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
717 | "avatar_properties_request", serverURI); | ||
718 | |||
719 | ArrayList dataArray = (ArrayList)result["data"]; | ||
720 | |||
721 | if (dataArray != null && dataArray[0] != null) | ||
722 | { | ||
723 | Hashtable d = (Hashtable)dataArray[0]; | ||
724 | return d; | ||
725 | } | ||
726 | return result; | ||
727 | } | ||
728 | |||
729 | public void RequestAvatarProperties(IClientAPI remoteClient, UUID avatarID) | ||
730 | { | ||
731 | if ( String.IsNullOrEmpty(avatarID.ToString()) || String.IsNullOrEmpty(remoteClient.AgentId.ToString())) | ||
732 | { | ||
733 | // Looking for a reason that some viewers are sending null Id's | ||
734 | m_log.InfoFormat("[PROFILE]: This should not happen remoteClient.AgentId {0} - avatarID {1}", remoteClient.AgentId, avatarID); | ||
735 | return; | ||
736 | } | ||
737 | |||
738 | // Can't handle NPC yet... | ||
739 | ScenePresence p = FindPresence(avatarID); | ||
740 | |||
741 | if (null != p) | ||
742 | { | ||
743 | if (p.PresenceType == PresenceType.Npc) | ||
744 | return; | ||
745 | } | ||
746 | |||
747 | IScene s = remoteClient.Scene; | ||
748 | if (!(s is Scene)) | ||
749 | return; | ||
750 | |||
751 | Scene scene = (Scene)s; | ||
752 | |||
753 | string serverURI = string.Empty; | ||
754 | bool foreign = GetUserProfileServerURI(avatarID, out serverURI); | ||
755 | |||
756 | UserAccount account = null; | ||
757 | Dictionary<string,object> userInfo; | ||
758 | |||
759 | if (!foreign) | ||
760 | { | ||
761 | account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, avatarID); | ||
762 | } | ||
763 | else | ||
764 | { | ||
765 | userInfo = new Dictionary<string, object>(); | ||
766 | } | ||
767 | |||
768 | Byte[] charterMember = new Byte[1]; | ||
769 | string born = String.Empty; | ||
770 | uint flags = 0x00; | ||
771 | |||
772 | if (null != account) | ||
773 | { | ||
774 | if (account.UserTitle == "") | ||
775 | { | ||
776 | charterMember[0] = (Byte)((account.UserFlags & 0xf00) >> 8); | ||
777 | } | ||
778 | else | ||
779 | { | ||
780 | charterMember = Utils.StringToBytes(account.UserTitle); | ||
781 | } | ||
782 | |||
783 | born = Util.ToDateTime(account.Created).ToString( | ||
784 | "M/d/yyyy", CultureInfo.InvariantCulture); | ||
785 | flags = (uint)(account.UserFlags & 0xff); | ||
786 | } | ||
787 | else | ||
788 | { | ||
789 | if (GetUserProfileData(avatarID, out userInfo) == true) | ||
790 | { | ||
791 | if ((string)userInfo["user_title"] == "") | ||
792 | { | ||
793 | charterMember[0] = (Byte)(((Byte)userInfo["user_flags"] & 0xf00) >> 8); | ||
794 | } | ||
795 | else | ||
796 | { | ||
797 | charterMember = Utils.StringToBytes((string)userInfo["user_title"]); | ||
798 | } | ||
799 | |||
800 | int val_born = (int)userInfo["user_created"]; | ||
801 | born = Util.ToDateTime(val_born).ToString( | ||
802 | "M/d/yyyy", CultureInfo.InvariantCulture); | ||
803 | |||
804 | // picky, picky | ||
805 | int val_flags = (int)userInfo["user_flags"]; | ||
806 | flags = (uint)(val_flags & 0xff); | ||
807 | } | ||
808 | } | ||
809 | |||
810 | Hashtable profileData = GetProfileData(avatarID); | ||
811 | string profileUrl = string.Empty; | ||
812 | string aboutText = String.Empty; | ||
813 | string firstLifeAboutText = String.Empty; | ||
814 | UUID image = UUID.Zero; | ||
815 | UUID firstLifeImage = UUID.Zero; | ||
816 | UUID partner = UUID.Zero; | ||
817 | uint wantMask = 0; | ||
818 | string wantText = String.Empty; | ||
819 | uint skillsMask = 0; | ||
820 | string skillsText = String.Empty; | ||
821 | string languages = String.Empty; | ||
822 | |||
823 | if (profileData["ProfileUrl"] != null) | ||
824 | profileUrl = profileData["ProfileUrl"].ToString(); | ||
825 | if (profileData["AboutText"] != null) | ||
826 | aboutText = profileData["AboutText"].ToString(); | ||
827 | if (profileData["FirstLifeAboutText"] != null) | ||
828 | firstLifeAboutText = profileData["FirstLifeAboutText"].ToString(); | ||
829 | if (profileData["Image"] != null) | ||
830 | image = new UUID(profileData["Image"].ToString()); | ||
831 | if (profileData["FirstLifeImage"] != null) | ||
832 | firstLifeImage = new UUID(profileData["FirstLifeImage"].ToString()); | ||
833 | if (profileData["Partner"] != null) | ||
834 | partner = new UUID(profileData["Partner"].ToString()); | ||
835 | |||
836 | // The PROFILE information is no longer stored in the user | ||
837 | // account. It now needs to be taken from the XMLRPC | ||
838 | // | ||
839 | remoteClient.SendAvatarProperties(avatarID, aboutText,born, | ||
840 | charterMember, firstLifeAboutText, | ||
841 | flags, | ||
842 | firstLifeImage, image, profileUrl, partner); | ||
843 | |||
844 | //Viewer expects interest data when it asks for properties. | ||
845 | if (profileData["wantmask"] != null) | ||
846 | wantMask = Convert.ToUInt32(profileData["wantmask"].ToString()); | ||
847 | if (profileData["wanttext"] != null) | ||
848 | wantText = profileData["wanttext"].ToString(); | ||
849 | |||
850 | if (profileData["skillsmask"] != null) | ||
851 | skillsMask = Convert.ToUInt32(profileData["skillsmask"].ToString()); | ||
852 | if (profileData["skillstext"] != null) | ||
853 | skillsText = profileData["skillstext"].ToString(); | ||
854 | |||
855 | if (profileData["languages"] != null) | ||
856 | languages = profileData["languages"].ToString(); | ||
857 | |||
858 | remoteClient.SendAvatarInterestsReply(avatarID, wantMask, wantText, | ||
859 | skillsMask, skillsText, languages); | ||
860 | } | ||
861 | |||
862 | public void UpdateAvatarProperties(IClientAPI remoteClient, UserProfileData newProfile) | ||
863 | { | ||
864 | // if it's the profile of the user requesting the update, then we change only a few things. | ||
865 | if (remoteClient.AgentId == newProfile.ID) | ||
866 | { | ||
867 | Hashtable ReqHash = new Hashtable(); | ||
868 | |||
869 | ReqHash["avatar_id"] = remoteClient.AgentId.ToString(); | ||
870 | ReqHash["ProfileUrl"] = newProfile.ProfileUrl; | ||
871 | ReqHash["Image"] = newProfile.Image.ToString(); | ||
872 | ReqHash["AboutText"] = newProfile.AboutText; | ||
873 | ReqHash["FirstLifeImage"] = newProfile.FirstLifeImage.ToString(); | ||
874 | ReqHash["FirstLifeAboutText"] = newProfile.FirstLifeAboutText; | ||
875 | |||
876 | string serverURI = string.Empty; | ||
877 | bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); | ||
878 | |||
879 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
880 | "avatar_properties_update", serverURI); | ||
881 | |||
882 | if (!Convert.ToBoolean(result["success"])) | ||
883 | { | ||
884 | remoteClient.SendAgentAlertMessage( | ||
885 | result["errorMessage"].ToString(), false); | ||
886 | } | ||
887 | |||
888 | RequestAvatarProperties(remoteClient, newProfile.ID); | ||
889 | } | ||
890 | } | ||
891 | |||
892 | private bool GetUserProfileServerURI(UUID userID, out string serverURI) | ||
893 | { | ||
894 | IUserManagement uManage = UserManagementModule; | ||
895 | |||
896 | if (!uManage.IsLocalGridUser(userID)) | ||
897 | { | ||
898 | serverURI = uManage.GetUserServerURL(userID, "ProfileServerURI"); | ||
899 | // Is Foreign | ||
900 | return true; | ||
901 | } | ||
902 | else | ||
903 | { | ||
904 | serverURI = m_ProfileServer; | ||
905 | // Is local | ||
906 | return false; | ||
907 | } | ||
908 | } | ||
909 | |||
910 | // | ||
911 | // Get the UserAccountBits | ||
912 | // | ||
913 | private bool GetUserProfileData(UUID userID, out Dictionary<string, object> userInfo) | ||
914 | { | ||
915 | IUserManagement uManage = UserManagementModule; | ||
916 | Dictionary<string,object> info = new Dictionary<string, object>(); | ||
917 | |||
918 | |||
919 | if (!uManage.IsLocalGridUser(userID)) | ||
920 | { | ||
921 | // Is Foreign | ||
922 | string home_url = uManage.GetUserServerURL(userID, "HomeURI"); | ||
923 | |||
924 | if (String.IsNullOrEmpty(home_url)) | ||
925 | { | ||
926 | info["user_flags"] = 0; | ||
927 | info["user_created"] = 0; | ||
928 | info["user_title"] = "Unavailable"; | ||
929 | |||
930 | userInfo = info; | ||
931 | return true; | ||
932 | } | ||
933 | |||
934 | UserAgentServiceConnector uConn = new UserAgentServiceConnector(home_url); | ||
935 | |||
936 | Dictionary<string, object> account = uConn.GetUserInfo(userID); | ||
937 | |||
938 | if (account.Count > 0) | ||
939 | { | ||
940 | if (account.ContainsKey("user_flags")) | ||
941 | info["user_flags"] = account["user_flags"]; | ||
942 | else | ||
943 | info["user_flags"] = ""; | ||
944 | |||
945 | if (account.ContainsKey("user_created")) | ||
946 | info["user_created"] = account["user_created"]; | ||
947 | else | ||
948 | info["user_created"] = ""; | ||
949 | |||
950 | info["user_title"] = "HG Visitor"; | ||
951 | } | ||
952 | else | ||
953 | { | ||
954 | info["user_flags"] = 0; | ||
955 | info["user_created"] = 0; | ||
956 | info["user_title"] = "HG Visitor"; | ||
957 | } | ||
958 | userInfo = info; | ||
959 | return true; | ||
960 | } | ||
961 | else | ||
962 | { | ||
963 | // Is local | ||
964 | Scene scene = m_Scenes[0]; | ||
965 | IUserAccountService uas = scene.UserAccountService; | ||
966 | UserAccount account = uas.GetUserAccount(scene.RegionInfo.ScopeID, userID); | ||
967 | |||
968 | info["user_flags"] = account.UserFlags; | ||
969 | info["user_created"] = account.Created; | ||
970 | |||
971 | if (!String.IsNullOrEmpty(account.UserTitle)) | ||
972 | info["user_title"] = account.UserTitle; | ||
973 | else | ||
974 | info["user_title"] = ""; | ||
975 | |||
976 | userInfo = info; | ||
977 | |||
978 | return false; | ||
979 | } | ||
980 | } | ||
981 | } | ||
982 | } | ||
diff --git a/addon-modules/OpenSimProfile/Robust.HG.ini.example b/addon-modules/OpenSimProfile/Robust.HG.ini.example new file mode 100644 index 0000000..d9407cb --- /dev/null +++ b/addon-modules/OpenSimProfile/Robust.HG.ini.example | |||
@@ -0,0 +1,3 @@ | |||
1 | ;; Update [LogonService] section to point to your profile server's url | ||
2 | ;; | ||
3 | SRV_ProfileServerURI = "http://127.0.0.1/profile.php" | ||
diff --git a/addon-modules/OpenSimProfile/prebuild.xml b/addon-modules/OpenSimProfile/prebuild.xml new file mode 100644 index 0000000..e653dee --- /dev/null +++ b/addon-modules/OpenSimProfile/prebuild.xml | |||
@@ -0,0 +1,40 @@ | |||
1 | <?xml version="1.0" ?> | ||
2 | <Project frameworkVersion="v3_5" name="OpenSimProfile.Modules" path="addon-modules/OpenSimProfile/Modules" type="Library"> | ||
3 | <Configuration name="Debug"> | ||
4 | <Options> | ||
5 | <OutputPath>../../../bin/</OutputPath> | ||
6 | </Options> | ||
7 | </Configuration> | ||
8 | <Configuration name="Release"> | ||
9 | <Options> | ||
10 | <OutputPath>../../../bin/</OutputPath> | ||
11 | </Options> | ||
12 | </Configuration> | ||
13 | |||
14 | <ReferencePath>../../../bin/</ReferencePath> | ||
15 | <Reference localCopy="false" name="System"/> | ||
16 | <Reference name="System.Xml"/> | ||
17 | <Reference name="System.Drawing"/> | ||
18 | <Reference name="System.Runtime.Remoting"/> | ||
19 | <Reference name="OpenMetaverseTypes" path="../../../bin/"/> | ||
20 | <Reference name="OpenMetaverse" path="../../../bin/"/> | ||
21 | <Reference name="Axiom.MathLib" path="../../../bin/"/> | ||
22 | <Reference name="OpenSim.Framework"/> | ||
23 | <Reference name="OpenSim.Data"/> | ||
24 | <Reference name="OpenSim.Region.Framework"/> | ||
25 | <Reference name="OpenSim.Framework.Console"/> | ||
26 | <Reference name="OpenSim.Framework.Servers"/> | ||
27 | <Reference name="OpenSim.Framework.Statistics"/> | ||
28 | <Reference name="OpenSim.Framework.Communications"/> | ||
29 | <Reference name="OpenSim.Region.Physics.Manager"/> | ||
30 | <Reference name="OpenSim.Services.Interfaces"/> | ||
31 | <Reference name="OpenSim.Services.Connectors"/> | ||
32 | <Reference name="Nini" path="../../../bin/"/> | ||
33 | <Reference name="log4net" path="../../../bin/"/> | ||
34 | <Reference name="XMLRPC" path="../../../bin/"/> | ||
35 | <Reference name="Mono.Addins" path="../../../bin/"/> | ||
36 | |||
37 | <Files> | ||
38 | <Match pattern="*.cs" recurse="true"/> | ||
39 | </Files> | ||
40 | </Project> | ||
diff --git a/addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs b/addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs new file mode 100644 index 0000000..2544614 --- /dev/null +++ b/addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs | |||
@@ -0,0 +1,753 @@ | |||
1 | using System; | ||
2 | using System.Collections; | ||
3 | using System.Collections.Generic; | ||
4 | using System.Globalization; | ||
5 | using System.Net; | ||
6 | using System.Net.Sockets; | ||
7 | using System.Reflection; | ||
8 | using System.Xml; | ||
9 | using OpenMetaverse; | ||
10 | using log4net; | ||
11 | using Nini.Config; | ||
12 | using Nwc.XmlRpc; | ||
13 | using OpenSim.Framework; | ||
14 | using OpenSim.Region.Framework.Interfaces; | ||
15 | using OpenSim.Region.Framework.Scenes; | ||
16 | using OpenSim.Services.Interfaces; | ||
17 | using Mono.Addins; | ||
18 | |||
19 | [assembly: Addin("OpenSearchModule", "0.1")] | ||
20 | [assembly: AddinDependency("OpenSim", "0.5")] | ||
21 | |||
22 | namespace OpenSimSearch.Modules.OpenSearch | ||
23 | { | ||
24 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")] | ||
25 | public class OpenSearchModule : ISearchModule, ISharedRegionModule | ||
26 | { | ||
27 | // | ||
28 | // Log module | ||
29 | // | ||
30 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
31 | |||
32 | // | ||
33 | // Module vars | ||
34 | // | ||
35 | private List<Scene> m_Scenes = new List<Scene>(); | ||
36 | private string m_SearchServer = ""; | ||
37 | private bool m_Enabled = true; | ||
38 | |||
39 | public void Initialise(IConfigSource config) | ||
40 | { | ||
41 | IConfig searchConfig = config.Configs["Search"]; | ||
42 | |||
43 | if (searchConfig == null) | ||
44 | { | ||
45 | m_Enabled = false; | ||
46 | return; | ||
47 | } | ||
48 | if (searchConfig.GetString("Module", "OpenSimSearch") != "OpenSimSearch") | ||
49 | { | ||
50 | m_Enabled = false; | ||
51 | return; | ||
52 | } | ||
53 | |||
54 | m_SearchServer = searchConfig.GetString("SearchURL", ""); | ||
55 | if (m_SearchServer == "") | ||
56 | { | ||
57 | m_log.Error("[SEARCH] No search server, disabling search"); | ||
58 | m_Enabled = false; | ||
59 | return; | ||
60 | } | ||
61 | else | ||
62 | { | ||
63 | m_log.Info("[SEARCH] Search module is activated"); | ||
64 | m_Enabled = true; | ||
65 | } | ||
66 | } | ||
67 | |||
68 | public void AddRegion(Scene scene) | ||
69 | { | ||
70 | if (!m_Enabled) | ||
71 | return; | ||
72 | |||
73 | // Hook up events | ||
74 | scene.EventManager.OnNewClient += OnNewClient; | ||
75 | |||
76 | // Take ownership of the ISearchModule service | ||
77 | scene.RegisterModuleInterface<ISearchModule>(this); | ||
78 | |||
79 | // Add our scene to our list... | ||
80 | lock(m_Scenes) | ||
81 | { | ||
82 | m_Scenes.Add(scene); | ||
83 | } | ||
84 | |||
85 | } | ||
86 | |||
87 | public void RemoveRegion(Scene scene) | ||
88 | { | ||
89 | if (!m_Enabled) | ||
90 | return; | ||
91 | |||
92 | scene.UnregisterModuleInterface<ISearchModule>(this); | ||
93 | m_Scenes.Remove(scene); | ||
94 | } | ||
95 | |||
96 | public void RegionLoaded(Scene scene) | ||
97 | { | ||
98 | } | ||
99 | |||
100 | public Type ReplaceableInterface | ||
101 | { | ||
102 | get { return null; } | ||
103 | } | ||
104 | |||
105 | public void PostInitialise() | ||
106 | { | ||
107 | } | ||
108 | |||
109 | public void Close() | ||
110 | { | ||
111 | } | ||
112 | |||
113 | public string Name | ||
114 | { | ||
115 | get { return "SearchModule"; } | ||
116 | } | ||
117 | |||
118 | public bool IsSharedModule | ||
119 | { | ||
120 | get { return true; } | ||
121 | } | ||
122 | |||
123 | /// New Client Event Handler | ||
124 | private void OnNewClient(IClientAPI client) | ||
125 | { | ||
126 | // Subscribe to messages | ||
127 | client.OnDirPlacesQuery += DirPlacesQuery; | ||
128 | client.OnDirFindQuery += DirFindQuery; | ||
129 | client.OnDirPopularQuery += DirPopularQuery; | ||
130 | client.OnDirLandQuery += DirLandQuery; | ||
131 | client.OnDirClassifiedQuery += DirClassifiedQuery; | ||
132 | // Response after Directory Queries | ||
133 | client.OnEventInfoRequest += EventInfoRequest; | ||
134 | client.OnClassifiedInfoRequest += ClassifiedInfoRequest; | ||
135 | client.OnMapItemRequest += HandleMapItemRequest; | ||
136 | } | ||
137 | |||
138 | // | ||
139 | // Make external XMLRPC request | ||
140 | // | ||
141 | private Hashtable GenericXMLRPCRequest(Hashtable ReqParams, string method) | ||
142 | { | ||
143 | ArrayList SendParams = new ArrayList(); | ||
144 | SendParams.Add(ReqParams); | ||
145 | |||
146 | // Send Request | ||
147 | XmlRpcResponse Resp; | ||
148 | try | ||
149 | { | ||
150 | XmlRpcRequest Req = new XmlRpcRequest(method, SendParams); | ||
151 | Resp = Req.Send(m_SearchServer, 30000); | ||
152 | } | ||
153 | catch (WebException ex) | ||
154 | { | ||
155 | m_log.ErrorFormat("[SEARCH]: Unable to connect to Search " + | ||
156 | "Server {0}. Exception {1}", m_SearchServer, ex); | ||
157 | |||
158 | Hashtable ErrorHash = new Hashtable(); | ||
159 | ErrorHash["success"] = false; | ||
160 | ErrorHash["errorMessage"] = "Unable to search at this time. "; | ||
161 | ErrorHash["errorURI"] = ""; | ||
162 | |||
163 | return ErrorHash; | ||
164 | } | ||
165 | catch (SocketException ex) | ||
166 | { | ||
167 | m_log.ErrorFormat( | ||
168 | "[SEARCH]: Unable to connect to Search Server {0}. " + | ||
169 | "Exception {1}", m_SearchServer, ex); | ||
170 | |||
171 | Hashtable ErrorHash = new Hashtable(); | ||
172 | ErrorHash["success"] = false; | ||
173 | ErrorHash["errorMessage"] = "Unable to search at this time. "; | ||
174 | ErrorHash["errorURI"] = ""; | ||
175 | |||
176 | return ErrorHash; | ||
177 | } | ||
178 | catch (XmlException ex) | ||
179 | { | ||
180 | m_log.ErrorFormat( | ||
181 | "[SEARCH]: Unable to connect to Search Server {0}. " + | ||
182 | "Exception {1}", m_SearchServer, ex); | ||
183 | |||
184 | Hashtable ErrorHash = new Hashtable(); | ||
185 | ErrorHash["success"] = false; | ||
186 | ErrorHash["errorMessage"] = "Unable to search at this time. "; | ||
187 | ErrorHash["errorURI"] = ""; | ||
188 | |||
189 | return ErrorHash; | ||
190 | } | ||
191 | if (Resp.IsFault) | ||
192 | { | ||
193 | Hashtable ErrorHash = new Hashtable(); | ||
194 | ErrorHash["success"] = false; | ||
195 | ErrorHash["errorMessage"] = "Unable to search at this time. "; | ||
196 | ErrorHash["errorURI"] = ""; | ||
197 | return ErrorHash; | ||
198 | } | ||
199 | Hashtable RespData = (Hashtable)Resp.Value; | ||
200 | |||
201 | return RespData; | ||
202 | } | ||
203 | |||
204 | protected void DirPlacesQuery(IClientAPI remoteClient, UUID queryID, | ||
205 | string queryText, int queryFlags, int category, string simName, | ||
206 | int queryStart) | ||
207 | { | ||
208 | Hashtable ReqHash = new Hashtable(); | ||
209 | ReqHash["text"] = queryText; | ||
210 | ReqHash["flags"] = queryFlags.ToString(); | ||
211 | ReqHash["category"] = category.ToString(); | ||
212 | ReqHash["sim_name"] = simName; | ||
213 | ReqHash["query_start"] = queryStart.ToString(); | ||
214 | |||
215 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
216 | "dir_places_query"); | ||
217 | |||
218 | if (!Convert.ToBoolean(result["success"])) | ||
219 | { | ||
220 | remoteClient.SendAgentAlertMessage( | ||
221 | result["errorMessage"].ToString(), false); | ||
222 | return; | ||
223 | } | ||
224 | |||
225 | ArrayList dataArray = (ArrayList)result["data"]; | ||
226 | |||
227 | int count = dataArray.Count; | ||
228 | if (count > 100) | ||
229 | count = 101; | ||
230 | |||
231 | DirPlacesReplyData[] data = new DirPlacesReplyData[count]; | ||
232 | |||
233 | int i = 0; | ||
234 | |||
235 | foreach (Object o in dataArray) | ||
236 | { | ||
237 | Hashtable d = (Hashtable)o; | ||
238 | |||
239 | data[i] = new DirPlacesReplyData(); | ||
240 | data[i].parcelID = new UUID(d["parcel_id"].ToString()); | ||
241 | data[i].name = d["name"].ToString(); | ||
242 | data[i].forSale = Convert.ToBoolean(d["for_sale"]); | ||
243 | data[i].auction = Convert.ToBoolean(d["auction"]); | ||
244 | data[i].dwell = Convert.ToSingle(d["dwell"]); | ||
245 | |||
246 | if (++i >= count) | ||
247 | break; | ||
248 | } | ||
249 | |||
250 | remoteClient.SendDirPlacesReply(queryID, data); | ||
251 | } | ||
252 | |||
253 | public void DirPopularQuery(IClientAPI remoteClient, UUID queryID, uint queryFlags) | ||
254 | { | ||
255 | Hashtable ReqHash = new Hashtable(); | ||
256 | ReqHash["flags"] = queryFlags.ToString(); | ||
257 | |||
258 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
259 | "dir_popular_query"); | ||
260 | |||
261 | if (!Convert.ToBoolean(result["success"])) | ||
262 | { | ||
263 | remoteClient.SendAgentAlertMessage( | ||
264 | result["errorMessage"].ToString(), false); | ||
265 | return; | ||
266 | } | ||
267 | |||
268 | ArrayList dataArray = (ArrayList)result["data"]; | ||
269 | |||
270 | int count = dataArray.Count; | ||
271 | if (count > 100) | ||
272 | count = 101; | ||
273 | |||
274 | DirPopularReplyData[] data = new DirPopularReplyData[count]; | ||
275 | |||
276 | int i = 0; | ||
277 | |||
278 | foreach (Object o in dataArray) | ||
279 | { | ||
280 | Hashtable d = (Hashtable)o; | ||
281 | |||
282 | data[i] = new DirPopularReplyData(); | ||
283 | data[i].parcelID = new UUID(d["parcel_id"].ToString()); | ||
284 | data[i].name = d["name"].ToString(); | ||
285 | data[i].dwell = Convert.ToSingle(d["dwell"]); | ||
286 | |||
287 | if (++i >= count) | ||
288 | break; | ||
289 | } | ||
290 | |||
291 | remoteClient.SendDirPopularReply(queryID, data); | ||
292 | } | ||
293 | |||
294 | public void DirLandQuery(IClientAPI remoteClient, UUID queryID, | ||
295 | uint queryFlags, uint searchType, int price, int area, | ||
296 | int queryStart) | ||
297 | { | ||
298 | Hashtable ReqHash = new Hashtable(); | ||
299 | ReqHash["flags"] = queryFlags.ToString(); | ||
300 | ReqHash["type"] = searchType.ToString(); | ||
301 | ReqHash["price"] = price.ToString(); | ||
302 | ReqHash["area"] = area.ToString(); | ||
303 | ReqHash["query_start"] = queryStart.ToString(); | ||
304 | |||
305 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
306 | "dir_land_query"); | ||
307 | |||
308 | if (!Convert.ToBoolean(result["success"])) | ||
309 | { | ||
310 | remoteClient.SendAgentAlertMessage( | ||
311 | result["errorMessage"].ToString(), false); | ||
312 | return; | ||
313 | } | ||
314 | |||
315 | ArrayList dataArray = (ArrayList)result["data"]; | ||
316 | int count = 0; | ||
317 | |||
318 | /* Count entries in dataArray with valid region name to */ | ||
319 | /* prevent allocating data array with too many entries. */ | ||
320 | foreach (Object o in dataArray) | ||
321 | { | ||
322 | Hashtable d = (Hashtable)o; | ||
323 | |||
324 | if (d["name"] != null) | ||
325 | ++count; | ||
326 | } | ||
327 | |||
328 | if (count > 100) | ||
329 | count = 101; | ||
330 | |||
331 | DirLandReplyData[] data = new DirLandReplyData[count]; | ||
332 | |||
333 | int i = 0; | ||
334 | |||
335 | foreach (Object o in dataArray) | ||
336 | { | ||
337 | Hashtable d = (Hashtable)o; | ||
338 | |||
339 | if (d["name"] == null) | ||
340 | continue; | ||
341 | |||
342 | data[i] = new DirLandReplyData(); | ||
343 | data[i].parcelID = new UUID(d["parcel_id"].ToString()); | ||
344 | data[i].name = d["name"].ToString(); | ||
345 | data[i].auction = Convert.ToBoolean(d["auction"]); | ||
346 | data[i].forSale = Convert.ToBoolean(d["for_sale"]); | ||
347 | data[i].salePrice = Convert.ToInt32(d["sale_price"]); | ||
348 | data[i].actualArea = Convert.ToInt32(d["area"]); | ||
349 | |||
350 | if (++i >= count) | ||
351 | break; | ||
352 | } | ||
353 | |||
354 | remoteClient.SendDirLandReply(queryID, data); | ||
355 | } | ||
356 | |||
357 | public void DirFindQuery(IClientAPI remoteClient, UUID queryID, | ||
358 | string queryText, uint queryFlags, int queryStart) | ||
359 | { | ||
360 | if ((queryFlags & 1) != 0) //People (1 << 0) | ||
361 | { | ||
362 | DirPeopleQuery(remoteClient, queryID, queryText, queryFlags, | ||
363 | queryStart); | ||
364 | return; | ||
365 | } | ||
366 | else if ((queryFlags & 32) != 0) //DateEvents (1 << 5) | ||
367 | { | ||
368 | DirEventsQuery(remoteClient, queryID, queryText, queryFlags, | ||
369 | queryStart); | ||
370 | return; | ||
371 | } | ||
372 | } | ||
373 | |||
374 | public void DirPeopleQuery(IClientAPI remoteClient, UUID queryID, | ||
375 | string queryText, uint queryFlags, int queryStart) | ||
376 | { | ||
377 | List<UserAccount> accounts = m_Scenes[0].UserAccountService.GetUserAccounts(m_Scenes[0].RegionInfo.ScopeID, queryText); | ||
378 | |||
379 | DirPeopleReplyData[] data = | ||
380 | new DirPeopleReplyData[accounts.Count]; | ||
381 | |||
382 | int i = 0; | ||
383 | |||
384 | foreach (UserAccount item in accounts) | ||
385 | { | ||
386 | data[i] = new DirPeopleReplyData(); | ||
387 | |||
388 | data[i].agentID = item.PrincipalID; | ||
389 | data[i].firstName = item.FirstName; | ||
390 | data[i].lastName = item.LastName; | ||
391 | data[i].group = ""; | ||
392 | data[i].online = false; | ||
393 | data[i].reputation = 0; | ||
394 | i++; | ||
395 | } | ||
396 | |||
397 | remoteClient.SendDirPeopleReply(queryID, data); | ||
398 | } | ||
399 | |||
400 | public void DirEventsQuery(IClientAPI remoteClient, UUID queryID, | ||
401 | string queryText, uint queryFlags, int queryStart) | ||
402 | { | ||
403 | Hashtable ReqHash = new Hashtable(); | ||
404 | ReqHash["text"] = queryText; | ||
405 | ReqHash["flags"] = queryFlags.ToString(); | ||
406 | ReqHash["query_start"] = queryStart.ToString(); | ||
407 | |||
408 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
409 | "dir_events_query"); | ||
410 | |||
411 | if (!Convert.ToBoolean(result["success"])) | ||
412 | { | ||
413 | remoteClient.SendAgentAlertMessage( | ||
414 | result["errorMessage"].ToString(), false); | ||
415 | return; | ||
416 | } | ||
417 | |||
418 | ArrayList dataArray = (ArrayList)result["data"]; | ||
419 | |||
420 | int count = dataArray.Count; | ||
421 | if (count > 100) | ||
422 | count = 101; | ||
423 | |||
424 | DirEventsReplyData[] data = new DirEventsReplyData[count]; | ||
425 | |||
426 | int i = 0; | ||
427 | |||
428 | foreach (Object o in dataArray) | ||
429 | { | ||
430 | Hashtable d = (Hashtable)o; | ||
431 | |||
432 | data[i] = new DirEventsReplyData(); | ||
433 | data[i].ownerID = new UUID(d["owner_id"].ToString()); | ||
434 | data[i].name = d["name"].ToString(); | ||
435 | data[i].eventID = Convert.ToUInt32(d["event_id"]); | ||
436 | data[i].date = d["date"].ToString(); | ||
437 | data[i].unixTime = Convert.ToUInt32(d["unix_time"]); | ||
438 | data[i].eventFlags = Convert.ToUInt32(d["event_flags"]); | ||
439 | |||
440 | if (++i >= count) | ||
441 | break; | ||
442 | } | ||
443 | |||
444 | remoteClient.SendDirEventsReply(queryID, data); | ||
445 | } | ||
446 | |||
447 | public void DirClassifiedQuery(IClientAPI remoteClient, UUID queryID, | ||
448 | string queryText, uint queryFlags, uint category, | ||
449 | int queryStart) | ||
450 | { | ||
451 | Hashtable ReqHash = new Hashtable(); | ||
452 | ReqHash["text"] = queryText; | ||
453 | ReqHash["flags"] = queryFlags.ToString(); | ||
454 | ReqHash["category"] = category.ToString(); | ||
455 | ReqHash["query_start"] = queryStart.ToString(); | ||
456 | |||
457 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
458 | "dir_classified_query"); | ||
459 | |||
460 | if (!Convert.ToBoolean(result["success"])) | ||
461 | { | ||
462 | remoteClient.SendAgentAlertMessage( | ||
463 | result["errorMessage"].ToString(), false); | ||
464 | return; | ||
465 | } | ||
466 | |||
467 | ArrayList dataArray = (ArrayList)result["data"]; | ||
468 | |||
469 | int count = dataArray.Count; | ||
470 | if (count > 100) | ||
471 | count = 101; | ||
472 | |||
473 | DirClassifiedReplyData[] data = new DirClassifiedReplyData[count]; | ||
474 | |||
475 | int i = 0; | ||
476 | |||
477 | foreach (Object o in dataArray) | ||
478 | { | ||
479 | Hashtable d = (Hashtable)o; | ||
480 | |||
481 | data[i] = new DirClassifiedReplyData(); | ||
482 | data[i].classifiedID = new UUID(d["classifiedid"].ToString()); | ||
483 | data[i].name = d["name"].ToString(); | ||
484 | data[i].classifiedFlags = Convert.ToByte(d["classifiedflags"]); | ||
485 | data[i].creationDate = Convert.ToUInt32(d["creation_date"]); | ||
486 | data[i].expirationDate = Convert.ToUInt32(d["expiration_date"]); | ||
487 | data[i].price = Convert.ToInt32(d["priceforlisting"]); | ||
488 | |||
489 | if (++i >= count) | ||
490 | break; | ||
491 | } | ||
492 | |||
493 | remoteClient.SendDirClassifiedReply(queryID, data); | ||
494 | } | ||
495 | |||
496 | public void EventInfoRequest(IClientAPI remoteClient, uint queryEventID) | ||
497 | { | ||
498 | Hashtable ReqHash = new Hashtable(); | ||
499 | ReqHash["eventID"] = queryEventID.ToString(); | ||
500 | |||
501 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
502 | "event_info_query"); | ||
503 | |||
504 | if (!Convert.ToBoolean(result["success"])) | ||
505 | { | ||
506 | remoteClient.SendAgentAlertMessage( | ||
507 | result["errorMessage"].ToString(), false); | ||
508 | return; | ||
509 | } | ||
510 | |||
511 | ArrayList dataArray = (ArrayList)result["data"]; | ||
512 | if (dataArray.Count == 0) | ||
513 | { | ||
514 | // something bad happened here, if we could return an | ||
515 | // event after the search, | ||
516 | // we should be able to find it here | ||
517 | // TODO do some (more) sensible error-handling here | ||
518 | remoteClient.SendAgentAlertMessage("Couldn't find this event.", | ||
519 | false); | ||
520 | return; | ||
521 | } | ||
522 | |||
523 | Hashtable d = (Hashtable)dataArray[0]; | ||
524 | EventData data = new EventData(); | ||
525 | data.eventID = Convert.ToUInt32(d["event_id"]); | ||
526 | data.creator = d["creator"].ToString(); | ||
527 | data.name = d["name"].ToString(); | ||
528 | data.category = d["category"].ToString(); | ||
529 | data.description = d["description"].ToString(); | ||
530 | data.date = d["date"].ToString(); | ||
531 | data.dateUTC = Convert.ToUInt32(d["dateUTC"]); | ||
532 | data.duration = Convert.ToUInt32(d["duration"]); | ||
533 | data.cover = Convert.ToUInt32(d["covercharge"]); | ||
534 | data.amount = Convert.ToUInt32(d["coveramount"]); | ||
535 | data.simName = d["simname"].ToString(); | ||
536 | Vector3.TryParse(d["globalposition"].ToString(), out data.globalPos); | ||
537 | data.eventFlags = Convert.ToUInt32(d["eventflags"]); | ||
538 | |||
539 | remoteClient.SendEventInfoReply(data); | ||
540 | } | ||
541 | |||
542 | public void ClassifiedInfoRequest(UUID queryClassifiedID, IClientAPI remoteClient) | ||
543 | { | ||
544 | Hashtable ReqHash = new Hashtable(); | ||
545 | ReqHash["classifiedID"] = queryClassifiedID.ToString(); | ||
546 | |||
547 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
548 | "classifieds_info_query"); | ||
549 | |||
550 | if (!Convert.ToBoolean(result["success"])) | ||
551 | { | ||
552 | remoteClient.SendAgentAlertMessage( | ||
553 | result["errorMessage"].ToString(), false); | ||
554 | return; | ||
555 | } | ||
556 | |||
557 | //The viewer seems to issue an info request even when it is | ||
558 | //creating a new classified which means the data hasn't been | ||
559 | //saved to the database yet so there is no info to find. | ||
560 | ArrayList dataArray = (ArrayList)result["data"]; | ||
561 | if (dataArray.Count == 0) | ||
562 | { | ||
563 | // Something bad happened here if we could not return an | ||
564 | // event after the search. We should be able to find it here. | ||
565 | // TODO do some (more) sensible error-handling here | ||
566 | // remoteClient.SendAgentAlertMessage("Couldn't find data for classified ad.", | ||
567 | // false); | ||
568 | return; | ||
569 | } | ||
570 | |||
571 | Hashtable d = (Hashtable)dataArray[0]; | ||
572 | |||
573 | Vector3 globalPos = new Vector3(); | ||
574 | Vector3.TryParse(d["posglobal"].ToString(), out globalPos); | ||
575 | |||
576 | remoteClient.SendClassifiedInfoReply( | ||
577 | new UUID(d["classifieduuid"].ToString()), | ||
578 | new UUID(d["creatoruuid"].ToString()), | ||
579 | Convert.ToUInt32(d["creationdate"]), | ||
580 | Convert.ToUInt32(d["expirationdate"]), | ||
581 | Convert.ToUInt32(d["category"]), | ||
582 | d["name"].ToString(), | ||
583 | d["description"].ToString(), | ||
584 | new UUID(d["parceluuid"].ToString()), | ||
585 | Convert.ToUInt32(d["parentestate"]), | ||
586 | new UUID(d["snapshotuuid"].ToString()), | ||
587 | d["simname"].ToString(), | ||
588 | globalPos, | ||
589 | d["parcelname"].ToString(), | ||
590 | Convert.ToByte(d["classifiedflags"]), | ||
591 | Convert.ToInt32(d["priceforlisting"])); | ||
592 | } | ||
593 | |||
594 | public void HandleMapItemRequest(IClientAPI remoteClient, uint flags, | ||
595 | uint EstateID, bool godlike, | ||
596 | uint itemtype, ulong regionhandle) | ||
597 | { | ||
598 | //The following constant appears to be from GridLayerType enum | ||
599 | //defined in OpenMetaverse/GridManager.cs of libopenmetaverse. | ||
600 | if (itemtype == (uint)OpenMetaverse.GridItemType.LandForSale) | ||
601 | { | ||
602 | Hashtable ReqHash = new Hashtable(); | ||
603 | |||
604 | //The flags are: SortAsc (1 << 15), PerMeterSort (1 << 17) | ||
605 | ReqHash["flags"] = "163840"; | ||
606 | ReqHash["type"] = "4294967295"; //This is -1 in 32 bits | ||
607 | ReqHash["price"] = "0"; | ||
608 | ReqHash["area"] = "0"; | ||
609 | ReqHash["query_start"] = "0"; | ||
610 | |||
611 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
612 | "dir_land_query"); | ||
613 | |||
614 | if (!Convert.ToBoolean(result["success"])) | ||
615 | { | ||
616 | remoteClient.SendAgentAlertMessage( | ||
617 | result["errorMessage"].ToString(), false); | ||
618 | return; | ||
619 | } | ||
620 | |||
621 | ArrayList dataArray = (ArrayList)result["data"]; | ||
622 | |||
623 | int count = dataArray.Count; | ||
624 | if (count > 100) | ||
625 | count = 101; | ||
626 | |||
627 | List<mapItemReply> mapitems = new List<mapItemReply>(); | ||
628 | string ParcelRegionUUID; | ||
629 | string[] landingpoint; | ||
630 | |||
631 | foreach (Object o in dataArray) | ||
632 | { | ||
633 | Hashtable d = (Hashtable)o; | ||
634 | |||
635 | if (d["name"] == null) | ||
636 | continue; | ||
637 | |||
638 | mapItemReply mapitem = new mapItemReply(); | ||
639 | |||
640 | ParcelRegionUUID = d["region_UUID"].ToString(); | ||
641 | |||
642 | foreach (Scene scene in m_Scenes) | ||
643 | { | ||
644 | if (scene.RegionInfo.RegionID.ToString() == ParcelRegionUUID) | ||
645 | { | ||
646 | landingpoint = d["landing_point"].ToString().Split('/'); | ||
647 | |||
648 | mapitem.x = (uint)((scene.RegionInfo.RegionLocX * 256) + | ||
649 | Convert.ToDecimal(landingpoint[0])); | ||
650 | mapitem.y = (uint)((scene.RegionInfo.RegionLocY * 256) + | ||
651 | Convert.ToDecimal(landingpoint[1])); | ||
652 | break; | ||
653 | } | ||
654 | } | ||
655 | |||
656 | mapitem.id = new UUID(d["parcel_id"].ToString()); | ||
657 | mapitem.Extra = Convert.ToInt32(d["area"]); | ||
658 | mapitem.Extra2 = Convert.ToInt32(d["sale_price"]); | ||
659 | mapitem.name = d["name"].ToString(); | ||
660 | |||
661 | mapitems.Add(mapitem); | ||
662 | } | ||
663 | |||
664 | remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); | ||
665 | mapitems.Clear(); | ||
666 | } | ||
667 | |||
668 | if (itemtype == (uint)OpenMetaverse.GridItemType.PgEvent || | ||
669 | itemtype == (uint)OpenMetaverse.GridItemType.MatureEvent || | ||
670 | itemtype == (uint)OpenMetaverse.GridItemType.AdultEvent) | ||
671 | { | ||
672 | Hashtable ReqHash = new Hashtable(); | ||
673 | |||
674 | //Find the maturity level | ||
675 | int maturity = (1 << 24); | ||
676 | |||
677 | //Find the maturity level | ||
678 | if (itemtype == (uint)OpenMetaverse.GridItemType.MatureEvent) | ||
679 | maturity = (1 << 25); | ||
680 | else | ||
681 | { | ||
682 | if (itemtype == (uint)OpenMetaverse.GridItemType.AdultEvent) | ||
683 | maturity = (1 << 26); | ||
684 | } | ||
685 | |||
686 | //The flags are: SortAsc (1 << 15), PerMeterSort (1 << 17) | ||
687 | maturity |= 163840; | ||
688 | |||
689 | //Character before | is number of days before/after current date | ||
690 | //Characters after | is the number for a category | ||
691 | ReqHash["text"] = "0|0"; | ||
692 | ReqHash["flags"] = maturity.ToString(); | ||
693 | ReqHash["query_start"] = "0"; | ||
694 | |||
695 | Hashtable result = GenericXMLRPCRequest(ReqHash, | ||
696 | "dir_events_query"); | ||
697 | |||
698 | if (!Convert.ToBoolean(result["success"])) | ||
699 | { | ||
700 | remoteClient.SendAgentAlertMessage( | ||
701 | result["errorMessage"].ToString(), false); | ||
702 | return; | ||
703 | } | ||
704 | |||
705 | ArrayList dataArray = (ArrayList)result["data"]; | ||
706 | |||
707 | List<mapItemReply> mapitems = new List<mapItemReply>(); | ||
708 | string ParcelRegionUUID; | ||
709 | string[] landingpoint; | ||
710 | |||
711 | foreach (Object o in dataArray) | ||
712 | { | ||
713 | Hashtable d = (Hashtable)o; | ||
714 | |||
715 | if (d["name"] == null) | ||
716 | continue; | ||
717 | |||
718 | mapItemReply mapitem = new mapItemReply(); | ||
719 | |||
720 | ParcelRegionUUID = d["region_UUID"].ToString(); | ||
721 | |||
722 | foreach (Scene scene in m_Scenes) | ||
723 | { | ||
724 | if (scene.RegionInfo.RegionID.ToString() == ParcelRegionUUID) | ||
725 | { | ||
726 | landingpoint = d["landing_point"].ToString().Split('/'); | ||
727 | |||
728 | mapitem.x = (uint)((scene.RegionInfo.RegionLocX * 256) + | ||
729 | Convert.ToDecimal(landingpoint[0])); | ||
730 | mapitem.y = (uint)((scene.RegionInfo.RegionLocY * 256) + | ||
731 | Convert.ToDecimal(landingpoint[1])); | ||
732 | break; | ||
733 | } | ||
734 | } | ||
735 | |||
736 | mapitem.id = UUID.Random(); | ||
737 | mapitem.Extra = (int)Convert.ToInt32(d["unix_time"]); | ||
738 | mapitem.Extra2 = (int)Convert.ToInt32(d["event_id"]); | ||
739 | mapitem.name = d["name"].ToString(); | ||
740 | |||
741 | mapitems.Add(mapitem); | ||
742 | } | ||
743 | |||
744 | remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); | ||
745 | mapitems.Clear(); | ||
746 | } | ||
747 | } | ||
748 | |||
749 | public void Refresh() | ||
750 | { | ||
751 | } | ||
752 | } | ||
753 | } | ||
diff --git a/addon-modules/OpenSimSearch/README b/addon-modules/OpenSimSearch/README new file mode 100644 index 0000000..ce826e3 --- /dev/null +++ b/addon-modules/OpenSimSearch/README | |||
@@ -0,0 +1,294 @@ | |||
1 | OpenSimSearch add-on module for Open Simulator | ||
2 | |||
3 | Requirements | ||
4 | |||
5 | The webserver needs PHP support with support for CURL and XMLRPC. | ||
6 | NOTE: Not all webservers may have CURL and XMLRPC support for PHP. | ||
7 | |||
8 | |||
9 | About the files | ||
10 | |||
11 | README - The file you are currently reading | ||
12 | |||
13 | bin/OpenSimSearch.Modules.dll - A pre-compiled module you can use | ||
14 | |||
15 | OpenSimSearch/ - Source code for OpenSim search module | ||
16 | |||
17 | webroot/*.php - Files to install on webserver | ||
18 | |||
19 | webroot/sql/ossearch.sql - This will create the needed database tables | ||
20 | webroot/sql/update-*.sql - Files used to update an older installation | ||
21 | |||
22 | webroot/wiredux/ - Files to add to an installation of wiredux | ||
23 | |||
24 | |||
25 | How it works | ||
26 | |||
27 | If you are looking for a detailed explanation of how the add-on search system | ||
28 | works you should study the contents of the files which accompany this README | ||
29 | file. What follows is a general overview of the search package to give you an | ||
30 | idea of how the pieces fit together in case you have any problems. | ||
31 | |||
32 | There are three main components to OpenSimSearch which are a database, a DLL | ||
33 | file, and several PHP files. | ||
34 | |||
35 | Most of the tables in the search database contain details on items which can | ||
36 | appear in the results of a search. The exception is the hostsregister table. | ||
37 | The hostsregister table contains the list of OpenSim instances which will be | ||
38 | checked periodically for information about searchable items. | ||
39 | |||
40 | When an OpenSim instance starts it accesses the register.php script (using the | ||
41 | URL of the data_services entry in OpenSim.ini) to record the host address and | ||
42 | port of the instance in the hostsregister table of the database. The host and | ||
43 | port entries are used by parser.php to retrieve the data to be indexed from | ||
44 | the running instances. | ||
45 | |||
46 | Take note of the last part of the previous paragraph where it stated that | ||
47 | parser.php is used to index the data from an OpenSim instance and not from a | ||
48 | region. The parser retrieves data from an OpenSim instance. If the instance | ||
49 | is running a single region the data for a single region will be updated. If | ||
50 | the instance is running multiple regions the data for all regions hosted by | ||
51 | that instance will be indexed. | ||
52 | |||
53 | OpenSim instances use the data snapshot module to create an XML based record | ||
54 | of all searchable items in the regions they host. The XML record is retrieved | ||
55 | by parser.php using a URL created from the host, port and the query string | ||
56 | "?method=collector" (eg. http://127.0.0.1:9001/?method=collector). The | ||
57 | parser.php file will get one host/port pair from the hostregister table each | ||
58 | time it is called. It will parse the XML data from the OpenSim instance and | ||
59 | save the data to the various tables of the search database. | ||
60 | |||
61 | The query.php file is the heart of the search process. It receives an XML | ||
62 | message from the DLL based on the search request originating in a viewer. | ||
63 | The XML message is parsed to determine the type of search being performed, | ||
64 | SQL queries are built and executed to retrieve the data from the database | ||
65 | tables, and finally, the results are sent to the DLL file as an XML message | ||
66 | for any final processing before being passed along to the viewer for display. | ||
67 | |||
68 | |||
69 | Before you begin | ||
70 | |||
71 | In the information that follows there are references to settings to be made | ||
72 | in the OpenSim.ini files. As of the release of the 0.7 version of OpenSim | ||
73 | some of the settings may no longer be in the OpenSim.ini file but may be | ||
74 | found in the OpenSimDefault.ini file. If a section of the OpenSim.ini file | ||
75 | mentioned in the information which follows doesn't exist in your copy of | ||
76 | the file, you will either need to add the section and settings to the main | ||
77 | OpenSim.ini file or make the changes to the OpenSimDefaults.ini file. | ||
78 | |||
79 | |||
80 | Compiling the module | ||
81 | |||
82 | Adding the OpenSimSearch C# source file to the source tree of OpenSim so it | ||
83 | will be compiled at the same time as OpenSim is compiled is the easiest way | ||
84 | to create to the DLL file needed by OpenSim. | ||
85 | |||
86 | Copy the OpenSimSearch directory in to the addon-modules directory of your | ||
87 | OpenSim source tree. After you have done that, compile OpenSim in the usual | ||
88 | way (runprebuild and nant) to create the DLL file. When nant finishes, you | ||
89 | will have an OpenSimSearch.Modules.dll file in the main bin directory of | ||
90 | your OpenSim source tree along with a matching .pdb (or .mdb) file. These | ||
91 | two files will be copied to the bin directory of your OpenSim instances | ||
92 | during the installation or update steps. | ||
93 | |||
94 | |||
95 | First time installation | ||
96 | |||
97 | The first installation step is the creation of a database that will hold | ||
98 | the data to be searched. If you have already installed the add-on module | ||
99 | osprofile you should use the same database as osprofile as it shares some | ||
100 | tables in common with ossearch. If you don't have osprofile installed you | ||
101 | will need to create a new database that will hold the search tables. Having | ||
102 | decided on the database to be used, use ossearch.sql (located in the | ||
103 | webroot/sql directory) to create the required tables in the database. The | ||
104 | name of this database will be needed when configuring one of the PHP files | ||
105 | in the next steps. | ||
106 | |||
107 | Copy the PHP files (located in the webroot directory) to your webserver. | ||
108 | Remember to set the permissions on the file so the webserver may access the | ||
109 | files. Use a text editor to open databaseinfo.php and enter the name or IP | ||
110 | address of the database server, the name of the database, and the user name | ||
111 | and password required to access the database. | ||
112 | |||
113 | The next part of the installation process is to set up and configure your | ||
114 | OpenSim instances. | ||
115 | |||
116 | Copy the two OpenSimSearch.Modules files created during the compile steps | ||
117 | (above) in to the bin directory of each of your OpenSim instances. The next | ||
118 | part of the installation process requires some changes to the OpenSim.ini in | ||
119 | all of your OpenSim instances. | ||
120 | |||
121 | Add the following lines to all OpenSim.ini files: | ||
122 | |||
123 | [Search] | ||
124 | ;/////////////////////////////////////////////////////////////////////////// | ||
125 | ;// The SearchURL is important. It must be correct or search won't work. // | ||
126 | ;// // | ||
127 | ;// Change the URL to point to the query.php file on your own HTTP server // | ||
128 | ;SearchURL = http://192.168.0.1/query.php | ||
129 | ;// // | ||
130 | ;/////////////////////////////////////////////////////////////////////////// | ||
131 | |||
132 | NOTE: You do not need to indent the above lines when adding them to your own | ||
133 | OpenSim.ini files. The lines were indented to offset them from the rest of the | ||
134 | text in this file. | ||
135 | |||
136 | Uncomment and edit the SearchURL line so it contains the correct address for | ||
137 | the webserver and needed path to where the query.php file was installed on | ||
138 | your webserver. | ||
139 | |||
140 | To allow the Teleport and Show on Map buttons to work properly (for search | ||
141 | results containing locations) the following two lines must be added to the | ||
142 | [Modules] section of the OpenSim.ini file: | ||
143 | |||
144 | LandServices = "RemoteLandServicesConnector" | ||
145 | LandServiceInConnector = "true" | ||
146 | |||
147 | The last changes to be made to the OpenSim.ini file are in the [DataSnapshot] | ||
148 | section. Change index_sims to true. You can have data_exposure set to all but | ||
149 | it is better to leave it on minimum so users can control what items can appear | ||
150 | in search results by using the "Show In Search" checkboxes. You can leave | ||
151 | default_snapshot_period commented out or you can uncomment it and set it to | ||
152 | whatever value you wish. The section on optimizing the configuration will | ||
153 | help you to decide an appropriate value for this setting. | ||
154 | |||
155 | The last change for OpenSim.ini is the setting for data_services. This line | ||
156 | must be uncommented and contain a valid URL for your webserver and the path | ||
157 | to the register.php file which you installed earlier. If you do not enter a | ||
158 | valid URL the OS instance will not get listed in the hostsregister table and | ||
159 | search data for the regions hosted by the OS instance will not be recorded or | ||
160 | updated. After entering the URL, exit and save the updated OpenSim.ini file. | ||
161 | |||
162 | |||
163 | Updating an existing installation | ||
164 | |||
165 | Updating an existing installation of ossearch is just a matter of copying a | ||
166 | few files to the same places where you had previously installed the files. | ||
167 | |||
168 | Copy all of the PHP files (located in the webroot directory) EXCEPT for | ||
169 | databaseinfo.php to the directory on your webserver where you place the | ||
170 | previous copies. If you also copy databaseinfo.php when copying the other | ||
171 | PHP files you will have to edit databaseinfo.php and reset the information | ||
172 | used to connect to the database. | ||
173 | |||
174 | Copy the two OpenSimSearch.Modules files created during compilation to the | ||
175 | bin directory of each of your OpenSim instances. | ||
176 | |||
177 | Finally, execute all of the SQL files located in the webroot/sql directory | ||
178 | where the filenames start with "update-". This step is very important to | ||
179 | make certain your database tables are up-to-date. | ||
180 | |||
181 | |||
182 | Configuration | ||
183 | |||
184 | With everything in place, the next step is to set up a task on your webserver | ||
185 | (or some other machine) which will invoke the URL to the parser.php file on | ||
186 | your webserver on a regular basis to ensure the contents of the database are | ||
187 | kept up-to-date. For a machine running Linux you can set up a cron job. For | ||
188 | a machine running Windows you can use Windows Scheduler. | ||
189 | |||
190 | See the section on optimizing the configuration to help you decide how often | ||
191 | the parser.php should be run. | ||
192 | |||
193 | |||
194 | Optimizing the configuration | ||
195 | |||
196 | When you change what items are to be found in search by clicking the checkbox | ||
197 | "Show In Search" or by removing an item that was set to show in search results | ||
198 | there is a delay before the change is reflected in the database tables. There | ||
199 | are four main factors that affect the length of this delay. They are: the | ||
200 | number of OpenSim instances, the value of default_snapshot_period used by the | ||
201 | DataSnapshot module, the time between calls to parser.php, and the number of | ||
202 | OpenSim instances processed each time parser.php is run. You can't easily | ||
203 | control the number of instances but you can control the other factors. | ||
204 | |||
205 | To explain how the factors affect the delay take a grid with 100 regions. If | ||
206 | you have one region per instance you have 100 instances. Using the default | ||
207 | settings and calling parser once an hour it would take 100 hours, or more than | ||
208 | four days, for changes to appear in search results. A more realistic setup is | ||
209 | one where you have an average of 4 regions run by each OpenSim instance. This | ||
210 | reduces the delay to 25 hours. This is a rather long delay for a relatively | ||
211 | small number of regions and instances. The delay can be reduced substantially. | ||
212 | |||
213 | A simple way to reduce the delay is to run parser.php more frequently. If | ||
214 | parser.php is run once every 15 minutes instead of once an hour (60 minutes) | ||
215 | the delay is reduced by a factor of 4 from 25 hours to 6.25 hours. Much better | ||
216 | but still a long delay. The delay can be reduced even further but to do so | ||
217 | requires a change to the parser.php file. | ||
218 | |||
219 | Near the end of the parser.php file is a SQL query that ends with "LIMIT 0,1". | ||
220 | It is the value "1" which is important. That value limits the number of OS | ||
221 | instances that will be processed each time parser.php is run. If the value is | ||
222 | changed from 1 to 3 then three OS instances will be processed on each run of | ||
223 | the parser.php file. This reduces the delay by a factor of 3 to just over | ||
224 | 2 hours. This is a much better delay than the original value of 25 hours. | ||
225 | |||
226 | For those of you who like math, the amount of delay can be expressed using | ||
227 | the following simple formula: | ||
228 | delay = # instances * time between runs of parser / limit value in parser | ||
229 | |||
230 | Three factors affecting the delay have been discussed but earlier it was | ||
231 | stated that there are four factors. The fourth factor is the value of | ||
232 | default_snapshot_period value located in the [DataSnapshot] section of the | ||
233 | OpenSim.ini file. This setting is specified in seconds and controls how often | ||
234 | the data used by parser.php will be updated. This setting doesn't have any | ||
235 | direct impact on the delay between updates of the database but if it is set | ||
236 | incorrecty it can efffectively increase the delay between database updates. | ||
237 | |||
238 | The example grid setup discussed earlier was adjusted to reduce the update | ||
239 | delay to just over 2 hours (2 hours and 5 minutes to be more precise). If the | ||
240 | value of default_snapshot_period is set to a value slightly greater than the | ||
241 | calculated delay it would be possible for parser.php to be called twice before | ||
242 | the data it retrieves would have been updated. This would turn a delay of two | ||
243 | hours into a delay of four hours. The proper setting is one that is *less* | ||
244 | than the delay calculated from the other three factors discussed earlier. | ||
245 | |||
246 | Given a delay of just over 2 hours, a good value for default_snapshot_period | ||
247 | would be 7200 (2 hours expressed in seconds). By keeping the value of this | ||
248 | setting close to, but less than, the delay between when parser.php is used | ||
249 | to get the data for an instance will minimize any overhead imposed on the | ||
250 | OpenSim instance when it creates the snapshot of searchable items while, at | ||
251 | the same time, ensures the data will have been updated by the next time the | ||
252 | parser.php is run to update the database. | ||
253 | |||
254 | A final comment about the setting for the time between runs of parser.php and | ||
255 | the value in the limit statement in that file. Avoid running parser.php too | ||
256 | frequently or setting the value in the LIMIT statement too high. Doing either | ||
257 | can cause unnecessary overhead or high loads on the webserver used to run | ||
258 | parser.php, or on the database server while it updates all the tables with | ||
259 | the latest information from the OpenSim instances. | ||
260 | |||
261 | |||
262 | Additional Information | ||
263 | |||
264 | A few words about event listings and the events database table. | ||
265 | Support is included for events but the event listings need to be created | ||
266 | using an external webpage. | ||
267 | |||
268 | The category for an event is stored as a number. The numbers for the | ||
269 | categories are as follows: | ||
270 | 0 - Any (NOTE: Event information will show "*Unspecified*") | ||
271 | 18- Discussion | ||
272 | 19- Sports | ||
273 | 20- Live Music | ||
274 | 22- Commercial | ||
275 | 23- Nightlife/Entertainment | ||
276 | 24- Games/Contests | ||
277 | 25- Pageants | ||
278 | 26- Education | ||
279 | 27- Arts and Culture | ||
280 | 28- Charity/Support Groups | ||
281 | 29- Miscellaneous | ||
282 | |||
283 | The dateUTC field is a timestamp for the event in UTC time. | ||
284 | |||
285 | The covercharge field is a boolean. Set it to 0 if there is no cover charge | ||
286 | for the event. When covercharge is not 0, the amount is in the coveramount | ||
287 | field. (It seems silly to require the boolean but this has been left in to | ||
288 | avoid any compatability issues.) | ||
289 | |||
290 | The globalPos field is the location of the event as a global grid coordinate. | ||
291 | The format is "x/y/z". where x and y are the grid X and Y positions (times | ||
292 | 256) plus the x and y offset within the region named by the simname field. | ||
293 | |||
294 | The eventflags field is 0 for a PG event, 1 for Mature, and 2 for Adult. | ||
diff --git a/addon-modules/OpenSimSearch/prebuild.xml b/addon-modules/OpenSimSearch/prebuild.xml new file mode 100644 index 0000000..961ebf9 --- /dev/null +++ b/addon-modules/OpenSimSearch/prebuild.xml | |||
@@ -0,0 +1,39 @@ | |||
1 | <?xml version="1.0" ?> | ||
2 | <Project frameworkVersion="v3_5" name="OpenSimSearch.Modules" path="addon-modules/OpenSimSearch/Modules" type="Library"> | ||
3 | <Configuration name="Debug"> | ||
4 | <Options> | ||
5 | <OutputPath>../../../bin/</OutputPath> | ||
6 | </Options> | ||
7 | </Configuration> | ||
8 | <Configuration name="Release"> | ||
9 | <Options> | ||
10 | <OutputPath>../../../bin/</OutputPath> | ||
11 | </Options> | ||
12 | </Configuration> | ||
13 | |||
14 | <ReferencePath>../../../bin/</ReferencePath> | ||
15 | <Reference localCopy="false" name="System"/> | ||
16 | <Reference name="System.Xml"/> | ||
17 | <Reference name="System.Drawing"/> | ||
18 | <Reference name="System.Runtime.Remoting"/> | ||
19 | <Reference name="OpenMetaverseTypes" path="../../../bin/"/> | ||
20 | <Reference name="OpenMetaverse" path="../../../bin/"/> | ||
21 | <Reference name="Axiom.MathLib" path="../../../bin/"/> | ||
22 | <Reference name="OpenSim.Framework"/> | ||
23 | <Reference name="OpenSim.Data"/> | ||
24 | <Reference name="OpenSim.Region.Framework"/> | ||
25 | <Reference name="OpenSim.Framework.Console"/> | ||
26 | <Reference name="OpenSim.Framework.Servers"/> | ||
27 | <Reference name="OpenSim.Framework.Statistics"/> | ||
28 | <Reference name="OpenSim.Framework.Communications"/> | ||
29 | <Reference name="OpenSim.Region.Physics.Manager"/> | ||
30 | <Reference name="OpenSim.Services.Interfaces"/> | ||
31 | <Reference name="Nini" path="../../../bin/"/> | ||
32 | <Reference name="log4net" path="../../../bin/"/> | ||
33 | <Reference name="XMLRPC" path="../../../bin/"/> | ||
34 | <Reference name="Mono.Addins" path="../../../bin/"/> | ||
35 | |||
36 | <Files> | ||
37 | <Match pattern="*.cs" recurse="true"/> | ||
38 | </Files> | ||
39 | </Project> | ||