Compare commits

..

246 Commits

Author SHA1 Message Date
Justin Clark-Casey (justincc)
dd0858e204 For osGetGridNick(), osGetGridName(), osGetGridLoginURI() and osGetGridCustom(), try to read from the [GridInfoService] section on standalone rather than [GridInfo]
[GridInfoService] is the section that's actually in bin/config-include/StandaloneCommon.ini.example
2012-05-10 00:49:15 +01:00
Justin Clark-Casey (justincc)
25fa6ee699 refactor: Instead of performing a ScenePresence lookup twice over LocateClientObject() and GetClientScene(), do the lookup just once in LocateClientObject() 2012-05-10 00:49:08 +01:00
Justin Clark-Casey (justincc)
84dfffe0aa Fix a bug in FriendsModule.StatusNotify() where all subsequent friends would not be notified once a non-local friend was found. 2012-05-10 00:48:57 +01:00
Justin Clark-Casey (justincc)
aba803c447 Change LongCallTime on WebUtil to 3000, to match the time where request handling is considered "slow".
This may be the wrong thing to do but stops lots of log spam in HG setups now that the monitoring is extended to other outgoing calls.
LongCallTime may need to be made configurable.
2012-05-10 00:47:59 +01:00
Justin Clark-Casey (justincc)
79aae63aff minor: Tweak BaseHttpServer message to make it clear that this relates to slow handling of inbound requests. 2012-05-10 00:47:43 +01:00
Justin Clark-Casey (justincc)
59e93b8ee3 Extend 'slow' request logging to other server outbound requests (forms, rest, async rest) as well as the existing logging on outbound OSD requests.
Also prints out the first 100 chars of any slow request data since this can contain useful info (such as agent ID).
2012-05-10 00:47:26 +01:00
Justin Clark-Casey (justincc)
e0e63f312f Reinsert a 2000ms delay before closing a no longer required agent on the source region after teleport to resolve Imprudence teleport problems.
Viewers 1 and 3 are fine with doing this immediately.  However, Imprudence has a small delay (<200ms, >500ms) after receiving the AgentCompleteMovement reply packet on the destination region before regarding that region as the currnet region.
If Imprudence receives a DisableSimulator in this period, it quits.
We are not restoring the full 5000ms delay since this brings back a bug where teleports permanently fail if an avatar tries to teleport back too quickly.
This commit also sends the AgentCompleteMovement packet to the client before telling the source region to release its old agent, in order to further cut down any possibility of the DisableSimulator being recieved before the AgentMovementComplete.
2012-05-10 00:39:57 +01:00
Snoopy Pfeffer
d453372f4e Fixes Mantis #5999. llSetLinkPrimitiveParams with PRIM_BUMP_SHINY did cause a runtime error. 2012-05-10 00:39:50 +01:00
Justin Clark-Casey (justincc)
0f5a77c5bd Remove the somewhat misleading logging of the string length of some unknown requests, as this appeared to be some kind of numbered error code.
This brings these messages into line with similar messages that did not do this.
2012-05-10 00:39:43 +01:00
Justin Clark-Casey (justincc)
0c96d7ea5c minor: resolve some mono compiler warnings 2012-05-10 00:39:35 +01:00
Justin Clark-Casey (justincc)
4b947cd6d3 Implement optional name and description on http stream handlers so that we can relate a slow request to what the handler actually does and the agent it serves, if applicable.
This is most useful for capabilities where the url is not self-describing.
2012-05-10 00:38:26 +01:00
Justin Clark-Casey (justincc)
ca586ca809 Comment out the five second sleep in etm.DoTeleport() if the old agent needs to be closed because it is no longer in the child's view distance.
This sleep appears unnecessary since a sleep has already occurred in WaitForCallback() whilst waiting for the destination region to notify of teleport success.
There are no async operations between this sleep and the WaitForCallback()
If this sleep is present, then teleporting back to the source region within 5 seconds results in a disconnection.
If this sleep is commented out then teleporting quickly back and forth between two simulators appears to work without issue.
Tested on standalone, local grid and distributed grid.
Please revert if there's something that I've missed.
2012-05-10 00:29:02 +01:00
Justin Clark-Casey (justincc)
880358f46b Remove some test code that accidentally crept in with 9d2e1c67 2012-05-10 00:28:53 +01:00
Justin Clark-Casey (justincc)
3393babb7d Add regression test for teleporting between neighbouring regions on the same simulator
This adds a non-advertised wait_for_callback option in [EntityTransfer].  Default is always true.
Teleport tests disable the wait for callback from the destination region in order to run within a single thread.
2012-05-10 00:28:47 +01:00
Justin Clark-Casey (justincc)
c3ae90c067 Move max teleport distance check down into etm.DoTeleport() since this should apply to all teleport calls, not just those through Teleport() 2012-05-10 00:28:27 +01:00
Justin Clark-Casey (justincc)
25983f20a0 refactor: Split most of EntityTransferModule.Teleport() into its same region and different region teleport components.
DoTeleport() now retrives IEventQueue itself rather than requiring it to be passed in.
2012-05-10 00:25:25 +01:00
Justin Clark-Casey (justincc)
1441758bc6 Create TestHelpers.EnableLogging() and DisableLogging() to turn logging on and off within tests.
This makes *.Tests.dll.config files no longer needed, hence deleted.
2012-05-10 00:25:14 +01:00
Justin Clark-Casey (justincc)
c7bbeb4490 Add request verb and url to error messages in WebUtil that lack this.
Make exception printing consistent across windows and mono.

Conflicts:

	OpenSim/Framework/WebUtil.cs
2012-05-10 00:21:09 +01:00
Diva Canto
6145f90423 Slight rewording of output messages. 2012-05-10 00:17:36 +01:00
Diva Canto
2e397d1514 HG: Moved User-level code down to the HGEntityTransferModule where it belongs. 2012-05-10 00:16:51 +01:00
Diva Canto
a5d0a29dd9 Moved the inventory manipulation from HGEntityTransferModule to HGInventoryAccessModule where it belongs. They need to exchange some events, so added those to EventManager. Those events (TeleportStart and TeleportFail) are nice to have anyway. 2012-05-10 00:10:18 +01:00
Melanie
de843fd0a8 Implement bulk inventory update over CAPS (not recursive by design,
do NOT CHANGE THIS, needed for HG 2.0)
2012-05-10 00:05:47 +01:00
Melanie
ced4eeddcf Typo fix 2012-05-10 00:05:41 +01:00
Melanie
093910e90e Fix typos 2012-05-10 00:05:32 +01:00
Melanie
37685ec1b4 Start on Bulk inventory update via CAPS. Not functional yet. HG v2 2012-05-10 00:05:26 +01:00
Melanie
ccd7d35b3f Add a corresponding method for items. HG v2 2012-05-10 00:05:19 +01:00
Melanie
de7e0d7e52 Add SendRemoveInventoryFolders which allows to remove one or more
folders from the viewer's inventory view. For HG v2.0. More to come
2012-05-10 00:05:13 +01:00
Diva Canto
89ee03a24d HG: beginning of a more restrictive inventory access procedure (optional). Experimental: we'll try switching the root folder from under the viewer. 2012-05-10 00:02:14 +01:00
Melanie
79d1d3ca55 Commit the avination Teleport() methods (adaptedto justincc's changes) 2012-05-09 23:58:42 +01:00
Justin Clark-Casey (justincc)
675c208c7e zero out SP velocity before calling SP.Teleport(), as the client expects (though this is also effectively done by physics at the moment) 2012-05-09 23:58:35 +01:00
Justin Clark-Casey (justincc)
b3307850ab refactor: Combine ScenePresence.Teleport() and TeleportWithMomentum()
These are identical apart from setting Velocity = zero, which has no practical effect anyway since this is zeroed when the avatar is added back to the physics scene.
2012-05-09 23:58:27 +01:00
Oren Hurvitz
eaa840dbd9 OSSL: fixed the threat level check for osParseJSONNew 2012-05-09 23:57:49 +01:00
Justin Clark-Casey (justincc)
f64089fa6c Restore _parent_scene.actor_name_map[prim_geom] = this; accidentally removed from ODEPrim.SetGeom.
This occurred in 7a574be3fd from Sat 21 Apr 2012.
This should fix collision detection.
Mnay thanks to tglion for the spot and the fix in http://opensimulator.org/mantis/view.php?id=5988
2012-05-09 23:57:27 +01:00
Justin Clark-Casey (justincc)
5157d2023d refactor: simply some properties code in BasicPhysicsPlugin 2012-05-09 23:52:41 +01:00
Justin Clark-Casey (justincc)
2cd927bb14 Fix bug where setting phantom on a prim would result in a server log message rather than setting phantom.
This was an oversight when removing some race conditions from PhysicsActor setting recently.
Regression tests extended to probe this code path.
Extending regression tests required implementation of a BasicPhysicsPrim (there was none before).  However, BasicPhysics plugin is still of no current practical use other than to fill in as a component for other parts of regression testing.
2012-05-09 23:52:04 +01:00
Justin Clark-Casey (justincc)
2889961622 Comment out spurious Body != IntPtr.Zero code after disableBody(), since disableBody() sets Body == IntPtr.Zero on all code paths. 2012-05-09 23:51:56 +01:00
Justin Clark-Casey (justincc)
232f59749e refactor: Simplify ODEPrim.AddChildPrim() by returning early where appropriate. 2012-05-09 23:51:48 +01:00
Justin Clark-Casey (justincc)
d368a10cc7 Add test for setting physics in a linkset 2012-05-09 23:51:41 +01:00
Justin Clark-Casey (justincc)
139b848774 Add regression test for prim status when root prim in a new linkset is non-physical 2012-05-09 23:51:34 +01:00
Justin Clark-Casey (justincc)
74a5226af5 Fix a bug where linking a non-physical prim with a physical prim as root would make the non-physical prim phantom rather than part of the physics object.
On region restart, the whole object would become physical as expected.
Observed behaviour from elsewhere is that all prims in a new linkset should take on the status of the root prim.
Add regression test for this behaviour.
2012-05-09 23:51:25 +01:00
Justin Clark-Casey (justincc)
c7ddc7a633 Remove redundant prim_geom != IntPtr.Zero checks in ODEPrim.
prim_geom == IntPtr.Zero only before a new add prim taint is processed (which is the first taint) or in operations such as scale change which are done in taint or under lock.
Therefore, we can remove these checks which were not consistently applied anyway.
If there is a genuine problem, better to see it quickly in a NullReferenceException than hide the bug.
2012-05-09 23:39:47 +01:00
Justin Clark-Casey (justincc)
265707d21c If a physical prim is manually moved (e.g. by a user) then set the geometry position as well as the body position
This is necessary to stop the moved prim snapping back to the original position on deselection if moved only once
This resolves http://opensimulator.org/mantis/view.php?id=5966
2012-05-09 23:39:37 +01:00
Justin Clark-Casey (justincc)
eb39d1c4d4 Comment out debug [ASYNC DELETER] messages for now. 2012-04-30 19:29:17 +01:00
Justin Clark-Casey (justincc)
1197d48fc7 Remove mono compiler warning. Adjust message log to error from info 2012-04-30 19:29:08 +01:00
Justin Clark-Casey (justincc)
23e1a55ed5 Add text about using double quotes to surround console command arguments containing spaces to "help" text.
e.g. show object name "My long object name"
2012-04-30 19:29:01 +01:00
Justin Clark-Casey (justincc)
ba2a539603 Put scene object related console commands into new "Objects" help category rather than "Regions" 2012-04-30 19:28:54 +01:00
Justin Clark-Casey (justincc)
8a8093d8dd Add flags information (phantom, physics, etc.) to "show object" and "show part" console commands 2012-04-30 19:28:41 +01:00
Justin Clark-Casey (justincc)
3fb0103452 Add regression test for teleporting an agent between separated regions on the same simulator.
This involves a large amount of change in test scene setup code to allow test scenes to share shared modules
SetupScene is now an instance method that requires an instantiation of SceneHelpers, though other SceneHelpers methods are still static
May split these out into separate classes in the future.
2012-04-30 19:27:21 +01:00
Justin Clark-Casey (justincc)
aeefdaedc7 minor: make NPC tests run in a given order, comment out log lines in mock region data plugins, null out scene in script and npc torture tests, add other doc comments to torture tests 2012-04-30 19:18:54 +01:00
Justin Clark-Casey (justincc)
756d1f917f Stop individually deleting objects at the end of each ObjectTortureTest.
We can now do this since the entire scene and all objects within it are now successfully gc'd at the end of these tests.
This greatly improves the time taken to run each test (by reducing teardown time, not the time to actually do the test work that we're interested in).
Slightly simplifies config read in Scene constructor to help facilitate this.
2012-04-30 19:18:47 +01:00
Justin Clark-Casey (justincc)
6fa3dffad2 Use a fully stubbed out MockConsole for unit tests rather than inheriting from CommandConsole.
This is so that the static MainConsole.Instance doesn't retain references to methods registered by scene and other modules to service commands.
This prevents the scene from being garbage collected at the end of a test.
This is not the final thing preventing GC - next up is the timer started by SimStatsReporter that holds a reference to Scene that prevents end of test gc.
2012-04-30 19:17:06 +01:00
Justin Clark-Casey (justincc)
123e781cb3 Make TestSetPhysicsSinglePrim() actually add the object to the scene in order to test more code paths. 2012-04-30 19:15:07 +01:00
Justin Clark-Casey (justincc)
ed9bf5b0c6 Add regression test for prim status when root prim in a new linkset is non-physical 2012-04-30 19:14:35 +01:00
Justin Clark-Casey (justincc)
dd4e39ca1d refactor: extract common setup code in SceneObjectStatusTests 2012-04-30 19:13:33 +01:00
Justin Clark-Casey (justincc)
5a551b074b Add TestSetPhysics() to SOP status tests 2012-04-30 19:13:24 +01:00
Justin Clark-Casey (justincc)
c1a9355865 Tweak log messages on local region to region teleport path to help with problem resolution. 2012-04-30 19:05:27 +01:00
Diva Canto
3ea6c25fb6 Teleports: bounce off repeated requests of teleporting the same agent. Some scripts do that, and that fails the whole thing. 2012-04-30 19:05:24 +01:00
Justin Clark-Casey (justincc)
2d3135448c Comment out old Scene.HandleLogOffUserFromGrid() to reduce client closing analysis complexity 2012-04-30 19:01:30 +01:00
Justin Clark-Casey (justincc)
36a99af37c minor: Add more detail to unauthorized caps client message 2012-04-30 19:01:23 +01:00
Justin Clark-Casey (justincc)
2aefd15913 minor: Add region name to dropped inbound packet message 2012-04-30 19:01:13 +01:00
Justin Clark-Casey (justincc)
6bc55b1086 minor: Add avatar name to removing agent log message 2012-04-30 19:00:47 +01:00
Justin Clark-Casey (justincc)
7058ce2c70 Comment out avatar move to target message for now. 2012-04-30 19:00:39 +01:00
Justin Clark-Casey (justincc)
8e111e9018 Add regression test TestSameRegionTeleport() 2012-04-30 18:59:50 +01:00
Justin Clark-Casey (justincc)
d8bd7ca436 Comment out AvatarService.SetAvatar debug log line for now 2012-04-30 18:59:43 +01:00
Justin Clark-Casey (justincc)
392f73a000 Comment out some debug ATTACHMENTS log messages for now. 2012-04-30 18:59:35 +01:00
Justin Clark-Casey (justincc)
67efbaf33b Comment out the noisier AVFACTORY log messages for now.
Permanently comment out warnings about ScenePresence not being found - this is entirely expected if the avatar has alraedy logged out or left the scene.
2012-04-30 18:59:27 +01:00
Justin Clark-Casey (justincc)
0d06740148 Improve teleport log debug and error messages to tell us who is teleporting. 2012-04-30 18:58:37 +01:00
Diva Canto
eda6947a22 Changed the Map-related messages from Info to Debug. They're debug messages. 2012-04-30 18:49:07 +01:00
Justin Clark-Casey (justincc)
f331173145 Add online/offline indicator to "friends show" region console command.
Improve output table formatting.
2012-04-30 18:48:57 +01:00
Justin Clark-Casey (justincc)
a666cd9e1f Add osForceAttachToAvatar() and osForceDetachFromAvatar()
These behave identically to llAttachToAvatar() and llDetachFromAvatar() except that they do not enforce the PERMISSION_ATTACH check
Intended for use in completely controlled dedicated environments where these checks are more a UI hinderance than a help.
Threat level high.
2012-04-24 00:50:32 +01:00
Justin Clark-Casey (justincc)
2657aa6987 Replace common code to fetch self inventory item (as opposed to uuid) with GetSelfInventoryItem()
However, at some point it would be far more convenient to receive the TaskInventoryItem in the constructor rather than just the item UUID, so we don't have to constantly refetch our self item.
2012-04-24 00:45:09 +01:00
Justin Clark-Casey (justincc)
6610cd2332 refactor: Replace calls to InventorySelf() with existing m_itemID in LSL_Api
There's no point look up an item ID that we already have.
2012-04-24 00:44:57 +01:00
Justin Clark-Casey (justincc)
0c8b44d514 Add more exception detail to Exception and IOException throws in BaseHttpServer.HandleRequest() 2012-04-24 00:40:19 +01:00
Justin Clark-Casey (justincc)
ec46ff4445 Stop teleports from dropping tall avatars through or embedding them in the floor when lured by short avatars.
This involves giving the ceiling of the Z-component in a lure rather than the floor.
Ideally we would give the exact float compensating for relative avatar height but it looks like that isn't possible with the parcel id format used in lures
2012-04-24 00:37:37 +01:00
Melanie
db891880e6 Fix a logic error in app domain creation 2012-04-24 00:36:54 +01:00
Melanie
9c433e8c50 Don't re-add the assembly resolver for each script if not creating the appdomain 2012-04-24 00:36:46 +01:00
Justin Clark-Casey (justincc)
5141863ae3 On "show part" command, show link number.
This replaces the Parts count which was rather pointless for a prim (it was either 1 if a child or the number of parts if the root).
This information is still avaliable on the "show object" command.
2012-04-18 23:02:02 +01:00
Justin Clark-Casey (justincc)
d23550dea5 minor: Add some method doc. Add warnings since calling SOG link/delink methods directly rather than through Scene may allow race conditions. 2012-04-18 23:01:56 +01:00
Justin Clark-Casey (justincc)
c3b12510ae Add TestGetChildPartPositionAfterObjectRotation() 2012-04-18 23:01:42 +01:00
Justin Clark-Casey (justincc)
b2685e3671 Add test TestGetChildPartPosition() 2012-04-18 23:01:33 +01:00
Justin Clark-Casey (justincc)
601d2ddbf3 Move some public methods on WebStatsModule to private to reduce some static analysis complexity.
There's no obvious reason for these methods to be public.
2012-04-18 23:01:27 +01:00
Justin Clark-Casey (justincc)
a5e8fe70af Use INSERT OR REPLACE INTO sql in WebStatsModule for session update rather than separate insert and update statements 2012-04-18 23:01:12 +01:00
Justin Clark-Casey (justincc)
9b3a96aa81 correct bug where f_invalid was being inserted on a webstats update for an existing session rather than d_world_kb 2012-04-18 23:01:06 +01:00
Justin Clark-Casey (justincc)
d674e815bd Simplify WebStatsModule by removing the uncompleted migrations section.
Use "create table if not exists" instead.
Client stats data is transitory data that it is not worth migrating.
2012-04-18 23:01:00 +01:00
Justin Clark-Casey (justincc)
c2e696d686 Fix bug in WebStatsModule where an exception would always be output on update if the user teleported to another region on that simulator.
This was because update was looking for an existing stats record unique in session id, agent id and region id.
But if the user teleports to another region then region id changes.
WebStatsModule promptly doesn't find the existing record and tries to insert a new one, but only session id is the primary key and that's still the same, which makes things go bang.
This makes the update search only on the unique session id.
This is only an issue with simulators that have multiple regions where the webstats module is enabled.
2012-04-18 23:00:51 +01:00
Justin Clark-Casey (justincc)
411dbb8df4 Add GroupPosition and GetWorldPosition() checks to TestGetRootPartPosition() 2012-04-18 23:00:43 +01:00
Justin Clark-Casey (justincc)
b22dfbbf15 minor: make test names consistent 2012-04-18 23:00:36 +01:00
Justin Clark-Casey (justincc)
4c39a3fdbf refactor: move common init code into SetUp() in SceneObjectSpatialTests 2012-04-18 23:00:30 +01:00
Justin Clark-Casey (justincc)
f3eb366f24 refactor: put SOG position test in a separate TestSceneObjectGroupPosition() 2012-04-18 23:00:22 +01:00
Justin Clark-Casey (justincc)
a20d1b8f6b Add simple RelativePosition and OffsetPosition checks to TestGetRootPartPosition 2012-04-18 23:00:14 +01:00
Justin Clark-Casey (justincc)
0a51289332 Add very basic TestGetRootPartPosition() test 2012-04-18 23:00:02 +01:00
Justin Clark-Casey (justincc)
97ebfb00b4 Rather than having a FromFolderID property on every single prim and only ever using the root prim one, store on SOG instead.
This reduces pointless memory usage.
2012-04-18 22:59:24 +01:00
Justin Clark-Casey (justincc)
c7d664f9d0 Store FromItemID for attachments once on SOG instead of on every SOP and only ever using the root part entry.
This eliminates some pointless memory use.
2012-04-18 22:59:18 +01:00
Justin Clark-Casey (justincc)
c2fbaaa95d refactor: Eliminate unnecessary SOP.m_physActor 2012-04-18 22:59:08 +01:00
Justin Clark-Casey (justincc)
84d4b390f1 remove possible PhysActor unexpectedly null race conditions when changing prim collision status
factor out common SOP physics scene adding code into a common SOP.AddToPhysics() that is the counterpart to the existing RemoveFromPhysics()
2012-04-18 22:58:17 +01:00
Justin Clark-Casey (justincc)
54e59099b7 Fix more SOP.PhysActor race conditions in LSL_Api 2012-04-18 22:58:10 +01:00
Justin Clark-Casey (justincc)
d9585ba37e Eliminate race condition where many callers would check SOP.PhysicsActor != null then assume it was still not null in later code.
Another thread could come and turn off physics for a part (null PhysicsActor) at any point.
Had to turn off localCopy on warp3D CoreModules section in prebuild.xml since on current nant this copies all DLLs in bin/ which can be a very large number with compiled DLLs
No obvious reason for doing that copy - nothing else does it.
2012-04-18 22:58:03 +01:00
Justin Clark-Casey (justincc)
45c617b5c3 Allow llRegionSayTo() to work on the PUBLIC_CHANNEL, as per http://wiki.secondlife.com/wiki/LlRegionSayTo
Addresses http://opensimulator.org/mantis/view.php?id=5950
2012-04-18 22:55:30 +01:00
Justin Clark-Casey (justincc)
4b0c78c64f minor: remove some now unneeded code from FriendsCommandsModule 2012-04-18 22:55:12 +01:00
Justin Clark-Casey (justincc)
dd36e23a62 Make default "show friends" console command show friends fetched from the friends service.
There is no a --cache option which will show friends from the local cache if available.
2012-04-18 22:55:05 +01:00
Justin Clark-Casey (justincc)
e2dade05d9 Lock NullFriendsData.m_Data for consistency and against concurrent read/write 2012-04-18 22:54:57 +01:00
Justin Clark-Casey (justincc)
fbd61106cb refactor: Move "friends show cache" console command out into separate FriendsCommandsModule.
Expose required methods on IFriendsModule.  Rename GetFriends() -> GetFriendsFromCache() for self-documentation
2012-04-18 22:54:45 +01:00
Justin Clark-Casey (justincc)
67e66a2d34 Add simple login test with online friends. Add IFriendsModule.GrantRights() for granting rights via a module call.
Rename IFriendsModule.GetFriendPerms() -> GetRightsGrantedByFriend() to be more self-documenting and consistent with friends module terminology.
Add some method doc.
2012-04-18 22:46:10 +01:00
Justin Clark-Casey (justincc)
59911963ca refactor: Stop passing both IClientAPI and agentID to friend event listeners, these are redundant. Replace a few magic numbers with FriendRights enum already used elsewhere. 2012-04-18 22:45:31 +01:00
Justin Clark-Casey (justincc)
dc2a4a6ccd Add simple regression test for logging in with offline friends. Don't expect to receive any in this instance. 2012-04-18 22:41:19 +01:00
Justin Clark-Casey (justincc)
81fb0b4f07 Add back parts of reverted changes that were not concerned with child agent caching.
This adds ScenePresence to IClientAPI.SceneAgent earlier on in the add client process so that its information is available to EventManager.OnNewClient() and OnClientLogin()
Also add a code comment as to why we're caching friend information for child agents.
2012-04-18 22:41:12 +01:00
Melanie
a06c8fb7b2 Also add OSS header to interface 2012-04-18 22:41:05 +01:00
Melanie
05e70f76a9 Change namespace on CallingCardModule and correct interface file placemant. Also ass OpenSource header 2012-04-18 22:36:24 +01:00
Melanie
b9f4836d3e Committing the Avination calling card module 2012-04-18 22:36:03 +01:00
Melanie
ef77dc932b Adding the Avination calling card interface 2012-04-18 22:35:57 +01:00
Justin Clark-Casey (justincc)
bd5b1d4d48 Earlier fix to remove localCopy on prebuild 2012-04-18 22:35:27 +01:00
Melanie
5c2ffa260c Pushing the Avination Calling card hooks. Module to follow. 2012-04-18 22:35:11 +01:00
Justin Clark-Casey (justincc)
23d04fa25c Add "friends show cache <first-name> <last-name>" command for debugging purposes.
This adds a reverse lookup (name -> ID) to IUserManagement instead of hitting the UserAccountService directly.
2012-04-18 22:11:42 +01:00
Justin Clark-Casey (justincc)
67dbce4512 minor: Add some documentation to OnNewClient and OnClientClosed events 2012-04-18 22:11:31 +01:00
Justin Clark-Casey (justincc)
46170fd0d8 minor: clean up some code formatting in VivoxVoiceModule.cs 2012-04-18 22:11:18 +01:00
Justin Clark-Casey (justincc)
f85a453dc8 Allow the user to enter help topics in upper or lowercase.
Forcing uppercase (e.g. help Assets) is too annoying.
Thanks to WhiteStar for pointing this out.
2012-04-18 22:10:27 +01:00
nebadon
bcfe48e05b fix yield prolog so it compiles with mono 2.11 there has been a bugzilla
report files with mono project in regards to this change, this simply
lets us move forward with using mono 2.11 for now :
https://bugzilla.xamarin.com/show_bug.cgi?id=4052
2012-04-18 22:10:05 +01:00
Justin Clark-Casey (justincc)
722ca250ea Move "change region" command into general category 2012-04-18 22:05:45 +01:00
Justin Clark-Casey (justincc)
f4df128e52 Uses shorter AddCommand form for "show estates" 2012-04-18 22:05:38 +01:00
Justin Clark-Casey (justincc)
8cc5322b39 Display help commander topics in capitalized form - the commands themselves are still lowercase.
Also convert the estate commands to simply AddCommand() calls so that commands from two different modules can be placed in the same category
2012-04-18 22:05:24 +01:00
Justin Clark-Casey (justincc)
1480845597 Change "help" to display categories/module list then "help <category/module>" to display commands in a category.
This is to deal with the hundred lines of command splurge when one previously typed "help"
Modelled somewhat on the mysql console
One can still type help <command> to get per command help at any point.
Categories capitalized to avoid conflict with the all-lowercase commands (except for commander system, as of yet).
Does not affect command parsing or any other aspects of the console apart from the help system.
Backwards compatible with existing modules.
2012-04-18 22:05:03 +01:00
Justin Clark-Casey (justincc)
c877e73463 Comment out log message about sending periodic appearance updates. 2012-03-30 02:33:26 +01:00
Justin Clark-Casey (justincc)
3ce5e8eb6c Add information about SendPeriodicAppearanceUpdates to OpenSimDefaults.ini for now.
Default remains false.
2012-03-30 02:33:18 +01:00
Justin Clark-Casey (justincc)
e6b12e1f9d Add experimental SendPeriodicAppearanceUpdates = true/false setting to [Startup] in OpenSim.ini
On osgrid and other places, I have observed that manually sending appearance updates from the console often relieves grey avatar syndrome.
Despite hunting high and low, I haven't been able to find where this packet is sometimes being lost - it might be a persistent viewer bug for all I know.
Therefore, this experimental setting resends appearance data for everybody in the scene every 60 seconds.  These packets are small and the viewer only fetches texture
data if it doesn't already have it.
Default is false.
2012-03-30 02:33:10 +01:00
Justin Clark-Casey (justincc)
bfbbd4ccba Add a scene maintenance thread in parallel to the heartbeat thread. The maintenance thread will end up running regular jobs that don't need to be in the main scene loop.
The idea is to make the critical main scene loop as skinny as possible - it doesn't need to run things that aren't time critical and don't depend on update ordering.
This will be done gradually over time to try and uncover any issues.  Many non-criticial scene loop activities are being launched on separate threadpool threads anyway.
This may also allow modules to register their own maintenance jobs without having to maintain their own timers and threads.
Currently the maintenance loop runs once a second, as opposed to the 89ms scene loop.
2012-03-30 02:33:01 +01:00
Justin Clark-Casey (justincc)
fec7016665 Remove unnecessary shutting down check in Scene.Heartbeat(). Add some method doc. Rename HeartbeatThread, shuttingdown to conform to code standards. 2012-03-30 02:32:53 +01:00
Justin Clark-Casey (justincc)
47fe6170b2 Rename Scene.StartTimer() to Start() - this method no longer uses a timer. Comment out more effectively unused old heartbeat code. 2012-03-30 02:32:34 +01:00
Justin Clark-Casey (justincc)
24b5fb8523 Add commented out section on collisions switch in Scene.SetSceneCoreDebug().
This was not implemented before the recent changes but should be at some point.
2012-03-30 02:24:03 +01:00
Justin Clark-Casey (justincc)
e8cd9688ce If "debug scene updates true" then print out to log when a garbage collection occurs. 2012-03-30 02:23:27 +01:00
Justin Clark-Casey (justincc)
fa952f6d35 Add Scene.DebugUpdates switch which, if turned on, will print out a warning when a frame updates takes longer than twice the desired time
This is controlled via "debug scene updates true|false" on the region console.
Also fix an oversight with "debug scene teleport true|false"
2012-03-30 02:23:20 +01:00
Justin Clark-Casey (justincc)
df55fd69af Incorporate scene teleporting debugging into "debug scene teleport true|false" command 2012-03-30 02:23:13 +01:00
Justin Clark-Casey (justincc)
019fc4c1f2 Replace "scene debug true false true" console command with "scene debug scripting true" or other parameters as appropriate.
This is to allow individual switching of scene debug settings and to provide flexibiltiy for additional settings.
2012-03-30 02:22:59 +01:00
Justin Clark-Casey (justincc)
22ea441feb fix compile error from last commit 2012-03-30 02:22:57 +01:00
Justin Clark-Casey (justincc)
9f5b33e52e refactor: simplify EstateManagementModule.handleEstateDebugRegionRequest() 2012-03-30 02:21:42 +01:00
Justin Clark-Casey (justincc)
92837c4f89 Add ability to log warn if a frame takes longer than twice the expected time. Currently commented out. 2012-03-30 02:21:32 +01:00
Justin Clark-Casey (justincc)
68ce06f40f remove unnecessary tmpFrameMS, use maintc instead for frame time calculation 2012-03-30 02:21:04 +01:00
Justin Clark-Casey (justincc)
279b31c75b Move frame loop entirely within Scene.Update() for better future performance analysis and stat accuracy.
Update() now accepts a frames parameter which can control the number of frames updated.
-1 will update until shutdown.
The watchdog updating moves above the maintc recalculation for any required sleep since it should be accounted for within the frame.
2012-03-30 02:20:46 +01:00
Justin Clark-Casey (justincc)
3117b3cd88 refactor: precalculate the fixed movement factor for avatar tilting (sqrt(2)) rather than doing it multiple times on every move. 2012-03-30 02:20:35 +01:00
Justin Clark-Casey (justincc)
9b547f76e7 refactor: Eliminate unnecessary duplicate avCapsuleTilted 2012-03-30 02:20:29 +01:00
Justin Clark-Casey (justincc)
3d6675784a Remove pointless ThreadAbortException catching in a test that isn't run anyway. 2012-03-30 02:20:21 +01:00
Justin Clark-Casey (justincc)
26f50eadd1 Remove some pointless catching/throwing in the scene loop. 2012-03-30 02:20:14 +01:00
Justin Clark-Casey (justincc)
6e8496ffc5 Comment out unused scene loop restart code.
This has actually been unused since at least 0.7.2 due to earlier changes.
2012-03-30 02:18:13 +01:00
Justin Clark-Casey (justincc)
88d6c4ec0e Use m_lastFrameTick instead of m_lastUpdate in Scene.GetHealth(). m_lastUpdate is no longer properly updated and is redundant anyway. 2012-03-30 02:18:05 +01:00
Justin Clark-Casey (justincc)
135eeb45d6 Change flavour to extended 2012-03-30 02:16:11 +01:00
Justin Clark-Casey (justincc)
54ee59c0bb Add Extended flavour option to opensim version information.
This flavour is for changes in addition to the 0.7.3-post-fixes branch that are too large to be considered fixes but should be reasonably stable.
This flavour will almost certainly never see a formal release.
2012-03-30 02:13:37 +01:00
Justin Clark-Casey (justincc)
4a5c61a33d Enable voice by default on parcels to weaken effects of viewer 2/3 ParcelVoiceInfoRequest bug
Viewer 2/3 contains a bug where the viewer will constantly retry ParcelVoiceInfoRequest requests on voice-disabled parcels where voice is otherwise available.
Attempts to fix this server-side have not been successful - sending a non-OK http code (e.g. a 404) just makes the viewer request again immediately.
Dropping the request entirely is a bit better but the viewer still retries after a minute.
Estate settings already enabled voice by default so doing the same for parcels.  This only has an effect if you have any voice system active at all.
Ultimately, the re-request bug needs to be fixed viewer-side (LL suffers from the same issue!) but it might be worth implementing the drop request hack.
2012-03-30 02:03:52 +01:00
Diva Canto
179c0f5f56 Merge branch '0.7.3-post-fixes' of ssh://opensimulator.org/var/git/opensim into 0.7.3-post-fixes 2012-03-22 20:36:01 -07:00
Justin Clark-Casey (justincc)
4dbf937707 Comment out login parameters debug output accidentally included with c4b2d24 2012-03-22 23:33:08 +00:00
Justin Clark-Casey (justincc)
9edb57e5e9 Comment out a terrain save-tile debugging message that accidentally crept in with c4b2d24 2012-03-22 23:17:54 +00:00
Justin Clark-Casey (justincc)
4d15ad63bf refactor: simplify code for checks when part.OwnerID != destPart.OwnerID in MoveTaskInventoryItem() 2012-03-22 23:03:00 +00:00
Justin Clark-Casey (justincc)
4021709371 Fix llGiveInventory() so that it checks the destination part for AllowInventoryDrop, not the source.
This allows llAllowInventoryDrop() to work.
Regression test added for this case.
2012-03-22 23:02:49 +00:00
Justin Clark-Casey (justincc)
3c13f5c3aa Add llGiveInventory() test from object to object where both objects are owned by the same user. 2012-03-22 23:02:35 +00:00
Justin Clark-Casey (justincc)
381517b451 Add prim name to "[MESH]: No recognized physics mesh..." log message 2012-03-22 23:01:39 +00:00
Justin Clark-Casey (justincc)
6ecf36d49c Fix small typo 2012-03-22 22:47:03 +00:00
Justin Clark-Casey (justincc)
64eb4b8408 Fix crash where two scene loop threads could changes m_MeshToTriMeshMap at the same time.
Have to lock m_MeshToTriMeshMap as property is static and with more than one region two scene loops could try to manipulate at the same time.
2012-03-22 22:46:45 +00:00
Diva Canto
318da3fdcd Added new simple_build_permissions config to the .ini and .example files. 2012-03-22 14:34:46 -07:00
Melanie
50c99fcda6 Change a false false to be truly true - or is this statement false?
Fixes perms boo-boo
2012-03-22 14:34:33 -07:00
Melanie
321de1f263 Rework Diva's patch to simplify it 2012-03-22 14:34:19 -07:00
Diva Canto
fa30ace67d Fixed borkness with map search introduce by my last changes to it. 2012-03-20 17:49:05 -07:00
Justin Clark-Casey (justincc)
92baa79253 Add some doc about the EventManager.OnLoginsEnabled event. 2012-03-19 22:49:34 +00:00
Justin Clark-Casey (justincc)
e861b45313 Fix a bug where logins to standalones would fail if the RegionReady module was not active
Unfortunately, the OnLoginsEnabled event is currently only guaranteed to fire if the RegionReady module is active.
However, we can instantiate the AuthorizationService in the module RegionLoaded method since by this time all other modules will have been loaded
2012-03-19 22:49:14 +00:00
Justin Clark-Casey (justincc)
cf91ac68b6 Stop console command "xengine status" throwing an exception if there are no scripts in a region.
Addresses http://opensimulator.org/mantis/view.php?id=5940
2012-03-19 21:45:18 +00:00
Justin Clark-Casey (justincc)
d4aba13526 Clean up "save iar" help 2012-03-19 21:33:03 +00:00
Justin Clark-Casey (justincc)
d36c7c3782 minor: reuse threadpool count we just fetched instead of fetching it again 2012-03-19 21:32:50 +00:00
Justin Clark-Casey (justincc)
4385fcdeae Add total scripts count to "show threads"
However, this returns 0 on Mono (at least on 2.6.7)!  So not showing if it is zero.
2012-03-19 21:32:42 +00:00
Justin Clark-Casey (justincc)
04eb170624 refactor: separate out console and status report generation parts of XEngine 2012-03-19 21:32:34 +00:00
Justin Clark-Casey (justincc)
4803686078 Improve threadpool reporting to "show threads" console command (also gets printed out periodically) 2012-03-19 21:32:25 +00:00
Justin Clark-Casey (justincc)
20b0fda3bb Add process working memory to "show stats" memory statistics.
This shows the actual amount of RAM being taken up by OpenSimulator (objects + vm overhead)
2012-03-19 21:32:16 +00:00
Justin Clark-Casey (justincc)
4e5f823595 In Top Scripts report, don't show scripts with no or less than 1 microsecond of execution time.
This is to make the report clearer and less confusing.
2012-03-19 21:32:02 +00:00
Justin Clark-Casey (justincc)
a74408d1d2 Aggregate script execution times by linksets rather than individual prims.
This is for the top scripts report.
2012-03-19 21:31:53 +00:00
Justin Clark-Casey (justincc)
8206537efd Fix owner name display in "Top Colliders" and "Top Script" region reports. 2012-03-19 21:31:45 +00:00
Justin Clark-Casey (justincc)
a9a77bb3ab Replace script-lines-per-second with the script execution time scaled by its measurement period and an idealised frame time.
The previous lines-per-second measurement used for top scripts report was inaccurate, since lines executed does not reflect time taken to execute.
Also, every fetch of the report would reset all the numbers limiting its usefulness and we weren't even guaranteed to see the top 100.
The actual measurement value should be script execution time per frame but XEngine does not work this way.
Therefore, we use actual script execution time scaled by the measurement period and an idealised frame time.
This is still not ideal but gives reasonable results and allows scripts to be compared.
This commit moves script execution time calculations from SceneGraph into IScriptModule implementations.
2012-03-19 21:31:38 +00:00
Justin Clark-Casey (justincc)
6390de689d Remove property/field duplication in ScriptInstance where it's unnecessary. 2012-03-19 21:31:17 +00:00
Justin Clark-Casey (justincc)
41ce19836b Simplify some logic in the ScriptInstance constructor - running is set to false in both if/else branches 2012-03-19 21:31:07 +00:00
Melanie
883a4f6fff FireAndForget scripted rez - port from Avination 2012-03-19 21:30:31 +00:00
Justin Clark-Casey (justincc)
5f1da80fc1 minor: correct indentation levels 2012-03-19 21:30:16 +00:00
Justin Clark-Casey (justincc)
64217d67f6 Remove duplication of m_RunEvents and Running 2012-03-19 21:29:45 +00:00
Justin Clark-Casey (justincc)
b01c79354c Fix a problem where multiple near simultaneous calls to llDie() from multiple scripts in the same linkset can cause unnecessary thread aborts.
The first llDie() could lock Scene.m_deleting_scene_object.
The second llDie() would then wait at this lock.
The first llDie() would go on to remove the second script but always abort it since the second script's WorkItem would not go away.
Easiest solution here is to remove the m_deleting_scene_object since it's no longer justified - we no longer lock m_parts but take a copy instead.
This also requires an adjustment in XEngine.OnRemoveScript not to use instance.ObjectID instead when firing the OnObjectRemoved event.
2012-03-19 21:29:34 +00:00
Justin Clark-Casey (justincc)
9ecbcb787c Alleviate an issue where calling Thread.Abort() on script WorkItems can fail to release locks, resulting in a crippled simulator.
This seems to be a particular problem with ReaderWriterLockSlim, though other locks can be affected as well.
It has been seen to happen when llDie() is called in a linkset running more than one script.
Alleviation here means supplying a ScriptInstance.Stop() timeout of 1000ms rather than 0ms, to give events a chance to complete.
Also, we check the IsRunning status at the top of the ScriptInstance.EventProcessor() so that another event doesn't start in the mean time.
Ultimately, a better solution may have to be found since a long-running event would still exceed the timeout and be aborted.
2012-03-19 21:29:24 +00:00
Justin Clark-Casey (justincc)
e17e376b04 refactor: rename ScriptInstance.m_CurrentResult to m_CurrentWorkItem to make it more understandable as to what it is and what it does (hold a thread pool work item for a waiting of in-progress event)
Also add other various illustrative comments
2012-03-19 21:29:09 +00:00
Justin Clark-Casey (justincc)
1b4ea4f178 Add max thread and min thread information to "xengine status" region console command 2012-03-19 21:28:54 +00:00
Melanie
1de29fb362 Change OpenSim.ini.example to reflect how to actually enable prim limits,
as opposed to how it was first designed.
2012-03-19 21:28:37 +00:00
Justin Clark-Casey (justincc)
7e4bd492fd Add documentation to make more explicit the difference between OnRezScript and OnNewScript in the event manager
OnNewScript fires when a script is added to a scene
OnRezScript fires when the script actually runs (i.e. after permission checks, state retrieval, etc.)
2012-03-19 21:26:32 +00:00
Justin Clark-Casey (justincc)
588d56503d Remove unnecessary explicit ElapsedEventHandler in SimReporter 2012-03-19 21:26:12 +00:00
Justin Clark-Casey (justincc)
e9602656f8 Add sensor, dataserver requests, timer and listener counts to "xengine status" command.
This is for diagnostic purposes.
2012-03-19 21:26:04 +00:00
Justin Clark-Casey (justincc)
0116d418f0 Fix TestSyntaxError() and TestSyntaxErrorDeclaringVariableInForLoop()
They were all failing assertions but the exceptions these threw were caught as expected Exceptions.
I don't think we can easily distinguish these from the Exceptions that we're expecting.
So for now we'll do some messy manually checking with boolean setting instead.
This patch also corrects the assertions themselves.
2012-03-19 21:25:41 +00:00
Justin Clark-Casey (justincc)
9992974c66 Get all test methods in OpenSim.Region.ScriptEngine.Tests.dll to report that they're running 2012-03-19 21:25:30 +00:00
Justin Clark-Casey (justincc)
ba27d8a389 Fix off by one error in script error reporting. 2012-03-19 21:25:20 +00:00
Justin Clark-Casey (justincc)
f96e985763 Simplify NPCModuleTests code by putting the NPCModule in an instance variable rather than making each test fetch it seperately.
Also rename instance variables in the test to conform to naming standards and for understandability
2012-03-19 21:25:07 +00:00
Diva Canto
5b9eaae50d Region access control! Region operators can now specify things like DisallowForeigners (means what it says) and DisallowResidents (means that only admins and managers can get into the region). This puts the never-completed AuthorizationService to good use. Note that I didn't implement a grid-wide Authorization service; this service implementation is done entirely locally on the simulator. This can be changed as usual by pluging in a different AuthorizationServicesConnector. 2012-03-17 19:49:14 -07:00
Diva Canto
74a13f7e3b Fixes mantis #5923 2012-03-17 19:47:05 -07:00
Diva Canto
3e88fc8aad Datasnapshot: added "secret" to the registration/deregistration query so that data providers can verify authenticity if they want. 2012-03-16 17:27:45 -07:00
Diva Canto
881740d702 DataSnapshot: renamed gridserverURL to gatekeeperURL, and normalimzed the capitalization of 'name' to lower case, also in the same <grid> section. 2012-03-16 17:27:29 -07:00
Diva Canto
9a643a1bb9 Terrain: added [Terrain] section with an option to load an initial flat terrain. Default is still pinhead island. I much rather have a flat land in the beginning.
Conflicts:

	bin/OpenSim.ini.example
2012-03-16 13:14:26 -07:00
Diva Canto
a5488650ff More on map search: send extra messages to the user regarding the region being found or not, because the UI is horribly confusing -- places profile is always "loading..." whether the region exists or not. 2012-03-15 20:24:26 -07:00
Diva Canto
a275127a65 More on SLURLs and V3. This is hacky, but it works. Basically, we have to redefine the encoding of HG URLs because the viewer messes them up. Examples of what works and doesn't work:
- secondlife://ucigrid00.nacs.uci.edu|8002/128/128 <-- works throughout the viewer
- secondlife://http|!!ucigrid00.nacs.uci.edu|8002+Test+Zone+1/128/128 <-- works throughout the viewer
- secondlife://http|!!grid.sciencesim.com!grid!hypergrid.php+Yellowstone01+74/128/128 <-- works throughout
- secondlife://http%3A%2F%2Fucigrid00.nacs.uci.edu%3A8002%20UCI%20Central%201/128/128 <-- works in chat, but not as URLs in the webkit
2012-03-15 16:14:20 -07:00
Diva Canto
02db31db6a Revert "Revert "More hacking around viewer bug""
This reverts commit 0434758a0d.
2012-03-15 16:14:07 -07:00
Diva Canto
8bd813e6fc Revert "Revert "Hack around https://jira.secondlife.com/browse/VWR-28570""
This reverts commit 09ff121654.
2012-03-15 16:13:50 -07:00
Diva Canto
09ff121654 Revert "Hack around https://jira.secondlife.com/browse/VWR-28570"
This reverts commit d7651a389e.
2012-03-15 14:38:22 -07:00
Diva Canto
0434758a0d Revert "More hacking around viewer bug"
This reverts commit 8bb0a71083.
2012-03-15 14:38:03 -07:00
Diva Canto
8bb0a71083 More hacking around viewer bug 2012-03-15 11:04:56 -07:00
Diva Canto
d7651a389e Hack around https://jira.secondlife.com/browse/VWR-28570 2012-03-15 10:18:55 -07:00
Justin Clark-Casey (justincc)
824318a0c1 Go back to setting appearance directly in NPCModule.SetAppearance() to fix mantis 5914
The part reverted is from commit 2ebb421.
Unfortunately, IAvatarFactoryModule.SetAppearance() does not transfer attachments.
I'm not sure how to do this separately, unfortunately I'll need to leave it to Dan :)
Regression test added for this case.
Mantis ref: http://opensimulator.org/mantis/view.php?id=5914
2012-03-06 01:32:44 +00:00
Justin Clark-Casey (justincc)
5e9ed22e84 Merge branch '0.7.3-post-fixes' of ssh://opensimulator.org/var/git/opensim into 0.7.3-post-fixes 2012-03-06 00:48:24 +00:00
Chris Hart
a6c611e7c9 Updates to MSSQL store for 0.7.3 to include:
* Telehub support
* Bugfix to Friends lookups
* Updates to Creator fields to store up to 255 characters for HG item creator storage
2012-03-06 00:45:58 +00:00
Diva Canto
72b325f8b5 Send the right name and creation date on the BasicProfileModule. 2012-03-01 19:58:57 -08:00
Justin Clark-Casey (justincc)
54d0514b13 Move SenseRepeaters.Count check inside the SenseRepeatListLock.
No methods in the List class are thread safe in the MS specification/documentation
2012-03-02 00:37:48 +00:00
Justin Clark-Casey (justincc)
58b1c3cec0 lock SenseRepeatListLock when added a new sensor during script reconstitution.
This is already being done in the other place where a sensor is added.
Adding a sensor whilst another thread is iterating over the sensor list can cause a concurrency exception.
2012-03-02 00:37:42 +00:00
Justin Clark-Casey (justincc)
71641523a3 Flick 0.7.3-post-fixes to Flavour.Post_Fixes 2012-02-29 23:43:41 +00:00
Justin Clark-Casey (justincc)
94c5e25c3b Extend distsrc target to cleanup the files generated by running prebuild 2012-02-29 22:32:32 +00:00
Justin Clark-Casey (justincc)
20bad0aa6c Merge branch '0.7.3-post-fixes' of ssh://opensimulator.org/var/git/opensim into 0.7.3-post-fixes 2012-02-29 22:22:42 +00:00
Justin Clark-Casey (justincc)
e7f23a6218 Switch flavour to release 2012-02-29 22:10:33 +00:00
Justin Clark-Casey (justincc)
25c29db8b6 Don't start pCampbot if the user doesn't supply bot firstname, lastname stub and password 2012-02-29 22:04:17 +00:00
Justin Clark-Casey (justincc)
1750fba9ce Call Dispose() via using() on SqliteCommands in WebStatsModule after use. 2012-02-29 22:04:04 +00:00
PixelTomsen
b18e410586 PRIM_SCULPT_FLAG_INVERT, PRIM_SCULPT_FLAG_MIRROR implemented
http://opensimulator.org/mantis/view.php?id=5763
2012-02-29 22:03:28 +00:00
Justin Clark-Casey (justincc)
38d5e1fab3 Move libopenjpeg native libraries into lib32 and lib64 as appropriate. 2012-02-29 22:03:15 +00:00
Justin Clark-Casey (justincc)
4180c32eb1 Remove some more unused Bullet libraries. 2012-02-29 22:03:08 +00:00
Justin Clark-Casey (justincc)
5115229fdf Remove old libbulletnet native libraries. These are not used in the current generation bullet physics plugin. 2012-02-29 22:02:59 +00:00
Justin Clark-Casey (justincc)
e8f2d814e7 Move other sqlite and ode 32-bit and 64-bit libraries into lib32 or lib64 as appropriate. 2012-02-29 22:02:50 +00:00
Diva Canto
1fda8c5a86 HG: Remove async in posting assets to foreign grid. Mono hates concurrency there. 2012-02-26 14:30:58 -08:00
Justin Clark-Casey (justincc)
6b77b55d40 Merge branch 'master' into 0.7.3-post-fixes 2012-02-25 00:49:16 +00:00
Justin Clark-Casey (justincc)
82cdb08c1f Merge branch 'master' into 0.7.3-post-fixes 2012-02-25 00:49:05 +00:00
Justin Clark-Casey (justincc)
dafcb3bcd7 Merge branch 'master' into 0.7.3-post-fixes 2012-02-24 23:35:59 +00:00
Diva Canto
3259b1d1e0 Amend to last commit: synchronize access to queues. 2012-02-20 11:13:02 -08:00
Diva Canto
512910a51f More improvements on agent position updates: if the target sims fail, blacklist them for 2 min, so that we don't keep doing remote calls that fail. 2012-02-20 11:00:01 -08:00
Diva Canto
fdda57cf10 Merge branch '0.7.3-post-fixes' of ssh://opensimulator.org/var/git/opensim into 0.7.3-post-fixes 2012-02-19 16:48:09 -08:00
Diva Canto
ec8e34950d One more tweak related to the previous 2 commits. 2012-02-19 16:47:04 -08:00
Diva Canto
93964ef3a4 Amend to last commit. This should have been committed too. 2012-02-19 16:46:47 -08:00
Diva Canto
5c8af6a136 A few more tweaks on position updates and create child agents. Mono hates concurrent uses of the same TCP connection, and even of the connections to the same server. So let's stop doing it. This patch makes movement much smoother when there are lots of neighbours. 2012-02-19 16:46:32 -08:00
PixelTomsen
4d0c8aca05 Fix:OmegaX, OmegaY and OmegaZ not saved for child prims http://opensimulator.org/mantis/view.php?id=5893
Signed-off-by: nebadon <michael@osgrid.org>
2012-02-19 13:49:51 -05:00
PixelTomsen
8fc16ece96 Fix:Fly setting for Parcel dosen't work http://opensimulator.org/mantis/view.php?id=5887
Signed-off-by: nebadon <michael@osgrid.org>
2012-02-19 13:49:41 -05:00
BlueWall
fcbb375e8f Use localy defined name, TPFlags, for Constants.TeleportFlags 2012-02-19 12:42:05 -05:00
BlueWall
49c65279fa Route logins according to Estate, Telehub and TeleportFlags 2012-02-19 12:41:44 -05:00
BlueWall
86e8a56fe1 Propagate our teleport flags on logins 2012-02-19 12:41:30 -05:00
BlueWall
b199330682 Parcel sales support to SQLite 2012-02-19 12:41:11 -05:00
BlueWall
7a7ebaebd1 Fillin missing SQLite support for Telehubs 2012-02-19 12:40:46 -05:00
BlueWall
164ae0b24b Fix missing telehub handling on login 2012-02-19 12:40:26 -05:00
Diva Canto
7156545fca This should smooth movement in heteregeneous networks by a lot: cache the region by position instead of looking it up all the time -- this was being done during the main update loop! 2012-02-18 22:15:58 -08:00
Justin Clark-Casey (justincc)
73a5abf4d9 Merge branch 'master' into 0.7.3-post-fixes 2012-02-17 04:04:38 +00:00
Justin Clark-Casey (justincc)
630c8dc828 switch version flavour to rc2 2012-02-17 04:03:59 +00:00
Justin Clark-Casey (justincc)
6de89246c2 Merge branch 'master' into 0.7.3-post-fixes 2012-02-17 00:02:16 +00:00
Justin Clark-Casey (justincc)
96973a5778 Merge branch 'master' into 0.7.3-post-fixes 2012-02-16 03:39:25 +00:00
Justin Clark-Casey (justincc)
96843f2b17 Merge branch 'master' into 0.7.3-post-fixes 2012-02-15 02:41:50 +00:00
Justin Clark-Casey (justincc)
8a36f54cf4 Merge branch 'master' into 0.7.3-post-fixes 2012-02-06 20:54:21 +00:00
Justin Clark-Casey (justincc)
1a14e660d2 Merge branch 'master' into 0.7.3-post-fixes 2012-02-04 02:03:49 +00:00
Justin Clark-Casey (justincc)
2502aae5db Switch flavour to RC1. It will still be a while before RC1 is released. 2012-02-04 01:26:29 +00:00
2325 changed files with 267424 additions and 410630 deletions

23
.gitattributes vendored
View File

@@ -1,23 +0,0 @@
*.lsl binary
*.dat binary
*.bmp binary
*.BMP binary
*.gif binary
*.ico binary
*.j2c binary
*.j2k binary
*.jpg binary
*.png binary
*.tga binary
*.tif binary
*.db2 binary
*.llm binary
*.nib binary
*.rtf binary
*.ttf binary
*.ogg binary
*.dll binary
*.exe binary
*.so binary
*.dylib binary
*.anim binary

View File

@@ -1,62 +0,0 @@
name: .msbuildnet6
on:
push:
branches: [ "master" ]
paths:
- '**.cs'
workflow_dispatch:
jobs:
build:
if: github.repository == 'opensim/opensim'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: shortsha
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: preBuild
run: bash ${GITHUB_WORKSPACE}/runprebuild.sh
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build
id: build
run: dotnet build --configuration Release OpenSim.sln
- name: release
if: success()
run: zip -r LastDotNetBuild.zip bin ThirdPartyLicenses README.md CONTRIBUTORS.txt LICENSE.txt
- uses: softprops/action-gh-release@v1
if: success()
with:
tag_name: r${{ steps.vars.outputs.sha_short }}
name: LastDotNetAutoBuild
files: LastDotNetBuild.zip
- name: report push to irc if from main OpenSim repository
if: github.event_name == 'push' && github.repository_owner == 'opensim'
uses: rectalogic/notify-irc@v1
with:
channel: "#opensim-dev"
server: "irc.libera.chat"
nickname: osgithub
message: |
${{ github.actor }} pushed to ${{ github.repository }}
${{ join(github.event.commits.*.message, '\n') }}
dotnet compile: ${{ steps.build.conclusion }}
- name: manual report to irc if from main OpenSim repository
if: github.event_name == 'workflow_dispatch' && github.repository_owner == 'opensim'
uses: rectalogic/notify-irc@v1
with:
channel: "#opensim-dev"
server: "irc.libera.chat"
nickname: osgithub
message: |
${{ github.repository }}
dotnet compile: ${{ steps.build.conclusion }}

37
.gitignore vendored
View File

@@ -1,6 +1,5 @@
.project
.settings
.gitignore
*.csproj
*.csproj.user
*.build
@@ -11,16 +10,6 @@
*.pidb
*.dll.build
*.dll
*.log
.idea
*.bak
*.lnk
# Ignore .user and .suo files as these are user preference specific
# http://stackoverflow.com/questions/72298/should-i-add-the-visual-studio-suo-and-user-files-to-source-control
*.suo
*.user
*.VisualState.xml
*/*/obj
*/*/*/obj
@@ -34,24 +23,16 @@
*/*/*/*/*/bin
*/*/*/*/*/*/bin
*/*/*/*/*/*/*/bin
.vs/
addon-modules/
bin/Debug/*.dll
bin/*.dll.mdb
bin/*.db
bin/*.db-journal
bin/addin-db-*
bin/*.dll
bin/OpenSim.vshost.exe.config
bin/OpenSim.32BitLaunch.vshost.exe.config
bin/OpenSim.32BitLaunch.log
UpgradeLog.XML
_UpgradeReport_Files/
bin/ScriptEngines/*-*-*-*-*
bin/ScriptEngines/*.dll
bin/ScriptEngines/*/*.dll
bin/ScriptEngines/*/*.state
bin/ScriptEngines/Yengine/*
bin/*.maddin
bin/*.exe
bin/*.ini
@@ -60,10 +41,6 @@ bin/Physics*
bin/Terrain*
bin/Regions/*
bin/UserAssets
bin/assetcache
bin/maptiles
bin/bakes
bin/MeshCache
bin/estate_settings.xml
bin/config-include/CenomeCache.ini
bin/config-include/FlotsamCache.ini
@@ -76,18 +53,12 @@ bin/OpenSim.Grid.InventoryServer.log
bin/OpenSim.Grid.MessagingServer.log
bin/OpenSim.Grid.UserServer.log
bin/OpenSim.log
bin/OpenSimStats.log
bin/Robust.log
bin/RobustStats.log
bin/OpenSimConsoleHistory.txt
bin/RobustConsoleHistory.txt
bin/*.Tests.log
bin/*.manifest
bin/crashes/
Examples/*.dll
OpenSim.build
OpenSim.sln
OpenSim.userprefs
OpenSim.suo
Prebuild/Prebuild.build
Prebuild/Prebuild.sln
TestResult.xml
@@ -99,6 +70,7 @@ TAGS
Makefile.local
bin/.version
compile.bat
addon-modules
OpenSim/Data/Tests/test-results/
OpenSim/Framework/Serialization/Tests/test-results/
OpenSim/Framework/Servers/Tests/test-results/
@@ -115,8 +87,3 @@ OpenSim/Region/ScriptEngine/test-results/
OpenSim/Tests/Common/test-results/
OpenSim/Tests/test-results/
test-results/
doc/html
doc/doxygen.error.log
bin/OpenSim.ini
*.patch

23
.hgignore Normal file
View File

@@ -0,0 +1,23 @@
^tailor.state.old$
^tailor.state.journal$
\.csproj$
\.csproj\.user$
\.mdp$
\.mds$
\.dll\.build$
^bin/Debug/.+\.dll$
^bin/.+\.db$
^bin/OpenSim\.ini$
^bin/OpenSim\.log$
^bin/estate_settings\.xml$
^bin/Regions
bin/.+\.dll.mdb$
bin/addin-db-
bin/.+\.dll$
bin/.+\.maddin$
Examples/.+\.dll$
^bin/.+\.exe
^(OpenSim|Prebuild)/.+\.(exe|exe\.build|exe\.mdb)$
^OpenSim\.(build|sln)$
^Prebuild/Prebuild\.(build|sln)$
.+~$

241
.nant/local.include Normal file
View File

@@ -0,0 +1,241 @@
<!-- -*- xml -*- -->
<!-- please leave the top comment for us emacs folks -->
<property name="nunitcmd" value="nunit-console" />
<!-- This target produces a source distribution of OpenSimulator -->
<!-- TODO: A few parameters still need to be tweaked after running this - need to do this automatically with sed or similar -->
<target name="distsrc">
<copy file="bin/OpenSim.ini.example" tofile="bin/OpenSim.ini"/>
<copy file="bin/config-include/StandaloneCommon.ini.example" tofile="bin/config-include/StandaloneCommon.ini"/>
<copy file="bin/config-include/FlotsamCache.ini.example" tofile="bin/config-include/FlotsamCache.ini"/>
<!-- delete files generated by runprebuild.sh which had to be run in order to generate the build file for this target-->
<delete>
<fileset basedir="OpenSim">
<include name="**/*.build"/>
<include name="**/*.csproj*"/>
<include name="**/*.dll.build"/>
<include name="**/*.pidb"/>
<exclude name="Tools/OpenSim.32BitLaunch/**"/>
<exclude name="Tools/Robust.32BitLaunch/**"/>
<exclude name="Tools/LaunchSLClient/**"/>
</fileset>
</delete>
<delete>
<fileset>
<include name="OpenSim.build"/>
<include name="OpenSim.sln"/>
</fileset>
</delete>
</target>
<property name="distbindir" value="distbin" />
<!-- This target produces a binary directory called distbin/ in OpenSim/bin which contains everything needed for binary distribution -->
<!-- For safety/laziness sake, we're going to take the approach of deleting known extraneous files here rather than
trying to copy across only the essential ones -->
<target name="distbin">
<delete dir="${distbindir}"/>
<copy todir="${distbindir}">
<fileset>
<include name="**"/>
</fileset>
</copy>
<delete dir="${distbindir}/OpenSim"/>
<delete dir="${distbindir}/Prebuild"/>
<delete dir="${distbindir}/%temp%"/>
<delete dir="${distbindir}/.nant"/>
<delete>
<fileset basedir="${distbindir}">
<include name="compile.bat"/>
<include name="BUILDING.txt"/>
<include name="Makefile"/>
<include name="nant-color"/>
<include name="OpenSim.*"/>
<include name="prebuild.xml"/>
<include name="runprebuild*"/>
<include name="TESTING.txt"/>
<include name="TestResult.xml"/>
<include name="bin/OpenSim.Server.ini"/>
<include name="bin/Regions/Regions.ini"/>
<include name="bin/*.db"/>
<include name="**/.git/**"/>
<include name=".gitignore"/>
<include name=".hgignore"/>
</fileset>
</delete>
</target>
<target name="test" depends="build, find-nunit">
<setenv name="MONO_THREADS_PER_CPU" value="100" />
<!-- Unit Test Assembly -->
<!-- if you want to add more unit tests it's important that you add
the assembly here as an exec, and you add the fail clause later.
This lets all the unit tests run and tells you if they fail at the
end, instead of stopping short -->
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.tests">
<arg value="./bin/OpenSim.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.framework.tests">
<arg value="./bin/OpenSim.Framework.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.framework.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.framework.servers.tests">
<arg value="./bin/OpenSim.Framework.Servers.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.framework.servers.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.framework.serialization.tests">
<arg value="./bin/OpenSim.Framework.Serialization.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.framework.serialization.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.region.clientstack.lindencaps.tests">
<arg value="./bin/OpenSim.Region.ClientStack.LindenCaps.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.clientstack.lindencaps.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.region.clientstack.lindenudp.tests">
<arg value="./bin/OpenSim.Region.ClientStack.LindenUDP.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.clientstack.lindenudp.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.region.scriptengine.tests">
<arg value="./bin/OpenSim.Region.ScriptEngine.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.scriptengine.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.region.coremodules.tests">
<arg value="./bin/OpenSim.Region.CoreModules.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.coremodules.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.region.optionalmodules.tests">
<arg value="./bin/OpenSim.Region.OptionalModules.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.optionalmodules.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.region.framework.tests">
<arg value="./bin/OpenSim.Region.Framework.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.framework.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.data.tests">
<arg value="./bin/OpenSim.Data.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.data.tests)==0}" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.capabilities.handlers.tests">
<arg value="./bin/OpenSim.Capabilities.Handlers.Tests.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.capabilities.handlers.tests)==0}" />
<delete dir="%temp%"/>
</target>
<target name="torture" depends="build, find-nunit">
<setenv name="MONO_THREADS_PER_CPU" value="100" />
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.tests.torture">
<arg value="./bin/OpenSim.Tests.Torture.dll" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.tests.torture)==0}" />
<delete dir="%temp%"/>
</target>
<target name="find-nunit">
<exec program="which" failonerror="false"
resultproperty="hasnunit2">
<arg value="nunit-console2" />
</exec>
<property name="nunitcmd" value="nunit-console2"
if="${int::parse(hasnunit2)==0}" />
<property name="nunitcmd" value="nunit-console"
if="${int::parse(hasnunit2)==1}" />
</target>
<!-- this is used for panda test execution -->
<!-- work in progress -->
<target name="test-xml" depends="build, find-nunit">
<mkdir dir="test-results" failonerror="false" />
<!-- Unit Test Assembly -->
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.tests">
<arg value="./bin/OpenSim.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.framework.tests">
<arg value="./bin/OpenSim.Framework.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Framework.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.framework.serialization.tests">
<arg value="./bin/OpenSim.Framework.Serialization.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Framework.Serialization.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.framework.servers.tests">
<arg value="./bin/OpenSim.Framework.Servers.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Framework.Servers.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.region.clientstack.lindencaps.tests">
<arg value="./bin/OpenSim.Region.ClientStack.LindenCaps.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Region.ClientStack.LindenCaps.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.region.clientstack.lindenudp.tests">
<arg value="./bin/OpenSim.Region.ClientStack.LindenUDP.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Region.ClientStack.LindenUDP.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.region.scriptengine.tests">
<arg value="./bin/OpenSim.Region.ScriptEngine.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Region.ScriptEngine.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.region.coremodules.tests">
<arg value="./bin/OpenSim.Region.CoreModules.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Region.CoreModules.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.region.optionalmodules.tests">
<arg value="./bin/OpenSim.Region.OptionalModules.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Region.OptionalModules.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.region.framework.tests">
<arg value="./bin/OpenSim.Region.Framework.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Region.Framework.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.data.tests">
<arg value="./bin/OpenSim.Data.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Data.Tests.dll-Results.xml" />
</exec>
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.capabilities.handlers.tests">
<arg value="./bin/OpenSim.Capabilities.Handlers.Tests.dll" />
<arg value="-xml=test-results/OpenSim.Capabilities.Handlers.Tests.dll-Results.xml" />
</exec>
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.framework.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.framework.servers.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.clientstack.lindenudp.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.scriptengine.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.coremodules.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.optionalmodules.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.region.framework.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.data.tests)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.capabilities.handlers.tests)==0}" />
</target>
<target name="doxygen">
<exec program="doxygen" workingdir="doc" commandline="doxygen.conf" />
</target>

View File

@@ -1,89 +0,0 @@
# git clone
get or update source from git
`git clone git://opensimulator.org/git/opensim`
# Building on Windows
## Requirements
To building under Windows, the following is required:
* [dotnet 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
optionally also
* [Visual Studio .NET](https://visualstudio.microsoft.com/vs/features/net-development/), version 2022 or later
### Building
To create the project files, run
`runprebuild.bat`
run
`compile.bat`
Or load the generated OpenSim.sln into Visual Studio and build the solution.
Configure, see below
Now just run `OpenSim.exe` from the `bin` folder, and set up the region.
# Building on Linux / Mac
## Requirements
* [dotnet 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
* libgdiplus
if you have mono 6.x complete, you already have libgdiplus, otherwise you need to install it
using a package manager for your operating system, like apt, brew, macports, etc
for example on debian:
`apt-get update && apt-get install -y apt-utils libgdiplus libc6-dev`
### Building
To create the project files, run:
`./runprebuild.sh`
then run
`dotnet build --configuration Release OpenSim.sln`
Configure. See below
run `./opensim.sh` from the `bin` folder, and set up the region
# Configure #
## Standalone mode ##
Copy `OpenSim.ini.example` to `OpenSim.ini` in the `bin/` directory, and verify the `[Const]` section, correcting for your case.
On `[Architecture]` section uncomment only the line with Standalone.ini if you do now want HG, or the line with StandaloneHypergrid.ini if you do
copy the `StandaloneCommon.ini.example` to `StandaloneCommon.ini` in the `bin/config-include` directory.
The StandaloneCommon.ini file describes the database and backend services that OpenSim will use, and is set to use sqlite by default, which requires no setup.
## Grid mode ##
Each grid may have its own requirements, so FOLLOW your Grid instructions!
in general:
Copy `OpenSim.ini.example` to `OpenSim.ini` in the `bin/` directory, and verify the `[Const]` section, correcting for your case
On `[Architecture]` section uncomment only the line with Grid.ini if you do now want HG, or the line with GridHypergrid.ini if you do
and copy the `GridCommon.ini.example` file to `GridCommon.ini` inside the `bin/config-include` directory and edit as necessary
# References
* http://opensimulator.org/wiki/Build_Instructions
* http://opensimulator.org/wiki/Configuration

39
BUILDING.txt Normal file
View File

@@ -0,0 +1,39 @@
==== Building OpenSim ====
=== Building on Windows ===
Steps:
* runprebuild.bat
* Load OpenSim.sln into Visual Studio .NET and build the solution.
* chdir bin
* copy OpenSim.ini.example to OpenSim.ini and other appropriate files in bin/config-include
* run OpenSim.exe
=== Building on Linux ===
Prereqs:
* Mono >= 2.4.3
* Nant >= 0.85
* On some Linux distributions you may need to install additional packages.
See http://opensimulator.org/wiki/Dependencies for more information.
* May also use xbuild (included in mono distributions)
* May use Monodevelop, a cross-platform IDE
From the distribution type:
* ./runprebuild.sh
* nant (or xbuild)
* cd bin
* copy OpenSim.ini.example to OpenSim.ini and other appropriate files in bin/config-include
* run mono OpenSim.exe
=== Using Monodevelop ===
From the distribution type:
* ./runprebuild.sh
* type monodevelop OpenSim.sln
=== References ===
Helpful resources:
* http://opensimulator.org/wiki/Build_Instructions

View File

@@ -1,22 +1,39 @@
The following people have contributed to OpenSim (Thank you for your effort!)
<<<>>>>The following people have contributed to OpenSim (Thank you
for your effort!)
= Current OpenSim Developers (in very rough order of appearance) =
These folks represent the current core team for OpenSim, and are the
people that make the day to day of OpenSim happen.
* justincc (OSVW Consulting, justincc.org)
* chi11ken (Genkii)
* dahlia
* Melanie Thielker
* Diva (Crista Lopes, University of California, Irvine)
* Robert Adams (MisterBlue)
* Kevin Cozens
* Leal Duarte (Ubit Umarov)
* Dan Lake (Intel)
* Marck
* Mic Bowman (Intel)
* BlueWall (James Hughes)
* Nebadon Izumi (Michael Cerquoni, OSgrid)
* Snoopy Pfeffer
* Richard Adams (Intel)
= Core Developers Following the White Rabbit =
Core developers who have temporarily (we hope) gone chasing the white rabbit.
They are in all similar to the active core developers, except that they haven't
been that active lately, so their voting rights are awaiting their come back.
* Nebadon Izumi (Michael Cerquoni, OSgrid)
* Alicia Raven
* MW (Tribal Media AB)
* Adam Frisby (DeepThink Pty Ltd)
* lbsa71 (Tribal Media AB)
* Teravus (w3z)
* Ckrinke (Charles Krinke)
* Dr Scofield aka Dirk Husemann (IBM Research - Zurich)
* mikem (3Di)
* Homer_Horwitz
* nlin (3Di)
* Arthur Rodrigo S Valadares (IBM)
* John Hurliman
= Past Open Sim Developers =
These folks are alumns of the OpenSim core group, but are now
@@ -38,123 +55,65 @@ where we are today.
* adjohn (Genkii)
* idb (Ian Brown)
* Johan Berntsson (3Di)
* MW (Tribal Media AB)
* Adam Frisby (DeepThink Pty Ltd)
* lbsa71 (Tribal Media AB)
* Ckrinke (Charles Krinke)
* Dr Scofield aka Dirk Husemann (IBM Research - Zurich)
* mikem (3Di)
* Homer_Horwitz
* nlin (3Di)
* John Hurliman
* chi11ken (Genkii)
* dahlia
* justincc (OSVW Consulting, justincc.org)
* Arthur Rodrigo S Valadares (IBM)
* BlueWall (James Hughes)
* Dan Lake
* Marck
* Mic Bowman
* Oren Hurvitz (Kitely)
* Snoopy Pfeffer
* Teravus (w3z)
= Additional OpenSim Contributors =
These folks have contributed code patches or content to OpenSimulator to help make it
These folks have contributed code patches to OpenSim to help make it
what it is today.
* A_Biondi
* aduffy70
* Ai Austin
* A_Biondi
* alex_carnell
* Alan Webb (IBM)
* Aleric
* Allen Kerensky
* BigFootAg
* Bill Blight
* BlueWall Slade
* bobshaffer2
* brianw/Sir_Ahzz
* CharlieO
* ChrisDown
* Chris Yeoh (IBM)
* cinderblocks
* controlbreak
* coyled
* ctrlaltdavid (David Rowe)
* Daedius
* daTwitch
* Dev Random
* devalnor-#708
* dmiles (Daxtron Labs)
* Dong Jun Lan (IBM)
* DoranZemlja
* Drake Arconis
* dr0b3rts
* dslake
* eeyore
* daTwitch
* devalnor-#708
* dmiles (Daxtron Labs)
* dslake (Intel)
* FredoChaplin
* FreakyTech
* Garmin Kawaguichi
* Gavin Hird
* Gerhard
* Godfrey
* Greg C.
* Grumly57
* GuduleLapointe
* Ewe Loon
* Fernando Oliveira
* Fly-Man
* Flyte Xevious
* Freaky Tech
* Garmin Kawaguichi
* Geir Noklebye
* Glenn Martin (MOSES)
* Gryc Ueusp
* H-H-H (ginge264)
* Hiro Lecker
* Iain Oliver
* Imaze Rhiano
* Intimidated
* Jak Daniels
* Jeff Kelly
* Jeremy Bongio (IBM)
* jhurliman
* John R Sohn (XenReborn)
* jonc
* Jon Cundill
* Junta Kohime
* Kayne
* Kevin Cozens
* kinoc (Daxtron Labs)
* Kira
* Kitto Flora
* KittyLiu
* Kurt Taylor (IBM)
* Lani Global
* lickx
* lillith_xue
* lkalif
* LuciusSirnah
* lulurun
* M.Igarashi
* Magnuz Binder
* maimedleech
* Mana Janus
* Mandarinka Tasty
* MarcelEdward
* Matt Lehmann
* mewtwo0641
* Mic Bowman
* Michelle Argus
* Michael Cortez (The Flotsam Project, http://osflotsam.org/)
* Michael Heilmann (MOSES)
* Micheil Merlin
* Mike Osias (IBM)
* Mike Pitman (IBM)
* Mike Rieker (Dreamnation)
* mikemig
* mikkopa/_someone - RealXtend
* Misterblue
* Misterblue (Intel)
* Mircea Kitsune
* mpallari
* MrMonkE
@@ -163,75 +122,61 @@ what it is today.
* nornalbion
* Omar Vera Ustariz (IBM)
* openlifegrid.com
* Oren Hurvitz (Kitely)
* otakup0pe
* Pixel Tomsen
* Quill Littlefeather
* ralphos
* RemedyTomm
* Revolution
* Richard Alimi (IBM)
* Rick Alther (IBM)
* Rob Smart (IBM)
* Robert Louden (MOSES)
* Roger Kirkman (zadark)
* rtomita
* Ruud Lathorp
* SachaMagne
* Salahzar Stenvaag
* satguru p srivastava
* sempuki
* Shy Robbiani
* SignpostMarv
* SpotOn3D
* Stefan_Boom / stoehr
* Steven Zielinski (MOSES)
* Stolen Ruby
* Strawberry Fride
* Talun
* TechplexEngineer (Blake Bourque)
* TBG Renfold
* Terry Ford
* tglion
* tlaukkan/Tommil (Tommi S. E. Laukkanen, Bubble Cloud)
* TomDataWorks
* TomTheDragon (muckwaddle)
* tyre
* uriesk
* Vegaslon <vegaslon@gmail.com>
* Vincent Sylvester
* VikingErik
* Vytek
* webmage (IBM)
* Xantor
* Y. Nitta
* YoshikoFazuku
* YZh
* Zackary Geers aka Kunnis Basiat
* Zha Ewry
* ziah
= LSL Devs =
* Alondria
* CharlieO
* Tedd
* Melanie Thielker
= Testers =
* Ai Austin
* CharlieO (LSL)
* Ckrinke
* openlifegrid.com
This software uses components from the following developers:
* Sleepycat Software (Berkeley DB)
* Aurora-Sim (http://aurora-sim.org)
* SQLite (Public Domain)
* XmlRpcCS (http://xmlrpccs.sf.net/)
* MySQL, Inc. (MySQL Connector/NET)
* NUnit (http://www.nunit.org)
* AGEIA Inc. (PhysX)
* Russel L. Smith (ODE)
* Erwin Coumans (Bullet)
* Prebuild (http://sourceforge.net/projects/dnpb/)
* LibOpenMetaverse (http://lib.openmetaverse.org/)
* DotNetOpenMail v0.5.8b (http://dotnetopenmail.sourceforge.net)

7241
OpenSim.FxCop Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
namespace OpenSim.Groups
{
public class ForeignImporter
{
IUserManagement m_UserManagement;
public ForeignImporter(IUserManagement uman)
{
m_UserManagement = uman;
}
public GroupMembersData ConvertGroupMembersData(ExtendedGroupMembersData _m)
{
GroupMembersData m = new GroupMembersData();
m.AcceptNotices = _m.AcceptNotices;
m.AgentPowers = _m.AgentPowers;
m.Contribution = _m.Contribution;
m.IsOwner = _m.IsOwner;
m.ListInProfile = _m.ListInProfile;
m.OnlineStatus = _m.OnlineStatus;
m.Title = _m.Title;
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
Util.ParseUniversalUserIdentifier(_m.AgentID, out m.AgentID, out url, out first, out last, out tmp);
if (url != string.Empty)
m_UserManagement.AddUser(m.AgentID, first, last, url);
return m;
}
public GroupRoleMembersData ConvertGroupRoleMembersData(ExtendedGroupRoleMembersData _rm)
{
GroupRoleMembersData rm = new GroupRoleMembersData();
rm.RoleID = _rm.RoleID;
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
Util.ParseUniversalUserIdentifier(_rm.MemberID, out rm.MemberID, out url, out first, out last, out tmp);
if (url != string.Empty)
m_UserManagement.AddUser(rm.MemberID, first, last, url);
return rm;
}
}
}

View File

@@ -1,533 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using OpenSim.Framework;
using OpenMetaverse;
namespace OpenSim.Groups
{
public class ExtendedGroupRecord : GroupRecord
{
public int MemberCount;
public int RoleCount;
public string ServiceLocation;
public string FounderUUI;
}
public class ExtendedGroupMembershipData : GroupMembershipData
{
public string AccessToken;
}
public class ExtendedGroupMembersData
{
// This is the only difference: this is a string
public string AgentID;
public int Contribution;
public string OnlineStatus;
public ulong AgentPowers;
public string Title;
public bool IsOwner;
public bool ListInProfile;
public bool AcceptNotices;
public string AccessToken;
}
public class ExtendedGroupRoleMembersData
{
public UUID RoleID;
// This is the only difference: this is a string
public string MemberID;
}
public struct ExtendedGroupNoticeData
{
public UUID NoticeID;
public uint Timestamp;
public string FromName;
public string Subject;
public bool HasAttachment;
public byte AttachmentType;
public string AttachmentName;
public UUID AttachmentItemID;
public string AttachmentOwnerID;
public GroupNoticeData ToGroupNoticeData()
{
GroupNoticeData n = new GroupNoticeData();
n.FromName = this.FromName;
n.AssetType = this.AttachmentType;
n.HasAttachment = this.HasAttachment;
n.NoticeID = this.NoticeID;
n.Subject = this.Subject;
n.Timestamp = this.Timestamp;
return n;
}
}
public class GroupsDataUtils
{
public static string Sanitize(string s)
{
return s == null ? string.Empty : s;
}
public static Dictionary<string, object> GroupRecord(ExtendedGroupRecord grec)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
if (grec == null)
return dict;
dict["AllowPublish"] = grec.AllowPublish.ToString();
dict["Charter"] = Sanitize(grec.Charter);
dict["FounderID"] = grec.FounderID.ToString();
dict["FounderUUI"] = Sanitize(grec.FounderUUI);
dict["GroupID"] = grec.GroupID.ToString();
dict["GroupName"] = Sanitize(grec.GroupName);
dict["InsigniaID"] = grec.GroupPicture.ToString();
dict["MaturePublish"] = grec.MaturePublish.ToString();
dict["MembershipFee"] = grec.MembershipFee.ToString();
dict["OpenEnrollment"] = grec.OpenEnrollment.ToString();
dict["OwnerRoleID"] = grec.OwnerRoleID.ToString();
dict["ServiceLocation"] = Sanitize(grec.ServiceLocation);
dict["ShownInList"] = grec.ShowInList.ToString();
dict["MemberCount"] = grec.MemberCount.ToString();
dict["RoleCount"] = grec.RoleCount.ToString();
return dict;
}
public static ExtendedGroupRecord GroupRecord(Dictionary<string, object> dict)
{
if (dict == null)
return null;
ExtendedGroupRecord grec = new ExtendedGroupRecord();
if (dict.ContainsKey("AllowPublish") && dict["AllowPublish"] != null)
grec.AllowPublish = bool.Parse(dict["AllowPublish"].ToString());
if (dict.ContainsKey("Charter") && dict["Charter"] != null)
grec.Charter = dict["Charter"].ToString();
else
grec.Charter = string.Empty;
if (dict.ContainsKey("FounderID") && dict["FounderID"] != null)
grec.FounderID = UUID.Parse(dict["FounderID"].ToString());
if (dict.ContainsKey("FounderUUI") && dict["FounderUUI"] != null)
grec.FounderUUI = dict["FounderUUI"].ToString();
else
grec.FounderUUI = string.Empty;
if (dict.ContainsKey("GroupID") && dict["GroupID"] != null)
grec.GroupID = UUID.Parse(dict["GroupID"].ToString());
if (dict.ContainsKey("GroupName") && dict["GroupName"] != null)
grec.GroupName = dict["GroupName"].ToString();
else
grec.GroupName = string.Empty;
if (dict.ContainsKey("InsigniaID") && dict["InsigniaID"] != null)
grec.GroupPicture = UUID.Parse(dict["InsigniaID"].ToString());
if (dict.ContainsKey("MaturePublish") && dict["MaturePublish"] != null)
grec.MaturePublish = bool.Parse(dict["MaturePublish"].ToString());
if (dict.ContainsKey("MembershipFee") && dict["MembershipFee"] != null)
grec.MembershipFee = Int32.Parse(dict["MembershipFee"].ToString());
if (dict.ContainsKey("OpenEnrollment") && dict["OpenEnrollment"] != null)
grec.OpenEnrollment = bool.Parse(dict["OpenEnrollment"].ToString());
if (dict.ContainsKey("OwnerRoleID") && dict["OwnerRoleID"] != null)
grec.OwnerRoleID = UUID.Parse(dict["OwnerRoleID"].ToString());
if (dict.ContainsKey("ServiceLocation") && dict["ServiceLocation"] != null)
grec.ServiceLocation = dict["ServiceLocation"].ToString();
else
grec.ServiceLocation = string.Empty;
if (dict.ContainsKey("ShownInList") && dict["ShownInList"] != null)
grec.ShowInList = bool.Parse(dict["ShownInList"].ToString());
if (dict.ContainsKey("MemberCount") && dict["MemberCount"] != null)
grec.MemberCount = Int32.Parse(dict["MemberCount"].ToString());
if (dict.ContainsKey("RoleCount") && dict["RoleCount"] != null)
grec.RoleCount = Int32.Parse(dict["RoleCount"].ToString());
return grec;
}
public static Dictionary<string, object> GroupMembershipData(ExtendedGroupMembershipData membership)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
if (membership == null)
return dict;
dict["AcceptNotices"] = membership.AcceptNotices.ToString();
dict["AccessToken"] = Sanitize(membership.AccessToken);
dict["Active"] = membership.Active.ToString();
dict["ActiveRole"] = membership.ActiveRole.ToString();
dict["AllowPublish"] = membership.AllowPublish.ToString();
dict["Charter"] = Sanitize(membership.Charter);
dict["Contribution"] = membership.Contribution.ToString();
dict["FounderID"] = membership.FounderID.ToString();
dict["GroupID"] = membership.GroupID.ToString();
dict["GroupName"] = Sanitize(membership.GroupName);
dict["GroupPicture"] = membership.GroupPicture.ToString();
dict["GroupPowers"] = membership.GroupPowers.ToString();
dict["GroupTitle"] = Sanitize(membership.GroupTitle);
dict["ListInProfile"] = membership.ListInProfile.ToString();
dict["MaturePublish"] = membership.MaturePublish.ToString();
dict["MembershipFee"] = membership.MembershipFee.ToString();
dict["OpenEnrollment"] = membership.OpenEnrollment.ToString();
dict["ShowInList"] = membership.ShowInList.ToString();
return dict;
}
public static ExtendedGroupMembershipData GroupMembershipData(Dictionary<string, object> dict)
{
if (dict == null)
return null;
ExtendedGroupMembershipData membership = new ExtendedGroupMembershipData();
if (dict.ContainsKey("AcceptNotices") && dict["AcceptNotices"] != null)
membership.AcceptNotices = bool.Parse(dict["AcceptNotices"].ToString());
if (dict.ContainsKey("AccessToken") && dict["AccessToken"] != null)
membership.AccessToken = dict["AccessToken"].ToString();
else
membership.AccessToken = string.Empty;
if (dict.ContainsKey("Active") && dict["Active"] != null)
membership.Active = bool.Parse(dict["Active"].ToString());
if (dict.ContainsKey("ActiveRole") && dict["ActiveRole"] != null)
membership.ActiveRole = UUID.Parse(dict["ActiveRole"].ToString());
if (dict.ContainsKey("AllowPublish") && dict["AllowPublish"] != null)
membership.AllowPublish = bool.Parse(dict["AllowPublish"].ToString());
if (dict.ContainsKey("Charter") && dict["Charter"] != null)
membership.Charter = dict["Charter"].ToString();
else
membership.Charter = string.Empty;
if (dict.ContainsKey("Contribution") && dict["Contribution"] != null)
membership.Contribution = Int32.Parse(dict["Contribution"].ToString());
if (dict.ContainsKey("FounderID") && dict["FounderID"] != null)
membership.FounderID = UUID.Parse(dict["FounderID"].ToString());
if (dict.ContainsKey("GroupID") && dict["GroupID"] != null)
membership.GroupID = UUID.Parse(dict["GroupID"].ToString());
if (dict.ContainsKey("GroupName") && dict["GroupName"] != null)
membership.GroupName = dict["GroupName"].ToString();
else
membership.GroupName = string.Empty;
if (dict.ContainsKey("GroupPicture") && dict["GroupPicture"] != null)
membership.GroupPicture = UUID.Parse(dict["GroupPicture"].ToString());
if (dict.ContainsKey("GroupPowers") && dict["GroupPowers"] != null)
membership.GroupPowers = UInt64.Parse(dict["GroupPowers"].ToString());
if (dict.ContainsKey("GroupTitle") && dict["GroupTitle"] != null)
membership.GroupTitle = dict["GroupTitle"].ToString();
else
membership.GroupTitle = string.Empty;
if (dict.ContainsKey("ListInProfile") && dict["ListInProfile"] != null)
membership.ListInProfile = bool.Parse(dict["ListInProfile"].ToString());
if (dict.ContainsKey("MaturePublish") && dict["MaturePublish"] != null)
membership.MaturePublish = bool.Parse(dict["MaturePublish"].ToString());
if (dict.ContainsKey("MembershipFee") && dict["MembershipFee"] != null)
membership.MembershipFee = Int32.Parse(dict["MembershipFee"].ToString());
if (dict.ContainsKey("OpenEnrollment") && dict["OpenEnrollment"] != null)
membership.OpenEnrollment = bool.Parse(dict["OpenEnrollment"].ToString());
if (dict.ContainsKey("ShowInList") && dict["ShowInList"] != null)
membership.ShowInList = bool.Parse(dict["ShowInList"].ToString());
return membership;
}
public static Dictionary<string, object> GroupMembersData(ExtendedGroupMembersData member)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dict["AcceptNotices"] = member.AcceptNotices.ToString();
dict["AccessToken"] = Sanitize(member.AccessToken);
dict["AgentID"] = Sanitize(member.AgentID);
dict["AgentPowers"] = member.AgentPowers.ToString();
dict["Contribution"] = member.Contribution.ToString();
dict["IsOwner"] = member.IsOwner.ToString();
dict["ListInProfile"] = member.ListInProfile.ToString();
dict["OnlineStatus"] = Sanitize(member.OnlineStatus);
dict["Title"] = Sanitize(member.Title);
return dict;
}
public static ExtendedGroupMembersData GroupMembersData(Dictionary<string, object> dict)
{
ExtendedGroupMembersData member = new ExtendedGroupMembersData();
if (dict == null)
return member;
if (dict.ContainsKey("AcceptNotices") && dict["AcceptNotices"] != null)
member.AcceptNotices = bool.Parse(dict["AcceptNotices"].ToString());
if (dict.ContainsKey("AccessToken") && dict["AccessToken"] != null)
member.AccessToken = Sanitize(dict["AccessToken"].ToString());
else
member.AccessToken = string.Empty;
if (dict.ContainsKey("AgentID") && dict["AgentID"] != null)
member.AgentID = Sanitize(dict["AgentID"].ToString());
else
member.AgentID = UUID.Zero.ToString();
if (dict.ContainsKey("AgentPowers") && dict["AgentPowers"] != null)
member.AgentPowers = UInt64.Parse(dict["AgentPowers"].ToString());
if (dict.ContainsKey("Contribution") && dict["Contribution"] != null)
member.Contribution = Int32.Parse(dict["Contribution"].ToString());
if (dict.ContainsKey("IsOwner") && dict["IsOwner"] != null)
member.IsOwner = bool.Parse(dict["IsOwner"].ToString());
if (dict.ContainsKey("ListInProfile") && dict["ListInProfile"] != null)
member.ListInProfile = bool.Parse(dict["ListInProfile"].ToString());
if (dict.ContainsKey("OnlineStatus") && dict["OnlineStatus"] != null)
member.OnlineStatus = Sanitize(dict["OnlineStatus"].ToString());
else
member.OnlineStatus = string.Empty;
if (dict.ContainsKey("Title") && dict["Title"] != null)
member.Title = Sanitize(dict["Title"].ToString());
else
member.Title = string.Empty;
return member;
}
public static Dictionary<string, object> GroupRolesData(GroupRolesData role)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dict["Description"] = Sanitize(role.Description);
dict["Members"] = role.Members.ToString();
dict["Name"] = Sanitize(role.Name);
dict["Powers"] = role.Powers.ToString();
dict["RoleID"] = role.RoleID.ToString();
dict["Title"] = Sanitize(role.Title);
return dict;
}
public static GroupRolesData GroupRolesData(Dictionary<string, object> dict)
{
GroupRolesData role = new GroupRolesData();
if (dict == null)
return role;
if (dict.ContainsKey("Description") && dict["Description"] != null)
role.Description = Sanitize(dict["Description"].ToString());
else
role.Description = string.Empty;
if (dict.ContainsKey("Members") && dict["Members"] != null)
role.Members = Int32.Parse(dict["Members"].ToString());
if (dict.ContainsKey("Name") && dict["Name"] != null)
role.Name = Sanitize(dict["Name"].ToString());
else
role.Name = string.Empty;
if (dict.ContainsKey("Powers") && dict["Powers"] != null)
role.Powers = UInt64.Parse(dict["Powers"].ToString());
if (dict.ContainsKey("Title") && dict["Title"] != null)
role.Title = Sanitize(dict["Title"].ToString());
else
role.Title = string.Empty;
if (dict.ContainsKey("RoleID") && dict["RoleID"] != null)
role.RoleID = UUID.Parse(dict["RoleID"].ToString());
return role;
}
public static Dictionary<string, object> GroupRoleMembersData(ExtendedGroupRoleMembersData rmember)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dict["RoleID"] = rmember.RoleID.ToString();
dict["MemberID"] = rmember.MemberID;
return dict;
}
public static ExtendedGroupRoleMembersData GroupRoleMembersData(Dictionary<string, object> dict)
{
ExtendedGroupRoleMembersData rmember = new ExtendedGroupRoleMembersData();
if (dict.ContainsKey("RoleID") && dict["RoleID"] != null)
rmember.RoleID = new UUID(dict["RoleID"].ToString());
if (dict.ContainsKey("MemberID") && dict["MemberID"] != null)
rmember.MemberID = dict["MemberID"].ToString();
return rmember;
}
public static Dictionary<string, object> GroupInviteInfo(GroupInviteInfo invite)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dict["InviteID"] = invite.InviteID.ToString();
dict["GroupID"] = invite.GroupID.ToString();
dict["RoleID"] = invite.RoleID.ToString();
dict["AgentID"] = invite.AgentID;
return dict;
}
public static GroupInviteInfo GroupInviteInfo(Dictionary<string, object> dict)
{
if (dict == null)
return null;
GroupInviteInfo invite = new GroupInviteInfo();
invite.InviteID = new UUID(dict["InviteID"].ToString());
invite.GroupID = new UUID(dict["GroupID"].ToString());
invite.RoleID = new UUID(dict["RoleID"].ToString());
invite.AgentID = Sanitize(dict["AgentID"].ToString());
return invite;
}
public static Dictionary<string, object> GroupNoticeData(ExtendedGroupNoticeData notice)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dict["NoticeID"] = notice.NoticeID.ToString();
dict["Timestamp"] = notice.Timestamp.ToString();
dict["FromName"] = Sanitize(notice.FromName);
dict["Subject"] = Sanitize(notice.Subject);
dict["HasAttachment"] = notice.HasAttachment.ToString();
dict["AttachmentItemID"] = notice.AttachmentItemID.ToString();
dict["AttachmentName"] = Sanitize(notice.AttachmentName);
dict["AttachmentType"] = notice.AttachmentType.ToString();
dict["AttachmentOwnerID"] = Sanitize(notice.AttachmentOwnerID);
return dict;
}
public static ExtendedGroupNoticeData GroupNoticeData(Dictionary<string, object> dict)
{
ExtendedGroupNoticeData notice = new ExtendedGroupNoticeData();
if (dict == null)
return notice;
notice.NoticeID = new UUID(dict["NoticeID"].ToString());
notice.Timestamp = UInt32.Parse(dict["Timestamp"].ToString());
notice.FromName = Sanitize(dict["FromName"].ToString());
notice.Subject = Sanitize(dict["Subject"].ToString());
notice.HasAttachment = bool.Parse(dict["HasAttachment"].ToString());
notice.AttachmentItemID = new UUID(dict["AttachmentItemID"].ToString());
notice.AttachmentName = dict["AttachmentName"].ToString();
notice.AttachmentType = byte.Parse(dict["AttachmentType"].ToString());
notice.AttachmentOwnerID = dict["AttachmentOwnerID"].ToString();
return notice;
}
public static Dictionary<string, object> GroupNoticeInfo(GroupNoticeInfo notice)
{
Dictionary<string, object> dict = GroupNoticeData(notice.noticeData);
dict["GroupID"] = notice.GroupID.ToString();
dict["Message"] = Sanitize(notice.Message);
return dict;
}
public static GroupNoticeInfo GroupNoticeInfo(Dictionary<string, object> dict)
{
GroupNoticeInfo notice = new GroupNoticeInfo();
notice.noticeData = GroupNoticeData(dict);
notice.GroupID = new UUID(dict["GroupID"].ToString());
notice.Message = Sanitize(dict["Message"].ToString());
return notice;
}
public static Dictionary<string, object> DirGroupsReplyData(DirGroupsReplyData g)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dict["GroupID"] = g.groupID;
dict["Name"] = g.groupName;
dict["NMembers"] = g.members;
dict["SearchOrder"] = g.searchOrder;
return dict;
}
public static DirGroupsReplyData DirGroupsReplyData(Dictionary<string, object> dict)
{
DirGroupsReplyData g;
g.groupID = new UUID(dict["GroupID"].ToString());
g.groupName = dict["Name"].ToString();
Int32.TryParse(dict["NMembers"].ToString(), out g.members);
float.TryParse(dict["SearchOrder"].ToString(), out g.searchOrder);
return g;
}
}
}

View File

@@ -1,841 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using log4net;
using Mono.Addins;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
using GridRegion = OpenSim.Services.Interfaces.GridRegion;
namespace OpenSim.Groups
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsMessagingModule")]
public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private List<Scene> m_sceneList = new List<Scene>();
private IPresenceService m_presenceService;
private IMessageTransferModule m_msgTransferModule = null;
private IUserManagement m_UserManagement = null;
private IGroupsServicesConnector m_groupData = null;
// Config Options
private bool m_groupMessagingEnabled;
private bool m_debugEnabled;
/// <summary>
/// If enabled, module only tries to send group IMs to online users by querying cached presence information.
/// </summary>
private bool m_messageOnlineAgentsOnly;
/// <summary>
/// Cache for online users.
/// </summary>
/// <remarks>
/// Group ID is key, presence information for online members is value.
/// Will only be non-null if m_messageOnlineAgentsOnly = true
/// We cache here so that group messages don't constantly have to re-request the online user list to avoid
/// attempted expensive sending of messages to offline users.
/// The tradeoff is that a user that comes online will not receive messages consistently from all other users
/// until caches have updated.
/// Therefore, we set the cache expiry to just 20 seconds.
/// </remarks>
private ExpiringCache<UUID, PresenceInfo[]> m_usersOnlineCache;
private int m_usersOnlineCacheExpirySeconds = 20;
private Dictionary<UUID, List<string>> m_groupsAgentsDroppedFromChatSession = new Dictionary<UUID, List<string>>();
private Dictionary<UUID, List<string>> m_groupsAgentsInvitedToChatSession = new Dictionary<UUID, List<string>>();
#region Region Module interfaceBase Members
public void Initialise(IConfigSource config)
{
IConfig groupsConfig = config.Configs["Groups"];
if (groupsConfig == null)
// Do not run this module by default.
return;
// if groups aren't enabled, we're not needed.
// if we're not specified as the connector to use, then we're not wanted
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|| (groupsConfig.GetString("MessagingModule", "") != Name))
{
m_groupMessagingEnabled = false;
return;
}
m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
if (!m_groupMessagingEnabled)
return;
m_messageOnlineAgentsOnly = groupsConfig.GetBoolean("MessageOnlineUsersOnly", false);
if (m_messageOnlineAgentsOnly)
{
m_usersOnlineCache = new ExpiringCache<UUID, PresenceInfo[]>();
}
else
{
m_log.Error("[Groups.Messaging]: GroupsMessagingModule V2 requires MessageOnlineUsersOnly = true");
m_groupMessagingEnabled = false;
return;
}
m_debugEnabled = groupsConfig.GetBoolean("MessagingDebugEnabled", m_debugEnabled);
m_log.InfoFormat(
"[Groups.Messaging]: GroupsMessagingModule enabled with MessageOnlineOnly = {0}, DebugEnabled = {1}",
m_messageOnlineAgentsOnly, m_debugEnabled);
}
public void AddRegion(Scene scene)
{
if (!m_groupMessagingEnabled)
return;
scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
m_sceneList.Add(scene);
scene.EventManager.OnNewClient += OnNewClient;
scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
scene.EventManager.OnClientLogin += OnClientLogin;
scene.AddCommand(
"Debug",
this,
"debug groups messaging verbose",
"debug groups messaging verbose <true|false>",
"This setting turns on very verbose groups messaging debugging",
HandleDebugGroupsMessagingVerbose);
}
public void RegionLoaded(Scene scene)
{
if (!m_groupMessagingEnabled)
return;
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
// No groups module, no groups messaging
if (m_groupData == null)
{
m_log.Error("[Groups.Messaging]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
RemoveRegion(scene);
return;
}
m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
// No message transfer module, no groups messaging
if (m_msgTransferModule == null)
{
m_log.Error("[Groups.Messaging]: Could not get MessageTransferModule");
RemoveRegion(scene);
return;
}
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
// No groups module, no groups messaging
if (m_UserManagement == null)
{
m_log.Error("[Groups.Messaging]: Could not get IUserManagement, GroupsMessagingModule is now disabled.");
RemoveRegion(scene);
return;
}
if (m_presenceService == null)
m_presenceService = scene.PresenceService;
}
public void RemoveRegion(Scene scene)
{
if (!m_groupMessagingEnabled)
return;
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
m_sceneList.Remove(scene);
scene.EventManager.OnNewClient -= OnNewClient;
scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
scene.EventManager.OnClientLogin -= OnClientLogin;
scene.UnregisterModuleInterface<IGroupsMessagingModule>(this);
}
public void Close()
{
if (!m_groupMessagingEnabled)
return;
if (m_debugEnabled) m_log.Debug("[Groups.Messaging]: Shutting down GroupsMessagingModule module.");
m_sceneList.Clear();
m_groupData = null;
m_msgTransferModule = null;
}
public Type ReplaceableInterface
{
get { return null; }
}
public string Name
{
get { return "Groups Messaging Module V2"; }
}
public void PostInitialise()
{
// NoOp
}
#endregion
private void HandleDebugGroupsMessagingVerbose(object modules, string[] args)
{
if (args.Length < 5)
{
MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
return;
}
bool verbose = false;
if (!bool.TryParse(args[4], out verbose))
{
MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
return;
}
m_debugEnabled = verbose;
MainConsole.Instance.Output("{0} verbose logging set to {1}", Name, m_debugEnabled);
}
/// <summary>
/// Not really needed, but does confirm that the group exists.
/// </summary>
public bool StartGroupChatSession(UUID agentID, UUID groupID)
{
if (m_debugEnabled)
m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID.ToString(), groupID, null);
if (groupInfo != null)
{
return true;
}
else
{
return false;
}
}
public void SendMessageToGroup(GridInstantMessage im, UUID groupID)
{
SendMessageToGroup(im, groupID, UUID.Zero, null);
}
public void SendMessageToGroup(
GridInstantMessage im, UUID groupID, UUID sendingAgentForGroupCalls, Func<GroupMembersData, bool> sendCondition)
{
int requestStartTick = Environment.TickCount;
UUID fromAgentID = new UUID(im.fromAgentID);
// Unlike current XmlRpcGroups, Groups V2 can accept UUID.Zero when a perms check for the requesting agent
// is not necessary.
List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(UUID.Zero.ToString(), groupID);
int groupMembersCount = groupMembers.Count;
PresenceInfo[] onlineAgents = null;
// In V2 we always only send to online members.
// Sending to offline members is not an option.
// We cache in order not to overwhelm the presence service on large grids with many groups. This does
// mean that members coming online will not see all group members until after m_usersOnlineCacheExpirySeconds has elapsed.
// (assuming this is the same across all grid simulators).
if (!m_usersOnlineCache.TryGetValue(groupID, out onlineAgents))
{
string[] t1 = groupMembers.ConvertAll<string>(gmd => gmd.AgentID.ToString()).ToArray();
onlineAgents = m_presenceService.GetAgents(t1);
m_usersOnlineCache.Add(groupID, onlineAgents, m_usersOnlineCacheExpirySeconds);
}
HashSet<string> onlineAgentsUuidSet = new HashSet<string>();
Array.ForEach<PresenceInfo>(onlineAgents, pi => onlineAgentsUuidSet.Add(pi.UserID));
groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList();
// if (m_debugEnabled)
// m_log.DebugFormat(
// "[Groups.Messaging]: SendMessageToGroup called for group {0} with {1} visible members, {2} online",
// groupID, groupMembersCount, groupMembers.Count());
im.imSessionID = groupID.Guid;
im.fromGroup = true;
IClientAPI thisClient = GetActiveClient(fromAgentID);
if (thisClient != null)
{
im.RegionID = thisClient.Scene.RegionInfo.RegionID.Guid;
}
if (im.binaryBucket is null || im.binaryBucket.Length == 0 || (im.binaryBucket.Length == 1 && im.binaryBucket[0] == 0))
{
ExtendedGroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), groupID, null);
if (groupInfo != null)
im.binaryBucket = Util.StringToBytes256(groupInfo.GroupName);
}
// Send to self first of all
im.toAgentID = im.fromAgentID;
im.fromGroup = true;
ProcessMessageFromGroupSession(im);
List<UUID> regions = new List<UUID>();
List<UUID> clientsAlreadySent = new List<UUID>();
// Then send to everybody else
foreach (GroupMembersData member in groupMembers)
{
if (member.AgentID.Guid == im.fromAgentID)
continue;
if (clientsAlreadySent.Contains(member.AgentID))
continue;
clientsAlreadySent.Add(member.AgentID);
if (sendCondition != null)
{
if (!sendCondition(member))
{
if (m_debugEnabled)
m_log.DebugFormat(
"[Groups.Messaging]: Not sending to {0} as they do not fulfill send condition",
member.AgentID);
continue;
}
}
else if (hasAgentDroppedGroupChatSession(member.AgentID.ToString(), groupID))
{
// Don't deliver messages to people who have dropped this session
if (m_debugEnabled)
m_log.DebugFormat("[Groups.Messaging]: {0} has dropped session, not delivering to them", member.AgentID);
continue;
}
im.toAgentID = member.AgentID.Guid;
IClientAPI client = GetActiveClient(member.AgentID);
if (client == null)
{
// If they're not local, forward across the grid
// BUT do it only once per region, please! Sim would be even better!
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} via Grid", member.AgentID);
bool reallySend = true;
if (onlineAgents != null)
{
PresenceInfo presence = onlineAgents.First(p => p.UserID == member.AgentID.ToString());
if (regions.Contains(presence.RegionID))
reallySend = false;
else
regions.Add(presence.RegionID);
}
if (reallySend)
{
// We have to create a new IM structure because the transfer module
// uses async send
GridInstantMessage msg = new GridInstantMessage(im, true);
m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { });
}
}
else
{
// Deliver locally, directly
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name);
ProcessMessageFromGroupSession(im);
}
}
if (m_debugEnabled)
m_log.DebugFormat(
"[Groups.Messaging]: SendMessageToGroup for group {0} with {1} visible members, {2} online took {3}ms",
groupID, groupMembersCount, groupMembers.Count(), Environment.TickCount - requestStartTick);
}
#region SimGridEventHandlers
void OnClientLogin(IClientAPI client)
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name);
}
private void OnNewClient(IClientAPI client)
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name);
ResetAgentGroupChatSessions(client.AgentId.ToString());
}
void OnMakeRootAgent(ScenePresence sp)
{
sp.ControllingClient.OnInstantMessage += OnInstantMessage;
}
void OnMakeChildAgent(ScenePresence sp)
{
sp.ControllingClient.OnInstantMessage -= OnInstantMessage;
}
private void OnGridInstantMessage(GridInstantMessage msg)
{
// The instant message module will only deliver messages of dialog types:
// MessageFromAgent, StartTyping, StopTyping, MessageFromObject
//
// Any other message type will not be delivered to a client by the
// Instant Message Module
UUID regionID = new UUID(msg.RegionID);
if (m_debugEnabled)
{
m_log.DebugFormat("[Groups.Messaging]: {0} called, IM from region {1}",
System.Reflection.MethodBase.GetCurrentMethod().Name, regionID);
DebugGridInstantMessage(msg);
}
// Incoming message from a group
if ((msg.fromGroup == true) && (msg.dialog == (byte)InstantMessageDialog.SessionSend))
{
// We have to redistribute the message across all members of the group who are here
// on this sim
UUID GroupID = new UUID(msg.imSessionID);
Scene aScene = m_sceneList[0];
GridRegion regionOfOrigin = aScene.GridService.GetRegionByUUID(aScene.RegionInfo.ScopeID, regionID);
List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(UUID.Zero.ToString(), GroupID);
//if (m_debugEnabled)
// foreach (GroupMembersData m in groupMembers)
// m_log.DebugFormat("[Groups.Messaging]: member {0}", m.AgentID);
foreach (Scene s in m_sceneList)
{
s.ForEachScenePresence(sp =>
{
// If we got this via grid messaging, it's because the caller thinks
// that the root agent is here. We should only send the IM to root agents.
if (sp.IsChildAgent)
return;
GroupMembersData m = groupMembers.Find(gmd =>
{
return gmd.AgentID == sp.UUID;
});
if (m.AgentID.IsZero())
{
if (m_debugEnabled)
m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he is not a member of the group", sp.UUID);
return;
}
// Check if the user has an agent in the region where
// the IM came from, and if so, skip it, because the IM
// was already sent via that agent
if (regionOfOrigin != null)
{
AgentCircuitData aCircuit = s.AuthenticateHandler.GetAgentCircuitData(sp.UUID);
if (aCircuit != null)
{
if (aCircuit.ChildrenCapSeeds.Keys.Contains(regionOfOrigin.RegionHandle))
{
if (m_debugEnabled)
m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he has an agent in region of origin", sp.UUID);
return;
}
else
{
if (m_debugEnabled)
m_log.DebugFormat("[Groups.Messaging]: not skipping agent {0}", sp.UUID);
}
}
}
UUID AgentID = sp.UUID;
msg.toAgentID = AgentID.Guid;
if (!hasAgentDroppedGroupChatSession(AgentID.ToString(), GroupID))
{
if (!hasAgentBeenInvitedToGroupChatSession(AgentID.ToString(), GroupID))
AddAgentToSession(AgentID, GroupID, msg);
else
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", sp.Name);
ProcessMessageFromGroupSession(msg);
}
}
});
}
}
}
private void ProcessMessageFromGroupSession(GridInstantMessage msg)
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID);
UUID AgentID = new UUID(msg.fromAgentID);
UUID GroupID = new UUID(msg.imSessionID);
UUID toAgentID = new UUID(msg.toAgentID);
switch (msg.dialog)
{
case (byte)InstantMessageDialog.SessionAdd:
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
break;
case (byte)InstantMessageDialog.SessionDrop:
AgentDroppedFromGroupChatSession(AgentID.ToString(), GroupID);
break;
case (byte)InstantMessageDialog.SessionSend:
// User hasn't dropped, so they're in the session,
// maybe we should deliver it.
IClientAPI client = GetActiveClient(new UUID(msg.toAgentID));
if (client != null)
{
// Deliver locally, directly
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} locally", client.Name);
if (!hasAgentDroppedGroupChatSession(toAgentID.ToString(), GroupID))
{
if (!hasAgentBeenInvitedToGroupChatSession(toAgentID.ToString(), GroupID))
// This actually sends the message too, so no need to resend it
// with client.SendInstantMessage
AddAgentToSession(toAgentID, GroupID, msg);
else
client.SendInstantMessage(msg);
}
}
else
{
m_log.WarnFormat("[Groups.Messaging]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID);
}
break;
default:
m_log.WarnFormat("[Groups.Messaging]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString());
break;
}
}
private void AddAgentToSession(UUID AgentID, UUID GroupID, GridInstantMessage msg)
{
// Agent not in session and hasn't dropped from session
// Add them to the session for now, and Invite them
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
IClientAPI activeClient = GetActiveClient(AgentID);
if (activeClient != null)
{
GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
if (groupInfo != null)
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Sending chatterbox invite instant message");
UUID fromAgent = new UUID(msg.fromAgentID);
// Force? open the group session dialog???
// and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg);
IEventQueue eq = activeClient.Scene.RequestModuleInterface<IEventQueue>();
if (eq != null)
{
eq.ChatterboxInvitation(
GroupID
, groupInfo.GroupName
, fromAgent
, msg.message
, AgentID
, msg.fromAgentName
, msg.dialog
, msg.timestamp
, msg.offline == 1
, (int)msg.ParentEstateID
, msg.Position
, 1
, new UUID(msg.imSessionID)
, msg.fromGroup
, OpenMetaverse.Utils.StringToBytes(groupInfo.GroupName)
);
var update = new GroupChatListAgentUpdateData(AgentID);
var updates = new List<GroupChatListAgentUpdateData> { update };
eq.ChatterBoxSessionAgentListUpdates(GroupID, new UUID(msg.toAgentID), updates);
}
}
}
}
#endregion
#region ClientEvents
private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
{
if (m_debugEnabled)
{
m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
DebugGridInstantMessage(im);
}
// Start group IM session
if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart))
{
if (m_debugEnabled) m_log.InfoFormat("[Groups.Messaging]: imSessionID({0}) toAgentID({1})", im.imSessionID, im.toAgentID);
UUID GroupID = new UUID(im.imSessionID);
UUID AgentID = new UUID(im.fromAgentID);
GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
if (groupInfo != null)
{
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, GroupID);
IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
if (queue != null)
{
var update = new GroupChatListAgentUpdateData(AgentID);
var updates = new List<GroupChatListAgentUpdateData> { update };
queue.ChatterBoxSessionAgentListUpdates(GroupID, remoteClient.AgentId, updates);
}
}
}
// Send a message from locally connected client to a group
if ((im.dialog == (byte)InstantMessageDialog.SessionSend))
{
UUID GroupID = new UUID(im.imSessionID);
UUID AgentID = new UUID(im.fromAgentID);
if (m_debugEnabled)
m_log.DebugFormat("[Groups.Messaging]: Send message to session for group {0} with session ID {1}", GroupID, im.imSessionID.ToString());
//If this agent is sending a message, then they want to be in the session
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
SendMessageToGroup(im, GroupID);
}
}
#endregion
void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID)
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
OSDMap moderatedMap = new OSDMap(4);
moderatedMap.Add("voice", OSD.FromBoolean(false));
OSDMap sessionMap = new OSDMap(4);
sessionMap.Add("moderated_mode", moderatedMap);
sessionMap.Add("session_name", OSD.FromString(groupName));
sessionMap.Add("type", OSD.FromInteger(0));
sessionMap.Add("voice_enabled", OSD.FromBoolean(false));
OSDMap bodyMap = new OSDMap(4);
bodyMap.Add("session_id", OSD.FromUUID(groupID));
bodyMap.Add("temp_session_id", OSD.FromUUID(groupID));
bodyMap.Add("success", OSD.FromBoolean(true));
bodyMap.Add("session_info", sessionMap);
IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
queue?.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
}
private void DebugGridInstantMessage(GridInstantMessage im)
{
// Don't log any normal IMs (privacy!)
if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent)
{
m_log.WarnFormat("[Groups.Messaging]: IM: fromGroup({0})", im.fromGroup ? "True" : "False");
m_log.WarnFormat("[Groups.Messaging]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentID({0})", im.fromAgentID.ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentName({0})", im.fromAgentName.ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: imSessionID({0})", im.imSessionID.ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: message({0})", im.message.ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: offline({0})", im.offline.ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: toAgentID({0})", im.toAgentID.ToString());
m_log.WarnFormat("[Groups.Messaging]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket"));
}
}
#region Client Tools
/// <summary>
/// Try to find an active IClientAPI reference for agentID giving preference to root connections
/// </summary>
private IClientAPI GetActiveClient(UUID agentID)
{
if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Looking for local client {0}", agentID);
IClientAPI child = null;
// Try root avatar first
foreach (Scene scene in m_sceneList)
{
ScenePresence sp = scene.GetScenePresence(agentID);
if (sp != null)
{
if (!sp.IsChildAgent)
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Found root agent for client : {0}", sp.ControllingClient.Name);
return sp.ControllingClient;
}
else
{
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Found child agent for client : {0}", sp.ControllingClient.Name);
child = sp.ControllingClient;
}
}
}
// If we didn't find a root, then just return whichever child we found, or null if none
if (child == null)
{
if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Could not find local client for agent : {0}", agentID);
}
else
{
if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Returning child agent for client : {0}", child.Name);
}
return child;
}
#endregion
#region GroupSessionTracking
public void ResetAgentGroupChatSessions(string agentID)
{
foreach (List<string> agentList in m_groupsAgentsDroppedFromChatSession.Values)
agentList.Remove(agentID);
foreach (List<string> agentList in m_groupsAgentsInvitedToChatSession.Values)
agentList.Remove(agentID);
}
public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID)
{
// If we're tracking this group, and we can find them in the tracking, then they've been invited
return m_groupsAgentsInvitedToChatSession.ContainsKey(groupID)
&& m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID);
}
public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID)
{
// If we're tracking drops for this group,
// and we find them, well... then they've dropped
return m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID)
&& m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID);
}
public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID)
{
if (m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
{
// If not in dropped list, add
if (!m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
{
m_groupsAgentsDroppedFromChatSession[groupID].Add(agentID);
}
}
}
public void AgentInvitedToGroupChatSession(string agentID, UUID groupID)
{
// Add Session Status if it doesn't exist for this session
CreateGroupChatSessionTracking(groupID);
// If nessesary, remove from dropped list
if (m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
{
m_groupsAgentsDroppedFromChatSession[groupID].Remove(agentID);
}
// Add to invited
if (!m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID))
m_groupsAgentsInvitedToChatSession[groupID].Add(agentID);
}
private void CreateGroupChatSessionTracking(UUID groupID)
{
if (!m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
{
m_groupsAgentsDroppedFromChatSession.Add(groupID, new List<string>());
m_groupsAgentsInvitedToChatSession.Add(groupID, new List<string>());
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,295 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using OpenSim.Framework;
using OpenSim.Server.Base;
using OpenMetaverse;
using log4net;
namespace OpenSim.Groups
{
public class GroupsServiceHGConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private string m_ServerURI;
private object m_Lock = new object();
public GroupsServiceHGConnector(string url)
{
m_ServerURI = url;
if (!m_ServerURI.EndsWith("/"))
m_ServerURI += "/";
m_log.DebugFormat("[Groups.HGConnector]: Groups server at {0}", m_ServerURI);
}
public bool CreateProxy(string RequestingAgentID, string AgentID, string accessToken, UUID groupID, string url, string name, out string reason)
{
reason = string.Empty;
Dictionary<string, object> sendData = new Dictionary<string,object>();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["AgentID"] = AgentID.ToString();
sendData["AccessToken"] = accessToken;
sendData["GroupID"] = groupID.ToString();
sendData["Location"] = url;
sendData["Name"] = name;
Dictionary<string, object> ret = MakeRequest("POSTGROUP", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
{
reason = ret["REASON"].ToString();
return false;
}
return true;
}
public void RemoveAgentFromGroup(string AgentID, UUID GroupID, string token)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID;
sendData["GroupID"] = GroupID.ToString();
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
MakeRequest("REMOVEAGENTFROMGROUP", sendData);
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, string token)
{
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
return null;
Dictionary<string, object> sendData = new Dictionary<string, object>();
if (!GroupID.IsZero())
sendData["GroupID"] = GroupID.ToString();
if (!string.IsNullOrEmpty(GroupName))
sendData["Name"] = GroupsDataUtils.Sanitize(GroupName);
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
Dictionary<string, object> ret = MakeRequest("GETGROUP", sendData);
if (ret == null)
return null;
if (!ret.ContainsKey("RESULT"))
return null;
if (ret["RESULT"].ToString() == "NULL")
return null;
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
}
public List<ExtendedGroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID, string token)
{
List<ExtendedGroupMembersData> members = new List<ExtendedGroupMembersData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
Dictionary<string, object> ret = MakeRequest("GETGROUPMEMBERS", sendData);
if (ret == null)
return members;
if (!ret.ContainsKey("RESULT"))
return members;
if (ret["RESULT"].ToString() == "NULL")
return members;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
ExtendedGroupMembersData m = GroupsDataUtils.GroupMembersData((Dictionary<string, object>)v);
members.Add(m);
}
return members;
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID, string token)
{
List<GroupRolesData> roles = new List<GroupRolesData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
Dictionary<string, object> ret = MakeRequest("GETGROUPROLES", sendData);
if (ret == null)
return roles;
if (!ret.ContainsKey("RESULT"))
return roles;
if (ret["RESULT"].ToString() == "NULL")
return roles;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary<string, object>)v);
roles.Add(m);
}
return roles;
}
public List<ExtendedGroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, string token)
{
List<ExtendedGroupRoleMembersData> rmembers = new List<ExtendedGroupRoleMembersData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
Dictionary<string, object> ret = MakeRequest("GETROLEMEMBERS", sendData);
if (ret == null)
return rmembers;
if (!ret.ContainsKey("RESULT"))
return rmembers;
if (ret["RESULT"].ToString() == "NULL")
return rmembers;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
ExtendedGroupRoleMembersData m = GroupsDataUtils.GroupRoleMembersData((Dictionary<string, object>)v);
rmembers.Add(m);
}
return rmembers;
}
public bool AddNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = groupID.ToString();
sendData["NoticeID"] = noticeID.ToString();
sendData["FromName"] = GroupsDataUtils.Sanitize(fromName);
sendData["Subject"] = GroupsDataUtils.Sanitize(subject);
sendData["Message"] = GroupsDataUtils.Sanitize(message);
sendData["HasAttachment"] = hasAttachment.ToString();
if (hasAttachment)
{
sendData["AttachmentType"] = attType.ToString();
sendData["AttachmentName"] = attName.ToString();
sendData["AttachmentItemID"] = attItemID.ToString();
sendData["AttachmentOwnerID"] = attOwnerID;
}
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("ADDNOTICE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
return false;
return true;
}
public bool VerifyNotice(UUID noticeID, UUID groupID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["NoticeID"] = noticeID.ToString();
sendData["GroupID"] = groupID.ToString();
Dictionary<string, object> ret = MakeRequest("VERIFYNOTICE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
return false;
return true;
}
//
//
//
//
//
#region Make Request
private Dictionary<string, object> MakeRequest(string method, Dictionary<string, object> sendData)
{
sendData["METHOD"] = method;
string reply = string.Empty;
try
{
lock (m_Lock)
reply = SynchronousRestFormsRequester.MakeRequest("POST",
m_ServerURI + "hg-groups",
ServerUtils.BuildQueryString(sendData));
}
catch
{
return null;
}
//m_log.DebugFormat("[XXX]: reply was {0}", reply);
if (string.IsNullOrEmpty(reply))
return null;
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply);
return replyData;
}
#endregion
}
}

View File

@@ -1,694 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using OpenSim.Framework;
using OpenSim.Framework.Monitoring;
using OpenSim.Framework.Servers;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
using OpenMetaverse;
using Mono.Addins;
using log4net;
using Nini.Config;
namespace OpenSim.Groups
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceHGConnectorModule")]
public class GroupsServiceHGConnectorModule : ISharedRegionModule, IGroupsServicesConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private bool m_Enabled = false;
private IGroupsServicesConnector m_LocalGroupsConnector;
private string m_LocalGroupsServiceLocation;
private IUserManagement m_UserManagement;
private IOfflineIMService m_OfflineIM;
private IMessageTransferModule m_Messaging;
private List<Scene> m_Scenes;
private ForeignImporter m_ForeignImporter;
private string m_ServiceLocation;
private IConfigSource m_Config;
private Dictionary<string, GroupsServiceHGConnector> m_NetworkConnectors = new Dictionary<string, GroupsServiceHGConnector>();
private RemoteConnectorCacheWrapper m_CacheWrapper; // for caching info of external group services
#region ISharedRegionModule
public void Initialise(IConfigSource config)
{
IConfig groupsConfig = config.Configs["Groups"];
if (groupsConfig == null)
return;
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|| (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name))
{
return;
}
m_Config = config;
m_ServiceLocation = groupsConfig.GetString("LocalService", "local"); // local or remote
m_LocalGroupsServiceLocation = groupsConfig.GetString("GroupsExternalURI", "http://127.0.0.1");
m_Scenes = new List<Scene>();
m_Enabled = true;
m_log.DebugFormat("[Groups]: Initializing {0} with LocalService {1}", this.Name, m_ServiceLocation);
}
public string Name
{
get { return "Groups HG Service Connector"; }
}
public Type ReplaceableInterface
{
get { return null; }
}
public void AddRegion(Scene scene)
{
if (!m_Enabled)
return;
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
m_Scenes.Add(scene);
scene.EventManager.OnNewClient += OnNewClient;
}
public void RemoveRegion(Scene scene)
{
if (!m_Enabled)
return;
scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
m_Scenes.Remove(scene);
}
public void RegionLoaded(Scene scene)
{
if (!m_Enabled)
return;
if (m_UserManagement == null)
{
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
m_OfflineIM = scene.RequestModuleInterface<IOfflineIMService>();
m_Messaging = scene.RequestModuleInterface<IMessageTransferModule>();
m_ForeignImporter = new ForeignImporter(m_UserManagement);
if (m_ServiceLocation.Equals("local"))
{
m_LocalGroupsConnector = new GroupsServiceLocalConnectorModule(m_Config, m_UserManagement);
// Also, if local, create the endpoint for the HGGroupsService
new HGGroupsServiceRobustConnector(m_Config, MainServer.Instance, string.Empty,
scene.RequestModuleInterface<IOfflineIMService>(), scene.RequestModuleInterface<IUserAccountService>());
}
else
m_LocalGroupsConnector = new GroupsServiceRemoteConnectorModule(m_Config, m_UserManagement);
m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement);
}
}
public void PostInitialise()
{
}
public void Close()
{
}
#endregion
private void OnNewClient(IClientAPI client)
{
client.OnCompleteMovementToRegion += OnCompleteMovementToRegion;
}
void OnCompleteMovementToRegion(IClientAPI client, bool arg2)
{
if (client.SceneAgent is ScenePresence sp)
{
if (sp.PresenceType != PresenceType.Npc)
{
AgentCircuitData aCircuit = sp.Scene.AuthenticateHandler.GetAgentCircuitData(client.AgentId);
if (aCircuit != null && (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0 &&
m_OfflineIM != null && m_Messaging != null)
{
List<GridInstantMessage> ims = m_OfflineIM.GetMessages(aCircuit.AgentID);
if (ims != null && ims.Count > 0)
foreach (GridInstantMessage im in ims)
m_Messaging.SendInstantMessage(im, delegate(bool success) { });
}
}
}
}
#region IGroupsServicesConnector
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
{
reason = string.Empty;
if (m_UserManagement.IsLocalGridUser(RequestingAgentID))
return m_LocalGroupsConnector.CreateGroup(RequestingAgentID, name, charter, showInList, insigniaID,
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
else
{
reason = "Only local grid users are allowed to create a new group";
return UUID.Zero;
}
}
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason)
{
reason = string.Empty;
string url = string.Empty;
string name = string.Empty;
if (IsLocal(groupID, out url, out name))
return m_LocalGroupsConnector.UpdateGroup(AgentUUI(RequestingAgentID), groupID, charter, showInList, insigniaID, membershipFee,
openEnrollment, allowPublish, maturePublish, out reason);
else
{
reason = "Changes to remote group not allowed. Please go to the group's original world.";
return false;
}
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
{
string url = string.Empty;
string name = string.Empty;
if (IsLocal(GroupID, out url, out name))
return m_LocalGroupsConnector.GetGroupRecord(AgentUUI(RequestingAgentID), GroupID, GroupName);
else if (url != string.Empty)
{
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, GroupID);
string accessToken = string.Empty;
if (membership != null)
accessToken = membership.AccessToken;
else
return null;
GroupsServiceHGConnector c = GetConnector(url);
if (c != null)
{
ExtendedGroupRecord grec = m_CacheWrapper.GetGroupRecord(RequestingAgentID, GroupID, GroupName, delegate
{
return c.GetGroupRecord(AgentUUIForOutside(RequestingAgentID), GroupID, GroupName, accessToken);
});
if (grec != null)
ImportForeigner(grec.FounderUUI);
return grec;
}
}
return null;
}
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
{
return m_LocalGroupsConnector.FindGroups(RequestingAgentIDstr, search);
}
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
{
string agentID = AgentUUI(RequestingAgentID);
return m_LocalGroupsConnector.GetGroupMembers(agentID, GroupID);
}
else if (!string.IsNullOrEmpty(url))
{
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, GroupID);
string accessToken = string.Empty;
if (membership != null)
accessToken = membership.AccessToken;
else
return null;
GroupsServiceHGConnector c = GetConnector(url);
if (c != null)
{
return m_CacheWrapper.GetGroupMembers(RequestingAgentID, GroupID, delegate
{
return c.GetGroupMembers(AgentUUIForOutside(RequestingAgentID), GroupID, accessToken);
});
}
}
return new List<GroupMembersData>();
}
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
{
reason = string.Empty;
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
return m_LocalGroupsConnector.AddGroupRole(AgentUUI(RequestingAgentID), groupID, roleID, name, description, title, powers, out reason);
else
{
reason = "Operation not allowed outside this group's origin world.";
return false;
}
}
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
return m_LocalGroupsConnector.UpdateGroupRole(AgentUUI(RequestingAgentID), groupID, roleID, name, description, title, powers);
else
{
return false;
}
}
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
m_LocalGroupsConnector.RemoveGroupRole(AgentUUI(RequestingAgentID), groupID, roleID);
else
{
return;
}
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID groupID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
return m_LocalGroupsConnector.GetGroupRoles(AgentUUI(RequestingAgentID), groupID);
else if (!string.IsNullOrEmpty(url))
{
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, groupID);
string accessToken = string.Empty;
if (membership != null)
accessToken = membership.AccessToken;
else
return null;
GroupsServiceHGConnector c = GetConnector(url);
if (c != null)
{
return m_CacheWrapper.GetGroupRoles(RequestingAgentID, groupID, delegate
{
return c.GetGroupRoles(AgentUUIForOutside(RequestingAgentID), groupID, accessToken);
});
}
}
return new List<GroupRolesData>();
}
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID groupID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
return m_LocalGroupsConnector.GetGroupRoleMembers(AgentUUI(RequestingAgentID), groupID);
else if (!string.IsNullOrEmpty(url))
{
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, groupID);
string accessToken = string.Empty;
if (membership != null)
accessToken = membership.AccessToken;
else
return null;
GroupsServiceHGConnector c = GetConnector(url);
if (c != null)
{
return m_CacheWrapper.GetGroupRoleMembers(RequestingAgentID, groupID, delegate
{
return c.GetGroupRoleMembers(AgentUUIForOutside(RequestingAgentID), groupID, accessToken);
});
}
}
return new List<GroupRoleMembersData>();
}
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
{
string url = string.Empty;
string name = string.Empty;
reason = string.Empty;
UUID uid = new UUID(AgentID);
if (IsLocal(GroupID, out url, out name))
{
if (m_UserManagement.IsLocalGridUser(uid)) // local user
{
// normal case: local group, local user
return m_LocalGroupsConnector.AddAgentToGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID, token, out reason);
}
else // local group, foreign user
{
// the user is accepting the invitation, or joining, where the group resides
token = UUID.Random().ToString();
bool success = m_LocalGroupsConnector.AddAgentToGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID, token, out reason);
if (success)
{
// Here we always return true. The user has been added to the local group,
// independent of whether the remote operation succeeds or not
url = m_UserManagement.GetUserServerURL(uid, "GroupsServerURI");
if (url.Length == 0)
{
reason = "You don't have an accessible groups server in your home world. You membership to this group in only within this grid.";
return true;
}
GroupsServiceHGConnector c = GetConnector(url);
if (c != null)
c.CreateProxy(AgentUUI(RequestingAgentID), AgentID, token, GroupID, m_LocalGroupsServiceLocation, name, out reason);
return true;
}
return false;
}
}
else if (m_UserManagement.IsLocalGridUser(uid)) // local user
{
// foreign group, local user. She's been added already by the HG service.
// Let's just check
if (m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID) != null)
return true;
}
reason = "Operation not allowed outside this group's origin world";
return false;
}
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
string url = string.Empty, name = string.Empty;
if (!IsLocal(GroupID, out url, out name) && url != string.Empty)
{
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
if (membership != null)
{
GroupsServiceHGConnector c = GetConnector(url);
if (c != null)
c.RemoveAgentFromGroup(AgentUUIForOutside(AgentID), GroupID, membership.AccessToken);
}
}
// remove from local service
m_LocalGroupsConnector.RemoveAgentFromGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
}
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
return m_LocalGroupsConnector.AddAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID, groupID, roleID, AgentUUI(agentID));
else
return false;
}
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
return m_LocalGroupsConnector.GetAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID);
}
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
m_LocalGroupsConnector.RemoveAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID);
}
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
m_LocalGroupsConnector.AddAgentToGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID);
}
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
m_LocalGroupsConnector.RemoveAgentFromGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID);
}
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
return m_LocalGroupsConnector.GetAgentGroupRoles(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
else
return new List<GroupRolesData>();
}
public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
m_LocalGroupsConnector.SetAgentActiveGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
}
public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID)
{
return m_LocalGroupsConnector.GetAgentActiveMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID));
}
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
m_LocalGroupsConnector.SetAgentActiveGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID);
}
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
{
m_LocalGroupsConnector.UpdateMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, AcceptNotices, ListInProfile);
}
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(GroupID, out url, out gname))
return m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
else
return null;
}
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
{
return m_LocalGroupsConnector.GetAgentGroupMemberships(AgentUUI(RequestingAgentID), AgentUUI(AgentID));
}
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
{
string url = string.Empty, gname = string.Empty;
if (IsLocal(groupID, out url, out gname))
{
if (m_LocalGroupsConnector.AddGroupNotice(AgentUUI(RequestingAgentID), groupID, noticeID, fromName, subject, message,
hasAttachment, attType, attName, attItemID, AgentUUI(attOwnerID)))
{
// then send the notice to every grid for which there are members in this group
List<GroupMembersData> members = m_LocalGroupsConnector.GetGroupMembers(AgentUUI(RequestingAgentID), groupID);
List<string> urls = new List<string>();
foreach (GroupMembersData m in members)
{
if (!m_UserManagement.IsLocalGridUser(m.AgentID))
{
string gURL = m_UserManagement.GetUserServerURL(m.AgentID, "GroupsServerURI");
if (!urls.Contains(gURL))
urls.Add(gURL);
}
}
// so we have the list of urls to send the notice to
// this may take a long time...
WorkManager.RunInThread(delegate
{
foreach (string u in urls)
{
GroupsServiceHGConnector c = GetConnector(u);
if (c != null)
{
c.AddNotice(AgentUUIForOutside(RequestingAgentID), groupID, noticeID, fromName, subject, message,
hasAttachment, attType, attName, attItemID, AgentUUIForOutside(attOwnerID));
}
}
}, null, string.Format("AddGroupNotice (agent {0}, group {1})", RequestingAgentID, groupID));
return true;
}
return false;
}
else
return false;
}
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
{
GroupNoticeInfo notice = m_LocalGroupsConnector.GetGroupNotice(AgentUUI(RequestingAgentID), noticeID);
if (notice != null && notice.noticeData.HasAttachment && notice.noticeData.AttachmentOwnerID != null)
ImportForeigner(notice.noticeData.AttachmentOwnerID);
return notice;
}
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
{
return m_LocalGroupsConnector.GetGroupNotices(AgentUUI(RequestingAgentID), GroupID);
}
#endregion
#region hypergrid groups
private string AgentUUI(string AgentIDStr)
{
UUID AgentID = UUID.Zero;
if (!UUID.TryParse(AgentIDStr, out AgentID) || AgentID.IsZero())
return UUID.Zero.ToString();
if (m_UserManagement.IsLocalGridUser(AgentID))
return AgentID.ToString();
AgentCircuitData agent = null;
foreach (Scene scene in m_Scenes)
{
agent = scene.AuthenticateHandler.GetAgentCircuitData(AgentID);
if (agent != null)
break;
}
if (agent != null)
return Util.ProduceUserUniversalIdentifier(agent);
// we don't know anything about this foreign user
// try asking the user management module, which may know more
return m_UserManagement.GetUserUUI(AgentID);
}
private string AgentUUIForOutside(string AgentIDStr)
{
UUID AgentID = UUID.Zero;
if (!UUID.TryParse(AgentIDStr, out AgentID) || AgentID.IsZero())
return UUID.ZeroString;
AgentCircuitData agent = null;
foreach (Scene scene in m_Scenes)
{
agent = scene.AuthenticateHandler.GetAgentCircuitData(AgentID);
if (agent != null)
break;
}
if (agent == null) // oops
return AgentID.ToString();
return Util.ProduceUserUniversalIdentifier(agent);
}
private UUID ImportForeigner(string uID)
{
UUID userID = UUID.Zero;
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
if (Util.ParseUniversalUserIdentifier(uID, out userID, out url, out first, out last, out tmp))
m_UserManagement.AddUser(userID, first, last, url);
return userID;
}
private bool IsLocal(UUID groupID, out string serviceLocation, out string name)
{
serviceLocation = string.Empty;
name = string.Empty;
if (groupID.Equals(UUID.Zero))
return true;
ExtendedGroupRecord group = m_LocalGroupsConnector.GetGroupRecord(UUID.Zero.ToString(), groupID, string.Empty);
if (group == null)
{
//m_log.DebugFormat("[XXX]: IsLocal? group {0} not found -- no.", groupID);
return false;
}
serviceLocation = group.ServiceLocation;
name = group.GroupName;
bool isLocal = (group.ServiceLocation.Length == 0);
//m_log.DebugFormat("[XXX]: IsLocal? {0}", isLocal);
return isLocal;
}
private GroupsServiceHGConnector GetConnector(string url)
{
lock (m_NetworkConnectors)
{
if (m_NetworkConnectors.ContainsKey(url))
return m_NetworkConnectors[url];
GroupsServiceHGConnector c = new GroupsServiceHGConnector(url);
m_NetworkConnectors[url] = c;
}
return m_NetworkConnectors[url];
}
#endregion
}
}

View File

@@ -1,445 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Handlers.Base;
using log4net;
using OpenMetaverse;
namespace OpenSim.Groups
{
public class HGGroupsServiceRobustConnector : ServiceConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private HGGroupsService m_GroupsService;
private string m_ConfigName = "Groups";
// Called by Robust shell
public HGGroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) :
this(config, server, configName, null, null)
{
}
// Called by the sim-bound module
public HGGroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName, IOfflineIMService im, IUserAccountService users) :
base(config, server, configName)
{
if (configName != String.Empty)
m_ConfigName = configName;
m_log.DebugFormat("[Groups.RobustHGConnector]: Starting with config name {0}", m_ConfigName);
string homeURI = Util.GetConfigVarFromSections<string>(config, "HomeURI",
new string[] { "Startup", "Hypergrid", m_ConfigName}, string.Empty);
if (homeURI.Length == 0)
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide the HomeURI [Startup] or in section {0}", m_ConfigName));
IConfig cnf = config.Configs[m_ConfigName];
if (cnf == null)
throw new Exception(String.Format("[Groups.RobustHGConnector]: {0} section does not exist", m_ConfigName));
if (im == null)
{
string imDll = cnf.GetString("OfflineIMService", string.Empty);
if (imDll.Length == 0)
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide OfflineIMService in section {0}", m_ConfigName));
Object[] args = new Object[] { config };
im = ServerUtils.LoadPlugin<IOfflineIMService>(imDll, args);
}
if (users == null)
{
string usersDll = cnf.GetString("UserAccountService", string.Empty);
if (usersDll.Length == 0)
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide UserAccountService in section {0}", m_ConfigName));
Object[] args = new Object[] { config };
users = ServerUtils.LoadPlugin<IUserAccountService>(usersDll, args);
}
m_GroupsService = new HGGroupsService(config, im, users, homeURI);
server.AddStreamHandler(new HGGroupsServicePostHandler(m_GroupsService));
}
}
public class HGGroupsServicePostHandler : BaseStreamHandler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private HGGroupsService m_GroupsService;
public HGGroupsServicePostHandler(HGGroupsService service) :
base("POST", "/hg-groups")
{
m_GroupsService = service;
}
protected override byte[] ProcessRequest(string path, Stream requestData,
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
string body;
using(StreamReader sr = new StreamReader(requestData))
body = sr.ReadToEnd();
body = body.Trim();
//m_log.DebugFormat("[XXX]: query String: {0}", body);
try
{
Dictionary<string, object> request =
ServerUtils.ParseQueryString(body);
if (!request.ContainsKey("METHOD"))
return FailureResult();
string method = request["METHOD"].ToString();
request.Remove("METHOD");
m_log.DebugFormat("[Groups.RobustHGConnector]: {0}", method);
switch (method)
{
case "POSTGROUP":
return HandleAddGroupProxy(request);
case "REMOVEAGENTFROMGROUP":
return HandleRemoveAgentFromGroup(request);
case "GETGROUP":
return HandleGetGroup(request);
case "ADDNOTICE":
return HandleAddNotice(request);
case "VERIFYNOTICE":
return HandleVerifyNotice(request);
case "GETGROUPMEMBERS":
return HandleGetGroupMembers(request);
case "GETGROUPROLES":
return HandleGetGroupRoles(request);
case "GETROLEMEMBERS":
return HandleGetRoleMembers(request);
}
m_log.DebugFormat("[Groups.RobustHGConnector]: unknown method request: {0}", method);
}
catch (Exception e)
{
m_log.Error(string.Format("[Groups.RobustHGConnector]: Exception {0} ", e.Message), e);
}
return FailureResult();
}
byte[] HandleAddGroupProxy(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID")
|| !request.ContainsKey("AgentID")
|| !request.ContainsKey("AccessToken") || !request.ContainsKey("Location"))
NullResult(result, "Bad network data");
else
{
string RequestingAgentID = request["RequestingAgentID"].ToString();
string agentID = request["AgentID"].ToString();
UUID groupID = new UUID(request["GroupID"].ToString());
string accessToken = request["AccessToken"].ToString();
string location = request["Location"].ToString();
string name = string.Empty;
if (request.ContainsKey("Name"))
name = request["Name"].ToString();
string reason = string.Empty;
bool success = m_GroupsService.CreateGroupProxy(RequestingAgentID, agentID, accessToken, groupID, location, name, out reason);
result["REASON"] = reason;
result["RESULT"] = success.ToString();
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleRemoveAgentFromGroup(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("AccessToken") || !request.ContainsKey("AgentID") ||
!request.ContainsKey("GroupID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string agentID = request["AgentID"].ToString();
string token = request["AccessToken"].ToString();
if (!m_GroupsService.RemoveAgentFromGroup(agentID, agentID, groupID, token))
NullResult(result, "Internal error");
else
result["RESULT"] = "true";
}
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
byte[] HandleGetGroup(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AccessToken"))
NullResult(result, "Bad network data");
else
{
string RequestingAgentID = request["RequestingAgentID"].ToString();
string token = request["AccessToken"].ToString();
UUID groupID = UUID.Zero;
string groupName = string.Empty;
if (request.ContainsKey("GroupID"))
groupID = new UUID(request["GroupID"].ToString());
if (request.ContainsKey("Name"))
groupName = request["Name"].ToString();
ExtendedGroupRecord grec = m_GroupsService.GetGroupRecord(RequestingAgentID, groupID, groupName, token);
if (grec == null)
NullResult(result, "Group not found");
else
result["RESULT"] = GroupsDataUtils.GroupRecord(grec);
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetGroupMembers(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
string token = request["AccessToken"].ToString();
List<ExtendedGroupMembersData> members = m_GroupsService.GetGroupMembers(requestingAgentID, groupID, token);
if (members == null || (members != null && members.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (ExtendedGroupMembersData m in members)
{
dict["m-" + i++] = GroupsDataUtils.GroupMembersData(m);
}
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetGroupRoles(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
string token = request["AccessToken"].ToString();
List<GroupRolesData> roles = m_GroupsService.GetGroupRoles(requestingAgentID, groupID, token);
if (roles == null || (roles != null && roles.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (GroupRolesData r in roles)
dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r);
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetRoleMembers(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
string token = request["AccessToken"].ToString();
List<ExtendedGroupRoleMembersData> rmembers = m_GroupsService.GetGroupRoleMembers(requestingAgentID, groupID, token);
if (rmembers == null || (rmembers != null && rmembers.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (ExtendedGroupRoleMembersData rm in rmembers)
dict["rm-" + i++] = GroupsDataUtils.GroupRoleMembersData(rm);
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleAddNotice(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("NoticeID") ||
!request.ContainsKey("FromName") || !request.ContainsKey("Subject") || !request.ContainsKey("Message") ||
!request.ContainsKey("HasAttachment"))
NullResult(result, "Bad network data");
else
{
bool hasAtt = bool.Parse(request["HasAttachment"].ToString());
byte attType = 0;
string attName = string.Empty;
string attOwner = string.Empty;
UUID attItem = UUID.Zero;
if (request.ContainsKey("AttachmentType"))
attType = byte.Parse(request["AttachmentType"].ToString());
if (request.ContainsKey("AttachmentName"))
attName = request["AttachmentType"].ToString();
if (request.ContainsKey("AttachmentItemID"))
attItem = new UUID(request["AttachmentItemID"].ToString());
if (request.ContainsKey("AttachmentOwnerID"))
attOwner = request["AttachmentOwnerID"].ToString();
bool success = m_GroupsService.AddNotice(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
new UUID(request["NoticeID"].ToString()), request["FromName"].ToString(), request["Subject"].ToString(),
request["Message"].ToString(), hasAtt, attType, attName, attItem, attOwner);
result["RESULT"] = success.ToString();
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleVerifyNotice(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("NoticeID") || !request.ContainsKey("GroupID"))
NullResult(result, "Bad network data");
else
{
UUID noticeID = new UUID(request["NoticeID"].ToString());
UUID groupID = new UUID(request["GroupID"].ToString());
bool success = m_GroupsService.VerifyNotice(noticeID, groupID);
//m_log.DebugFormat("[XXX]: VerifyNotice returned {0}", success);
result["RESULT"] = success.ToString();
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
//
//
//
//
//
#region Helpers
private void NullResult(Dictionary<string, object> result, string reason)
{
result["RESULT"] = "NULL";
result["REASON"] = reason;
}
private byte[] FailureResult()
{
Dictionary<string, object> result = new Dictionary<string, object>();
NullResult(result, "Unknown method");
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
#endregion
}
}

View File

@@ -1,112 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using OpenMetaverse;
using OpenSim.Framework;
namespace OpenSim.Groups
{
public interface IGroupsServicesConnector
{
UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee,
bool openEnrollment, bool allowPublish, bool maturePublish, UUID founderID, out string reason);
bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason);
ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName);
List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search);
List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID);
bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason);
bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers);
void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID);
List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID);
List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID);
bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason);
void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID);
bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID);
GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID);
void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID);
void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID);
void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID);
List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID);
void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID);
ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID);
void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID);
void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile);
/// <summary>
/// Get information about a specific group to which the user belongs.
/// </summary>
/// <param name="RequestingAgentID">The agent requesting the information.</param>
/// <param name="AgentID">The agent requested.</param>
/// <param name="GroupID">The group requested.</param>
/// <returns>
/// If the user is a member of the group then the data structure is returned. If not, then null is returned.
/// </returns>
ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID);
/// <summary>
/// Get information about the groups to which a user belongs.
/// </summary>
/// <param name="RequestingAgentID">The agent requesting the information.</param>
/// <param name="AgentID">The agent requested.</param>
/// <returns>
/// Information about the groups to which the user belongs. If the user belongs to no groups then an empty
/// list is returned.
/// </returns>
List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID);
bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID);
GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID);
List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID);
}
public class GroupInviteInfo
{
public UUID GroupID = UUID.Zero;
public UUID RoleID = UUID.Zero;
public string AgentID = string.Empty;
public UUID InviteID = UUID.Zero;
}
public class GroupNoticeInfo
{
public ExtendedGroupNoticeData noticeData = new ExtendedGroupNoticeData();
public UUID GroupID = UUID.Zero;
public string Message = string.Empty;
}
}

View File

@@ -1,326 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using OpenSim.Framework;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
using OpenMetaverse;
using Mono.Addins;
using log4net;
using Nini.Config;
namespace OpenSim.Groups
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceLocalConnectorModule")]
public class GroupsServiceLocalConnectorModule : ISharedRegionModule, IGroupsServicesConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private bool m_Enabled = false;
private GroupsService m_GroupsService;
private IUserManagement m_UserManagement;
private List<Scene> m_Scenes;
private ForeignImporter m_ForeignImporter;
#region constructors
public GroupsServiceLocalConnectorModule()
{
}
public GroupsServiceLocalConnectorModule(IConfigSource config, IUserManagement uman)
{
Init(config);
m_UserManagement = uman;
m_ForeignImporter = new ForeignImporter(uman);
}
#endregion
private void Init(IConfigSource config)
{
m_GroupsService = new GroupsService(config);
m_Scenes = new List<Scene>();
}
#region ISharedRegionModule
public void Initialise(IConfigSource config)
{
IConfig groupsConfig = config.Configs["Groups"];
if (groupsConfig == null)
return;
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|| (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name))
{
return;
}
Init(config);
m_Enabled = true;
m_log.DebugFormat("[Groups]: Initializing {0}", this.Name);
}
public string Name
{
get { return "Groups Local Service Connector"; }
}
public Type ReplaceableInterface
{
get { return null; }
}
public void AddRegion(Scene scene)
{
if (!m_Enabled)
return;
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
m_Scenes.Add(scene);
}
public void RemoveRegion(Scene scene)
{
if (!m_Enabled)
return;
scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
m_Scenes.Remove(scene);
}
public void RegionLoaded(Scene scene)
{
if (!m_Enabled)
return;
if (m_UserManagement == null)
{
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
m_ForeignImporter = new ForeignImporter(m_UserManagement);
}
}
public void PostInitialise()
{
}
public void Close()
{
}
#endregion
#region IGroupsServicesConnector
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
{
m_log.DebugFormat("[Groups]: Creating group {0}", name);
reason = string.Empty;
return m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID,
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
}
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason)
{
reason = string.Empty;
m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
return true;
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
{
if (!GroupID.IsZero())
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID);
else if (GroupName != null)
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupName);
return null;
}
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
{
return m_GroupsService.FindGroups(RequestingAgentIDstr, search);
}
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
{
List<ExtendedGroupMembersData> _members = m_GroupsService.GetGroupMembers(RequestingAgentID, GroupID);
if (_members != null && _members.Count > 0)
{
List<GroupMembersData> members = _members.ConvertAll<GroupMembersData>(new Converter<ExtendedGroupMembersData, GroupMembersData>(m_ForeignImporter.ConvertGroupMembersData));
return members;
}
return new List<GroupMembersData>();
}
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
{
return m_GroupsService.AddGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, out reason);
}
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
{
return m_GroupsService.UpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers);
}
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
{
m_GroupsService.RemoveGroupRole(RequestingAgentID, groupID, roleID);
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID)
{
return m_GroupsService.GetGroupRoles(RequestingAgentID, GroupID);
}
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID)
{
List<ExtendedGroupRoleMembersData> _rm = m_GroupsService.GetGroupRoleMembers(RequestingAgentID, GroupID);
if (_rm != null && _rm.Count > 0)
{
List<GroupRoleMembersData> rm = _rm.ConvertAll<GroupRoleMembersData>(new Converter<ExtendedGroupRoleMembersData, GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData));
return rm;
}
return new List<GroupRoleMembersData>();
}
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
{
return m_GroupsService.AddAgentToGroup(RequestingAgentID, AgentID, GroupID, RoleID, token, out reason);
}
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
m_GroupsService.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
}
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
{
return m_GroupsService.AddAgentToGroupInvite(RequestingAgentID, inviteID, groupID, roleID, agentID);
}
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID);
}
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID);
}
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
m_GroupsService.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
}
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
m_GroupsService.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
}
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
{
return m_GroupsService.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID);
}
public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
m_GroupsService.SetAgentActiveGroup(RequestingAgentID, AgentID, GroupID);
}
public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID)
{
return m_GroupsService.GetAgentActiveMembership(RequestingAgentID, AgentID);
}
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
m_GroupsService.SetAgentActiveGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
}
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
{
m_GroupsService.UpdateMembership(RequestingAgentID, AgentID, GroupID, AcceptNotices, ListInProfile);
}
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
{
return m_GroupsService.GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID);
}
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
{
return m_GroupsService.GetAgentGroupMemberships(RequestingAgentID, AgentID);
}
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
{
return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message,
hasAttachment, attType, attName, attItemID, attOwnerID);
}
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
{
GroupNoticeInfo notice = m_GroupsService.GetGroupNotice(RequestingAgentID, noticeID);
//if (notice != null && notice.noticeData.HasAttachment && notice.noticeData.AttachmentOwnerID != null)
//{
// UUID userID = UUID.Zero;
// string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
// Util.ParseUniversalUserIdentifier(notice.noticeData.AttachmentOwnerID, out userID, out url, out first, out last, out tmp);
// if (url != string.Empty)
// m_UserManagement.AddUser(userID, first, last, url);
//}
return notice;
}
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
{
return m_GroupsService.GetGroupNotices(RequestingAgentID, GroupID);
}
#endregion
}
}

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Mono.Addins;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenSim.Addons.Groups")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("http://opensimulator.org")]
[assembly: AssemblyProduct("OpenSim.Addons.Groups")]
[assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("313d4865-d179-4735-9b5a-fe74885878b2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
[assembly: Addin("OpenSim.Groups", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]

View File

@@ -1,696 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using OpenSim.Framework;
using OpenSim.Framework.ServiceAuth;
using OpenSim.Server.Base;
using OpenMetaverse;
using log4net;
using Nini.Config;
namespace OpenSim.Groups
{
public class GroupsServiceRemoteConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private string m_ServerURI;
private IServiceAuth m_Auth;
private object m_Lock = new object();
public GroupsServiceRemoteConnector(IConfigSource config)
{
IConfig groupsConfig = config.Configs["Groups"];
string url = groupsConfig.GetString("GroupsServerURI", string.Empty);
if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
throw new Exception(string.Format("[Groups.RemoteConnector]: Malformed groups server URL {0}. Fix it or disable the Groups feature.", url));
m_ServerURI = url;
if (!m_ServerURI.EndsWith("/"))
m_ServerURI += "/";
/// This is from BaseServiceConnector
string authType = Util.GetConfigVarFromSections<string>(config, "AuthType", new string[] { "Network", "Groups" }, "None");
switch (authType)
{
case "BasicHttpAuthentication":
m_Auth = new BasicHttpAuthentication(config, "Groups");
break;
}
///
m_log.DebugFormat("[Groups.RemoteConnector]: Groups server at {0}, authentication {1}",
m_ServerURI, (m_Auth == null ? "None" : m_Auth.GetType().ToString()));
}
public ExtendedGroupRecord CreateGroup(string RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
{
reason = string.Empty;
ExtendedGroupRecord rec = new ExtendedGroupRecord();
rec.AllowPublish = allowPublish;
rec.Charter = charter;
rec.FounderID = founderID;
rec.GroupName = name;
rec.GroupPicture = insigniaID;
rec.MaturePublish = maturePublish;
rec.MembershipFee = membershipFee;
rec.OpenEnrollment = openEnrollment;
rec.ShowInList = showInList;
Dictionary<string, object> sendData = GroupsDataUtils.GroupRecord(rec);
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "ADD";
Dictionary<string, object> ret = MakeRequest("PUTGROUP", sendData);
if (ret == null)
return null;
if (ret["RESULT"].ToString() == "NULL")
{
reason = ret["REASON"].ToString();
return null;
}
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
}
public ExtendedGroupRecord UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish)
{
ExtendedGroupRecord rec = new ExtendedGroupRecord();
rec.AllowPublish = allowPublish;
rec.Charter = charter;
rec.GroupPicture = insigniaID;
rec.MaturePublish = maturePublish;
rec.GroupID = groupID;
rec.MembershipFee = membershipFee;
rec.OpenEnrollment = openEnrollment;
rec.ShowInList = showInList;
Dictionary<string, object> sendData = GroupsDataUtils.GroupRecord(rec);
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "UPDATE";
Dictionary<string, object> ret = MakeRequest("PUTGROUP", sendData);
if (ret == null || (ret != null && (!ret.ContainsKey("RESULT") || ret["RESULT"].ToString() == "NULL")))
return null;
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
{
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
return null;
Dictionary<string, object> sendData = new Dictionary<string, object>();
if (!GroupID.IsZero())
sendData["GroupID"] = GroupID.ToString();
if (!string.IsNullOrEmpty(GroupName))
sendData["Name"] = GroupsDataUtils.Sanitize(GroupName);
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETGROUP", sendData);
if (ret == null || (ret != null && (!ret.ContainsKey("RESULT") || ret["RESULT"].ToString() == "NULL")))
return null;
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
}
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string query)
{
List<DirGroupsReplyData> hits = new List<DirGroupsReplyData>();
if (string.IsNullOrEmpty(query))
return hits;
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["Query"] = query;
sendData["RequestingAgentID"] = RequestingAgentIDstr;
Dictionary<string, object> ret = MakeRequest("FINDGROUPS", sendData);
if (ret == null)
return hits;
if (!ret.ContainsKey("RESULT"))
return hits;
if (ret["RESULT"].ToString() == "NULL")
return hits;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
DirGroupsReplyData m = GroupsDataUtils.DirGroupsReplyData((Dictionary<string, object>)v);
hits.Add(m);
}
return hits;
}
public GroupMembershipData AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
{
reason = string.Empty;
Dictionary<string, object> sendData = new Dictionary<string,object>();
sendData["AgentID"] = AgentID;
sendData["GroupID"] = GroupID.ToString();
sendData["RoleID"] = RoleID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["AccessToken"] = token;
Dictionary<string, object> ret = MakeRequest("ADDAGENTTOGROUP", sendData);
if (ret == null)
return null;
if (!ret.ContainsKey("RESULT"))
return null;
if (ret["RESULT"].ToString() == "NULL")
{
reason = ret["REASON"].ToString();
return null;
}
return GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)ret["RESULT"]);
}
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID;
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
MakeRequest("REMOVEAGENTFROMGROUP", sendData);
}
public ExtendedGroupMembershipData GetMembership(string RequestingAgentID, string AgentID, UUID GroupID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID;
if (!GroupID.IsZero())
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETMEMBERSHIP", sendData);
if (ret == null)
return null;
if (!ret.ContainsKey("RESULT"))
return null;
if (ret["RESULT"].ToString() == "NULL")
return null;
return GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)ret["RESULT"]);
}
public List<GroupMembershipData> GetMemberships(string RequestingAgentID, string AgentID)
{
List<GroupMembershipData> memberships = new List<GroupMembershipData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID;
sendData["ALL"] = "true";
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETMEMBERSHIP", sendData);
if (ret == null)
return memberships;
if (!ret.ContainsKey("RESULT"))
return memberships;
if (ret["RESULT"].ToString() == "NULL")
return memberships;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
GroupMembershipData m = GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)v);
memberships.Add(m);
}
return memberships;
}
public List<ExtendedGroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
{
List<ExtendedGroupMembersData> members = new List<ExtendedGroupMembersData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETGROUPMEMBERS", sendData);
if (ret == null)
return members;
if (!ret.ContainsKey("RESULT"))
return members;
if (ret["RESULT"].ToString() == "NULL")
return members;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
ExtendedGroupMembersData m = GroupsDataUtils.GroupMembersData((Dictionary<string, object>)v);
members.Add(m);
}
return members;
}
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
{
reason = string.Empty;
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = groupID.ToString();
sendData["RoleID"] = roleID.ToString();
sendData["Name"] = GroupsDataUtils.Sanitize(name);
sendData["Description"] = GroupsDataUtils.Sanitize(description);
sendData["Title"] = GroupsDataUtils.Sanitize(title);
sendData["Powers"] = powers.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "ADD";
Dictionary<string, object> ret = MakeRequest("PUTROLE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
{
reason = ret["REASON"].ToString();
return false;
}
return true;
}
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = groupID.ToString();
sendData["RoleID"] = roleID.ToString();
sendData["Name"] = GroupsDataUtils.Sanitize(name);
sendData["Description"] = GroupsDataUtils.Sanitize(description);
sendData["Title"] = GroupsDataUtils.Sanitize(title);
sendData["Powers"] = powers.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "UPDATE";
Dictionary<string, object> ret = MakeRequest("PUTROLE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
return false;
return true;
}
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = groupID.ToString();
sendData["RoleID"] = roleID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
MakeRequest("REMOVEROLE", sendData);
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID)
{
List<GroupRolesData> roles = new List<GroupRolesData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETGROUPROLES", sendData);
if (ret == null)
return roles;
if (!ret.ContainsKey("RESULT"))
return roles;
if (ret["RESULT"].ToString() == "NULL")
return roles;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary<string, object>)v);
roles.Add(m);
}
return roles;
}
public List<ExtendedGroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID)
{
List<ExtendedGroupRoleMembersData> rmembers = new List<ExtendedGroupRoleMembersData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETROLEMEMBERS", sendData);
if (ret == null)
return rmembers;
if (!ret.ContainsKey("RESULT"))
return rmembers;
if (ret["RESULT"].ToString() == "NULL")
return rmembers;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
ExtendedGroupRoleMembersData m = GroupsDataUtils.GroupRoleMembersData((Dictionary<string, object>)v);
rmembers.Add(m);
}
return rmembers;
}
public bool AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID.ToString();
sendData["GroupID"] = GroupID.ToString();
sendData["RoleID"] = RoleID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "ADD";
Dictionary<string, object> ret = MakeRequest("AGENTROLE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
return false;
return true;
}
public bool RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID.ToString();
sendData["GroupID"] = GroupID.ToString();
sendData["RoleID"] = RoleID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "DELETE";
Dictionary<string, object> ret = MakeRequest("AGENTROLE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
return false;
return true;
}
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
{
List<GroupRolesData> roles = new List<GroupRolesData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID.ToString();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETAGENTROLES", sendData);
if (ret == null)
return roles;
if (!ret.ContainsKey("RESULT"))
return roles;
if (ret["RESULT"].ToString() == "NULL")
return roles;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary<string, object>)v);
roles.Add(m);
}
return roles;
}
public GroupMembershipData SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID.ToString();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "GROUP";
Dictionary<string, object> ret = MakeRequest("SETACTIVE", sendData);
if (ret == null)
return null;
if (!ret.ContainsKey("RESULT"))
return null;
if (ret["RESULT"].ToString() == "NULL")
return null;
return GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)ret["RESULT"]);
}
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID.ToString();
sendData["GroupID"] = GroupID.ToString();
sendData["RoleID"] = RoleID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "ROLE";
MakeRequest("SETACTIVE", sendData);
}
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["AgentID"] = AgentID.ToString();
sendData["GroupID"] = GroupID.ToString();
sendData["AcceptNotices"] = AcceptNotices.ToString();
sendData["ListInProfile"] = ListInProfile.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
MakeRequest("UPDATEMEMBERSHIP", sendData);
}
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["InviteID"] = inviteID.ToString();
sendData["GroupID"] = groupID.ToString();
sendData["RoleID"] = roleID.ToString();
sendData["AgentID"] = agentID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "ADD";
Dictionary<string, object> ret = MakeRequest("INVITE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true") // it may return "NULL"
return false;
return true;
}
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["InviteID"] = inviteID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "GET";
Dictionary<string, object> ret = MakeRequest("INVITE", sendData);
if (ret == null)
return null;
if (!ret.ContainsKey("RESULT"))
return null;
if (ret["RESULT"].ToString() == "NULL")
return null;
return GroupsDataUtils.GroupInviteInfo((Dictionary<string, object>)ret["RESULT"]);
}
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["InviteID"] = inviteID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
sendData["OP"] = "DELETE";
MakeRequest("INVITE", sendData);
}
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = groupID.ToString();
sendData["NoticeID"] = noticeID.ToString();
sendData["FromName"] = GroupsDataUtils.Sanitize(fromName);
sendData["Subject"] = GroupsDataUtils.Sanitize(subject);
sendData["Message"] = GroupsDataUtils.Sanitize(message);
sendData["HasAttachment"] = hasAttachment.ToString();
if (hasAttachment)
{
sendData["AttachmentType"] = attType.ToString();
sendData["AttachmentName"] = attName.ToString();
sendData["AttachmentItemID"] = attItemID.ToString();
sendData["AttachmentOwnerID"] = attOwnerID;
}
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("ADDNOTICE", sendData);
if (ret == null)
return false;
if (!ret.ContainsKey("RESULT"))
return false;
if (ret["RESULT"].ToString().ToLower() != "true")
return false;
return true;
}
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["NoticeID"] = noticeID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETNOTICES", sendData);
if (ret == null)
return null;
if (!ret.ContainsKey("RESULT"))
return null;
if (ret["RESULT"].ToString() == "NULL")
return null;
return GroupsDataUtils.GroupNoticeInfo((Dictionary<string, object>)ret["RESULT"]);
}
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
{
List<ExtendedGroupNoticeData> notices = new List<ExtendedGroupNoticeData>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["GroupID"] = GroupID.ToString();
sendData["RequestingAgentID"] = RequestingAgentID;
Dictionary<string, object> ret = MakeRequest("GETNOTICES", sendData);
if (ret == null)
return notices;
if (!ret.ContainsKey("RESULT"))
return notices;
if (ret["RESULT"].ToString() == "NULL")
return notices;
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
{
ExtendedGroupNoticeData m = GroupsDataUtils.GroupNoticeData((Dictionary<string, object>)v);
notices.Add(m);
}
return notices;
}
#region Make Request
private Dictionary<string, object> MakeRequest(string method, Dictionary<string, object> sendData)
{
sendData["METHOD"] = method;
string reply = string.Empty;
lock (m_Lock)
reply = SynchronousRestFormsRequester.MakeRequest("POST",
m_ServerURI + "groups",
ServerUtils.BuildQueryString(sendData),
m_Auth);
if (reply.Length == 0)
return null;
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(
reply);
return replyData;
}
#endregion
}
}

View File

@@ -1,408 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Text;
using OpenSim.Framework;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenMetaverse;
using Mono.Addins;
using log4net;
using Nini.Config;
namespace OpenSim.Groups
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceRemoteConnectorModule")]
public class GroupsServiceRemoteConnectorModule : ISharedRegionModule, IGroupsServicesConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private bool m_Enabled = false;
private GroupsServiceRemoteConnector m_GroupsService;
private IUserManagement m_UserManagement;
private List<Scene> m_Scenes;
private RemoteConnectorCacheWrapper m_CacheWrapper;
#region constructors
public GroupsServiceRemoteConnectorModule()
{
}
public GroupsServiceRemoteConnectorModule(IConfigSource config, IUserManagement uman)
{
Init(config);
m_UserManagement = uman;
m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement);
}
#endregion
private void Init(IConfigSource config)
{
m_GroupsService = new GroupsServiceRemoteConnector(config);
m_Scenes = new List<Scene>();
}
#region ISharedRegionModule
public void Initialise(IConfigSource config)
{
IConfig groupsConfig = config.Configs["Groups"];
if (groupsConfig == null)
return;
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|| (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name))
{
return;
}
Init(config);
m_Enabled = true;
m_log.DebugFormat("[Groups.RemoteConnector]: Initializing {0}", this.Name);
}
public string Name
{
get { return "Groups Remote Service Connector"; }
}
public Type ReplaceableInterface
{
get { return null; }
}
public void AddRegion(Scene scene)
{
if (!m_Enabled)
return;
m_log.DebugFormat("[Groups.RemoteConnector]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
m_Scenes.Add(scene);
}
public void RemoveRegion(Scene scene)
{
if (!m_Enabled)
return;
scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
m_Scenes.Remove(scene);
}
public void RegionLoaded(Scene scene)
{
if (!m_Enabled)
return;
if (m_UserManagement == null)
{
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement);
}
}
public void PostInitialise()
{
}
public void Close()
{
}
#endregion
#region IGroupsServicesConnector
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
{
m_log.DebugFormat("[Groups.RemoteConnector]: Creating group {0}", name);
string r = string.Empty;
UUID groupID = m_CacheWrapper.CreateGroup(RequestingAgentID, delegate
{
return m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID,
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out r);
});
reason = r;
return groupID;
}
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason)
{
string r = string.Empty;
bool success = m_CacheWrapper.UpdateGroup(groupID, delegate
{
return m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
});
reason = r;
return success;
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
{
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
return null;
return m_CacheWrapper.GetGroupRecord(RequestingAgentID,GroupID,GroupName, delegate
{
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
});
}
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
{
// TODO!
return m_GroupsService.FindGroups(RequestingAgentIDstr, search);
}
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
{
string agentFullID = AgentID;
m_log.DebugFormat("[Groups.RemoteConnector]: Add agent {0} to group {1}", agentFullID, GroupID);
string r = string.Empty;
bool success = m_CacheWrapper.AddAgentToGroup(RequestingAgentID, AgentID, GroupID, delegate
{
return m_GroupsService.AddAgentToGroup(RequestingAgentID, agentFullID, GroupID, RoleID, token, out r);
});
reason = r;
return success;
}
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
m_CacheWrapper.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID, delegate
{
m_GroupsService.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
});
}
public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
{
m_CacheWrapper.SetAgentActiveGroup(AgentID, delegate
{
return m_GroupsService.SetAgentActiveGroup(RequestingAgentID, AgentID, GroupID);
});
}
public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID)
{
return m_CacheWrapper.GetAgentActiveMembership(AgentID, delegate
{
return m_GroupsService.GetMembership(RequestingAgentID, AgentID, UUID.Zero);
});
}
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
{
return m_CacheWrapper.GetAgentGroupMembership(AgentID, GroupID, delegate
{
return m_GroupsService.GetMembership(RequestingAgentID, AgentID, GroupID);
});
}
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
{
return m_CacheWrapper.GetAgentGroupMemberships(AgentID, delegate
{
return m_GroupsService.GetMemberships(RequestingAgentID, AgentID);
});
}
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
{
return m_CacheWrapper.GetGroupMembers(RequestingAgentID, GroupID, delegate
{
return m_GroupsService.GetGroupMembers(RequestingAgentID, GroupID);
});
}
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
{
string r = string.Empty;
bool success = m_CacheWrapper.AddGroupRole(groupID, roleID, description, name, powers, title, delegate
{
return m_GroupsService.AddGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, out r);
});
reason = r;
return success;
}
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
{
return m_CacheWrapper.UpdateGroupRole(groupID, roleID, name, description, title, powers, delegate
{
return m_GroupsService.UpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers);
});
}
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
{
m_CacheWrapper.RemoveGroupRole(RequestingAgentID, groupID, roleID, delegate
{
m_GroupsService.RemoveGroupRole(RequestingAgentID, groupID, roleID);
});
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID)
{
return m_CacheWrapper.GetGroupRoles(RequestingAgentID, GroupID, delegate
{
return m_GroupsService.GetGroupRoles(RequestingAgentID, GroupID);
});
}
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID)
{
return m_CacheWrapper.GetGroupRoleMembers(RequestingAgentID, GroupID, delegate
{
return m_GroupsService.GetGroupRoleMembers(RequestingAgentID, GroupID);
});
}
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
m_CacheWrapper.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID, delegate
{
return m_GroupsService.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
});
}
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
m_CacheWrapper.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID, delegate
{
return m_GroupsService.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
});
}
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
{
return m_CacheWrapper.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID, delegate
{
return m_GroupsService.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID); ;
});
}
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
{
m_CacheWrapper.SetAgentActiveGroupRole(AgentID, GroupID, delegate
{
m_GroupsService.SetAgentActiveGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
});
}
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
{
m_CacheWrapper.UpdateMembership(AgentID, GroupID, AcceptNotices, ListInProfile, delegate
{
m_GroupsService.UpdateMembership(RequestingAgentID, AgentID, GroupID, AcceptNotices, ListInProfile);
});
}
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
{
return m_GroupsService.AddAgentToGroupInvite(RequestingAgentID, inviteID, groupID, roleID, agentID);
}
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID);
}
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
{
m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID);
}
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
{
GroupNoticeInfo notice = new GroupNoticeInfo();
notice.GroupID = groupID;
notice.Message = message;
notice.noticeData = new ExtendedGroupNoticeData();
notice.noticeData.AttachmentItemID = attItemID;
notice.noticeData.AttachmentName = attName;
notice.noticeData.AttachmentOwnerID = attOwnerID.ToString();
notice.noticeData.AttachmentType = attType;
notice.noticeData.FromName = fromName;
notice.noticeData.HasAttachment = hasAttachment;
notice.noticeData.NoticeID = noticeID;
notice.noticeData.Subject = subject;
notice.noticeData.Timestamp = (uint)Util.UnixTimeSinceEpoch();
return m_CacheWrapper.AddGroupNotice(groupID, noticeID, notice, delegate
{
return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message,
hasAttachment, attType, attName, attItemID, attOwnerID);
});
}
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
{
return m_CacheWrapper.GetGroupNotice(noticeID, delegate
{
return m_GroupsService.GetGroupNotice(RequestingAgentID, noticeID);
});
}
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
{
return m_CacheWrapper.GetGroupNotices(GroupID, delegate
{
return m_GroupsService.GetGroupNotices(RequestingAgentID, GroupID);
});
}
#endregion
}
}

View File

@@ -1,817 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Framework.ServiceAuth;
using OpenSim.Server.Handlers.Base;
using log4net;
using OpenMetaverse;
namespace OpenSim.Groups
{
public class GroupsServiceRobustConnector : ServiceConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private GroupsService m_GroupsService;
private string m_ConfigName = "Groups";
public GroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) :
base(config, server, configName)
{
string key = string.Empty;
if (configName != String.Empty)
m_ConfigName = configName;
m_log.DebugFormat("[Groups.RobustConnector]: Starting with config name {0}", m_ConfigName);
IConfig groupsConfig = config.Configs[m_ConfigName];
if (groupsConfig != null)
{
key = groupsConfig.GetString("SecretKey", string.Empty);
m_log.DebugFormat("[Groups.RobustConnector]: Starting with secret key {0}", key);
}
// else
// m_log.DebugFormat("[Groups.RobustConnector]: Unable to find {0} section in configuration", m_ConfigName);
m_GroupsService = new GroupsService(config);
IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName);
server.AddStreamHandler(new GroupsServicePostHandler(m_GroupsService, auth));
}
}
public class GroupsServicePostHandler : BaseStreamHandler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private GroupsService m_GroupsService;
public GroupsServicePostHandler(GroupsService service, IServiceAuth auth) :
base("POST", "/groups", auth)
{
m_GroupsService = service;
}
protected override byte[] ProcessRequest(string path, Stream requestData,
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
string body;
using(StreamReader sr = new StreamReader(requestData))
body = sr.ReadToEnd();
body = body.Trim();
//m_log.DebugFormat("[XXX]: query String: {0}", body);
try
{
Dictionary<string, object> request =
ServerUtils.ParseQueryString(body);
if (!request.ContainsKey("METHOD"))
return FailureResult();
string method = request["METHOD"].ToString();
request.Remove("METHOD");
// m_log.DebugFormat("[Groups.Handler]: {0}", method);
switch (method)
{
case "PUTGROUP":
return HandleAddOrUpdateGroup(request);
case "GETGROUP":
return HandleGetGroup(request);
case "ADDAGENTTOGROUP":
return HandleAddAgentToGroup(request);
case "REMOVEAGENTFROMGROUP":
return HandleRemoveAgentFromGroup(request);
case "GETMEMBERSHIP":
return HandleGetMembership(request);
case "GETGROUPMEMBERS":
return HandleGetGroupMembers(request);
case "PUTROLE":
return HandlePutRole(request);
case "REMOVEROLE":
return HandleRemoveRole(request);
case "GETGROUPROLES":
return HandleGetGroupRoles(request);
case "GETROLEMEMBERS":
return HandleGetRoleMembers(request);
case "AGENTROLE":
return HandleAgentRole(request);
case "GETAGENTROLES":
return HandleGetAgentRoles(request);
case "SETACTIVE":
return HandleSetActive(request);
case "UPDATEMEMBERSHIP":
return HandleUpdateMembership(request);
case "INVITE":
return HandleInvite(request);
case "ADDNOTICE":
return HandleAddNotice(request);
case "GETNOTICES":
return HandleGetNotices(request);
case "FINDGROUPS":
return HandleFindGroups(request);
}
m_log.DebugFormat("[GROUPS HANDLER]: unknown method request: {0}", method);
}
catch (Exception e)
{
m_log.Error(string.Format("[GROUPS HANDLER]: Exception {0} ", e.Message), e);
}
return FailureResult();
}
byte[] HandleAddOrUpdateGroup(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
ExtendedGroupRecord grec = GroupsDataUtils.GroupRecord(request);
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("OP"))
NullResult(result, "Bad network data");
else
{
string RequestingAgentID = request["RequestingAgentID"].ToString();
string reason = string.Empty;
string op = request["OP"].ToString();
if (op == "ADD")
{
grec.GroupID = m_GroupsService.CreateGroup(RequestingAgentID, grec.GroupName, grec.Charter, grec.ShowInList, grec.GroupPicture, grec.MembershipFee,
grec.OpenEnrollment, grec.AllowPublish, grec.MaturePublish, grec.FounderID, out reason);
}
else if (op == "UPDATE")
{
m_GroupsService.UpdateGroup(RequestingAgentID, grec.GroupID, grec.Charter, grec.ShowInList, grec.GroupPicture, grec.MembershipFee,
grec.OpenEnrollment, grec.AllowPublish, grec.MaturePublish);
}
if (!grec.GroupID.IsZero())
{
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, grec.GroupID);
if (grec == null)
NullResult(result, "Internal Error");
else
result["RESULT"] = GroupsDataUtils.GroupRecord(grec);
}
else
NullResult(result, reason);
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetGroup(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID"))
NullResult(result, "Bad network data");
else
{
string RequestingAgentID = request["RequestingAgentID"].ToString();
ExtendedGroupRecord grec = null;
if (request.ContainsKey("GroupID"))
{
UUID groupID = new UUID(request["GroupID"].ToString());
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, groupID);
}
else if (request.ContainsKey("Name"))
{
string name = request["Name"].ToString();
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, name);
}
if (grec == null)
NullResult(result, "Group not found");
else
result["RESULT"] = GroupsDataUtils.GroupRecord(grec);
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleAddAgentToGroup(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") ||
!request.ContainsKey("GroupID") || !request.ContainsKey("RoleID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
UUID roleID = new UUID(request["RoleID"].ToString());
string agentID = request["AgentID"].ToString();
string requestingAgentID = request["RequestingAgentID"].ToString();
string token = string.Empty;
string reason = string.Empty;
if (request.ContainsKey("AccessToken"))
token = request["AccessToken"].ToString();
if (!m_GroupsService.AddAgentToGroup(requestingAgentID, agentID, groupID, roleID, token, out reason))
NullResult(result, reason);
else
{
GroupMembershipData membership = m_GroupsService.GetAgentGroupMembership(requestingAgentID, agentID, groupID);
if (membership == null)
NullResult(result, "Internal error");
else
result["RESULT"] = GroupsDataUtils.GroupMembershipData((ExtendedGroupMembershipData)membership);
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleRemoveAgentFromGroup(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || !request.ContainsKey("GroupID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string agentID = request["AgentID"].ToString();
string requestingAgentID = request["RequestingAgentID"].ToString();
if (!m_GroupsService.RemoveAgentFromGroup(requestingAgentID, agentID, groupID))
NullResult(result, string.Format("Insufficient permissions. {0}", agentID));
else
result["RESULT"] = "true";
}
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
byte[] HandleGetMembership(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID"))
NullResult(result, "Bad network data");
else
{
string agentID = request["AgentID"].ToString();
UUID groupID = UUID.Zero;
if (request.ContainsKey("GroupID"))
groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
bool all = request.ContainsKey("ALL");
if (!all)
{
ExtendedGroupMembershipData membership = null;
if (groupID.IsZero())
{
membership = m_GroupsService.GetAgentActiveMembership(requestingAgentID, agentID);
}
else
{
membership = m_GroupsService.GetAgentGroupMembership(requestingAgentID, agentID, groupID);
}
if (membership == null)
NullResult(result, "No such membership");
else
result["RESULT"] = GroupsDataUtils.GroupMembershipData(membership);
}
else
{
List<GroupMembershipData> memberships = m_GroupsService.GetAgentGroupMemberships(requestingAgentID, agentID);
if (memberships == null || (memberships != null && memberships.Count == 0))
{
NullResult(result, "No memberships");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (GroupMembershipData m in memberships)
dict["m-" + i++] = GroupsDataUtils.GroupMembershipData((ExtendedGroupMembershipData)m);
result["RESULT"] = dict;
}
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetGroupMembers(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
List<ExtendedGroupMembersData> members = m_GroupsService.GetGroupMembers(requestingAgentID, groupID);
if (members == null || (members != null && members.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (ExtendedGroupMembersData m in members)
{
dict["m-" + i++] = GroupsDataUtils.GroupMembersData(m);
}
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandlePutRole(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID") ||
!request.ContainsKey("Name") || !request.ContainsKey("Description") || !request.ContainsKey("Title") ||
!request.ContainsKey("Powers") || !request.ContainsKey("OP"))
NullResult(result, "Bad network data");
else
{
string op = request["OP"].ToString();
string reason = string.Empty;
bool success = false;
if (op == "ADD")
success = m_GroupsService.AddGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
new UUID(request["RoleID"].ToString()), request["Name"].ToString(), request["Description"].ToString(),
request["Title"].ToString(), UInt64.Parse(request["Powers"].ToString()), out reason);
else if (op == "UPDATE")
success = m_GroupsService.UpdateGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
new UUID(request["RoleID"].ToString()), request["Name"].ToString(), request["Description"].ToString(),
request["Title"].ToString(), UInt64.Parse(request["Powers"].ToString()));
result["RESULT"] = success.ToString();
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleRemoveRole(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID"))
NullResult(result, "Bad network data");
else
{
m_GroupsService.RemoveGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
new UUID(request["RoleID"].ToString()));
result["RESULT"] = "true";
}
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
byte[] HandleGetGroupRoles(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
List<GroupRolesData> roles = m_GroupsService.GetGroupRoles(requestingAgentID, groupID);
if (roles == null || (roles != null && roles.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (GroupRolesData r in roles)
dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r);
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetRoleMembers(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string requestingAgentID = request["RequestingAgentID"].ToString();
List<ExtendedGroupRoleMembersData> rmembers = m_GroupsService.GetGroupRoleMembers(requestingAgentID, groupID);
if (rmembers == null || (rmembers != null && rmembers.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (ExtendedGroupRoleMembersData rm in rmembers)
dict["rm-" + i++] = GroupsDataUtils.GroupRoleMembersData(rm);
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleAgentRole(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID") ||
!request.ContainsKey("AgentID") || !request.ContainsKey("OP"))
NullResult(result, "Bad network data");
else
{
string op = request["OP"].ToString();
bool success = false;
if (op == "ADD")
success = m_GroupsService.AddAgentToGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
else if (op == "DELETE")
success = m_GroupsService.RemoveAgentFromGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
result["RESULT"] = success.ToString();
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetAgentRoles(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AgentID"))
NullResult(result, "Bad network data");
else
{
UUID groupID = new UUID(request["GroupID"].ToString());
string agentID = request["AgentID"].ToString();
string requestingAgentID = request["RequestingAgentID"].ToString();
List<GroupRolesData> roles = m_GroupsService.GetAgentGroupRoles(requestingAgentID, agentID, groupID);
if (roles == null || (roles != null && roles.Count == 0))
{
NullResult(result, "No members");
}
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (GroupRolesData r in roles)
dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r);
result["RESULT"] = dict;
}
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleSetActive(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") ||
!request.ContainsKey("AgentID") || !request.ContainsKey("OP"))
{
NullResult(result, "Bad network data");
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
else
{
string op = request["OP"].ToString();
if (op == "GROUP")
{
ExtendedGroupMembershipData group = m_GroupsService.SetAgentActiveGroup(request["RequestingAgentID"].ToString(),
request["AgentID"].ToString(), new UUID(request["GroupID"].ToString()));
if (group == null)
NullResult(result, "Internal error");
else
result["RESULT"] = GroupsDataUtils.GroupMembershipData(group);
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
else if (op == "ROLE" && request.ContainsKey("RoleID"))
{
m_GroupsService.SetAgentActiveGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
result["RESULT"] = "true";
}
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
}
byte[] HandleUpdateMembership(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || !request.ContainsKey("GroupID") ||
!request.ContainsKey("AcceptNotices") || !request.ContainsKey("ListInProfile"))
NullResult(result, "Bad network data");
else
{
m_GroupsService.UpdateMembership(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(), new UUID(request["GroupID"].ToString()),
bool.Parse(request["AcceptNotices"].ToString()), bool.Parse(request["ListInProfile"].ToString()));
result["RESULT"] = "true";
}
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
byte[] HandleInvite(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("InviteID"))
{
NullResult(result, "Bad network data");
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
else
{
string op = request["OP"].ToString();
if (op == "ADD" && request.ContainsKey("GroupID") && request.ContainsKey("RoleID") && request.ContainsKey("AgentID"))
{
bool success = m_GroupsService.AddAgentToGroupInvite(request["RequestingAgentID"].ToString(),
new UUID(request["InviteID"].ToString()), new UUID(request["GroupID"].ToString()),
new UUID(request["RoleID"].ToString()), request["AgentID"].ToString());
result["RESULT"] = success.ToString();
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
else if (op == "DELETE")
{
m_GroupsService.RemoveAgentToGroupInvite(request["RequestingAgentID"].ToString(), new UUID(request["InviteID"].ToString()));
result["RESULT"] = "true";
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
else if (op == "GET")
{
GroupInviteInfo invite = m_GroupsService.GetAgentToGroupInvite(request["RequestingAgentID"].ToString(),
new UUID(request["InviteID"].ToString()));
if (invite != null)
result["RESULT"] = GroupsDataUtils.GroupInviteInfo(invite);
else
result["RESULT"] = "NULL";
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
NullResult(result, "Bad OP in request");
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
}
}
byte[] HandleAddNotice(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("NoticeID") ||
!request.ContainsKey("FromName") || !request.ContainsKey("Subject") || !request.ContainsKey("Message") ||
!request.ContainsKey("HasAttachment"))
NullResult(result, "Bad network data");
else
{
bool hasAtt = bool.Parse(request["HasAttachment"].ToString());
byte attType = 0;
string attName = string.Empty;
string attOwner = string.Empty;
UUID attItem = UUID.Zero;
if (request.ContainsKey("AttachmentType"))
attType = byte.Parse(request["AttachmentType"].ToString());
if (request.ContainsKey("AttachmentName"))
attName = request["AttachmentName"].ToString();
if (request.ContainsKey("AttachmentItemID"))
attItem = new UUID(request["AttachmentItemID"].ToString());
if (request.ContainsKey("AttachmentOwnerID"))
attOwner = request["AttachmentOwnerID"].ToString();
bool success = m_GroupsService.AddGroupNotice(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
new UUID(request["NoticeID"].ToString()), request["FromName"].ToString(), request["Subject"].ToString(),
request["Message"].ToString(), hasAtt, attType, attName, attItem, attOwner);
result["RESULT"] = success.ToString();
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGetNotices(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID"))
NullResult(result, "Bad network data");
else if (request.ContainsKey("NoticeID")) // just one
{
GroupNoticeInfo notice = m_GroupsService.GetGroupNotice(request["RequestingAgentID"].ToString(), new UUID(request["NoticeID"].ToString()));
if (notice == null)
NullResult(result, "NO such notice");
else
result["RESULT"] = GroupsDataUtils.GroupNoticeInfo(notice);
}
else if (request.ContainsKey("GroupID")) // all notices for group
{
List<ExtendedGroupNoticeData> notices = m_GroupsService.GetGroupNotices(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()));
if (notices == null || (notices != null && notices.Count == 0))
NullResult(result, "No notices");
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (ExtendedGroupNoticeData n in notices)
dict["n-" + i++] = GroupsDataUtils.GroupNoticeData(n);
result["RESULT"] = dict;
}
}
else
NullResult(result, "Bad OP in request");
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleFindGroups(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("Query"))
NullResult(result, "Bad network data");
List<DirGroupsReplyData> hits = m_GroupsService.FindGroups(request["RequestingAgentID"].ToString(), request["Query"].ToString());
if (hits == null || (hits != null && hits.Count == 0))
NullResult(result, "No hits");
else
{
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (DirGroupsReplyData n in hits)
dict["n-" + i++] = GroupsDataUtils.DirGroupsReplyData(n);
result["RESULT"] = dict;
}
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
#region Helpers
private void NullResult(Dictionary<string, object> result, string reason)
{
result["RESULT"] = "NULL";
result["REASON"] = reason;
}
private byte[] FailureResult()
{
Dictionary<string, object> result = new Dictionary<string, object>();
NullResult(result, "Unknown method");
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
private byte[] FailureResult(string reason)
{
Dictionary<string, object> result = new Dictionary<string, object>();
NullResult(result, reason);
string xmlString = ServerUtils.BuildXmlResponse(result);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
#endregion
}
}

View File

@@ -1,814 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using OpenSim.Framework;
//using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
using OpenMetaverse;
namespace OpenSim.Groups
{
public delegate ExtendedGroupRecord GroupRecordDelegate();
public delegate GroupMembershipData GroupMembershipDelegate();
public delegate List<GroupMembershipData> GroupMembershipListDelegate();
public delegate List<ExtendedGroupMembersData> GroupMembersListDelegate();
public delegate List<GroupRolesData> GroupRolesListDelegate();
public delegate List<ExtendedGroupRoleMembersData> RoleMembersListDelegate();
public delegate GroupNoticeInfo NoticeDelegate();
public delegate List<ExtendedGroupNoticeData> NoticeListDelegate();
public delegate void VoidDelegate();
public delegate bool BooleanDelegate();
public class RemoteConnectorCacheWrapper
{
private const int GROUPS_CACHE_TIMEOUT = 1 * 60; // 1 minutes
private ForeignImporter m_ForeignImporter;
private HashSet<string> m_ActiveRequests = new HashSet<string>();
// This all important cache caches objects of different types:
// group-<GroupID> or group-<Name> => ExtendedGroupRecord
// active-<AgentID> => GroupMembershipData
// membership-<AgentID>-<GroupID> => GroupMembershipData
// memberships-<AgentID> => List<GroupMembershipData>
// members-<RequestingAgentID>-<GroupID> => List<ExtendedGroupMembersData>
// role-<RoleID> => GroupRolesData
// roles-<GroupID> => List<GroupRolesData> ; all roles in the group
// roles-<GroupID>-<AgentID> => List<GroupRolesData> ; roles that the agent has
// rolemembers-<RequestingAgentID>-<GroupID> => List<ExtendedGroupRoleMembersData>
// notice-<noticeID> => GroupNoticeInfo
// notices-<GroupID> => List<ExtendedGroupNoticeData>
private ExpiringCacheOS<string, object> m_Cache = new ExpiringCacheOS<string, object>(30000);
public RemoteConnectorCacheWrapper(IUserManagement uman)
{
m_ForeignImporter = new ForeignImporter(uman);
}
public UUID CreateGroup(UUID RequestingAgentID, GroupRecordDelegate d)
{
//m_log.DebugFormat("[Groups.RemoteConnector]: Creating group {0}", name);
//reason = string.Empty;
//ExtendedGroupRecord group = m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID,
// membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
ExtendedGroupRecord group = d();
if (group == null)
return UUID.Zero;
if (!group.GroupID.IsZero())
{
m_Cache.AddOrUpdate("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT);
m_Cache.Remove("memberships-" + RequestingAgentID.ToString());
}
return group.GroupID;
}
public bool UpdateGroup(UUID groupID, GroupRecordDelegate d)
{
//reason = string.Empty;
//ExtendedGroupRecord group = m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
ExtendedGroupRecord group = d();
if (!group.GroupID.IsZero())
m_Cache.AddOrUpdate("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT);
return true;
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, GroupRecordDelegate d)
{
//if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
// return null;
object group = null;
bool firstCall = false;
string cacheKey = "group-";
if (GroupID.IsZero())
cacheKey += GroupName;
else
cacheKey += GroupID.ToString();
//m_log.DebugFormat("[XXX]: GetGroupRecord {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out group))
{
//m_log.DebugFormat("[XXX]: GetGroupRecord {0} cached!", cacheKey);
return (ExtendedGroupRecord)group;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
//group = m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
group = d();
lock (m_Cache)
{
m_Cache.AddOrUpdate(cacheKey, group, GROUPS_CACHE_TIMEOUT);
return (ExtendedGroupRecord)group;
}
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, GroupMembershipDelegate d)
{
GroupMembershipData membership = d();
if (membership == null)
return false;
lock (m_Cache)
{
// first, remove everything! add a user is a heavy-duty op
m_Cache.Clear();
m_Cache.AddOrUpdate("active-" + AgentID.ToString(), membership, GROUPS_CACHE_TIMEOUT);
m_Cache.AddOrUpdate("membership-" + AgentID.ToString() + "-" + GroupID.ToString(), membership, GROUPS_CACHE_TIMEOUT);
}
return true;
}
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, VoidDelegate d)
{
d();
string AgentIDToString = AgentID.ToString();
string cacheKey = "active-" + AgentIDToString;
m_Cache.Remove(cacheKey);
cacheKey = "memberships-" + AgentIDToString;
m_Cache.Remove(cacheKey);
string GroupIDToString = GroupID.ToString();
cacheKey = "membership-" + AgentIDToString + "-" + GroupIDToString;
m_Cache.Remove(cacheKey);
cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupIDToString;
m_Cache.Remove(cacheKey);
cacheKey = "roles-" + "-" + GroupIDToString + "-" + AgentIDToString;
m_Cache.Remove(cacheKey);
}
public void SetAgentActiveGroup(string AgentID, GroupMembershipDelegate d)
{
GroupMembershipData activeGroup = d();
string cacheKey = "active-" + AgentID.ToString();
m_Cache.AddOrUpdate(cacheKey, activeGroup, GROUPS_CACHE_TIMEOUT);
}
public ExtendedGroupMembershipData GetAgentActiveMembership(string AgentID, GroupMembershipDelegate d)
{
object membership = null;
bool firstCall = false;
string cacheKey = "active-" + AgentID.ToString();
//m_log.DebugFormat("[XXX]: GetAgentActiveMembership {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out membership))
{
//m_log.DebugFormat("[XXX]: GetAgentActiveMembership {0} cached!", cacheKey);
return (ExtendedGroupMembershipData)membership;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
membership = d();
m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT);
return (ExtendedGroupMembershipData)membership;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public ExtendedGroupMembershipData GetAgentGroupMembership(string AgentID, UUID GroupID, GroupMembershipDelegate d)
{
object membership = null;
bool firstCall = false;
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
//m_log.DebugFormat("[XXX]: GetAgentGroupMembership {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out membership))
{
//m_log.DebugFormat("[XXX]: GetAgentGroupMembership {0}", cacheKey);
return (ExtendedGroupMembershipData)membership;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
membership = d();
m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT);
return (ExtendedGroupMembershipData)membership;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public List<GroupMembershipData> GetAgentGroupMemberships(string AgentID, GroupMembershipListDelegate d)
{
object memberships = null;
bool firstCall = false;
string cacheKey = "memberships-" + AgentID.ToString();
//m_log.DebugFormat("[XXX]: GetAgentGroupMemberships {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out memberships))
{
//m_log.DebugFormat("[XXX]: GetAgentGroupMemberships {0} cached!", cacheKey);
return (List<GroupMembershipData>)memberships;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
memberships = d();
m_Cache.AddOrUpdate(cacheKey, memberships, GROUPS_CACHE_TIMEOUT);
return (List<GroupMembershipData>)memberships;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID, GroupMembersListDelegate d)
{
object members = null;
bool firstCall = false;
// we need to key in also on the requester, because different ppl have different view privileges
string cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
//m_log.DebugFormat("[XXX]: GetGroupMembers {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out members))
{
List<ExtendedGroupMembersData> xx = (List<ExtendedGroupMembersData>)members;
return xx.ConvertAll<GroupMembersData>(new Converter<ExtendedGroupMembersData, GroupMembersData>(m_ForeignImporter.ConvertGroupMembersData));
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
List<ExtendedGroupMembersData> _members = d();
if (_members != null && _members.Count > 0)
members = _members.ConvertAll<GroupMembersData>(new Converter<ExtendedGroupMembersData, GroupMembersData>(m_ForeignImporter.ConvertGroupMembersData));
else
members = new List<GroupMembersData>();
m_Cache.AddOrUpdate(cacheKey, _members, GROUPS_CACHE_TIMEOUT);
return (List<GroupMembersData>)members;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public bool AddGroupRole(UUID groupID, UUID roleID, string description, string name, ulong powers, string title, BooleanDelegate d)
{
if (d())
{
GroupRolesData role = new GroupRolesData();
role.Description = description;
role.Members = 0;
role.Name = name;
role.Powers = powers;
role.RoleID = roleID;
role.Title = title;
m_Cache.AddOrUpdate("role-" + roleID.ToString(), role, GROUPS_CACHE_TIMEOUT);
// also remove this list
m_Cache.Remove("roles-" + groupID.ToString());
return true;
}
return false;
}
public bool UpdateGroupRole(UUID groupID, UUID roleID, string name, string description, string title, ulong powers, BooleanDelegate d)
{
if (d())
{
object role;
lock (m_Cache)
if (m_Cache.TryGetValue("role-" + roleID.ToString(), out role))
{
GroupRolesData r = (GroupRolesData)role;
r.Description = description;
r.Name = name;
r.Powers = powers;
r.Title = title;
m_Cache.AddOrUpdate("role-" + roleID.ToString(), r, GROUPS_CACHE_TIMEOUT);
}
return true;
}
else
{
m_Cache.Remove("role-" + roleID.ToString());
// also remove these lists, because they will have an outdated role
m_Cache.Remove("roles-" + groupID.ToString());
return false;
}
}
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, VoidDelegate d)
{
d();
lock (m_Cache)
{
m_Cache.Remove("role-" + roleID.ToString());
// also remove the list, because it will have an removed role
m_Cache.Remove("roles-" + groupID.ToString());
m_Cache.Remove("roles-" + groupID.ToString() + "-" + RequestingAgentID.ToString());
m_Cache.Remove("rolemembers-" + RequestingAgentID.ToString() + "-" + groupID.ToString());
}
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID, GroupRolesListDelegate d)
{
object roles = null;
bool firstCall = false;
string cacheKey = "roles-" + GroupID.ToString();
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out roles))
return (List<GroupRolesData>)roles;
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
roles = d();
if (roles != null)
{
m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT);
return (List<GroupRolesData>)roles;
}
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, RoleMembersListDelegate d)
{
object rmembers = null;
bool firstCall = false;
// we need to key in also on the requester, because different ppl have different view privileges
string cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
//m_log.DebugFormat("[XXX]: GetGroupRoleMembers {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out rmembers))
{
List<ExtendedGroupRoleMembersData> xx = (List<ExtendedGroupRoleMembersData>)rmembers;
return xx.ConvertAll<GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData);
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
List<ExtendedGroupRoleMembersData> _rmembers = d();
if (_rmembers != null && _rmembers.Count > 0)
rmembers = _rmembers.ConvertAll<GroupRoleMembersData>(new Converter<ExtendedGroupRoleMembersData, GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData));
else
rmembers = new List<GroupRoleMembersData>();
// For some strange reason, when I cache the list of GroupRoleMembersData,
// it gets emptied out. The TryGet gets an empty list...
//m_Cache.AddOrUpdate(cacheKey, rmembers, GROUPS_CACHE_TIMEOUT);
// Caching the list of ExtendedGroupRoleMembersData doesn't show that issue
// I don't get it.
m_Cache.AddOrUpdate(cacheKey, _rmembers, GROUPS_CACHE_TIMEOUT);
return (List<GroupRoleMembersData>)rmembers;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, BooleanDelegate d)
{
if (d())
{
lock (m_Cache)
{
// update the cached role
string cacheKey = "role-" + RoleID.ToString();
object obj;
if (m_Cache.TryGetValue(cacheKey, out obj))
{
GroupRolesData r = (GroupRolesData)obj;
r.Members++;
}
// add this agent to the list of role members
cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
if (m_Cache.TryGetValue(cacheKey, out obj))
{
try
{
// This may throw an exception, in which case the agentID is not a UUID but a full ID
// In that case, let's just remove the whoe things from the cache
UUID id = new UUID(AgentID);
List<ExtendedGroupRoleMembersData> xx = (List<ExtendedGroupRoleMembersData>)obj;
List<GroupRoleMembersData> rmlist = xx.ConvertAll<GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData);
GroupRoleMembersData rm = new GroupRoleMembersData();
rm.MemberID = id;
rm.RoleID = RoleID;
rmlist.Add(rm);
}
catch
{
m_Cache.Remove(cacheKey);
}
}
// Remove the cached info about this agent's roles
// because we don't have enough local info about the new role
cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
if (m_Cache.Contains(cacheKey))
m_Cache.Remove(cacheKey);
}
}
}
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, BooleanDelegate d)
{
if (d())
{
lock (m_Cache)
{
// update the cached role
string cacheKey = "role-" + RoleID.ToString();
object obj;
if (m_Cache.TryGetValue(cacheKey, out obj))
{
GroupRolesData r = (GroupRolesData)obj;
r.Members--;
}
cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
m_Cache.Remove(cacheKey);
cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
m_Cache.Remove(cacheKey);
}
}
}
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID, GroupRolesListDelegate d)
{
object roles = null;
bool firstCall = false;
string cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
//m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out roles))
{
//m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0} cached!", cacheKey);
return (List<GroupRolesData>)roles;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
roles = d();
m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT);
return (List<GroupRolesData>)roles;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public void SetAgentActiveGroupRole(string AgentID, UUID GroupID, VoidDelegate d)
{
d();
lock (m_Cache)
{
// Invalidate cached info, because it has ActiveRoleID and Powers
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
m_Cache.Remove(cacheKey);
cacheKey = "memberships-" + AgentID.ToString();
m_Cache.Remove(cacheKey);
}
}
public void UpdateMembership(string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile, VoidDelegate d)
{
d();
lock (m_Cache)
{
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
if (m_Cache.Contains(cacheKey))
m_Cache.Remove(cacheKey);
cacheKey = "memberships-" + AgentID.ToString();
m_Cache.Remove(cacheKey);
cacheKey = "active-" + AgentID.ToString();
object m = null;
if (m_Cache.TryGetValue(cacheKey, out m))
{
GroupMembershipData membership = (GroupMembershipData)m;
membership.ListInProfile = ListInProfile;
membership.AcceptNotices = AcceptNotices;
}
}
}
public bool AddGroupNotice(UUID groupID, UUID noticeID, GroupNoticeInfo notice, BooleanDelegate d)
{
if (d())
{
m_Cache.AddOrUpdate("notice-" + noticeID.ToString(), notice, GROUPS_CACHE_TIMEOUT);
string cacheKey = "notices-" + groupID.ToString();
m_Cache.Remove(cacheKey);
return true;
}
return false;
}
public GroupNoticeInfo GetGroupNotice(UUID noticeID, NoticeDelegate d)
{
object notice = null;
bool firstCall = false;
string cacheKey = "notice-" + noticeID.ToString();
//m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out notice))
{
return (GroupNoticeInfo)notice;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
GroupNoticeInfo _notice = d();
m_Cache.AddOrUpdate(cacheKey, _notice, GROUPS_CACHE_TIMEOUT);
return _notice;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
public List<ExtendedGroupNoticeData> GetGroupNotices(UUID GroupID, NoticeListDelegate d)
{
object notices = null;
bool firstCall = false;
string cacheKey = "notices-" + GroupID.ToString();
//m_log.DebugFormat("[XXX]: GetGroupNotices {0}", cacheKey);
while (true)
{
lock (m_Cache)
{
if (m_Cache.TryGetValue(cacheKey, out notices))
{
//m_log.DebugFormat("[XXX]: GetGroupNotices {0} cached!", cacheKey);
return (List<ExtendedGroupNoticeData>)notices;
}
// not cached
if (!m_ActiveRequests.Contains(cacheKey))
{
m_ActiveRequests.Add(cacheKey);
firstCall = true;
}
}
if (firstCall)
{
try
{
notices = d();
m_Cache.AddOrUpdate(cacheKey, notices, GROUPS_CACHE_TIMEOUT);
return (List<ExtendedGroupNoticeData>)notices;
}
finally
{
m_ActiveRequests.Remove(cacheKey);
}
}
else
Thread.Sleep(50);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Data;
using OpenSim.Services.Interfaces;
using OpenSim.Services.Base;
namespace OpenSim.Groups
{
public class GroupsServiceBase : ServiceBase
{
protected IGroupsData m_Database = null;
protected IGridUserData m_GridUserService = null;
public GroupsServiceBase(IConfigSource config, string cName)
: base(config)
{
string dllName = String.Empty;
string connString = String.Empty;
string realm = "os_groups";
string usersRealm = "GridUser";
string configName = (cName.Length == 0) ? "Groups" : cName;
//
// Try reading the [DatabaseService] section, if it exists
//
IConfig dbConfig = config.Configs["DatabaseService"];
if (dbConfig != null)
{
if (dllName.Length == 0)
dllName = dbConfig.GetString("StorageProvider", String.Empty);
if (connString.Length == 0)
connString = dbConfig.GetString("ConnectionString", String.Empty);
}
//
// [Groups] section overrides [DatabaseService], if it exists
//
IConfig groupsConfig = config.Configs[configName];
if (groupsConfig != null)
{
dllName = groupsConfig.GetString("StorageProvider", dllName);
connString = groupsConfig.GetString("ConnectionString", connString);
realm = groupsConfig.GetString("Realm", realm);
}
//
// We tried, but this doesn't exist. We can't proceed.
//
if (dllName.Equals(String.Empty))
throw new Exception("No StorageProvider configured");
m_Database = LoadPlugin<IGroupsData>(dllName, new Object[] { connString, realm });
if (m_Database == null)
throw new Exception("Could not find a storage interface in the given module " + dllName);
//
// [GridUserService] section overrides [DatabaseService], if it exists
//
IConfig usersConfig = config.Configs["GridUserService"];
if (usersConfig != null)
{
dllName = usersConfig.GetString("StorageProvider", dllName);
connString = usersConfig.GetString("ConnectionString", connString);
usersRealm = usersConfig.GetString("Realm", usersRealm);
}
m_GridUserService = LoadPlugin<IGridUserData>(dllName, new Object[] { connString, usersRealm });
if (m_GridUserService == null)
throw new Exception("Could not find a storage inferface for the given users module " + dllName);
}
}
}

View File

@@ -1,361 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Timers;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Data;
using OpenSim.Framework;
using OpenSim.Services.Interfaces;
namespace OpenSim.Groups
{
public class HGGroupsService : GroupsService
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IOfflineIMService m_OfflineIM;
private IUserAccountService m_UserAccounts;
private string m_HomeURI;
public HGGroupsService(IConfigSource config, IOfflineIMService im, IUserAccountService users, string homeURI)
: base(config, string.Empty)
{
m_OfflineIM = im;
m_UserAccounts = users;
m_HomeURI = homeURI;
if (!m_HomeURI.EndsWith("/"))
m_HomeURI += "/";
}
#region HG specific operations
public bool CreateGroupProxy(string RequestingAgentID, string agentID, string accessToken, UUID groupID, string serviceLocation, string name, out string reason)
{
reason = string.Empty;
Uri uri = null;
try
{
uri = new Uri(serviceLocation);
}
catch (UriFormatException)
{
reason = "Bad location for group proxy";
return false;
}
// Check if it already exists
GroupData grec = m_Database.RetrieveGroup(groupID);
if (grec == null ||
(grec != null && grec.Data["Location"] != string.Empty && grec.Data["Location"].ToLower() != serviceLocation.ToLower()))
{
// Create the group
grec = new GroupData();
grec.GroupID = groupID;
grec.Data = new Dictionary<string, string>();
grec.Data["Name"] = name + " @ " + uri.Authority;
grec.Data["Location"] = serviceLocation;
grec.Data["Charter"] = string.Empty;
grec.Data["InsigniaID"] = UUID.Zero.ToString();
grec.Data["FounderID"] = UUID.Zero.ToString();
grec.Data["MembershipFee"] = "0";
grec.Data["OpenEnrollment"] = "0";
grec.Data["ShowInList"] = "0";
grec.Data["AllowPublish"] = "0";
grec.Data["MaturePublish"] = "0";
grec.Data["OwnerRoleID"] = UUID.Zero.ToString();
if (!m_Database.StoreGroup(grec))
return false;
}
if (grec.Data["Location"].Length == 0)
{
reason = "Cannot add proxy membership to non-proxy group";
return false;
}
UUID uid = UUID.Zero;
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
Util.ParseUniversalUserIdentifier(RequestingAgentID, out uid, out url, out first, out last, out tmp);
string fromName = first + "." + last + "@" + url;
// Invite to group again
InviteToGroup(fromName, groupID, new UUID(agentID), grec.Data["Name"]);
// Stick the proxy membership in the DB already
// we'll delete it if the agent declines the invitation
MembershipData membership = new MembershipData();
membership.PrincipalID = agentID;
membership.GroupID = groupID;
membership.Data = new Dictionary<string, string>();
membership.Data["SelectedRoleID"] = UUID.Zero.ToString();
membership.Data["Contribution"] = "0";
membership.Data["ListInProfile"] = "1";
membership.Data["AcceptNotices"] = "1";
membership.Data["AccessToken"] = accessToken;
m_Database.StoreMember(membership);
return true;
}
public bool RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, string token)
{
// check the token
MembershipData membership = m_Database.RetrieveMember(GroupID, AgentID);
if (membership != null)
{
if (token != string.Empty && token.Equals(membership.Data["AccessToken"]))
{
return RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
}
else
{
m_log.DebugFormat("[Groups.HGGroupsService]: access token {0} did not match stored one {1}", token, membership.Data["AccessToken"]);
return false;
}
}
else
{
m_log.DebugFormat("[Groups.HGGroupsService]: membership not found for {0}", AgentID);
return false;
}
}
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string groupName, string token)
{
// check the token
if (!VerifyToken(GroupID, RequestingAgentID, token))
return null;
ExtendedGroupRecord grec;
if (GroupID.IsZero())
grec = GetGroupRecord(RequestingAgentID, groupName);
else
grec = GetGroupRecord(RequestingAgentID, GroupID);
if (grec != null)
FillFounderUUI(grec);
return grec;
}
public List<ExtendedGroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID, string token)
{
if (!VerifyToken(GroupID, RequestingAgentID, token))
return new List<ExtendedGroupMembersData>();
List<ExtendedGroupMembersData> members = GetGroupMembers(RequestingAgentID, GroupID);
// convert UUIDs to UUIs
members.ForEach(delegate (ExtendedGroupMembersData m)
{
if (m.AgentID.ToString().Length == 36) // UUID
{
UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, new UUID(m.AgentID));
if (account != null)
m.AgentID = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI);
}
});
return members;
}
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID, string token)
{
if (!VerifyToken(GroupID, RequestingAgentID, token))
return new List<GroupRolesData>();
return GetGroupRoles(RequestingAgentID, GroupID);
}
public List<ExtendedGroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, string token)
{
if (!VerifyToken(GroupID, RequestingAgentID, token))
return new List<ExtendedGroupRoleMembersData>();
List<ExtendedGroupRoleMembersData> rolemembers = GetGroupRoleMembers(RequestingAgentID, GroupID);
// convert UUIDs to UUIs
rolemembers.ForEach(delegate(ExtendedGroupRoleMembersData m)
{
if (m.MemberID.ToString().Length == 36) // UUID
{
UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, new UUID(m.MemberID));
if (account != null)
m.MemberID = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI);
}
});
return rolemembers;
}
public bool AddNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
{
// check that the group proxy exists
ExtendedGroupRecord grec = GetGroupRecord(RequestingAgentID, groupID);
if (grec == null)
{
m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to non-existent group proxy");
return false;
}
// check that the group is remote
if (grec.ServiceLocation.Length == 0)
{
m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to local (non-proxy) group");
return false;
}
// check that there isn't already a notice with the same ID
if (GetGroupNotice(RequestingAgentID, noticeID) != null)
{
m_log.DebugFormat("[Groups.HGGroupsService]: a notice with the same ID already exists", grec.ServiceLocation);
return false;
}
// This has good intentions (security) but it will potentially DDS the origin...
// We'll need to send a proof along with the message. Maybe encrypt the message
// using key pairs
//
//// check that the notice actually exists in the origin
//GroupsServiceHGConnector c = new GroupsServiceHGConnector(grec.ServiceLocation);
//if (!c.VerifyNotice(noticeID, groupID))
//{
// m_log.DebugFormat("[Groups.HGGroupsService]: notice does not exist at origin {0}", grec.ServiceLocation);
// return false;
//}
// ok, we're good!
return _AddNotice(groupID, noticeID, fromName, subject, message, hasAttachment, attType, attName, attItemID, attOwnerID);
}
public bool VerifyNotice(UUID noticeID, UUID groupID)
{
GroupNoticeInfo notice = GetGroupNotice(string.Empty, noticeID);
if (notice == null)
return false;
if (notice.GroupID != groupID)
return false;
return true;
}
#endregion
private void InviteToGroup(string fromName, UUID groupID, UUID invitedAgentID, string groupName)
{
// Todo: Security check, probably also want to send some kind of notification
UUID InviteID = UUID.Random();
if (AddAgentToGroupInvite(InviteID, groupID, invitedAgentID.ToString()))
{
Guid inviteUUID = InviteID.Guid;
GridInstantMessage msg = new GridInstantMessage();
msg.imSessionID = inviteUUID;
// msg.fromAgentID = agentID.Guid;
msg.fromAgentID = groupID.Guid;
msg.toAgentID = invitedAgentID.Guid;
//msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
msg.timestamp = 0;
msg.fromAgentName = fromName;
msg.message = string.Format("Please confirm your acceptance to join group {0}.", groupName);
msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupInvitation;
msg.fromGroup = true;
msg.offline = (byte)0;
msg.ParentEstateID = 0;
msg.Position = Vector3.Zero;
msg.RegionID = UUID.Zero.Guid;
msg.binaryBucket = new byte[20];
string reason = string.Empty;
m_OfflineIM.StoreMessage(msg, out reason);
}
}
private bool AddAgentToGroupInvite(UUID inviteID, UUID groupID, string agentID)
{
// Check whether the invitee is already a member of the group
MembershipData m = m_Database.RetrieveMember(groupID, agentID);
if (m != null)
return false;
// Check whether there are pending invitations and delete them
InvitationData invite = m_Database.RetrieveInvitation(groupID, agentID);
if (invite != null)
m_Database.DeleteInvite(invite.InviteID);
invite = new InvitationData();
invite.InviteID = inviteID;
invite.PrincipalID = agentID;
invite.GroupID = groupID;
invite.RoleID = UUID.Zero;
invite.Data = new Dictionary<string, string>();
return m_Database.StoreInvitation(invite);
}
private void FillFounderUUI(ExtendedGroupRecord grec)
{
UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, grec.FounderID);
if (account != null)
grec.FounderUUI = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI);
}
private bool VerifyToken(UUID groupID, string agentID, string token)
{
// check the token
MembershipData membership = m_Database.RetrieveMember(groupID, agentID);
if (membership != null)
{
if (token != string.Empty && token.Equals(membership.Data["AccessToken"]))
return true;
else
m_log.DebugFormat("[Groups.HGGroupsService]: access token {0} did not match stored one {1}", token, membership.Data["AccessToken"]);
}
else
m_log.DebugFormat("[Groups.HGGroupsService]: membership not found for {0}", agentID);
return false;
}
}
}

View File

@@ -1,255 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using log4net;
using Mono.Addins;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Client;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
namespace OpenSim.OfflineIM
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "OfflineIMConnectorModule")]
public class OfflineIMRegionModule : ISharedRegionModule, IOfflineIMService
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private bool m_Enabled = false;
private List<Scene> m_SceneList = new List<Scene>();
IMessageTransferModule m_TransferModule = null;
private bool m_ForwardOfflineGroupMessages = true;
private IOfflineIMService m_OfflineIMService;
public void Initialise(IConfigSource config)
{
IConfig cnf = config.Configs["Messaging"];
if (cnf == null)
return;
if (cnf != null && cnf.GetString("OfflineMessageModule", string.Empty) != Name)
return;
m_Enabled = true;
string serviceLocation = cnf.GetString("OfflineMessageURL", string.Empty);
if (serviceLocation.Length == 0)
m_OfflineIMService = new OfflineIMService(config);
else
m_OfflineIMService = new OfflineIMServiceRemoteConnector(config);
m_ForwardOfflineGroupMessages = cnf.GetBoolean("ForwardOfflineGroupMessages", m_ForwardOfflineGroupMessages);
m_log.DebugFormat("[OfflineIM.V2]: Offline messages enabled by {0}", Name);
}
public void AddRegion(Scene scene)
{
if (!m_Enabled)
return;
scene.RegisterModuleInterface<IOfflineIMService>(this);
m_SceneList.Add(scene);
scene.EventManager.OnNewClient += OnNewClient;
}
public void RegionLoaded(Scene scene)
{
if (!m_Enabled)
return;
if (m_TransferModule == null)
{
m_TransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
if (m_TransferModule == null)
{
scene.EventManager.OnNewClient -= OnNewClient;
m_SceneList.Clear();
m_log.Error("[OfflineIM.V2]: No message transfer module is enabled. Disabling offline messages");
}
m_TransferModule.OnUndeliveredMessage += UndeliveredMessage;
}
}
public void RemoveRegion(Scene scene)
{
if (!m_Enabled)
return;
m_SceneList.Remove(scene);
scene.EventManager.OnNewClient -= OnNewClient;
m_TransferModule.OnUndeliveredMessage -= UndeliveredMessage;
scene.ForEachClient(delegate(IClientAPI client)
{
client.OnRetrieveInstantMessages -= RetrieveInstantMessages;
});
}
public void PostInitialise()
{
}
public string Name
{
get { return "Offline Message Module V2"; }
}
public Type ReplaceableInterface
{
get { return null; }
}
public void Close()
{
m_SceneList.Clear();
}
private Scene FindScene(UUID agentID)
{
foreach (Scene s in m_SceneList)
{
ScenePresence presence = s.GetScenePresence(agentID);
if (presence != null && !presence.IsChildAgent)
return s;
}
return null;
}
private IClientAPI FindClient(UUID agentID)
{
foreach (Scene s in m_SceneList)
{
ScenePresence presence = s.GetScenePresence(agentID);
if (presence != null && !presence.IsChildAgent)
return presence.ControllingClient;
}
return null;
}
private void OnNewClient(IClientAPI client)
{
client.OnRetrieveInstantMessages += RetrieveInstantMessages;
}
private void RetrieveInstantMessages(IClientAPI client)
{
m_log.DebugFormat("[OfflineIM.V2]: Retrieving stored messages for {0}", client.AgentId);
List<GridInstantMessage> msglist = m_OfflineIMService.GetMessages(client.AgentId);
if (msglist == null)
m_log.DebugFormat("[OfflineIM.V2]: WARNING null message list.");
foreach (GridInstantMessage im in msglist)
{
if (im.dialog == (byte)InstantMessageDialog.InventoryOffered)
// send it directly or else the item will be given twice
client.SendInstantMessage(im);
else
{
// Send through scene event manager so all modules get a chance
// to look at this message before it gets delivered.
//
// Needed for proper state management for stored group
// invitations
//
Scene s = client.Scene as Scene;
if (s != null)
{
im.offline = 1;
s.EventManager.TriggerIncomingInstantMessage(im);
}
}
}
}
private void UndeliveredMessage(GridInstantMessage im)
{
if (im.dialog != (byte)InstantMessageDialog.MessageFromObject &&
im.dialog != (byte)InstantMessageDialog.MessageFromAgent &&
im.dialog != (byte)InstantMessageDialog.GroupNotice &&
im.dialog != (byte)InstantMessageDialog.GroupInvitation &&
im.dialog != (byte)InstantMessageDialog.InventoryOffered)
{
return;
}
if (!m_ForwardOfflineGroupMessages)
{
if (im.dialog == (byte)InstantMessageDialog.GroupNotice ||
im.dialog == (byte)InstantMessageDialog.GroupInvitation)
return;
}
string reason = string.Empty;
bool success = m_OfflineIMService.StoreMessage(im, out reason);
if (im.dialog == (byte)InstantMessageDialog.MessageFromAgent)
{
IClientAPI client = FindClient(new UUID(im.fromAgentID));
if (client == null)
return;
client.SendInstantMessage(new GridInstantMessage(
null, new UUID(im.toAgentID),
"System", new UUID(im.fromAgentID),
(byte)InstantMessageDialog.MessageFromAgent,
"User is not logged in. " +
(success ? "Message saved." : "Message not saved: " + reason),
false, new Vector3()));
}
}
#region IOfflineIM
public List<GridInstantMessage> GetMessages(UUID principalID)
{
return m_OfflineIMService.GetMessages(principalID);
}
public bool StoreMessage(GridInstantMessage im, out string reason)
{
return m_OfflineIMService.StoreMessage(im, out reason);
}
public void DeleteMessages(UUID userID)
{
m_OfflineIMService.DeleteMessages(userID);
}
#endregion
}
}

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Mono.Addins;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenSim.Addons.OfflineIM")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("http://opensimulator.org")]
[assembly: AssemblyProduct("OpenSim.Addons.OfflineIM")]
[assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a16a9905-4393-4872-9fca-4c81bedbd9f2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
[assembly: Addin("OpenSim.OfflineIM", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]

View File

@@ -1,183 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using OpenSim.Framework;
using OpenSim.Framework.ServiceAuth;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenMetaverse;
using log4net;
using Nini.Config;
namespace OpenSim.OfflineIM
{
public class OfflineIMServiceRemoteConnector : IOfflineIMService
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private string m_ServerURI = string.Empty;
private IServiceAuth m_Auth;
private object m_Lock = new object();
public OfflineIMServiceRemoteConnector(string url)
{
m_ServerURI = url;
m_log.DebugFormat("[OfflineIM.V2.RemoteConnector]: Offline IM server at {0}", m_ServerURI);
}
public OfflineIMServiceRemoteConnector(IConfigSource config)
{
IConfig cnf = config.Configs["Messaging"];
if (cnf == null)
{
m_log.WarnFormat("[OfflineIM.V2.RemoteConnector]: Missing Messaging configuration");
return;
}
m_ServerURI = cnf.GetString("OfflineMessageURL", string.Empty);
/// This is from BaseServiceConnector
string authType = Util.GetConfigVarFromSections<string>(config, "AuthType", new string[] { "Network", "Messaging" }, "None");
switch (authType)
{
case "BasicHttpAuthentication":
m_Auth = new BasicHttpAuthentication(config, "Messaging");
break;
}
///
m_log.DebugFormat("[OfflineIM.V2.RemoteConnector]: Offline IM server at {0} with auth {1}",
m_ServerURI, (m_Auth == null ? "None" : m_Auth.GetType().ToString()));
}
#region IOfflineIMService
public List<GridInstantMessage> GetMessages(UUID principalID)
{
List<GridInstantMessage> ims = new List<GridInstantMessage>();
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["PrincipalID"] = principalID;
Dictionary<string, object> ret = MakeRequest("GET", sendData);
if (ret == null)
return ims;
if (!ret.TryGetValue("RESULT", out object resultobj))
return ims;
if(resultobj is string result)
{
if (result == "NULL" || result.Equals("false", StringComparison.InvariantCultureIgnoreCase))
{
if (ret.TryGetValue("REASON", out object rso))
m_log.Debug($"[OfflineIM.V2.RemoteConnector]: GetMessages for {principalID} failed: {rso}");
else
m_log.Debug($"[OfflineIM.V2.RemoteConnector]: GetMessages for {principalID} failed: Unknown error");
return ims;
}
}
else if(resultobj is Dictionary<string, object> resultdic)
{
foreach (object v in resultdic.Values)
{
if (v is Dictionary<string, object> vdic)
{
GridInstantMessage m = OfflineIMDataUtils.GridInstantMessage(vdic);
ims.Add(m);
}
}
}
return ims;
}
public bool StoreMessage(GridInstantMessage im, out string reason)
{
Dictionary<string, object> sendData = OfflineIMDataUtils.GridInstantMessage(im);
Dictionary<string, object> ret = MakeRequest("STORE", sendData);
if (ret == null)
{
reason = "Bad response from server";
return false;
}
if(ret.TryGetValue("RESULT", out object o))
{
string result = o.ToString();
if (result == "NULL" || result.Equals("false", StringComparison.InvariantCultureIgnoreCase))
{
if(ret.TryGetValue("REASON", out object ro))
reason = ro.ToString();
else
reason = "Unknown error";
return false;
}
}
reason = string.Empty;
return true;
}
public void DeleteMessages(UUID userID)
{
Dictionary<string, object> sendData = new Dictionary<string, object>();
sendData["UserID"] = userID;
MakeRequest("DELETE", sendData);
}
#endregion
#region Make Request
private Dictionary<string, object> MakeRequest(string method, Dictionary<string, object> sendData)
{
sendData["METHOD"] = method;
string reply = string.Empty;
lock (m_Lock)
reply = SynchronousRestFormsRequester.MakeRequest("POST",
m_ServerURI + "/offlineim",
ServerUtils.BuildQueryString(sendData),
m_Auth);
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply);
return replyData;
}
#endregion
}
}

View File

@@ -1,223 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Framework.ServiceAuth;
using OpenSim.Server.Handlers.Base;
using log4net;
using OpenMetaverse;
namespace OpenSim.OfflineIM
{
public class OfflineIMServiceRobustConnector : ServiceConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IOfflineIMService m_OfflineIMService;
private string m_ConfigName = "Messaging";
public OfflineIMServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) :
base(config, server, configName)
{
if (configName != String.Empty)
m_ConfigName = configName;
m_log.DebugFormat("[OfflineIM.V2.RobustConnector]: Starting with config name {0}", m_ConfigName);
m_OfflineIMService = new OfflineIMService(config);
IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName);
server.AddStreamHandler(new OfflineIMServicePostHandler(m_OfflineIMService, auth));
}
}
public class OfflineIMServicePostHandler : BaseStreamHandler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IOfflineIMService m_OfflineIMService;
public OfflineIMServicePostHandler(IOfflineIMService service, IServiceAuth auth) :
base("POST", "/offlineim", auth)
{
m_OfflineIMService = service;
}
protected override byte[] ProcessRequest(string path, Stream requestData,
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
StreamReader sr = new StreamReader(requestData);
string body = sr.ReadToEnd();
sr.Close();
body = body.Trim();
//m_log.DebugFormat("[XXX]: query String: {0}", body);
try
{
Dictionary<string, object> request =
ServerUtils.ParseQueryString(body);
if (!request.ContainsKey("METHOD"))
return FailureResult();
string method = request["METHOD"].ToString();
request.Remove("METHOD");
switch (method)
{
case "GET":
return HandleGet(request);
case "STORE":
return HandleStore(request);
case "DELETE":
return HandleDelete(request);
}
m_log.DebugFormat("[OFFLINE IM HANDLER]: unknown method request: {0}", method);
}
catch (Exception e)
{
m_log.Error(string.Format("[OFFLINE IM HANDLER]: Exception {0} ", e.Message), e);
}
return FailureResult();
}
byte[] HandleStore(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
GridInstantMessage im = OfflineIMDataUtils.GridInstantMessage(request);
string reason = string.Empty;
bool success = m_OfflineIMService.StoreMessage(im, out reason);
result["RESULT"] = success.ToString();
if (!success)
result["REASON"] = reason;
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleGet(Dictionary<string, object> request)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (!request.ContainsKey("PrincipalID"))
NullResult(result, "Bad network data");
else
{
UUID principalID = new UUID(request["PrincipalID"].ToString());
List<GridInstantMessage> ims = m_OfflineIMService.GetMessages(principalID);
Dictionary<string, object> dict = new Dictionary<string, object>();
int i = 0;
foreach (GridInstantMessage m in ims)
dict["im-" + i++] = OfflineIMDataUtils.GridInstantMessage(m);
result["RESULT"] = dict;
}
string xmlString = ServerUtils.BuildXmlResponse(result);
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
}
byte[] HandleDelete(Dictionary<string, object> request)
{
if (!request.ContainsKey("UserID"))
{
return FailureResult();
}
else
{
UUID userID = new UUID(request["UserID"].ToString());
m_OfflineIMService.DeleteMessages(userID);
return SuccessResult();
}
}
#region Helpers
private void NullResult(Dictionary<string, object> result, string reason)
{
result["RESULT"] = "NULL";
result["REASON"] = reason;
}
private byte[] FailureResult()
{
return BoolResult(false);
}
private byte[] SuccessResult()
{
return BoolResult(true);
}
private byte[] BoolResult(bool value)
{
XmlDocument doc = new XmlDocument();
XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration,
"", "");
doc.AppendChild(xmlnode);
XmlElement rootElement = doc.CreateElement("", "ServerResponse",
"");
doc.AppendChild(rootElement);
XmlElement result = doc.CreateElement("", "RESULT", "");
result.AppendChild(doc.CreateTextNode(value.ToString()));
rootElement.AppendChild(result);
return Util.DocToBytes(doc);
}
#endregion
}
}

View File

@@ -1,167 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Data;
using OpenSim.Framework;
using OpenSim.Services.Base;
using OpenSim.Services.Interfaces;
namespace OpenSim.OfflineIM
{
public class OfflineIMService : ServiceBase, IOfflineIMService
{
//private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IOfflineIMData m_Database = null;
private int m_MaxOfflineIMs = 25;
private XmlSerializer m_serializer;
private static bool m_Initialized = false;
public OfflineIMService(IConfigSource config) : base(config)
{
string dllName = string.Empty;
string connString = string.Empty;
string realm = "im_offline";
//
// Try reading the [DatabaseService] section, if it exists
//
IConfig dbConfig = config.Configs["DatabaseService"];
if (dbConfig is not null)
{
if (dllName.Length == 0)
dllName = dbConfig.GetString("StorageProvider", string.Empty);
if (connString.Length == 0)
connString = dbConfig.GetString("ConnectionString", string.Empty);
}
//
// [Messaging] section overrides [DatabaseService], if it exists
//
IConfig imConfig = config.Configs["Messaging"];
if (imConfig is not null)
{
dllName = imConfig.GetString("StorageProvider", dllName);
connString = imConfig.GetString("ConnectionString", connString);
realm = imConfig.GetString("Realm", realm);
m_MaxOfflineIMs = imConfig.GetInt("MaxOfflineIMs", m_MaxOfflineIMs);
}
//
// We tried, but this doesn't exist. We can't proceed.
//
if (string.IsNullOrEmpty(dllName))
throw new Exception("No StorageProvider configured");
m_Database = LoadPlugin<IOfflineIMData>(dllName, new Object[] { connString, realm });
if (m_Database is null)
throw new Exception("Could not find a storage interface in the given module " + dllName);
m_serializer = new XmlSerializer(typeof(GridInstantMessage));
if (!m_Initialized)
{
m_Database.DeleteOld();
m_Initialized = true;
}
}
public List<GridInstantMessage> GetMessages(UUID principalID)
{
List<GridInstantMessage> ims = new List<GridInstantMessage>();
OfflineIMData[] messages = m_Database.Get("PrincipalID", principalID.ToString());
if (messages is null || messages.Length == 0)
return ims;
foreach (OfflineIMData m in messages)
{
using (MemoryStream mstream = new MemoryStream(Encoding.UTF8.GetBytes(m.Data["Message"])))
{
GridInstantMessage im = (GridInstantMessage)m_serializer.Deserialize(mstream);
ims.Add(im);
}
}
// Then, delete them
m_Database.Delete("PrincipalID", principalID.ToString());
return ims;
}
public bool StoreMessage(GridInstantMessage im, out string reason)
{
reason = string.Empty;
// Check limits
UUID principalID = new UUID(im.toAgentID);
long count = m_Database.GetCount("PrincipalID", principalID.ToString());
if (count >= m_MaxOfflineIMs)
{
reason = "Number of offline IMs has maxed out";
return false;
}
string imXml;
using (MemoryStream mstream = new MemoryStream())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Util.UTF8NoBomEncoding;
using (XmlWriter writer = XmlWriter.Create(mstream, settings))
{
m_serializer.Serialize(writer, im);
writer.Flush();
imXml = Util.UTF8NoBomEncoding.GetString(mstream.ToArray());
}
}
OfflineIMData data = new OfflineIMData();
data.PrincipalID = principalID;
data.FromID = new UUID(im.fromAgentID);
data.Data = new Dictionary<string, string>();
data.Data["Message"] = imXml;
return m_Database.Store(data);
}
public void DeleteMessages(UUID userID)
{
m_Database.Delete("PrincipalID", userID.ToString());
m_Database.Delete("FromID", userID.ToString());
}
}
}

View File

@@ -1,428 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace osWebRtcVoice
{
// There are several different hashing systems ranging from int's to SHA versions.
// The model here is to create a hasher of the desired type, do Add's of things to
// hash, and complete with a Finish() to return a BHash that contains the hash value.
// Since some hash functions are incremental (doing Add's) while some are buffer
// oriented (create a hash of a byte buffer), the interface is built to cover both.
// Some optimizations are implemented internally (like not copying the buffer
// for buffer based hashers if Finish(bytes) is used).
//
// var hasher = new BHashSHA256();
// BHash bHash = hasher.Finish(buffer);
// byte[] theHash = bHash.ToByte();
//
// Note that BHash has IEquatable and IComparible so it can be used in Dictionaries
// and sorted Lists.
//
// The C# GetHashCode() method returns an int that is usually based on location.
// Signatures should really be at least 64 bits so these routines generate
// ulong's for hashes and fold them to make the int for GetHashCode().
// Create a BHasher, do a bunch of 'Add's, then Finish().
public interface IBHasher {
// Create a new object implementing BHasher, then Add values to be hashed
void Add(byte c);
void Add(ushort c);
void Add(uint c);
void Add(ulong c);
void Add(float c);
void Add(byte[] c, int offset, int len);
void Add(string c);
void Add(BHash c);
BHash Finish();
// Finish and add byte array.
// If no Add's before, can do the hashing without copying the byte array
BHash Finish(byte[] c);
BHash Finish(byte[] c, int offset, int len);
// Get the hash code after doing a Finish()
BHash Hash();
}
// ======================================================================
// BHasher computes a BHash which holds the hash value after Finish() is called.
public abstract class BHash : IEquatable<BHash>, IComparable<BHash> {
public abstract override string ToString();
public abstract byte[] ToBytes();
public abstract ulong ToULong(); // returns the hash of the hash if not int based hash
public abstract bool Equals(BHash other);
public abstract int CompareTo(BHash obj);
// public abstract int Compare(BHash x, BHash y); // TODO: do we need this function?
public abstract override int GetHashCode(); // to match the C# standard hash function
}
// A hash that is an UInt64
public class BHashULong : BHash {
protected ulong _hash;
public BHashULong() {
_hash = 5131;
}
public BHashULong(ulong initialHash) {
_hash = initialHash;
}
// the .NET GetHashCode uses an int. Make conversion easy.
public BHashULong(int initialHash) {
_hash = (ulong)initialHash;
}
public override string ToString() {
return _hash.ToString();
}
public override byte[] ToBytes() {
return BitConverter.GetBytes(_hash);
}
public override ulong ToULong() {
return _hash;
}
public override bool Equals(BHash other) {
bool ret = false;
if (other != null) {
BHash bh = other as BHashULong;
if (bh != null) {
ret = _hash.Equals(bh.ToULong());
}
}
return ret;
}
public override int CompareTo(BHash other) {
int ret = 1;
if (other != null) {
if (other is BHashULong bh) {
ret = _hash.CompareTo(bh.ToULong());
}
}
return ret;
}
public override int GetHashCode() {
ulong upper = (_hash >> 32) & 0xffffffff;
ulong lower = _hash & 0xffffffff;
return (int)(upper ^ lower);
}
}
// A hash that is an array of bytes
public class BHashBytes : BHash {
private readonly byte[] _hash;
public BHashBytes() {
_hash = new byte[0];
}
public BHashBytes(byte[] initialHash) {
_hash = initialHash;
}
public override string ToString() {
// BitConverter puts a hyphen between each byte. Remove them
return BitConverter.ToString(_hash).Replace("-", String.Empty);
}
public override byte[] ToBytes() {
return _hash;
}
public override ulong ToULong() {
return this.MakeHashCode();
}
public override bool Equals(BHash other) {
bool ret = false;
if (other != null) {
BHash bh = other as BHashBytes;
if (bh != null) {
ret = _hash.Equals(bh.ToBytes());
}
}
return ret;
}
public override int CompareTo(BHash other) {
int ret = 1;
if (other != null) {
BHash bh = other as BHashBytes;
if (bh != null) {
byte[] otherb = bh.ToBytes();
if (_hash.Length != otherb.Length) {
ret = _hash.Length.CompareTo(otherb.Length);
}
else {
ret = 0; // start off assuming they are equal
for (int ii = 0; ii < _hash.Length; ii++) {
ret = _hash[ii].CompareTo(otherb[ii]);
if (ret != 0) break;
}
}
}
}
return ret;
}
public override int GetHashCode()
{
ulong hashhash = this.MakeHashCode();
ulong upper = (hashhash >> 32 )& 0xffffffff;
ulong lower = hashhash & 0xffffffff;
return (int)(upper ^ lower);
}
public ulong MakeHashCode() {
ulong h = 5381;
for (int ii = 0; ii < _hash.Length; ii++) {
h = ((h << 5) + h) + (ulong)(_hash[ii]);
}
return h;
}
}
// ======================================================================
// ======================================================================
public abstract class BHasher : IBHasher
{
public BHasher() {
}
public abstract void Add(byte c);
public abstract void Add(ushort c);
public abstract void Add(uint c);
public abstract void Add(ulong c);
public abstract void Add(float c);
public abstract void Add(byte[] c, int offset, int len);
public abstract void Add(string c);
public abstract void Add(BHash c);
public abstract BHash Finish();
public abstract BHash Finish(byte[] c);
public abstract BHash Finish(byte[] c, int offset, int len);
public abstract BHash Hash();
}
// A hasher that builds up a buffer of bytes ('building') and then hashes over same
public abstract class BHasherBytes : BHasher {
protected byte[] building;
protected int buildingLoc;
protected int allocStep = 1024;
public BHasherBytes() : base() {
building = new byte[allocStep];
buildingLoc = 0;
}
private byte[] oneByte = new byte[1];
public override void Add(byte c) {
oneByte[0] = c;
AddBytes(oneByte, 0, 1);
}
public override void Add(ushort c) {
byte[] bytes = BitConverter.GetBytes(c);
AddBytes(bytes, 0, bytes.Length);
}
public override void Add(uint c) {
byte[] bytes = BitConverter.GetBytes(c);
AddBytes(bytes, 0, bytes.Length);
}
public override void Add(ulong c) {
byte[] bytes = BitConverter.GetBytes(c);
AddBytes(bytes, 0, bytes.Length);
}
public override void Add(float c) {
byte[] bytes = BitConverter.GetBytes(c);
AddBytes(bytes, 0, bytes.Length);
}
public override void Add(byte[] c, int offset, int len) {
AddBytes(c, offset, len);
}
public override void Add(string c)
{
byte[] bytes = Encoding.UTF8.GetBytes(c);
AddBytes(bytes, 0, bytes.Length);
}
public override void Add(BHash c) {
byte[] bytes = c.ToBytes();
AddBytes(bytes, 0, bytes.Length);
}
// Implemented by derived class
// public abstract BHash Finish();
// Helper function for simple byte array
public override BHash Finish(byte[] c) {
return this.Finish(c, 0, c.Length);
}
// Implemented by derived class
// public abstract BHash Finish(byte[] c, int offset, int len);
// Implemented by derived class
// public abstract BHash Hash();
// Add the given number of bytes to the byte array being built
protected void AddBytes(byte[] addition, int offset, int len) {
// byte[] tempBytes = new byte[len]; // DEBUG DEBUG
// Array.Copy(addition, offset, tempBytes, 0, len); // DEBUG DEBUG
// System.Console.WriteLine(String.Format("AddBytes: offset={0}, len={1}, bytes={2}", // DEBUG DEBUG
// offset, len, BitConverter.ToString(tempBytes).Replace("-", String.Empty))); // DEBUG DEBUG
if (len < 0 || offset < 0 || addition == null) {
throw new ArgumentException(String.Format("BHasherBytes.AddBytes: Bad parameters. offset={0}, len={1}",
offset, len));
}
if (offset + len > addition.Length) {
throw new ArgumentException(String.Format("BHasherBytes.AddBytes: addition parameters off end of array. addition.len={0}, offset={1}, len={2}",
addition.Length, offset, len));
}
if (len > 0) {
if (buildingLoc + len > building.Length) {
// New data requires expanding the data buffer
byte[] newBuilding = new byte[buildingLoc + len + allocStep];
Buffer.BlockCopy(building, 0, newBuilding, 0, buildingLoc);
building = newBuilding;
}
Buffer.BlockCopy(addition, offset, building, buildingLoc, len);
buildingLoc += len;
}
}
}
// ======================================================================
// ULong hash code taken from Meshmerizer
public class BHasherMdjb2 : BHasherBytes, IBHasher {
BHashULong hash = new BHashULong();
public BHasherMdjb2() : base() {
}
public override BHash Finish() {
hash = new BHashULong(ComputeMdjb2Hash(building, 0, buildingLoc));
return hash;
}
public override BHash Finish(byte[] c, int offset, int len) {
if (building.Length > 0) {
AddBytes(c, offset, len);
hash = new BHashULong(ComputeMdjb2Hash(building, 0, buildingLoc));
}
else {
// if no 'Add's were done, don't copy the input data
hash = new BHashULong(ComputeMdjb2Hash(c, offset, len));
}
return hash;
}
private ulong ComputeMdjb2Hash(byte[] c, int offset, int len) {
ulong h = 5381;
for (int ii = offset; ii < offset+len; ii++) {
h = ((h << 5) + h) + (ulong)(c[ii]);
}
return h;
}
public override BHash Hash() {
return hash;
}
}
// ======================================================================
public class BHasherMD5 : BHasherBytes, IBHasher {
BHashBytes hash = new BHashBytes();
public BHasherMD5() : base() {
}
public override BHash Finish() {
MD5 md5 = MD5.Create();
hash = new BHashBytes(md5.ComputeHash(building, 0, buildingLoc));
return hash;
}
public override BHash Finish(byte[] c) {
return this.Finish(c, 0, c.Length);
}
public override BHash Finish(byte[] c, int offset, int len) {
MD5 md5 = MD5.Create();
if (building.Length > 0) {
AddBytes(c, offset, len);
hash = new BHashBytes(md5.ComputeHash(building, 0, buildingLoc));
}
else {
// if no 'Add's were done, don't copy the input data
hash = new BHashBytes(md5.ComputeHash(c, offset, len));
}
return hash;
}
public override BHash Hash() {
return hash;
}
}
// ======================================================================
public class BHasherSHA256 : BHasherBytes, IBHasher {
BHashBytes hash = new BHashBytes();
public BHasherSHA256() : base() {
}
public override BHash Finish() {
using (SHA256 SHA256 = SHA256.Create()) {
hash = new BHashBytes(SHA256.ComputeHash(building, 0, buildingLoc));
}
return hash;
}
public override BHash Finish(byte[] c) {
return this.Finish(c, 0, c.Length);
}
public override BHash Finish(byte[] c, int offset, int len) {
using (SHA256 SHA256 = SHA256.Create()) {
if (buildingLoc > 0) {
AddBytes(c, offset, len);
hash = new BHashBytes(SHA256.ComputeHash(building, 0, buildingLoc));
}
else {
// if no 'Add's were done, don't copy the input data
hash = new BHashBytes(SHA256.ComputeHash(c, offset, len));
}
}
return hash;
}
public override BHash Hash() {
return hash;
}
}
}

View File

@@ -1,251 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using log4net;
namespace osWebRtcVoice
{
// Encapsulization of a Session to the Janus server
public class JanusAudioBridge : JanusPlugin
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[JANUS AUDIO BRIDGE]";
// Wrapper around the session connection to Janus-gateway
public JanusAudioBridge(JanusSession pSession) : base(pSession, "janus.plugin.audiobridge")
{
// m_log.DebugFormat("{0} JanusAudioBridge constructor", LogHeader);
}
public override void Dispose()
{
if (IsConnected)
{
// Close the handle
}
base.Dispose();
}
public async Task<AudioBridgeResp> SendAudioBridgeMsg(PluginMsgReq pMsg)
{
AudioBridgeResp ret = null;
try
{
ret = new AudioBridgeResp(await SendPluginMsg(pMsg));
}
catch (Exception e)
{
m_log.ErrorFormat("{0} SendPluginMsg. Exception {1}", LogHeader, e);
}
return ret;
}
/// <summary>
/// Create a room with the given criteria. This talks to Janus to create the room.
/// If the room with this RoomId already exists, just return it.
/// Janus could create and return the RoomId but this presumes that the Janus server
/// is only being used for our voice service.
/// </summary>
/// <param name="pRoomId">integer room ID to create</param>
/// <param name="pSpatial">boolean on whether room will be spatial or non-spatial</param>
/// <param name="pRoomDesc">added as "description" to the created room</param>
/// <returns></returns>
public async Task<JanusRoom> CreateRoom(int pRoomId, bool pSpatial, string pRoomDesc)
{
JanusRoom ret = null;
try
{
JanusMessageResp resp = await SendPluginMsg(new AudioBridgeCreateRoomReq(pRoomId, pSpatial, pRoomDesc));
AudioBridgeResp abResp = new AudioBridgeResp(resp);
m_log.DebugFormat("{0} CreateRoom. ReturnCode: {1}", LogHeader, abResp.AudioBridgeReturnCode);
switch (abResp.AudioBridgeReturnCode)
{
case "created":
ret = new JanusRoom(this, pRoomId);
break;
case "event":
if (abResp.AudioBridgeErrorCode == 486)
{
m_log.WarnFormat("{0} CreateRoom. Room {1} already exists. Reusing! {2}", LogHeader, pRoomId, abResp.ToString());
// if room already exists, just use it
ret = new JanusRoom(this, pRoomId);
}
else
{
m_log.ErrorFormat("{0} CreateRoom. XX Room creation failed: {1}", LogHeader, abResp.ToString());
}
break;
default:
m_log.ErrorFormat("{0} CreateRoom. YY Room creation failed: {1}", LogHeader, abResp.ToString());
break;
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} CreateRoom. Exception {1}", LogHeader, e);
}
return ret;
}
public async Task<bool> DestroyRoom(JanusRoom janusRoom)
{
bool ret = false;
try
{
JanusMessageResp resp = await SendPluginMsg(new AudioBridgeDestroyRoomReq(janusRoom.RoomId));
ret = true;
}
catch (Exception e)
{
m_log.ErrorFormat("{0} DestroyRoom. Exception {1}", LogHeader, e);
}
return ret;
}
// Constant used to denote that this is a spatial audio room for the region (as opposed to parcels)
public const int REGION_ROOM_ID = -999;
private Dictionary<int, JanusRoom> _rooms = new Dictionary<int, JanusRoom>();
// Calculate a room number for the given parameters. The room number is a hash of the parameters.
// The attempt is to deterministicly create a room number so all regions will generate the
// same room number across sessions and across the grid.
// getHashCode() is not deterministic across sessions.
public static int CalcRoomNumber(string pRegionId, string pChannelType, int pParcelLocalID, string pChannelID)
{
var hasher = new BHasherMdjb2();
// If there is a channel specified it must be group
switch (pChannelType)
{
case "local":
// A "local" channel is unique to the region and parcel
hasher.Add(pRegionId);
hasher.Add(pChannelType);
hasher.Add(pParcelLocalID);
break;
case "multiagent":
// A "multiagent" channel is unique to the grid
// should add a GridId here
hasher.Add(pChannelID);
hasher.Add(pChannelType);
break;
default:
throw new Exception("Unknown channel type: " + pChannelType);
}
var hashed = hasher.Finish();
// The "Abs()" is because Janus room number must be a positive integer
// And note that this is the BHash.GetHashCode() and not Object.getHashCode().
int roomNumber = Math.Abs(hashed.GetHashCode());
return roomNumber;
}
public async Task<JanusRoom> SelectRoom(string pRegionId, string pChannelType, bool pSpatial, int pParcelLocalID, string pChannelID)
{
int roomNumber = CalcRoomNumber(pRegionId, pChannelType, pParcelLocalID, pChannelID);
// Should be unique for the given use and channel type
m_log.DebugFormat("{0} SelectRoom: roomNumber={1}", LogHeader, roomNumber);
// Check to see if the room has already been created
lock (_rooms)
{
if (_rooms.ContainsKey(roomNumber))
{
return _rooms[roomNumber];
}
}
// The room doesn't exist. Create it.
string roomDesc = pRegionId + "/" + pChannelType + "/" + pParcelLocalID + "/" + pChannelID;
JanusRoom ret = await CreateRoom(roomNumber, pSpatial, roomDesc);
JanusRoom existingRoom = null;
if (ret is not null)
{
lock (_rooms)
{
if (_rooms.ContainsKey(roomNumber))
{
// If the room was created while we were waiting,
existingRoom = _rooms[roomNumber];
}
else
{
// Our room is the first one created. Save it.
_rooms[roomNumber] = ret;
}
}
}
if (existingRoom is not null)
{
// The room we created was already created by someone else. Delete ours and use the existing one
await DestroyRoom(ret);
ret = existingRoom;
}
return ret;
}
// Return the room with the given room ID or 'null' if no such room
public JanusRoom GetRoom(int pRoomId)
{
JanusRoom ret = null;
lock (_rooms)
{
_rooms.TryGetValue(pRoomId, out ret);
}
return ret;
}
public override void Handle_Event(JanusMessageResp pResp)
{
base.Handle_Event(pResp);
AudioBridgeResp abResp = new AudioBridgeResp(pResp);
if (abResp is not null && abResp.AudioBridgeReturnCode == "event")
{
// An audio bridge event!
m_log.DebugFormat("{0} Handle_Event. {1}", LogHeader, abResp.ToString());
}
}
public override void Handle_Message(JanusMessageResp pResp)
{
base.Handle_Message(pResp);
AudioBridgeResp abResp = new AudioBridgeResp(pResp);
if (abResp is not null && abResp.AudioBridgeReturnCode == "event")
{
// An audio bridge event!
m_log.DebugFormat("{0} Handle_Event. {1}", LogHeader, abResp.ToString());
}
}
}
}

View File

@@ -1,639 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using OpenMetaverse.StructuredData;
using OpenMetaverse;
using log4net;
namespace osWebRtcVoice
{
/// <summary>
/// Wrappers around the Janus requests and responses.
/// Since the messages are JSON and, because of the libraries we are using,
/// the internal structure is an OSDMap, these routines hold all the logic
/// to getting and setting the values in the JSON.
/// </summary>
public class JanusMessage
{
protected static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected static readonly string LogHeader = "[JANUS MESSAGE]";
protected OSDMap m_message = new OSDMap();
public JanusMessage()
{
}
// A basic Janus message is:
// {
// "janus": "operation",
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea",
// "session_id": 5645225333294848, // optional, gives the session ID
// "handle_id": 6969906757968657 // optional, gives the plugin handle ID
// "sender": 6969906757968657 // optional, gives the ID of the sending subsystem
// "jsep": { "type": "offer", "sdp": "..." } // optional, gives the SDP
// }
public JanusMessage(string pType) : this()
{
m_message["janus"] = pType;
m_message["transaction"] = UUID.Random().ToString();
}
public OSDMap RawBody => m_message;
public string TransactionId {
get { return m_message.TryGetString("transaction", out string tid) ? tid : null; }
set { m_message["transaction"] = value; }
}
public string Sender {
get { return m_message.TryGetString("sender", out string tid) ? tid : null; }
set { m_message["sender"] = value; }
}
public OSDMap Jsep {
get { return m_message.TryGetOSDMap("jsep", out OSDMap jsep) ? jsep : null; }
set { m_message["jsep"] = value; }
}
public void SetJsep(string pOffer, string pSdp)
{
m_message["jsep"] = new OSDMap()
{
{ "type", pOffer },
{ "sdp", pSdp }
};
}
public void AddAPIToken(string pToken)
{
m_message["apisecret"] = pToken;
}
// Note that the session_id is a long number in the JSON so we convert the string.
public string sessionId {
get { return m_message.TryGetValue("session_id", out OSD tmposd) ? OSDToLong(tmposd).ToString() : string.Empty; }
set { m_message["session_id"] = long.Parse(value); }
}
public bool hasSessionId { get { return m_message.ContainsKey("session_id"); } }
public void AddSessionId(string pToken)
{
AddSessionId(long.Parse(pToken));
}
public void AddSessionId(long pToken)
{
m_message["session_id"] = pToken;
}
public bool hasHandleId { get { return m_message.ContainsKey("handle_id"); } }
public void AddHandleId(string pToken)
{
m_message["handle_id"] = long.Parse(pToken);
}
public string sender
{
get { return m_message.TryGetString("sender", out string str) ? str : string.Empty; }
}
public virtual string ToJson()
{
return m_message.ToString();
}
public override string ToString()
{
return m_message.ToString();
}
// Utility function to convert an OSD object to an long. The OSD object can be an OSDInteger
// or an OSDArray of 4 or 8 integers.
// This exists because the JSON to OSD parser can return an OSDArray for a long number
// since there is not an OSDLong type.
// The design of the OSD conversion functions kinda needs one to know how the number
// is stored in order to extract it. Like, if it's stored as a long value (8 bytes)
// and one fetches it with .AsInteger(), it will return the first 4 bytes as an integer
// and not the long value. So this function looks at the type of the OSD object and
// extracts the number appropriately.
public static long OSDToLong(OSD pIn)
{
long ret = 0;
switch (pIn.Type)
{
case OSDType.Integer:
ret = (long)(pIn as OSDInteger).AsInteger();
break;
case OSDType.Binary:
byte[] value = (pIn as OSDBinary).value;
if (value.Length == 4)
{
ret = (long)(pIn as OSDBinary).AsInteger();
}
if (value.Length == 8)
{
ret = (pIn as OSDBinary).AsLong();
}
break;
case OSDType.Array:
if ((pIn as OSDArray).Count == 4)
{
ret = (long)pIn.AsInteger();
}
if ((pIn as OSDArray).Count == 8)
{
ret = pIn.AsLong();
}
break;
}
return ret;
}
}
// ==============================================================
// A Janus request message is a basic Janus message with an API token
public class JanusMessageReq : JanusMessage
{
public JanusMessageReq(string pType) : base(pType)
{
}
}
// ==============================================================
// Janus message response is a basic Janus message with the response data
// {
// "janus": "success",
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea", // ID of the requesting message
// "data": { ... } // the response data
// "error": { "code": 123, "reason": "..." } // if there was an error
// }
// The "janus" return code changes depending on the request. The above is for
// a successful response. Could be
// "event": for an event message (See JanusEventResp)
// "keepalive": for a keepalive event
public class JanusMessageResp : JanusMessage
{
public JanusMessageResp() : base()
{
}
public JanusMessageResp(string pType) : base(pType)
{
}
public JanusMessageResp(OSDMap pMap) : base()
{
m_message = pMap;
}
public static JanusMessageResp FromJson(string pJson)
{
var newBody = OSDParser.DeserializeJson(pJson) as OSDMap;
return new JanusMessageResp(newBody);
}
// Return the "data" portion of the response as an OSDMap or null if there is none
public OSDMap dataSection { get { return m_message.TryGetOSDMap("data", out OSDMap osdm) ? osdm : null; } }
// Check if a successful response code is in the response
public virtual bool isSuccess { get { return CheckReturnCode("success"); } }
public virtual bool isEvent { get { return CheckReturnCode("event"); } }
public virtual bool isError { get { return CheckReturnCode("error"); } }
public virtual bool CheckReturnCode(string pCode)
{
return ReturnCode == pCode;
}
public virtual string ReturnCode { get {
string ret = String.Empty;
if (m_message is not null && m_message.ContainsKey("janus"))
{
ret = m_message["janus"].AsString();
}
return ret;
} }
}
// ==============================================================
// An error response is a Janus response with an error code and reason.
// {
// "janus": "error",
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea", // ID of the requesting message
// "error": { "code": 123, "reason": "..." } // if there was an error
// }
public class ErrorResp : JanusMessageResp
{
public ErrorResp() : base("error")
{
}
public ErrorResp(string pType) : base(pType)
{
}
public ErrorResp(JanusMessageResp pResp) : base(pResp.RawBody)
{
}
public void SetError(int pCode, string pReason)
{
m_message["error"] = new OSDMap()
{
{ "code", pCode },
{ "reason", pReason }
};
}
// Dig through the response to get the error code or 0 if there is none
public int errorCode { get {
int ret = 0;
if (m_message.ContainsKey("error"))
{
var err = m_message["error"];
if (err is OSDMap)
ret = (int)OSDToLong((err as OSDMap)["code"]);
}
return ret;
}}
// Dig through the response to get the error reason or empty string if there is none
public string errorReason { get {
string ret = String.Empty;
if (m_message.ContainsKey("error"))
{
var err = m_message["error"];
if (err is OSDMap)
ret = (err as OSDMap)["reason"];
}
// return ((m_message["error"] as OSDMap)?["reason"]) ?? String.Empty;
return ret;
}}
}
// ==============================================================
// Create session request and response
public class CreateSessionReq : JanusMessageReq
{
public CreateSessionReq() : base("create")
{
}
}
public class CreateSessionResp : JanusMessageResp
{
public CreateSessionResp(JanusMessageResp pResp) : base(pResp.RawBody)
{ }
public string returnedId { get {
// The JSON response gives a long number (not a string)
// and the ODMap conversion interprets it as a long (OSDLong).
// If one just does a "ToString()" on the OSD object, you
// get an interpretation of the binary value.
return dataSection.ContainsKey("id") ? OSDToLong(dataSection["id"]).ToString() : String.Empty;
}}
}
// ==============================================================
public class DestroySessionReq : JanusMessageReq
{
public DestroySessionReq() : base("destroy")
{
// Doesn't include the session ID because it is the URI
}
}
// ==============================================================
public class TrickleReq : JanusMessageReq
{
// An empty trickle request is used to signal the end of the trickle
public TrickleReq(JanusViewerSession pVSession) : base("trickle")
{
m_message["candidate"] = new OSDMap()
{
{ "completed", true },
};
}
public TrickleReq(JanusViewerSession pVSession, OSD pCandidates) : base("trickle")
{
m_message["viewer_session"] = pVSession.ViewerSessionID;
if (pCandidates is OSDArray)
m_message["candidates"] = pCandidates;
else
m_message["candidate"] = pCandidates;
}
}
// ==============================================================
public class AttachPluginReq : JanusMessageReq
{
public AttachPluginReq(string pPlugin) : base("attach")
{
m_message["plugin"] = pPlugin;
}
}
public class AttachPluginResp : JanusMessageResp
{
public AttachPluginResp(JanusMessageResp pResp) : base(pResp.RawBody)
{ }
public string pluginId { get {
return dataSection.ContainsKey("id") ? OSDToLong(dataSection["id"]).ToString() : String.Empty;
}}
}
// ==============================================================
public class DetachPluginReq : JanusMessageReq
{
public DetachPluginReq() : base("detach")
{
// Doesn't include the session ID or plugin ID because it is the URI
}
}
// ==============================================================
public class HangupReq : JanusMessageReq
{
public HangupReq() : base("hangup")
{
// Doesn't include the session ID or plugin ID because it is the URI
}
}
// ==============================================================
// Plugin messages are defined here as wrappers around OSDMap.
// The ToJson() method is overridden to put the OSDMap into the
// message body.
// A plugin request is formatted like:
// {
// "janus": "message",
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea",
// "body": {
// "request": "create",
// "room": 10,
// "is_private": false,
// }
public class PluginMsgReq : JanusMessageReq
{
private OSDMap m_body = new OSDMap();
// Note that the passed OSDMap is placed in the "body" section of the message
public PluginMsgReq(OSDMap pBody) : base("message")
{
m_body = pBody;
}
public void AddStringToBody(string pKey, string pValue)
{
m_body[pKey] = pValue;
}
public void AddIntToBody(string pKey, int pValue)
{
m_body[pKey] = pValue;
}
public void AddBoolToBody(string pKey, bool pValue)
{
m_body[pKey] = pValue;
}
public void AddOSDToBody(string pKey, OSD pValue)
{
m_body[pKey] = pValue;
}
public override string ToJson()
{
m_message["body"] = m_body;
return base.ToJson();
}
}
// A plugin response is formatted like:
// {
// "janus": "success",
// "session_id": 5645225333294848,
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea",
// "sender": 6969906757968657,
// "plugindata": {
// "plugin": "janus.plugin.audiobridge",
// "data": {
// "audiobridge": "created",
// "room": 10,
// "permanent": false
// }
// }
public class PluginMsgResp : JanusMessageResp
{
public OSDMap m_pluginData;
public OSDMap m_data;
public PluginMsgResp(JanusMessageResp pResp) : base(pResp.RawBody)
{
if (m_message is not null && m_message.ContainsKey("plugindata"))
{
// Move the plugin data up into the m_data var so it is easier to get to
m_pluginData = m_message["plugindata"] as OSDMap;
if (m_pluginData is not null && m_pluginData.ContainsKey("data"))
{
m_data = m_pluginData["data"] as OSDMap;
// m_log.DebugFormat("{0} AudioBridgeResp. Found both plugindata and data: data={1}", LogHeader, m_data.ToString());
}
}
}
public OSDMap PluginRespData { get { return m_data; } }
// Get an integer value for a key in the response data or zero if not there
public int PluginRespDataInt(string pKey)
{
if (m_data is null)
return 0;
return m_data.ContainsKey(pKey) ? (int)OSDToLong(m_data[pKey]) : 0;
}
// Get a string value for a key in the response data or empty string if not there
public string PluginRespDataString(string pKey)
{
if (m_data is null)
return String.Empty;
return m_data.ContainsKey(pKey) ? m_data[pKey].AsString() : String.Empty;
}
}
// ==============================================================
// Plugin messages for the audio bridge.
// Audiobridge responses are formatted like:
// {
// "janus": "success",
// "session_id": 5645225333294848,
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea",
// "sender": 6969906757968657,
// "plugindata": {
// "plugin": "janus.plugin.audiobridge",
// "data": {
// "audiobridge": "created",
// "room": 10,
// "permanent": false
// }
// }
public class AudioBridgeResp: PluginMsgResp
{
public AudioBridgeResp(JanusMessageResp pResp) : base(pResp)
{
}
public override bool isSuccess { get { return PluginRespDataString("audiobridge") == "success"; } }
// Return the return code if it is in the response or empty string if not
public string AudioBridgeReturnCode { get { return PluginRespDataString("audiobridge"); } }
// Return the error code if it is in the response or zero if not
public int AudioBridgeErrorCode { get { return PluginRespDataInt("error_code"); } }
// Return the room ID if it is in the response or zero if not
public int RoomId { get { return PluginRespDataInt("room"); } }
}
// ==============================================================
public class AudioBridgeCreateRoomReq : PluginMsgReq
{
public AudioBridgeCreateRoomReq(int pRoomId) : this(pRoomId, false, null)
{
}
public AudioBridgeCreateRoomReq(int pRoomId, bool pSpatial, string pDesc) : base(new OSDMap() {
{ "room", pRoomId },
{ "request", "create" },
{ "is_private", false },
{ "permanent", false },
{ "sampling_rate", 48000 },
{ "spatial_audio", pSpatial },
{ "denoise", false },
{ "record", false }
})
{
if (!String.IsNullOrEmpty(pDesc))
AddStringToBody("description", pDesc);
}
}
// ==============================================================
public class AudioBridgeDestroyRoomReq : PluginMsgReq
{
public AudioBridgeDestroyRoomReq(int pRoomId) : base(new OSDMap() {
{ "request", "destroy" },
{ "room", pRoomId },
{ "permanent", true }
})
{
}
}
// ==============================================================
public class AudioBridgeJoinRoomReq : PluginMsgReq
{
public AudioBridgeJoinRoomReq(int pRoomId, string pAgentName) : base(new OSDMap() {
{ "request", "join" },
{ "room", pRoomId },
{ "display", pAgentName }
})
{
}
}
// A successful response contains the participant ID and the SDP
public class AudioBridgeJoinRoomResp : AudioBridgeResp
{
public AudioBridgeJoinRoomResp(JanusMessageResp pResp) : base(pResp)
{
}
public int ParticipantId { get { return PluginRespDataInt("id"); } }
}
// ==============================================================
public class AudioBridgeConfigRoomReq : PluginMsgReq
{
// TODO:
public AudioBridgeConfigRoomReq(int pRoomId, string pSdp) : base(new OSDMap() {
{ "request", "configure" },
})
{
}
}
public class AudioBridgeConfigRoomResp : AudioBridgeResp
{
// TODO:
public AudioBridgeConfigRoomResp(JanusMessageResp pResp) : base(pResp)
{
}
}
// ==============================================================
public class AudioBridgeLeaveRoomReq : PluginMsgReq
{
public AudioBridgeLeaveRoomReq(int pRoomId, int pAttendeeId) : base(new OSDMap() {
{ "request", "leave" },
{ "room", pRoomId },
{ "id", pAttendeeId }
})
{
}
}
// ==============================================================
public class AudioBridgeListRoomsReq : PluginMsgReq
{
public AudioBridgeListRoomsReq() : base(new OSDMap() {
{ "request", "list" }
})
{
}
}
// ==============================================================
public class AudioBridgeListParticipantsReq : PluginMsgReq
{
public AudioBridgeListParticipantsReq(int pRoom) : base(new OSDMap() {
{ "request", "listparticipants" },
{ "room", pRoom }
})
{
}
}
// ==============================================================
public class AudioBridgeEvent : AudioBridgeResp
{
public AudioBridgeEvent(JanusMessageResp pResp) : base(pResp)
{
}
}
// ==============================================================
// The LongPoll request returns events from the plugins. These are formatted
// like the other responses but are not responses to requests.
// They are formatted like:
// {
// "janus": "event",
// "sender": 6969906757968657,
// "transaction": "baefcec8-70c5-4e79-b2c1-d653b9617dea",
// "plugindata": {
// "plugin": "janus.plugin.audiobridge",
// "data": {
// "audiobridge": "event",
// "room": 10,
// "participants": 1,
// "participants": [
// {
// "id": 1234,
// "display": "John Doe",
// "audio_level": 0.0,
// "video_room": false,
// "video_muted": false,
// "audio_muted": false,
// "feed": 1234
// }
// ]
// }
// }
public class EventResp : JanusMessageResp
{
public EventResp() : base()
{
}
public EventResp(string pType) : base(pType)
{
}
public EventResp(JanusMessageResp pResp) : base(pResp.RawBody)
{
}
}
// ==============================================================
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using System.Threading.Tasks;
using OpenSim.Framework;
using OpenSim.Services.Interfaces;
using OpenSim.Services.Base;
using OpenMetaverse.StructuredData;
using OpenMetaverse;
using Nini.Config;
using log4net;
namespace osWebRtcVoice
{
// Encapsulization of a Session to the Janus server
public class JanusPlugin : IDisposable
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[JANUS PLUGIN]";
protected IConfigSource _Config;
protected JanusSession _JanusSession;
public string PluginName { get; private set; }
public string PluginId { get; private set; }
public string PluginUri { get ; private set ; }
public bool IsConnected => !String.IsNullOrEmpty(PluginId);
// Wrapper around the session connection to Janus-gateway
public JanusPlugin(JanusSession pSession, string pPluginName)
{
_JanusSession = pSession;
PluginName = pPluginName;
}
public virtual void Dispose()
{
if (IsConnected)
{
// Close the handle
}
}
public Task<JanusMessageResp> SendPluginMsg(OSDMap pParams)
{
return _JanusSession.SendToJanus(new PluginMsgReq(pParams), PluginUri);
}
public Task<JanusMessageResp> SendPluginMsg(PluginMsgReq pJMsg)
{
return _JanusSession.SendToJanus(pJMsg, PluginUri);
}
/// <summary>
/// Make the create a handle to a plugin within the session.
/// </summary>
/// <returns>TRUE if handle was created successfully</returns>
public async Task<bool> Activate(IConfigSource pConfig)
{
_Config = pConfig;
bool ret = false;
try
{
var resp = await _JanusSession.SendToSession(new AttachPluginReq(PluginName));
if (resp is not null && resp.isSuccess)
{
var handleResp = new AttachPluginResp(resp);
PluginId = handleResp.pluginId;
PluginUri = _JanusSession.SessionUri + "/" + PluginId;
m_log.DebugFormat("{0} Activate. Plugin attached. ID={1}, URL={2}", LogHeader, PluginId, PluginUri);
_JanusSession.PluginId = PluginId;
_JanusSession.OnEvent += Handle_Event;
_JanusSession.OnMessage += Handle_Message;
ret = true;
}
else
{
m_log.ErrorFormat("{0} Activate: failed to attach to plugin {1}", LogHeader, PluginName);
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} Activate: exception attaching to plugin {1}: {2}", LogHeader, PluginName, e);
}
return ret;
}
public virtual async Task<bool> Detach()
{
bool ret = false;
if (!IsConnected || _JanusSession is null)
{
m_log.WarnFormat("{0} Detach. Not connected", LogHeader);
return ret;
}
try
{
_JanusSession.OnEvent -= Handle_Event;
_JanusSession.OnMessage -= Handle_Message;
// We send the 'detach' message to the plugin URI
var resp = await _JanusSession.SendToJanus(new DetachPluginReq(), PluginUri);
if (resp is not null && resp.isSuccess)
{
m_log.DebugFormat("{0} Detach. Detached", LogHeader);
ret = true;
}
else
{
m_log.ErrorFormat("{0} Detach: failed", LogHeader);
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} Detach: exception {1}", LogHeader, e);
}
return ret;
}
public virtual void Handle_Event(JanusMessageResp pResp)
{
m_log.DebugFormat("{0} Handle_Event: {1}", LogHeader, pResp.ToString());
}
public virtual void Handle_Message(JanusMessageResp pResp)
{
m_log.DebugFormat("{0} Handle_Message: {1}", LogHeader, pResp.ToString());
}
}
}

View File

@@ -1,138 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using OpenSim.Framework;
using OpenSim.Services.Interfaces;
using OpenSim.Services.Base;
using OpenMetaverse.StructuredData;
using OpenMetaverse;
using Nini.Config;
using log4net;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace osWebRtcVoice
{
// Encapsulization of a Session to the Janus server
public class JanusRoom : IDisposable
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[JANUS ROOM]";
public int RoomId { get; private set; }
private JanusPlugin _AudioBridge;
// Wrapper around the session connection to Janus-gateway
public JanusRoom(JanusPlugin pAudioBridge, int pRoomId)
{
_AudioBridge = pAudioBridge;
RoomId = pRoomId;
}
public void Dispose()
{
// Close the room
}
public async Task<bool> JoinRoom(JanusViewerSession pVSession)
{
bool ret = false;
try
{
// m_log.DebugFormat("{0} JoinRoom. New joinReq for room {1}", LogHeader, RoomId);
// Discovered that AudioBridge doesn't care if the data portion is present
// and, if removed, the viewer complains that the "m=" sections are
// out of order. Not "cleaning" (removing the data section) seems to work.
// string cleanSdp = CleanupSdp(pSdp);
var joinReq = new AudioBridgeJoinRoomReq(RoomId, pVSession.AgentId.ToString());
// joinReq.SetJsep("offer", cleanSdp);
joinReq.SetJsep("offer", pVSession.Offer);
JanusMessageResp resp = await _AudioBridge.SendPluginMsg(joinReq);
AudioBridgeJoinRoomResp joinResp = new AudioBridgeJoinRoomResp(resp);
if (joinResp is not null && joinResp.AudioBridgeReturnCode == "joined")
{
pVSession.ParticipantId = joinResp.ParticipantId;
pVSession.Answer = joinResp.Jsep;
ret = true;
m_log.DebugFormat("{0} JoinRoom. Joined room {1}. Participant={2}", LogHeader, RoomId, pVSession.ParticipantId);
}
else
{
m_log.ErrorFormat("{0} JoinRoom. Failed to join room {1}. Resp={2}", LogHeader, RoomId, joinResp.ToString());
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} JoinRoom. Exception {1}", LogHeader, e);
}
return ret;
}
// TODO: this doesn't work.
// Not sure if it is needed. Janus generates Hangup events when the viewer leaves.
/*
public async Task<bool> Hangup(JanusViewerSession pAttendeeSession)
{
bool ret = false;
try
{
}
catch (Exception e)
{
m_log.ErrorFormat("{0} LeaveRoom. Exception {1}", LogHeader, e);
}
return ret;
}
*/
public async Task<bool> LeaveRoom(JanusViewerSession pAttendeeSession)
{
bool ret = false;
try
{
JanusMessageResp resp = await _AudioBridge.SendPluginMsg(
new AudioBridgeLeaveRoomReq(RoomId, pAttendeeSession.ParticipantId));
}
catch (Exception e)
{
m_log.ErrorFormat("{0} LeaveRoom. Exception {1}", LogHeader, e);
}
return ret;
}
}
}

View File

@@ -1,658 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Mime;
using System.Reflection;
using System.Threading.Tasks;
using OpenMetaverse.StructuredData;
using log4net;
using log4net.Core;
using System.Reflection.Metadata;
using System.Threading;
namespace osWebRtcVoice
{
// Encapsulization of a Session to the Janus server
public class JanusSession : IDisposable
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[JANUS SESSION]";
// Set to 'true' to get the messages send and received from Janus
private bool _MessageDetails = false;
private string _JanusServerURI = String.Empty;
private string _JanusAPIToken = String.Empty;
private string _JanusAdminURI = String.Empty;
private string _JanusAdminToken = String.Empty;
public string JanusServerURI => _JanusServerURI;
public string JanusAdminURI => _JanusAdminURI;
public string SessionId { get; private set; }
public string SessionUri { get ; private set ; }
public string PluginId { get; set; }
private CancellationTokenSource _CancelTokenSource = new CancellationTokenSource();
private HttpClient _HttpClient = new HttpClient();
public bool IsConnected { get; set; }
// Wrapper around the session connection to Janus-gateway
public JanusSession(string pServerURI, string pAPIToken, string pAdminURI, string pAdminToken, bool pDebugMessages = false)
{
m_log.DebugFormat("{0} JanusSession constructor", LogHeader);
_JanusServerURI = pServerURI;
_JanusAPIToken = pAPIToken;
_JanusAdminURI = pAdminURI;
_JanusAdminToken = pAdminToken;
_MessageDetails = pDebugMessages;
}
public void Dispose()
{
ClearEventSubscriptions();
if (IsConnected)
{
_ = DestroySession();
}
if (_HttpClient is not null)
{
_HttpClient.Dispose();
_HttpClient = null;
}
}
/// <summary>
/// Make the create session request to the Janus server, get the
/// sessionID and return TRUE if successful.
/// </summary>
/// <returns>TRUE if session was created successfully</returns>
public async Task<bool> CreateSession()
{
bool ret = false;
try
{
var resp = await SendToJanus(new CreateSessionReq());
if (resp is not null && resp.isSuccess)
{
var sessionResp = new CreateSessionResp(resp);
SessionId = sessionResp.returnedId;
IsConnected = true;
SessionUri = _JanusServerURI + "/" + SessionId;
m_log.DebugFormat("{0} CreateSession. Created. ID={1}, URL={2}", LogHeader, SessionId, SessionUri);
ret = true;
StartLongPoll();
}
else
{
m_log.ErrorFormat("{0} CreateSession: failed", LogHeader);
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} CreateSession: exception {1}", LogHeader, e);
}
return ret;
}
public async Task<bool> DestroySession()
{
bool ret = false;
try
{
JanusMessageResp resp = await SendToSession(new DestroySessionReq());
if (resp is not null && resp.isSuccess)
{
// Note that setting IsConnected to false will cause the long poll to exit
m_log.DebugFormat("{0} DestroySession. Destroyed", LogHeader);
}
else
{
if (resp.isError)
{
ErrorResp eResp = new ErrorResp(resp);
switch (eResp.errorCode)
{
case 458:
// This is the error code for a session that is already destroyed
m_log.DebugFormat("{0} DestroySession: session already destroyed", LogHeader);
break;
case 459:
// This is the error code for handle already destroyed
m_log.DebugFormat("{0} DestroySession: Handle not found", LogHeader);
break;
default:
m_log.ErrorFormat("{0} DestroySession: failed {1}", LogHeader, eResp.errorReason);
break;
}
}
else
{
m_log.ErrorFormat("{0} DestroySession: failed. Resp: {1}", LogHeader, resp.ToString());
}
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} DestroySession: exception {1}", LogHeader, e);
}
IsConnected = false;
_CancelTokenSource.Cancel();
return ret;
}
// ====================================================================
public async Task<JanusMessageResp> TrickleCandidates(JanusViewerSession pVSession, OSDArray pCandidates)
{
JanusMessageResp ret = null;
// if the audiobridge is active, the trickle message is sent to it
if (pVSession.AudioBridge is null)
{
ret = await SendToJanusNoWait(new TrickleReq(pVSession));
}
else
{
ret = await SendToJanusNoWait(new TrickleReq(pVSession), pVSession.AudioBridge.PluginUri);
}
return ret;
}
// ====================================================================
public async Task<JanusMessageResp> TrickleCompleted(JanusViewerSession pVSession)
{
JanusMessageResp ret = null;
// if the audiobridge is active, the trickle message is sent to it
if (pVSession.AudioBridge is null)
{
ret = await SendToJanusNoWait(new TrickleReq(pVSession));
}
else
{
ret = await SendToJanusNoWait(new TrickleReq(pVSession), pVSession.AudioBridge.PluginUri);
}
return ret;
}
// ====================================================================
public Dictionary<string, JanusPlugin> _Plugins = new Dictionary<string, JanusPlugin>();
public void AddPlugin(JanusPlugin pPlugin)
{
_Plugins.Add(pPlugin.PluginName, pPlugin);
}
// ====================================================================
// Post to the session
public async Task<JanusMessageResp> SendToSession(JanusMessageReq pReq)
{
return await SendToJanus(pReq, SessionUri);
}
private class OutstandingRequest
{
public string TransactionId;
public DateTime RequestTime;
public TaskCompletionSource<JanusMessageResp> TaskCompletionSource;
}
private Dictionary<string, OutstandingRequest> _OutstandingRequests = new Dictionary<string, OutstandingRequest>();
// Send a request directly to the Janus server.
// NOTE: this is probably NOT what you want to do. This is a direct call that is outside the session.
private async Task<JanusMessageResp> SendToJanus(JanusMessageReq pReq)
{
return await SendToJanus(pReq, _JanusServerURI);
}
/// <summary>
/// Send a request to the Janus server. This is the basic call that sends a request to the server.
/// The transaction ID is used to match the response to the request.
/// If the request returns an 'ack' response, the code waits for the matching event
/// before returning the response.
/// </summary>
/// <param name="pReq"></param>
/// <param name="pURI"></param>
/// <returns></returns>
public async Task<JanusMessageResp> SendToJanus(JanusMessageReq pReq, string pURI)
{
AddJanusHeaders(pReq);
// m_log.DebugFormat("{0} SendToJanus", LogHeader);
if (_MessageDetails) m_log.DebugFormat("{0} SendToJanus. URI={1}, req={2}", LogHeader, pURI, pReq.ToJson());
JanusMessageResp ret = null;
try
{
OutstandingRequest outReq = new OutstandingRequest
{
TransactionId = pReq.TransactionId,
RequestTime = DateTime.Now,
TaskCompletionSource = new TaskCompletionSource<JanusMessageResp>()
};
_OutstandingRequests.Add(pReq.TransactionId, outReq);
string reqStr = pReq.ToJson();
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Post, pURI);
reqMsg.Content = new StringContent(reqStr, System.Text.Encoding.UTF8, MediaTypeNames.Application.Json);
reqMsg.Headers.Add("Accept", "application/json");
HttpResponseMessage response = await _HttpClient.SendAsync(reqMsg, _CancelTokenSource.Token);
if (response.IsSuccessStatusCode)
{
string respStr = await response.Content.ReadAsStringAsync();
ret = JanusMessageResp.FromJson(respStr);
if (ret.CheckReturnCode("ack"))
{
// Some messages are asynchronous and completed with an event
if (_MessageDetails) m_log.DebugFormat("{0} SendToJanus: ack response {1}", LogHeader, respStr);
if (_OutstandingRequests.TryGetValue(pReq.TransactionId, out OutstandingRequest outstandingRequest))
{
ret = await outstandingRequest.TaskCompletionSource.Task;
_OutstandingRequests.Remove(pReq.TransactionId);
}
// If there is no OutstandingRequest, the request was not waiting for an event or already processed
}
else
{
// If the response is not an ack, that means a synchronous request/response so return the response
_OutstandingRequests.Remove(pReq.TransactionId);
if (_MessageDetails) m_log.DebugFormat("{0} SendToJanus: response {1}", LogHeader, respStr);
}
}
else
{
m_log.ErrorFormat("{0} SendToJanus: response not successful {1}", LogHeader, response);
_OutstandingRequests.Remove(pReq.TransactionId);
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} SendToJanus: exception {1}", LogHeader, e.Message);
}
return ret;
}
/// <summary>
/// Send a request to the Janus server but we just return the response and don't wait for any
/// event or anything.
/// There are some requests that are just fire-and-forget.
/// </summary>
/// <param name="pReq"></param>
/// <returns></returns>
private async Task<JanusMessageResp> SendToJanusNoWait(JanusMessageReq pReq, string pURI)
{
JanusMessageResp ret = new JanusMessageResp();
AddJanusHeaders(pReq);
try {
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Post, pURI);
string reqStr = pReq.ToJson();
reqMsg.Content = new StringContent(reqStr, System.Text.Encoding.UTF8, MediaTypeNames.Application.Json);
reqMsg.Headers.Add("Accept", "application/json");
HttpResponseMessage response = await _HttpClient.SendAsync(reqMsg);
string respStr = await response.Content.ReadAsStringAsync();
ret = JanusMessageResp.FromJson(respStr);
}
catch (Exception e)
{
m_log.ErrorFormat("{0} SendToJanusNoWait: exception {1}", LogHeader, e.Message);
}
return ret;
}
private async Task<JanusMessageResp> SendToJanusNoWait(JanusMessageReq pReq)
{
return await SendToJanusNoWait(pReq, SessionUri);
}
// There are various headers that are in most Janus requests. Add them here.
private void AddJanusHeaders(JanusMessageReq pReq)
{
// Authentication token
if (!String.IsNullOrEmpty(_JanusAPIToken))
{
pReq.AddAPIToken(_JanusAPIToken);
}
// Transaction ID that matches responses to requests
if (String.IsNullOrEmpty(pReq.TransactionId))
{
pReq.TransactionId = Guid.NewGuid().ToString();
}
// The following two are required for the WebSocket interface. They are optional for the
// HTTP interface since the session and plugin handle are in the URL.
// SessionId is added to the message if not already there
if (!pReq.hasSessionId && !String.IsNullOrEmpty(SessionId))
{
pReq.AddSessionId(SessionId);
}
// HandleId connects to the plugin
if (!pReq.hasHandleId && !String.IsNullOrEmpty(PluginId))
{
pReq.AddHandleId(PluginId);
}
}
bool TryGetOutstandingRequest(string pTransactionId, out OutstandingRequest pOutstandingRequest)
{
if (String.IsNullOrEmpty(pTransactionId))
{
pOutstandingRequest = null;
return false;
}
bool ret = false;
lock (_OutstandingRequests)
{
if (_OutstandingRequests.TryGetValue(pTransactionId, out pOutstandingRequest))
{
_OutstandingRequests.Remove(pTransactionId);
ret = true;
}
}
return ret;
}
public Task<JanusMessageResp> SendToJanusAdmin(JanusMessageReq pReq)
{
return SendToJanus(pReq, _JanusAdminURI);
}
public Task<JanusMessageResp> GetFromJanus()
{
return GetFromJanus(_JanusServerURI);
}
/// <summary>
/// Do a GET to the Janus server and return the response.
/// If the response is an HTTP error, we return fake JanusMessageResp with the error.
/// </summary>
/// <param name="pURI"></param>
/// <returns></returns>
public async Task<JanusMessageResp> GetFromJanus(string pURI)
{
if (!String.IsNullOrEmpty(_JanusAPIToken))
{
pURI += "?apisecret=" + _JanusAPIToken;
}
JanusMessageResp ret = null;
try
{
// m_log.DebugFormat("{0} GetFromJanus: URI = \"{1}\"", LogHeader, pURI);
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Get, pURI);
reqMsg.Headers.Add("Accept", "application/json");
HttpResponseMessage response = null;
try
{
response = await _HttpClient.SendAsync(reqMsg, _CancelTokenSource.Token);
if (response is not null && response.IsSuccessStatusCode)
{
string respStr = await response.Content.ReadAsStringAsync();
ret = JanusMessageResp.FromJson(respStr);
// m_log.DebugFormat("{0} GetFromJanus: response {1}", LogHeader, respStr);
}
else
{
m_log.ErrorFormat("{0} GetFromJanus: response not successful {1}", LogHeader, response);
var eResp = new ErrorResp("GETERROR");
// Add the sessionId so the proper session can be shut down
eResp.AddSessionId(SessionId);
if (response is not null)
{
eResp.SetError((int)response.StatusCode, response.ReasonPhrase);
}
else
{
eResp.SetError(0, "Connection refused");
}
ret = eResp;
}
}
catch (TaskCanceledException e)
{
m_log.DebugFormat("{0} GetFromJanus: task canceled: {1}", LogHeader, e.Message);
var eResp = new ErrorResp("GETERROR");
eResp.SetError(499, "Task canceled");
ret = eResp;
}
catch (Exception e)
{
m_log.ErrorFormat("{0} GetFromJanus: exception {1}", LogHeader, e.Message);
var eResp = new ErrorResp("GETERROR");
eResp.SetError(400, "Exception: " + e.Message);
ret = eResp;
}
}
catch (Exception e)
{
m_log.ErrorFormat("{0} GetFromJanus: exception {1}", LogHeader, e);
var eResp = new ErrorResp("GETERROR");
eResp.SetError(400, "Exception: " + e.Message);
ret = eResp;
}
return ret;
}
// ====================================================================
public delegate void JanusEventHandler(EventResp pResp);
// Not all the events are used. CS0067 is to suppress the warning that the event is not used.
#pragma warning disable CS0067,CS0414
public event JanusEventHandler OnKeepAlive;
public event JanusEventHandler OnServerInfo;
public event JanusEventHandler OnTrickle;
public event JanusEventHandler OnHangup;
public event JanusEventHandler OnDetached;
public event JanusEventHandler OnError;
public event JanusEventHandler OnEvent;
public event JanusEventHandler OnMessage;
public event JanusEventHandler OnJoined;
public event JanusEventHandler OnLeaving;
public event JanusEventHandler OnDisconnect;
#pragma warning restore CS0067,CS0414
public void ClearEventSubscriptions()
{
OnKeepAlive = null;
OnServerInfo = null;
OnTrickle = null;
OnHangup = null;
OnDetached = null;
OnError = null;
OnEvent = null;
OnMessage = null;
OnJoined = null;
OnLeaving = null;
OnDisconnect = null;
}
// ====================================================================
/// <summary>
/// In the REST API, events are returned by a long poll. This
/// starts the poll and calls the registed event handler when
/// an event is received.
/// </summary>
private void StartLongPoll()
{
bool running = true;
m_log.DebugFormat("{0} EventLongPoll", LogHeader);
Task.Run(async () => {
while (running && IsConnected)
{
try
{
var resp = await GetFromJanus(SessionUri);
if (resp is not null)
{
_ = Task.Run(() =>
{
EventResp eventResp = new EventResp(resp);
switch (resp.ReturnCode)
{
case "keepalive":
// These should happen every 30 seconds
// m_log.DebugFormat("{0} EventLongPoll: keepalive {1}", LogHeader, resp.ToString());
break;
case "server_info":
// Just info on the Janus instance
m_log.DebugFormat("{0} EventLongPoll: server_info {1}", LogHeader, resp.ToString());
break;
case "ack":
// 'ack' says the request was received and an event will follow
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: ack {1}", LogHeader, resp.ToString());
break;
case "success":
// success is a sync response that says the request was completed
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: success {1}", LogHeader, resp.ToString());
break;
case "trickle":
// got a trickle ICE candidate from Janus
// this is for reverse communication from Janus to the client and we don't do that
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: trickle {1}", LogHeader, resp.ToString());
OnTrickle?.Invoke(eventResp);
break;
case "webrtcup":
// ICE and DTLS succeeded, and so Janus correctly established a PeerConnection with the user/application;
m_log.DebugFormat("{0} EventLongPoll: webrtcup {1}", LogHeader, resp.ToString());
break;
case "hangup":
// The PeerConnection was closed, either by the user/application or by Janus itself;
// If one is in the room, when a "hangup" event happens, it means that the user left the room.
m_log.DebugFormat("{0} EventLongPoll: hangup {1}", LogHeader, resp.ToString());
OnHangup?.Invoke(eventResp);
break;
case "detached":
// a plugin asked the core to detach one of our handles
m_log.DebugFormat("{0} EventLongPoll: event {1}", LogHeader, resp.ToString());
OnDetached?.Invoke(eventResp);
break;
case "media":
// Janus is receiving (receiving: true/false) audio/video (type: "audio/video") on this PeerConnection;
m_log.DebugFormat("{0} EventLongPoll: media {1}", LogHeader, resp.ToString());
break;
case "slowlink":
// Janus detected a slowlink (uplink: true/false) on this PeerConnection;
m_log.DebugFormat("{0} EventLongPoll: slowlink {1}", LogHeader, resp.ToString());
break;
case "error":
m_log.DebugFormat("{0} EventLongPoll: error {1}", LogHeader, resp.ToString());
if (TryGetOutstandingRequest(resp.TransactionId, out OutstandingRequest outstandingRequest))
{
outstandingRequest.TaskCompletionSource.SetResult(resp);
}
else
{
OnError?.Invoke(eventResp);
m_log.ErrorFormat("{0} EventLongPoll: error with no transaction. {1}", LogHeader, resp.ToString());
}
break;
case "event":
if (_MessageDetails) m_log.DebugFormat("{0} EventLongPoll: event {1}", LogHeader, resp.ToString());
if (TryGetOutstandingRequest(resp.TransactionId, out OutstandingRequest outstandingRequest2))
{
// Someone is waiting for this event
outstandingRequest2.TaskCompletionSource.SetResult(resp);
}
else
{
m_log.ErrorFormat("{0} EventLongPoll: event no outstanding request {1}", LogHeader, resp.ToString());
OnEvent?.Invoke(eventResp);
}
break;
case "message":
m_log.DebugFormat("{0} EventLongPoll: message {1}", LogHeader, resp.ToString());
OnMessage?.Invoke(eventResp);
break;
case "timeout":
// Events for the audio bridge
m_log.DebugFormat("{0} EventLongPoll: timeout {1}", LogHeader, resp.ToString());
break;
case "joined":
// Events for the audio bridge
OnJoined?.Invoke(eventResp);
m_log.DebugFormat("{0} EventLongPoll: joined {1}", LogHeader, resp.ToString());
break;
case "leaving":
// Events for the audio bridge
OnLeaving?.Invoke(eventResp);
m_log.DebugFormat("{0} EventLongPoll: leaving {1}", LogHeader, resp.ToString());
break;
case "GETERROR":
// Special error response from the GET
var errorResp = new ErrorResp(resp);
switch (errorResp.errorCode)
{
case 404:
// "Not found" means there is a Janus server but the session is gone
m_log.ErrorFormat("{0} EventLongPoll: GETERROR Not Found. URI={1}: {2}",
LogHeader, SessionUri, resp.ToString());
break;
case 400:
// "Bad request" means the session is gone
m_log.ErrorFormat("{0} EventLongPoll: Bad Request. URI={1}: {2}",
LogHeader, SessionUri, resp.ToString());
break;
case 499:
// "Task canceled" means the long poll was canceled
m_log.DebugFormat("{0} EventLongPoll: Task canceled. URI={1}", LogHeader, SessionUri);
break;
default:
m_log.DebugFormat("{0} EventLongPoll: unknown response. URI={1}: {2}",
LogHeader, SessionUri, resp.ToString());
break;
}
// This will cause the long poll to exit
running = false;
OnDisconnect?.Invoke(eventResp);
break;
default:
m_log.DebugFormat("{0} EventLongPoll: unknown response {1}", LogHeader, resp.ToString());
break;
}
});
}
else
{
m_log.ErrorFormat("{0} EventLongPoll: failed. Response is null", LogHeader);
}
}
catch (Exception e)
{
// This will cause the long poll to exit
running = false;
m_log.ErrorFormat("{0} EventLongPoll: exception {1}", LogHeader, e);
}
}
m_log.InfoFormat("{0} EventLongPoll: Exiting long poll loop", LogHeader);
});
}
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Reflection;
using System.Threading.Tasks;
using OMV = OpenMetaverse;
using OpenMetaverse.StructuredData;
using log4net;
namespace osWebRtcVoice
{
public class JanusViewerSession : IVoiceViewerSession
{
protected static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected static readonly string LogHeader = "[JANUS VIEWER SESSION]";
// 'viewer_session' that is passed to and from the viewer
// IVoiceViewerSession.ViewerSessionID
public string ViewerSessionID { get; set; }
// IVoiceViewerSession.VoiceService
public IWebRtcVoiceService VoiceService { get; set; }
// The Janus server keeps track of the user by this ID
// IVoiceViewerSession.VoiceServiceSessionId
public string VoiceServiceSessionId { get; set; }
// IVoiceViewerSession.RegionId
public OMV.UUID RegionId { get; set; }
// IVoiceViewerSession.AgentId
public OMV.UUID AgentId { get; set; }
// Janus keeps track of the user by this ID
public int ParticipantId { get; set; }
// Connections to the Janus server
public JanusSession Session { get; set; }
public JanusAudioBridge AudioBridge { get; set; }
public JanusRoom Room { get; set; }
// This keeps copies of the offer/answer incase we need to resend
public string OfferOrig { get; set; }
public string Offer { get; set; }
// Contains "type" and "sdp" fields
public OSDMap Answer { get; set; }
public JanusViewerSession(IWebRtcVoiceService pVoiceService)
{
ViewerSessionID = OMV.UUID.Random().ToString();
VoiceService = pVoiceService;
m_log.Debug($"{LogHeader} JanusViewerSession created {ViewerSessionID}");
}
public JanusViewerSession(string pViewerSessionID, IWebRtcVoiceService pVoiceService)
{
ViewerSessionID = pViewerSessionID;
VoiceService = pVoiceService;
m_log.Debug($"{LogHeader} JanusViewerSession created {ViewerSessionID}");
}
// Send the messages to the voice service to try and get rid of the session
// IVoiceViewerSession.Shutdown
public async Task Shutdown()
{
m_log.DebugFormat($"{LogHeader} JanusViewerSession shutdown {ViewerSessionID}");
if (Room is not null)
{
var rm = Room;
Room = null;
await rm.LeaveRoom(this);
}
if (AudioBridge is not null)
{
var ab = AudioBridge;
AudioBridge = null;
await ab.Detach();
}
if (Session is not null)
{
var s = Session;
Session = null;
_ = await s.DestroySession().ConfigureAwait(false);
s.Dispose();
}
}
}
}

View File

@@ -1,478 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using System.Threading.Tasks;
using OpenSim.Framework;
using OpenSim.Services.Base;
using OpenMetaverse.StructuredData;
using OpenMetaverse;
using Nini.Config;
using log4net;
namespace osWebRtcVoice
{
public class WebRtcJanusService : ServiceBase, IWebRtcVoiceService
{
private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[JANUS WEBRTC SERVICE]";
private readonly IConfigSource _Config;
private bool _Enabled = false;
private string _JanusServerURI = string.Empty;
private string _JanusAPIToken = string.Empty;
private string _JanusAdminURI = string.Empty;
private string _JanusAdminToken = string.Empty;
private bool _MessageDetails = false;
// An extra "viewer session" that is created initially. Used to verify the service
// is working and for a handle for the console commands.
private JanusViewerSession _ViewerSession;
public WebRtcJanusService(IConfigSource pConfig) : base(pConfig)
{
Assembly assembly = Assembly.GetExecutingAssembly();
string version = assembly.GetName().Version?.ToString() ?? "unknown";
_log.DebugFormat("{0} WebRtcJanusService version {1}", LogHeader, version);
_Config = pConfig;
IConfig webRtcVoiceConfig = _Config.Configs["WebRtcVoice"];
if (webRtcVoiceConfig is not null)
{
_Enabled = webRtcVoiceConfig.GetBoolean("Enabled", false);
IConfig janusConfig = _Config.Configs["JanusWebRtcVoice"];
if (_Enabled && janusConfig is not null)
{
_JanusServerURI = janusConfig.GetString("JanusGatewayURI", string.Empty);
_JanusAPIToken = janusConfig.GetString("APIToken", string.Empty);
_JanusAdminURI = janusConfig.GetString("JanusGatewayAdminURI", string.Empty);
_JanusAdminToken = janusConfig.GetString("AdminAPIToken", string.Empty);
// Debugging options
_MessageDetails = janusConfig.GetBoolean("MessageDetails", false);
if (string.IsNullOrEmpty(_JanusServerURI) || string.IsNullOrEmpty(_JanusAPIToken) ||
string.IsNullOrEmpty(_JanusAdminURI) || string.IsNullOrEmpty(_JanusAdminToken))
{
_log.Error($"{LogHeader} JanusWebRtcVoice configuration section missing required fields");
_Enabled = false;
}
if (_Enabled)
{
if(!StartConnectionToJanus())
{
_log.Error($"{LogHeader} failed connection to Janus Gateway. Disabled");
_Enabled=false;
return;
}
RegisterConsoleCommands();
_log.Info($"{LogHeader} Enabled");
}
}
else
{
_log.Error($"{LogHeader} No JanusWebRtcVoice configuration section");
_Enabled = false;
}
}
else
{
_log.Error($"{LogHeader} No WebRtcVoice configuration section");
_Enabled = false;
}
}
// Here an initial session is created and then a handle to the audio bridge plugin
// is created for the console commands. Since webrtc PeerConnections that are created
// my Janus are per-session, the other sessions will be created by the viewer requests.
private bool StartConnectionToJanus()
{
_log.DebugFormat("{0} StartConnectionToJanus", LogHeader);
_ViewerSession = new JanusViewerSession(this);
//bad
return ConnectToSessionAndAudioBridge(_ViewerSession).Result;
}
private async Task<bool> ConnectToSessionAndAudioBridge(JanusViewerSession pViewerSession)
{
JanusSession janusSession = new JanusSession(_JanusServerURI, _JanusAPIToken, _JanusAdminURI, _JanusAdminToken, _MessageDetails);
if (await janusSession.CreateSession().ConfigureAwait(false))
{
_log.DebugFormat("{0} JanusSession created", LogHeader);
// Once the session is created, create a handle to the plugin for rooms
JanusAudioBridge audioBridge = new JanusAudioBridge(janusSession);
if (await audioBridge.Activate(_Config).ConfigureAwait(false))
{
_log.Debug($"{LogHeader} AudioBridgePluginHandle created");
// Requests through the capabilities will create rooms
janusSession.AddPlugin(audioBridge);
pViewerSession.VoiceServiceSessionId = janusSession.SessionId;
pViewerSession.Session = janusSession;
pViewerSession.AudioBridge = audioBridge;
janusSession.OnDisconnect += Handle_Hangup;
janusSession.OnHangup += Handle_Hangup;
return true;
}
_log.Error($"{LogHeader} JanusPluginHandle not created");
}
_log.Error($"{LogHeader} JanusSession not created");
return false;
}
private void Handle_Hangup(EventResp pResp)
{
if (pResp is not null)
{
var sessionId = pResp.sessionId;
_log.Debug($"{LogHeader} Handle_Hangup: {pResp.RawBody}, sessionId={sessionId}");
if (VoiceViewerSession.TryGetViewerSessionByVSSessionId(sessionId, out IVoiceViewerSession viewerSession))
{
// There is a viewer session associated with this session
DisconnectViewerSession(viewerSession as JanusViewerSession);
}
else
{
_log.Debug($"{LogHeader} Handle_Hangup: no session found. SessionId={sessionId}");
}
}
}
// Disconnect the viewer session. This is called when the viewer logs out or hangs up.
private void DisconnectViewerSession(JanusViewerSession pViewerSession)
{
if (pViewerSession is not null)
{
Task.Run(() =>
{
VoiceViewerSession.RemoveViewerSession(pViewerSession.ViewerSessionID);
// No need to wait for the session to be shutdown
_ = pViewerSession.Shutdown();
});
}
}
// The pRequest parameter is a straight conversion of the JSON request from the client.
// This is the logic that takes the client's request and converts it into
// operations on rooms in the audio bridge.
// IWebRtcVoiceService.ProvisionVoiceAccountRequest
public OSDMap ProvisionVoiceAccountRequest(IVoiceViewerSession pSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
return ProvisionVoiceAccountRequestBAD(pSession, pRequest, pUserID, pSceneID).Result;
}
public async Task<OSDMap> ProvisionVoiceAccountRequestBAD(IVoiceViewerSession pSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
OSDMap ret = null;
string errorMsg = null;
JanusViewerSession viewerSession = pSession as JanusViewerSession;
if (viewerSession is not null)
{
if (viewerSession.Session is null)
{
// This is a new session so we must create a new session and handle to the audio bridge
await ConnectToSessionAndAudioBridge(viewerSession).ConfigureAwait(false);
}
// TODO: need to keep count of users in a room to know when to close a room
bool isLogout = pRequest.TryGetBool("logout", out bool lgout) && lgout;
if (isLogout)
{
// The client is logging out. Exit the room.
if (viewerSession.Room is not null)
{
await viewerSession.Room.LeaveRoom(viewerSession);
viewerSession.Room = null;
}
return new OSDMap
{
{ "response", "closed" }
};
}
// Get the parameters that select the room
// To get here, voice_server_type has already been checked to be 'webrtc' and channel_type='local'
int parcel_local_id = pRequest.TryGetInt("parcel_local_id", out int pli) ? pli : JanusAudioBridge.REGION_ROOM_ID;
string channel_id = pRequest.TryGetString("channel_id", out string cli) ? cli : string.Empty;
string channel_credentials = pRequest.TryGetString("credentials", out string cred) ? cred : string.Empty;
string channel_type = pRequest["channel_type"].AsString();
bool isSpatial = channel_type == "local";
string voice_server_type = pRequest["voice_server_type"].AsString();
_log.DebugFormat("{0} ProvisionVoiceAccountRequest: parcel_id={1} channel_id={2} channel_type={3} voice_server_type={4}", LogHeader, parcel_local_id, channel_id, channel_type, voice_server_type);
if (pRequest.TryGetOSDMap("jsep", out OSDMap jsep))
{
// The jsep is the SDP from the client. This is the client's request to connect to the audio bridge.
string jsepType = jsep["type"].AsString();
string jsepSdp = jsep["sdp"].AsString();
if (jsepType == "offer")
{
// The client is sending an offer. Find the right room and join it.
// _log.DebugFormat("{0} ProvisionVoiceAccountRequest: jsep type={1} sdp={2}", LogHeader, jsepType, jsepSdp);
viewerSession.Room = await viewerSession.AudioBridge.SelectRoom(pSceneID.ToString(),
channel_type, isSpatial, parcel_local_id, channel_id).ConfigureAwait(false);
if (viewerSession.Room is null)
{
errorMsg = "room selection failed";
_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: room selection failed");
}
else {
viewerSession.Offer = jsepSdp;
viewerSession.OfferOrig = jsepSdp;
viewerSession.AgentId = pUserID;
if (await viewerSession.Room.JoinRoom(viewerSession).ConfigureAwait(false))
{
ret = new OSDMap
{
{ "jsep", viewerSession.Answer },
{ "viewer_session", viewerSession.ViewerSessionID }
};
}
else
{
errorMsg = "JoinRoom failed";
_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: JoinRoom failed");
}
}
}
else
{
errorMsg = "jsep type not offer";
_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: jsep type={jsepType} not offer");
}
}
else
{
errorMsg = "no jsep";
_log.Debug($"{LogHeader} ProvisionVoiceAccountRequest: no jsep. req={pRequest}");
}
}
else
{
errorMsg = "viewersession not JanusViewerSession";
_log.Error("{LogHeader} ProvisionVoiceAccountRequest: viewersession not JanusViewerSession");
}
if (!string.IsNullOrEmpty(errorMsg) && ret is null)
{
// The provision failed so build an error messgage to return
ret = new OSDMap
{
{ "response", "failed" },
{ "error", errorMsg }
};
}
return ret;
}
// IWebRtcVoiceService.VoiceAccountBalanceRequest
public OSDMap VoiceSignalingRequest(IVoiceViewerSession pSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
return VoiceSignalingRequestBAD(pSession, pRequest, pUserID, pSceneID).Result;
}
public async Task<OSDMap> VoiceSignalingRequestBAD(IVoiceViewerSession pSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
OSDMap ret = null;
JanusViewerSession viewerSession = pSession as JanusViewerSession;
JanusMessageResp resp = null;
if (viewerSession is not null)
{
// The request should be an array of candidates
if (pRequest.TryGetOSDMap("candidate", out OSDMap candidate))
{
if (candidate.TryGetBool("completed", out bool iscompleted) && iscompleted)
{
// The client has finished sending candidates
resp = await viewerSession.Session.TrickleCompleted(viewerSession).ConfigureAwait(false);
_log.DebugFormat($"{LogHeader} VoiceSignalingRequest: candidate completed");
}
else
{
}
}
else if (pRequest.TryGetOSDArray("candidates", out OSDArray candidates))
{
OSDArray candidatesArray = new OSDArray();
foreach (OSDMap cand in candidates)
{
candidatesArray.Add(new OSDMap() {
{ "candidate", cand["candidate"].AsString() },
{ "sdpMid", cand["sdpMid"].AsString() },
{ "sdpMLineIndex", cand["sdpMLineIndex"].AsLong() }
});
}
resp = await viewerSession.Session.TrickleCandidates(viewerSession, candidatesArray).ConfigureAwait(false);
_log.Debug($"{LogHeader} VoiceSignalingRequest: {candidatesArray.Count} candidates");
}
else
{
_log.Error($"{LogHeader} VoiceSignalingRequest: no 'candidate' or 'candidates'");
}
}
if (resp is null)
{
_log.ErrorFormat($"{LogHeader} VoiceSignalingRequest: no response so returning error");
ret = new OSDMap
{
{ "response", "error" }
};
}
else
{
ret = resp.RawBody;
}
return ret;
}
// This module should not be invoked with this signature
// IWebRtcVoiceService.ProvisionVoiceAccountRequest
public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
throw new NotImplementedException();
}
// This module should not be invoked with this signature
// IWebRtcVoiceService.VoiceSignalingRequest
public OSDMap VoiceSignalingRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
throw new NotImplementedException();
}
// The viewer session object holds all the connection information to Janus.
// IWebRtcVoiceService.CreateViewerSession
public IVoiceViewerSession CreateViewerSession(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
return new JanusViewerSession(this)
{
AgentId = pUserID,
RegionId = pSceneID
};
}
// ======================================================================================================
private void RegisterConsoleCommands()
{
if (_Enabled) {
MainConsole.Instance.Commands.AddCommand("Webrtc", false, "janus info",
"janus info",
"Show Janus server information",
HandleJanusInfo);
MainConsole.Instance.Commands.AddCommand("Webrtc", false, "janus list rooms",
"janus list rooms",
"List the rooms on the Janus server",
HandleJanusListRooms);
// List rooms
// List participants in a room
}
}
private void HandleJanusInfo(string module, string[] cmdparms)
{
if (_ViewerSession is not null && _ViewerSession.Session is not null)
{
WriteOut("{0} Janus session: {1}", LogHeader, _ViewerSession.Session.SessionId);
string infoURI = _ViewerSession.Session.JanusServerURI + "/info";
var resp = _ViewerSession.Session.GetFromJanus(infoURI).Result;
if (resp is not null)
MainConsole.Instance.Output(resp.ToJson());
}
}
private void HandleJanusListRooms(string module, string[] cmdparms)
{
if (_ViewerSession is not null && _ViewerSession.Session is not null && _ViewerSession.AudioBridge is not null)
{
var ab = _ViewerSession.AudioBridge;
var resp = ab.SendAudioBridgeMsg(new AudioBridgeListRoomsReq()).Result;
if (resp is not null && resp.isSuccess)
{
if (resp.PluginRespData.TryGetValue("list", out OSD list))
{
MainConsole.Instance.Output("");
MainConsole.Instance.Output(
" {0,10} {1,15} {2,5} {3,10} {4,7} {5,7}",
"Room", "Description", "Num", "SampleRate", "Spatial", "Recording");
foreach (OSDMap room in list as OSDArray)
{
int roomid = room["room"].AsInteger();
MainConsole.Instance.Output(
" {0,10} {1,15} {2,5} {3,10} {4,7} {5,7}",
roomid, room["description"], room["num_participants"],
room["sampling_rate"], room["spatial_audio"], room["record"]);
var participantResp = ab.SendAudioBridgeMsg(new AudioBridgeListParticipantsReq(roomid)).Result;
if (participantResp is not null && participantResp.AudioBridgeReturnCode == "participants")
{
if (participantResp.PluginRespData.TryGetValue("participants", out OSD participants))
{
foreach (OSDMap participant in participants as OSDArray)
{
MainConsole.Instance.Output(" {0}/{1},muted={2},talking={3},pos={4}",
participant["id"].AsLong(), participant["display"], participant["muted"],
participant["talking"], participant["spatial_position"]);
}
}
}
}
}
else
{
MainConsole.Instance.Output("No rooms");
}
}
else
{
MainConsole.Instance.Output("Failed to get room list");
}
}
}
private void WriteOut(string msg, params object[] args)
{
// m_log.InfoFormat(msg, args);
MainConsole.Instance.Output(msg, args);
}
}
}

View File

@@ -1,390 +0,0 @@
os-webrtc-janus original work, by Robert Adams, license changed to same BSD of rest of OpenSimulator
By Robert Adams, for OpenSimulator
Original license:
// Copyright 2024 Robert Adams (misterblue@misterblue.com)
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -1,234 +0,0 @@
# os-webrtc-janus
Addon-module for [OpenSimulator] to provide webrtc voice support
using Janus-gateway.
For an explanation of background and architecture,
this project was presented at the
[OpenSimulator Community Conference] 2024
in the presentation
[WebRTC Voice for OpenSimulator](https://www.youtube.com/watch?v=nL78fieIFYg).
This addon works by taking viewer requests for voice service and
using a separate, external [Janus-Gateway WebRTC server].
This can be configured to allow local region spatial voice
and grid-wide group and spatial voice. See the sections below.
For running that separate Janus server, check out
[os-webrtc-janus-docker] which has instructions for running
Janus-Gateway on Linux and Windows WSL using Docker.
Instructions for:
- [Building into OpenSimulator](#Building): Build OpenSimulator with WebRTC voice service
- [Configuring Simulator for Voice Services](#Configure_Simulator)
- [Configuring Robust Grid Service](#Configure_Robust)
- [Configure Standalone Region](#Configure_Standalone)
- [Managing Voice Service](#Managing_Voice) (console commands, etc)
**Note**: as of January 2024, this solution does not provide true spatial
voice service using Janus. There are people working on additions to Janus
to provide this but the existing solution provides only non-spatial
voice services using the `AudioBridge` Janus plugin. Additionally,
features like muting and individual avatar volume are not yet implemented.
<a id="Known_Issues"></a>
## Known Issues
- No spatial audio
- One can see your own "white dot" but you don't see other avatar's white dots
- No muting
- No individual volume control
And probably more found at [os-webrtc-janus issues](https://github.com/Misterblue/os-webrtc-janus/issues).
<a id="Building"></a>
## Building Plugin into OpenSimulator
`os-webrtc-janus` is integrated as a source build into [OpenSimulator].
It uses the [OpenSimulator] addon-module feature which makes the
build as easy as cloning the `os-webrtc-janus` sources into the
[OpenSimulator] source tree, running the build configuration script,
and then building OpenSimulator.
The steps are:
```
# Get the OpenSimulator sources
git clone git://opensimulator.org/git/opensim
cd opensim # cd into the top level OpenSim directory
# Fetch the WebRtc addon
cd addon-modules
git clone https://github.com/Misterblue/os-webrtc-janus.git
cd ..
# Build the project files
./runprebuild.sh
# Compile OpenSimulator with the webrtc addon
./compile.sh
# Copy the INI file for webrtc into a config dir that is read at boot
mkdir bin/config
cp addon-modules/os-webrtc-janus/os-webrtc-janus.ini bin/config
```
These building steps create several `.dll` files for `os-webrtc-janus`
in `bin/WebRtc*.dll`. Some adventurous people have found that, rather
than building the [OpenSimulator] sources, you can just copy the `.dll`s
into an existing `/bin` directory. Just make sure the `WebRtc*.dll` files
were built on the same version of [OpenSimulator] you are running.
<a id="Configure_Simulator"></a>
## Configure a Region for Voice
The last step in [Building](#Building) copied `os-webrtc-janus.ini` into
the `bin/config` directory. [OpenSimulator] reads all the `.ini` files
in that directory so this copy operation adds the configuration for `os-webrtc-janus`
and this is what needs to be configured for the simulator and region.
The sample `.ini` file has two sections: `[WebRtcVoice]` and `[JanusWebRtcVoice]`.
The `WebRtcVoice` section configures the what services the simulator uses
for WebRtc voice. The `[JanusWebRtcVoice]` section configures any connection
the simulator makes to the Janus server. The latter section is only updated
if this simulator is using a local Janus server for spatial voice.
The values for `SpatialVoiceService` and `NonSpatialVoiceService` point
either directly to a Janus service or to a Robust grid server that is providing
the grid voice service. Both these options are in the sample `os-webrtc-janus.ini`
file and the proper one should be uncommented.
The viewer makes requests for either spatial voice (used in the region and parcels)
or non-spatial voice (used for group chats or person-to-person voice conversations).
`os-webrtc-janus` allows these two types of voice connections to be handled by
different voice services. Thus there are two different configurations:
- all voice service is provided by the grid (both spatial and non-spatial point to a robust service), and
- the region simulator provides a local Janus server for region spatial voice while the grid service is used for group chats
#### Grid Only Voice Services
The most common configuration will be for a simulator that uses the grid supplied
voice services. For this configuration, `os-webrtc-janus.ini` would look like:
```
[WebRtcVoice]
Enabled = true
SpatialVoiceService = WebRtcVoice.dll:WebRtcVoiceServiceConnector
NonSpatialVoiceService = WebRtcVoice.dll:WebRtcVoiceServiceConnector
WebRtcVoiceServerURI = ${Const|PrivURL}:${Const|PrivatePort}
```
This directs both spatial and non-spatial voice to the grid service connector
and `WebRtcVoiceServerURI` points to the configured Robust grid service.
There is no need for a `[JanusWebRtcVoice]` section because all that is handled by the grid services.
#### Local Simulator Janus Service
In a grid setup, there might be a need for a single simulator/region to use its own
Janus server for either privacy or to off-load the grid voice service.
In this configuration, spatial voice is directed to the local Janus service
while the non-spatial voice goes to the grid services to allow grid wide group
chat and region independent person-to-person chat.
This is done with a `os-webrtc-janus.ini` that looks like:
```
[WebRtcVoice]
Enabled = true
SpatialVoiceService = WebRtcJanusService.dll:WebRtcJanusService
NonSpatialVoiceService = WebRtcVoice.dll:WebRtcVoiceServiceConnector
WebRtcVoiceServerURI = ${Const|PrivURL}:${Const|PrivatePort}
[JanusWebRtcVoice]
JanusGatewayURI = http://janus.example.org:14223/voice
APIToken = APITokenToNeverCheckIn
JanusGatewayAdminURI = http://janus.example.org/admin
AdminAPIToken = AdminAPITokenToNeverCheckIn
```
Notice that, since the simulator has its own Janus service, it must configure the
connection parameters to access that Janus service. The details of running and
configuring a Janus service is provided at [os-webrtc-janus-docker] but, the configuration
here needs to specify the URI to address the Janus server and the API keys
to allow this simulator access to its interfaces. The example above
contains just sample entries.
<a id="Configure_Robust"></a>
## Configure Robust Server for WebRTC Voice
For the grid services side, `os-webrtc-janus` is configured as an additional service
in the Robust OpenSimulator server. The additions to `Robust.ini` are:
```
...
[ServiceList]
...
VoiceServiceConnector = "${Const|PrivatePort}/WebRtcVoice.dll:WebRtcVoiceServerConnector"
...
[WebRtcVoice]
Enabled = true
SpatialVoiceService = WebRtcJanusService.dll:WebRtcJanusService
NonSpatialVoiceService = WebRtcJanusService.dll:WebRtcJanusService
[JanusWebRtcVoice]
JanusGatewayURI = http://janus.example.org:14223/voice
APIToken = APITokenToNeverCheckIn
JanusGatewayAdminURI = http://janus.example.org/admin
AdminAPIToken = AdminAPITokenToNeverCheckIn
...
```
This adds `VoiceServiceConnector` to the list of services presented by this Robust server
and adds the WebRtcVoice configuration that says to do both spatial and non-spatial voice
using the Janus server, and the configuration for the Janus server itself.
One can configure multiple Robust services to distribute the load of services
and a Robust server with only `VoiceServiceConnector` in its ServiceList is possible.
<a id="Configure_Standalone"></a>
## Configure Standalone Region
[OpenSimulator] can be run "standalone" where all the grid services and regions are
run in one simulator instance. Adding voice to this configuration is sometimes useful
for very private meetings or testing. For this configuration, a Janus server is set up
and the standalone simulator is configured to point all voice to that Janus server:
```
[WebRtcVoice]
Enabled = true
SpatialVoiceService = WebRtcJanusService.dll:WebRtcJanusService
NonSpatialVoiceService = WebRtcJanusService.dll:WebRtcJanusService
WebRtcVoiceServerURI = ${Const|PrivURL}:${Const|PrivatePort}
[JanusWebRtcVoice]
JanusGatewayURI = http://janus.example.org:14223/voice
APIToken = APITokenToNeverCheckIn
JanusGatewayAdminURI = http://janus.example.org/admin
AdminAPIToken = AdminAPITokenToNeverCheckIn
```
This directs both spatial and non-spatial voice to the Janus server
and configures the URI address of the Janus server and the API access
keys for that server.
<a id="Managing_Voice"></a>
## Managing Voice (Console commands)
There are a few console commands for checking on and controlling the voice system.
The current list of commands for the simulator can be listed with the
console command `help webrtc`.
This is a growing section and will be added to over time.
**webrtc list sessions** -- not implemented
**janus info** -- list many details of the Janus-Gateway configuration. Very ugly, non-formated JSON.
**janus list rooms** -- list the rooms that have been allocated in the `AudioBridge` Janus plugin
[SecondLife WebRTC Voice]: https://wiki.secondlife.com/wiki/WebRTC_Voice
[OpenSimulator]: http://opensimulator.org
[OpenSimulator Community Conference]: https://conference.opensimulator.org
[os-webrtc-janus]: https://github.com/Misterblue/os-webrtc-janus
[Janus-Gateway WebRTC server]: https://janus.conf.meetecho.com/
[os-webrtc-janus-docker]: https://github.com/Misterblue/os-webrtc-janus-docker

View File

@@ -1,55 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using OpenMetaverse;
using OpenMetaverse.StructuredData;
namespace osWebRtcVoice
{
/// <summary>
/// This is the interface for the voice service. It is used to connect
/// the user to the voice server and to handle the capability messages
/// from the viewer.
/// </summary>
public interface IWebRtcVoiceService
{
// The user is requesting a voice connection. The message contains the offer
// from the user and we must return the answer.
// If there are problems, the returned map will contain an error message.
// Initial calls to the voice server to get the user connected
public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pScene);
public OSDMap VoiceSignalingRequest(OSDMap pRequest, UUID pUserID, UUID pScene);
// Once connection state is looked up, the viewer session is passed in
public OSDMap ProvisionVoiceAccountRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pScene);
public OSDMap VoiceSignalingRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pScene);
// Create a viewer session with all the variables needed for the underlying implementation
public IVoiceViewerSession CreateViewerSession(OSDMap pRequest, UUID pUserID, UUID pScene);
}
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Linq;
using System.Collections.Generic;
using OpenMetaverse;
using System.Threading.Tasks;
namespace osWebRtcVoice
{
public class VoiceViewerSession : IVoiceViewerSession
{
// A simple session structure that is used when the connection is actually in the
// remote service.
public VoiceViewerSession(IWebRtcVoiceService pVoiceService, UUID pRegionId, UUID pAgentId)
{
RegionId = pRegionId;
AgentId = pAgentId;
ViewerSessionID = UUID.Random().ToString();
VoiceService = pVoiceService;
}
public string ViewerSessionID { get; set; }
public IWebRtcVoiceService VoiceService { get; set; }
public string VoiceServiceSessionId
{
get => throw new System.NotImplementedException();
set => throw new System.NotImplementedException();
}
public UUID RegionId { get; set; }
public UUID AgentId { get; set; }
// =====================================================================
// ViewerSessions hold the connection information for the client connection through to the voice service.
// This collection is static and is simulator wide so there will be sessions for all regions and all clients.
public static Dictionary<string, IVoiceViewerSession> ViewerSessions = new Dictionary<string, IVoiceViewerSession>();
// Get a viewer session by the viewer session ID
public static bool TryGetViewerSession(string pViewerSessionId, out IVoiceViewerSession pViewerSession)
{
lock (ViewerSessions)
{
return ViewerSessions.TryGetValue(pViewerSessionId, out pViewerSession);
}
}
// public static bool TryGetViewerSessionByAgentId(UUID pAgentId, out IVoiceViewerSession pViewerSession)
public static bool TryGetViewerSessionByAgentId(UUID pAgentId, out IEnumerable<KeyValuePair<string, IVoiceViewerSession>> pViewerSessions)
{
lock (ViewerSessions)
{
pViewerSessions = ViewerSessions.Where(v => v.Value.AgentId == pAgentId);
return pViewerSessions.Count() > 0;
}
}
// Get a viewer session by the VoiceService session ID
public static bool TryGetViewerSessionByVSSessionId(string pVSSessionId, out IVoiceViewerSession pViewerSession)
{
lock (ViewerSessions)
{
var sessions = ViewerSessions.Where(v => v.Value.VoiceServiceSessionId == pVSSessionId);
if (sessions.Count() > 0)
{
pViewerSession = sessions.First().Value;
return true;
}
pViewerSession = null;
return false;
}
}
public static void AddViewerSession(IVoiceViewerSession pSession)
{
lock (ViewerSessions)
{
ViewerSessions[pSession.ViewerSessionID] = pSession;
}
}
public static void RemoveViewerSession(string pSessionId)
{
lock (ViewerSessions)
{
ViewerSessions.Remove(pSessionId);
}
}
// Update a ViewSession from one ID to another.
// Remove the old session ID from the ViewerSessions collection, update the
// sessionID value in the IVoiceViewerSession, and add the session back to the
// collection.
// This is used in the kludge to synchronize a region's ViewerSessionID with the
// remote VoiceService's session ID.
public static void UpdateViewerSessionId(IVoiceViewerSession pSession, string pNewSessionId)
{
lock (ViewerSessions)
{
ViewerSessions.Remove(pSession.ViewerSessionID);
pSession.ViewerSessionID = pNewSessionId;
ViewerSessions[pSession.ViewerSessionID] = pSession;
}
}
public Task Shutdown()
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
using OpenSim.Server.Base;
using OpenSim.Server.Handlers.Base;
using System.Threading.Tasks;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using log4net;
using Nini.Config;
namespace osWebRtcVoice
{
// Class that provides the network interface to the WebRTC voice server.
// This is used by the Robust server to receive requests from the region servers
// and do the voice stuff on the WebRTC service (see WebRtcVoiceServiceConnector).
public class WebRtcVoiceServerConnector : IServiceConnector
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[WEBRTC VOICE SERVER CONNECTOR]";
private bool m_Enabled = false;
private bool m_MessageDetails = false;
private IWebRtcVoiceService m_WebRtcVoiceService;
public WebRtcVoiceServerConnector(IConfigSource pConfig, IHttpServer pServer, string pConfigName)
{
IConfig moduleConfig = pConfig.Configs["WebRtcVoice"];
if (moduleConfig is not null)
{
m_Enabled = moduleConfig.GetBoolean("Enabled", false);
if (m_Enabled)
{
m_log.InfoFormat("{0} WebRtcVoiceServerConnector enabled", LogHeader);
m_MessageDetails = moduleConfig.GetBoolean("MessageDetails", false);
// This creates the local service that handles the requests.
// The local service provides the IWebRtcVoiceService interface and directs the requests
// to the WebRTC service.
string localServiceModule = moduleConfig.GetString("LocalServiceModule", "WebRtcVoiceServiceModule.dll:WebRtcVoiceServiceModule");
m_log.DebugFormat("{0} loading {1}", LogHeader, localServiceModule);
object[] args = new object[0];
m_WebRtcVoiceService = ServerUtils.LoadPlugin<IWebRtcVoiceService>(localServiceModule, args);
// The WebRtcVoiceServiceModule is both an IWebRtcVoiceService and a ISharedRegionModule
// so we can initialize it as if it was the region module.
ISharedRegionModule sharedModule = m_WebRtcVoiceService as ISharedRegionModule;
if (sharedModule is null)
{
m_log.ErrorFormat("{0} local service module does not implement ISharedRegionModule", LogHeader);
m_Enabled = false;
return;
}
sharedModule.Initialise(pConfig);
// Now that we have someone to handle the requests, we can set up the handlers
pServer.AddJsonRPCHandler("provision_voice_account_request", Handle_ProvisionVoiceAccountRequest);
pServer.AddJsonRPCHandler("voice_signaling_request", Handle_VoiceSignalingRequest);
}
}
}
private bool Handle_ProvisionVoiceAccountRequest(OSDMap pJson, ref JsonRpcResponse pResponse)
{
bool ret = false;
m_log.DebugFormat("{0} Handle_ProvisionVoiceAccountRequest", LogHeader);
if (m_MessageDetails) m_log.DebugFormat("{0} PVAR: req={1}", LogHeader, pJson.ToString());
if (pJson.ContainsKey("params") && pJson["params"] is OSDMap paramsMap)
{
OSDMap request = paramsMap.ContainsKey("request") ? paramsMap["request"] as OSDMap : null;
UUID userID = paramsMap.ContainsKey("userID") ? paramsMap["userID"].AsUUID() : UUID.Zero;
UUID sceneID = paramsMap.ContainsKey("scene") ? paramsMap["scene"].AsUUID() : UUID.Zero;
try
{
if (m_WebRtcVoiceService is null)
{
m_log.ErrorFormat("{0} PVAR: no local service", LogHeader);
return false;
}
OSDMap resp = m_WebRtcVoiceService.ProvisionVoiceAccountRequest(request, userID, sceneID);
pResponse = new JsonRpcResponse();
pResponse.Result = resp;
if (m_MessageDetails) m_log.DebugFormat("{0} PVAR: resp={1}", LogHeader, resp.ToString());
ret = true;
}
catch (Exception e)
{
m_log.ErrorFormat("{0} PVAR: exception {1}", LogHeader, e);
}
}
else
{
m_log.ErrorFormat("{0} PVAR: missing parameters", LogHeader);
}
return ret;
}
private bool Handle_VoiceSignalingRequest(OSDMap pJson, ref JsonRpcResponse pResponse)
{
bool ret = false;
if (pJson.ContainsKey("params") && pJson["params"] is OSDMap paramsMap)
{
m_log.DebugFormat("{0} Handle_VoiceSignalingRequest", LogHeader);
if (m_MessageDetails) m_log.DebugFormat("{0} VSR: req={1}", LogHeader, paramsMap.ToString());
OSDMap request = paramsMap.ContainsKey("request") ? paramsMap["request"] as OSDMap : null;
UUID userID = paramsMap.ContainsKey("userID") ? paramsMap["userID"].AsUUID() : UUID.Zero;
UUID sceneID = paramsMap.ContainsKey("scene") ? paramsMap["scene"].AsUUID() : UUID.Zero;
try
{
OSDMap resp = m_WebRtcVoiceService.VoiceSignalingRequest(request, userID, sceneID);
pResponse = new JsonRpcResponse();
pResponse.Result = resp;
if (m_MessageDetails) m_log.DebugFormat("{0} VSR: resp={1}", LogHeader, resp.ToString());
ret = true;
}
catch (Exception e)
{
m_log.ErrorFormat("{0} VSR: exception {1}", LogHeader, e);
}
}
else
{
m_log.ErrorFormat("{0} VSR: missing parameters", LogHeader);
}
return ret;
}
}
}

View File

@@ -1,204 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Reflection;
using OpenSim.Framework;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using log4net;
using Nini.Config;
namespace osWebRtcVoice
{
// Class that provides the local IWebRtcVoiceService interface to the JsonRPC Robust
// server. This is used by the region servers to talk to the Robust server.
public class WebRtcVoiceServiceConnector : IWebRtcVoiceService
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[WEBRTC VOICE SERVICE CONNECTOR]";
private bool m_Enabled = false;
private bool m_MessageDetails = false;
private IConfigSource m_Config;
string m_serverURI = "http://localhost:8080";
public WebRtcVoiceServiceConnector(IConfigSource pConfig)
{
m_Config = pConfig;
IConfig moduleConfig = m_Config.Configs["WebRtcVoice"];
if (moduleConfig is not null)
{
m_Enabled = moduleConfig.GetBoolean("Enabled", false);
if (m_Enabled)
{
m_serverURI = moduleConfig.GetString("WebRtcVoiceServerURI", string.Empty);
if (string.IsNullOrWhiteSpace(m_serverURI))
{
m_log.Error($"{LogHeader} WebRtcVoiceServiceConnector enabled but no WebRtcVoiceServerURI specified");
m_Enabled = false;
}
else
{
m_log.Info($"{LogHeader} WebRtcVoiceServiceConnector enabled");
}
m_MessageDetails = moduleConfig.GetBoolean("MessageDetails", false);
}
}
}
// Create a local viewer session. This gets a local viewer session ID that is
// later changed when the ProvisionVoiceAccountRequest response is returned
// so that the viewer session ID is the same here as from the WebRTC service.
public IVoiceViewerSession CreateViewerSession(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
m_log.Debug($"{LogHeader} CreateViewerSession");
return new VoiceViewerSession(this, pUserID, pSceneID);
}
public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
m_log.Debug($"{LogHeader} ProvisionVoiceAccountRequest without ViewerSession. uID={pUserID}, sID={pSceneID}");
return null;
}
// Received a ProvisionVoiceAccountRequest from a viewer. Forward it to the WebRTC service.
public OSDMap ProvisionVoiceAccountRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
m_log.Debug($"{LogHeader} VoiceSignalingRequest. uID={pUserID}, sID={pSceneID}");
OSDMap req = new()
{
{ "request", pRequest },
{ "userID", pUserID.ToString() },
{ "scene", pSceneID.ToString() }
};
var resp = JsonRpcRequest("provision_voice_account_request", m_serverURI, req);
// Kludge to sync the viewer session number in our IVoiceViewerSession with the one from the WebRTC service.
if (resp.TryGetString("viewer_session", out string otherViewerSessionId))
{
m_log.Debug(
$"{LogHeader} ProvisionVoiceAccountRequest: syncing viewSessionID. old={pVSession.ViewerSessionID}, new={otherViewerSessionId}");
VoiceViewerSession.UpdateViewerSessionId(pVSession, otherViewerSessionId);
}
return resp;
}
public OSDMap VoiceSignalingRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
m_log.Debug($"{LogHeader} VoiceSignalingRequest without ViewerSession. uID={pUserID}, sID={pSceneID}");
return null;
}
public OSDMap VoiceSignalingRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
m_log.DebugFormat("{0} VoiceSignalingRequest. uID={1}, sID={2}", LogHeader, pUserID, pSceneID);
OSDMap req = new()
{
{ "request", pRequest },
{ "userID", pUserID.ToString() },
{ "scene", pSceneID.ToString() }
};
return JsonRpcRequest("voice_signaling_request", m_serverURI, req);
}
public OSDMap JsonRpcRequest(string method, string uri, OSDMap pParams)
{
string jsonId = UUID.Random().ToString();
if(string.IsNullOrWhiteSpace(uri))
return null;
OSDMap request = new()
{
{ "jsonrpc", OSD.FromString("2.0") },
{ "id", OSD.FromString(jsonId) },
{ "method", OSD.FromString(method) },
{ "params", pParams }
};
OSDMap outerResponse = null;
try
{
if (m_MessageDetails) m_log.Debug($"{LogHeader}: request: {request}");
outerResponse = WebUtil.PostToService(uri, request, 10000, true);
if (m_MessageDetails) m_log.Debug($"{LogHeader}: response: {outerResponse}");
}
catch (Exception e)
{
m_log.Error($"{LogHeader}: JsonRpc request '{method}' to {uri} failed: {e.Message}");
m_log.Debug($"{LogHeader}: request: {request}");
return new OSDMap()
{
{ "error", OSD.FromString(e.Message) }
};
}
if (!outerResponse.TryGetOSDMap("_Result", out OSDMap response))
{
string errm = $"JsonRpc request '{method}' to {1} returned an invalid response: {OSDParser.SerializeJsonString(outerResponse)}";
m_log.Error(errm);
return new OSDMap()
{
{ "error", errm }
};
}
OSD osdtmp;
if (response.TryGetValue("error", out osdtmp))
{
string errm = $"JsonRpc request '{method}' to {uri} returned an error: {OSDParser.SerializeJsonString(osdtmp)}";
m_log.Error(errm);
return new OSDMap()
{
{ "error", errm }
};
}
if (!response.TryGetOSDMap("result", out OSDMap resultmap ))
{
string errm = $"JsonRpc request '{method}' to {uri} returned result as non-OSDMap: {OSDParser.SerializeJsonString(outerResponse)}";
m_log.Error(errm);
return new OSDMap()
{
{ "error", errm }
};
}
return resultmap;
}
}
}

View File

@@ -1,500 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Net;
using System.Reflection;
using Mono.Addins;
using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using Caps = OpenSim.Framework.Capabilities.Caps;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
using log4net;
using Nini.Config;
[assembly: Addin("WebRtcVoiceRegionModule", "1.0")]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
namespace osWebRtcVoice
{
/// <summary>
/// This module provides the WebRTC voice interface for viewer clients..
///
/// In particular, it provides the following capabilities:
/// ProvisionVoiceAccountRequest, VoiceSignalingRequest and limited ChatSessionRequest
/// which are the user interface to the voice service.
///
/// Initially, when the user connects to the region, the region feature "VoiceServiceType" is
/// set to "webrtc" and the capabilities that support voice are enabled.
/// The capabilities then pass the user request information to the IWebRtcVoiceService interface
/// that has been registered for the reqion.
/// </summary>
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RegionVoiceModule")]
public class WebRtcVoiceRegionModule : ISharedRegionModule
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string logHeader = "[REGION WEBRTC VOICE]";
private static byte[] llsdUndefAnswerBytes = Util.UTF8.GetBytes("<llsd><undef /></llsd>");
private bool _MessageDetails = false;
// Control info
private static bool m_Enabled = false;
private IConfig m_Config;
// ISharedRegionModule.Initialize
public void Initialise(IConfigSource config)
{
m_Config = config.Configs["WebRtcVoice"];
if (m_Config is not null)
{
m_Enabled = m_Config.GetBoolean("Enabled", false);
if (m_Enabled)
{
_MessageDetails = m_Config.GetBoolean("MessageDetails", false);
m_log.Info($"{logHeader}: enabled");
}
}
}
// ISharedRegionModule.PostInitialize
public void PostInitialise()
{
}
// ISharedRegionModule.AddRegion
public void AddRegion(Scene scene)
{
// todo register module to get parcels changes etc
}
// ISharedRegionModule.RemoveRegion
public void RemoveRegion(Scene scene)
{
}
// ISharedRegionModule.RegionLoaded
public void RegionLoaded(Scene scene)
{
if (m_Enabled)
{
scene.EventManager.OnRegisterCaps += delegate (UUID agentID, Caps caps)
{
OnRegisterCaps(scene, agentID, caps);
};
ISimulatorFeaturesModule simFeatures = scene.RequestModuleInterface<ISimulatorFeaturesModule>();
simFeatures?.AddFeature("VoiceServerType", OSD.FromString("webrtc"));
}
}
// ISharedRegionModule.Close
public void Close()
{
}
// ISharedRegionModule.Name
public string Name
{
get { return "RegionVoiceModule"; }
}
// ISharedRegionModule.ReplaceableInterface
public Type ReplaceableInterface
{
get { return null; }
}
// <summary>
// OnRegisterCaps is invoked via the scene.EventManager
// everytime OpenSim hands out capabilities to a client
// (login, region crossing). We contribute three capabilities to
// the set of capabilities handed back to the client:
// ProvisionVoiceAccountRequest, VoiceSignalingRequest and limited ChatSessionRequest
//
// ProvisionVoiceAccountRequest allows the client to obtain
// voice communication information the the avater.
//
// VoiceSignalingRequest: Used for trickling ICE candidates.
//
// ChatSessionRequest
//
// Note that OnRegisterCaps is called here via a closure
// delegate containing the scene of the respective region (see
// Initialise()).
// </summary>
public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
{
m_log.Debug(
$"{logHeader}: OnRegisterCaps called with agentID {agentID} caps {caps} in scene {scene.Name}");
caps.RegisterSimpleHandler("ProvisionVoiceAccountRequest",
new SimpleStreamHandler("/" + UUID.Random(), (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) =>
{
ProvisionVoiceAccountRequest(httpRequest, httpResponse, agentID, scene);
}));
caps.RegisterSimpleHandler("VoiceSignalingRequest",
new SimpleStreamHandler("/" + UUID.Random(), (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) =>
{
VoiceSignalingRequest(httpRequest, httpResponse, agentID, scene);
}));
caps.RegisterSimpleHandler("ChatSessionRequest",
new SimpleStreamHandler("/" + UUID.Random(), (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) =>
{
ChatSessionRequest(httpRequest, httpResponse, agentID, scene);
}));
}
/// <summary>
/// Callback for a client request for Voice Account Details
/// </summary>
/// <param name="scene">current scene object of the client</param>
/// <param name="request"></param>
/// <param name="path"></param>
/// <param name="param"></param>
/// <param name="agentID"></param>
/// <param name="caps"></param>
/// <returns></returns>
public void ProvisionVoiceAccountRequest(IOSHttpRequest request, IOSHttpResponse response, UUID agentID, Scene scene)
{
// Get the voice service. If it doesn't exist, return an error.
IWebRtcVoiceService voiceService = scene.RequestModuleInterface<IWebRtcVoiceService>();
if (voiceService is null)
{
m_log.Error($"{logHeader}[ProvisionVoice]: voice service not loaded");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if(request.HttpMethod != "POST")
{
m_log.DebugFormat($"[{logHeader}][ProvisionVoice]: Not a POST request. Agent={agentID}");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
// Deserialize the request. Convert the LLSDXml to OSD for our use
OSDMap map = BodyToMap(request, "ProvisionVoiceAccountRequest");
if (map is null)
{
m_log.Error($"{logHeader}[ProvisionVoice]: No request data found. Agent={agentID}");
response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
// Make sure the request is for WebRtc voice
if (map.TryGetValue("voice_server_type", out OSD vstosd))
{
if (vstosd is OSDString vst && !((string)vst).Equals("webrtc", StringComparison.OrdinalIgnoreCase))
{
m_log.Warn($"{logHeader}[ProvisionVoice]: voice_server_type is not 'webrtc'. Request: {map}");
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.OK;
return;
}
}
if (_MessageDetails) m_log.DebugFormat($"{logHeader}[ProvisionVoice]: request: {map}");
if (map.TryGetString("channel_type", out string channelType))
{
//do fully not trust viewers voice parcel requests
if (channelType == "local")
{
if (!scene.RegionInfo.EstateSettings.AllowVoice)
{
m_log.Debug($"{logHeader}[ProvisionVoice]:region \"{scene.Name}\": voice not enabled in estate settings");
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.NotImplemented;
return;
}
if (scene.LandChannel == null)
{
m_log.Error($"{logHeader}[ProvisionVoice] region \"{scene.Name}\" land data not yet available");
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.NotImplemented;
return;
}
if(!scene.TryGetScenePresence(agentID, out ScenePresence sp))
{
m_log.Debug($"{logHeader}[ProvisionVoice]:avatar not found");
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if(map.TryGetInt("parcel_local_id", out int parcelID))
{
ILandObject parcel = scene.LandChannel.GetLandObject(parcelID);
if (parcel == null)
{
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
LandData land = parcel.LandData;
if (land == null)
{
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (!scene.RegionInfo.EstateSettings.TaxFree && (land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0)
{
m_log.Debug($"{logHeader}[ProvisionVoice]:parcel voice not allowed");
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
if ((land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) != 0)
{
map.Remove("parcel_local_id"); // estate channel
}
else if(parcel.IsRestrictedFromLand(agentID) || parcel.IsBannedFromLand(agentID))
{
// check Z distance?
m_log.Debug($"{logHeader}[ProvisionVoice]:agent not allowed on parcel");
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
}
}
}
// The checks passed. Send the request to the voice service.
OSDMap resp = voiceService.ProvisionVoiceAccountRequest(map, agentID, scene.RegionInfo.RegionID);
if(resp is not null)
{
if (_MessageDetails) m_log.DebugFormat($"{logHeader}[ProvisionVoice]: response: {resp}");
// TODO: check for errors and package the response
// Convert the OSD to LLSDXml for the response
string xmlResp = OSDParser.SerializeLLSDXmlString(resp);
response.RawBuffer = Util.UTF8.GetBytes(xmlResp);
response.StatusCode = (int)HttpStatusCode.OK;
}
else
{
m_log.DebugFormat($"{logHeader}[ProvisionVoice]: got null response");
response.StatusCode = (int)HttpStatusCode.OK;
}
return;
}
public void VoiceSignalingRequest(IOSHttpRequest request, IOSHttpResponse response, UUID agentID, Scene scene)
{
IWebRtcVoiceService voiceService = scene.RequestModuleInterface<IWebRtcVoiceService>();
if (voiceService is null)
{
m_log.ErrorFormat($"{logHeader}[VoiceSignalingRequest]: avatar \"{agentID}\": no voice service");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if(request.HttpMethod != "POST")
{
m_log.Error($"[{logHeader}][VoiceSignaling]: Not a POST request. Agent={agentID}");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
// Deserialize the request. Convert the LLSDXml to OSD for our use
OSDMap map = BodyToMap(request, "VoiceSignalingRequest");
if (map is null)
{
m_log.ErrorFormat($"{logHeader}[VoiceSignalingRequest]: No request data found. Agent={agentID}");
response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
// Make sure the request is for WebRTC voice
if (map.TryGetValue("voice_server_type", out OSD vstosd))
{
if (vstosd is OSDString vst && !((string)vst).Equals("webrtc", StringComparison.OrdinalIgnoreCase))
{
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.OK;
return;
}
}
OSDMap resp = voiceService.VoiceSignalingRequest(map, agentID, scene.RegionInfo.RegionID);
if (_MessageDetails) m_log.Debug($"{logHeader}[VoiceSignalingRequest]: Response: {resp}");
// TODO: check for errors and package the response
response.RawBuffer = llsdUndefAnswerBytes;
response.StatusCode = (int)HttpStatusCode.OK;
return;
}
/// <summary>
/// Callback for a client request for ChatSessionRequest.
/// The viewer sends this request when the user tries to start a P2P text or voice session
/// with another user. We need to generate a new session ID and return it to the client.
/// </summary>
/// <param name="request"></param>
/// <param name="response"></param>
/// <param name="agentID"></param>
/// <param name="scene"></param>
public void ChatSessionRequest(IOSHttpRequest request, IOSHttpResponse response, UUID agentID, Scene scene)
{
m_log.DebugFormat("{0}: ChatSessionRequest received for agent {1} in scene {2}", logHeader, agentID, scene.RegionInfo.RegionName);
if (request.HttpMethod != "POST")
{
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (!scene.TryGetScenePresence(agentID, out ScenePresence sp) || sp.IsDeleted)
{
m_log.Warn($"{logHeader} ChatSessionRequest: scene presence not found or deleted for agent {agentID}");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
OSDMap reqmap = BodyToMap(request, "[ChatSessionRequest]");
if (reqmap is null)
{
m_log.Warn($"{logHeader} ChatSessionRequest: message body not parsable in request for agent {agentID}");
response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
m_log.Debug($"{logHeader} ChatSessionRequest");
if (!reqmap.TryGetString("method", out string method))
{
m_log.Warn($"{logHeader} ChatSessionRequest: missing required 'method' field in request for agent {agentID}");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (!reqmap.TryGetUUID("session-id", out UUID sessionID))
{
m_log.Warn($"{logHeader} ChatSessionRequest: missing required 'session-id' field in request for agent {agentID}");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
switch (method.ToLower())
{
// Several different method requests that we don't know how to handle.
// Just return OK for now.
case "decline p2p voice":
case "decline invitation":
case "start conference":
case "fetch history":
response.StatusCode = (int)HttpStatusCode.OK;
break;
// Asking to start a P2P voice session. We need to generate a new session ID and return
// it to the client in a ChatterBoxSessionStartReply event.
case "start p2p voice":
UUID newSessionID;
if (reqmap.TryGetUUID("params", out UUID otherID))
newSessionID = new(otherID.ulonga ^ agentID.ulonga, otherID.ulongb ^ agentID.ulongb);
else
newSessionID = UUID.Random();
IEventQueue queue = scene.RequestModuleInterface<IEventQueue>();
if (queue is null)
{
m_log.ErrorFormat("{0}: no event queue for scene {1}", logHeader, scene.RegionInfo.RegionName);
response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
else
{
queue.ChatterBoxSessionStartReply(
newSessionID,
sp.Name,
2,
false,
true,
sessionID,
true,
string.Empty,
agentID);
response.StatusCode = (int)HttpStatusCode.OK;
}
break;
default:
response.StatusCode = (int)HttpStatusCode.BadRequest;
break;
}
}
/// <summary>
/// Convert the LLSDXml body of the request to an OSDMap for easier handling.
/// Also logs the request if message details is enabled.
/// </summary>
/// <param name="request"></param>
/// <param name="pCaller"></param>
/// <returns>'null' if the request body is empty or cannot be deserialized</returns>
private OSDMap BodyToMap(IOSHttpRequest request, string pCaller)
{
try
{
using Stream inputStream = request.InputStream;
if (inputStream.Length > 0)
{
OSD tmp = OSDParser.DeserializeLLSDXml(inputStream);
if (_MessageDetails)
m_log.Debug($"{pCaller} BodyToMap: Request: {tmp}");
if(tmp is OSDMap map)
return map;
}
}
catch
{
m_log.Debug($"{pCaller} BodyToMap: Fail to decode LLSDXml request");
}
return null;
}
}
}

View File

@@ -1,295 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Server.Base;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using Mono.Addins;
using log4net;
using Nini.Config;
[assembly: Addin("WebRtcVoiceServiceModule", "1.0")]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
namespace osWebRtcVoice
{
/// <summary>
/// Interface for the WebRtcVoiceService.
/// An instance of this is registered as the IWebRtcVoiceService for this region.
/// The function here is to direct the capability requests to the appropriate voice service.
/// For the moment, there are separate voice services for spatial and non-spatial voice
/// with the idea that a region could have a pre-region spatial voice service while
/// the grid could have a non-spatial voice service for group chat, etc.
/// Fancier configurations are possible.
/// </summary>
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WebRtcVoiceServiceModule")]
public class WebRtcVoiceServiceModule : ISharedRegionModule, IWebRtcVoiceService
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static string LogHeader = "[WEBRTC VOICE SERVICE MODULE]";
private static bool m_Enabled = false;
private IConfigSource m_Config;
private IWebRtcVoiceService m_spatialVoiceService;
private IWebRtcVoiceService m_nonSpatialVoiceService;
// =====================================================================
// ISharedRegionModule.Initialize
// Get configuration and load the modules that will handle spatial and non-spatial voice.
public void Initialise(IConfigSource pConfig)
{
m_Config = pConfig;
IConfig moduleConfig = m_Config.Configs["WebRtcVoice"];
if (moduleConfig is not null)
{
m_Enabled = moduleConfig.GetBoolean("Enabled", false);
if (m_Enabled)
{
// Get the DLLs for the two voice services
string spatialDllName = moduleConfig.GetString("SpatialVoiceService", string.Empty);
string nonSpatialDllName = moduleConfig.GetString("NonSpatialVoiceService", string.Empty);
if (string.IsNullOrEmpty(spatialDllName) && string.IsNullOrEmpty(nonSpatialDllName))
{
m_log.Error($"{LogHeader} No VoiceService specified in configuration");
m_Enabled = false;
return;
}
// Default non-spatial to spatial if not specified
if (string.IsNullOrEmpty(nonSpatialDllName))
{
m_log.Debug($"{LogHeader} nonSpatialDllName not specified. Defaulting to spatialDllName");
nonSpatialDllName = spatialDllName;
}
// Load the two voice services
m_log.Debug($"{LogHeader} Loading SpatialVoiceService from {spatialDllName}");
m_spatialVoiceService = ServerUtils.LoadPlugin<IWebRtcVoiceService>(spatialDllName, [m_Config]);
if (m_spatialVoiceService is null)
{
m_log.Error($"{LogHeader} Could not load SpatialVoiceService from {spatialDllName}, module disabled");
m_Enabled = false;
return;
}
m_log.Debug($"{LogHeader} Loading NonSpatialVoiceService from {nonSpatialDllName}");
if (spatialDllName != nonSpatialDllName)
{
m_nonSpatialVoiceService = ServerUtils.LoadPlugin<IWebRtcVoiceService>(nonSpatialDllName, [ m_Config ]);
if (m_nonSpatialVoiceService is null)
{
m_log.Error("{LogHeader} Could not load NonSpatialVoiceService from {nonSpatialDllName}");
m_Enabled = false;
}
}
if (m_Enabled)
{
m_log.Info($"{LogHeader} WebRtcVoiceService enabled");
}
}
}
}
// ISharedRegionModule.PostInitialize
public void PostInitialise()
{
}
// ISharedRegionModule.Close
public void Close()
{
}
// ISharedRegionModule.ReplaceableInterface
public Type ReplaceableInterface
{
get { return null; }
}
// ISharedRegionModule.Name
public string Name
{
get { return "WebRtcVoiceServiceModule"; }
}
// ISharedRegionModule.AddRegion
public void AddRegion(Scene scene)
{
if (m_Enabled)
{
m_log.Debug($"{LogHeader} Adding WebRtcVoiceService to region {scene.Name}");
scene.RegisterModuleInterface<IWebRtcVoiceService>(this);
// TODO: figure out what events we care about
// When new client (child or root) is added to scene, before OnClientLogin
// scene.EventManager.OnNewClient += Event_OnNewClient;
// When client is added on login.
// scene.EventManager.OnClientLogin += Event_OnClientLogin;
// New presence is added to scene. Child, root, and NPC. See Scene.AddNewAgent()
// scene.EventManager.OnNewPresence += Event_OnNewPresence;
// scene.EventManager.OnRemovePresence += Event_OnRemovePresence;
// update to client position (either this or 'significant')
// scene.EventManager.OnClientMovement += Event_OnClientMovement;
// "significant" update to client position
// scene.EventManager.OnSignificantClientMovement += Event_OnSignificantClientMovement;
}
}
// ISharedRegionModule.RemoveRegion
public void RemoveRegion(Scene scene)
{
if (m_Enabled)
{
scene.UnregisterModuleInterface<IWebRtcVoiceService>(this);
}
}
// ISharedRegionModule.RegionLoaded
public void RegionLoaded(Scene scene)
{
}
// =====================================================================
// Thought about doing this but currently relying on the voice service
// event ("hangup") to remove the viewer session.
private void Event_OnRemovePresence(UUID pAgentID)
{
// When a presence is removed, remove the viewer sessions for that agent
IEnumerable<KeyValuePair<string, IVoiceViewerSession>> vSessions;
if (VoiceViewerSession.TryGetViewerSessionByAgentId(pAgentID, out vSessions))
{
foreach(KeyValuePair<string, IVoiceViewerSession> v in vSessions)
{
m_log.DebugFormat("{0} Event_OnRemovePresence: removing viewer session {1}", LogHeader, v.Key);
VoiceViewerSession.RemoveViewerSession(v.Key);
v.Value.Shutdown();
}
}
}
// =====================================================================
// IWebRtcVoiceService
// IWebRtcVoiceService.ProvisionVoiceAccountRequest
public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
OSDMap response = null;
IVoiceViewerSession vSession = null;
if (pRequest.TryGetString("viewer_session", out string viewerSessionId))
{
// request has a viewer session. Use that to find the voice service
if (!VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession))
{
m_log.Error($"{0} ProvisionVoiceAccountRequest: viewer session {viewerSessionId} not found");
}
}
else
{
// the request does not have a viewer session. See if it's an initial request
if (pRequest.TryGetString("channel_type", out string channelType))
{
if (channelType == "local")
{
// TODO: check if this userId is making a new session (case that user is reconnecting)
vSession = m_spatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID);
VoiceViewerSession.AddViewerSession(vSession);
}
else
{
// TODO: check if this userId is making a new session (case that user is reconnecting)
vSession = m_nonSpatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID);
VoiceViewerSession.AddViewerSession(vSession);
}
}
else
{
m_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: no channel_type in request");
}
}
if (vSession is not null)
{
response = vSession.VoiceService.ProvisionVoiceAccountRequest(vSession, pRequest, pUserID, pSceneID);
}
return response;
}
// IWebRtcVoiceService.VoiceSignalingRequest
public OSDMap VoiceSignalingRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
OSDMap response = null;
IVoiceViewerSession vSession = null;
if (pRequest.TryGetString("viewer_session", out string viewerSessionId))
{
// request has a viewer session. Use that to find the voice service
if (VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession))
{
response = vSession.VoiceService.VoiceSignalingRequest(vSession, pRequest, pUserID, pSceneID);
}
else
{
m_log.ErrorFormat("{0} VoiceSignalingRequest: viewer session {1} not found", LogHeader, viewerSessionId);
}
}
else
{
m_log.ErrorFormat("{0} VoiceSignalingRequest: no viewer_session in request", LogHeader);
}
return response;
}
// This module should never be called with this signature
public OSDMap ProvisionVoiceAccountRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
throw new NotImplementedException();
}
// This module should never be called with this signature
public OSDMap VoiceSignalingRequest(IVoiceViewerSession pVSession, OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
throw new NotImplementedException();
}
public IVoiceViewerSession CreateViewerSession(OSDMap pRequest, UUID pUserID, UUID pSceneID)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Framework;
namespace OpenSim.ApplicationPlugins.LoadRegions
{
public class EstateLoaderFileSystem : IEstateLoader
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IConfigSource m_configSource;
private OpenSimBase m_application;
public EstateLoaderFileSystem(OpenSimBase openSim)
{
m_application = openSim;
}
public void SetIniConfigSource(IConfigSource configSource)
{
m_configSource = configSource;
}
public void LoadEstates()
{
string estateConfigPath = Path.Combine(Util.configDir(), "Estates");
IConfig startupConfig = m_configSource.Configs["Startup"];
if(startupConfig == null)
return;
estateConfigPath = startupConfig.GetString("regionload_estatesdir", estateConfigPath).Trim();
if(string.IsNullOrWhiteSpace(estateConfigPath))
return;
if (!Directory.Exists(estateConfigPath))
return; // if nothing there, don't bother
string[] iniFiles;
try
{
iniFiles = Directory.GetFiles(estateConfigPath, "*.ini");
}
catch
{
m_log.Error("[ESTATE LOADER FILE SYSTEM]: could not open " + estateConfigPath);
return;
}
// No Estate.ini Found
if (iniFiles == null || iniFiles.Length == 0)
return;
m_log.InfoFormat("[ESTATE LOADER FILE SYSTEM]: Loading estate config files from {0}", estateConfigPath);
List<int> existingEstates;
List<int> existingEstateIDs = m_application.EstateDataService.GetEstatesAll();
foreach (string file in iniFiles)
{
m_log.InfoFormat("[ESTATE LOADER FILE SYSTEM]: Loading config file {0}", file);
IConfigSource source = null;
try
{
source = new IniConfigSource(file);
}
catch
{
m_log.WarnFormat("[ESTATE LOADER FILE SYSTEM]: failed to parse file {0}", file);
}
if(source == null)
continue;
foreach (IConfig config in source.Configs)
{
// Read Estate Config From Source File
string estateName = config.Name;
if (string.IsNullOrWhiteSpace(estateName))
continue;
if (estateName.Length > 64) // need check this and if utf8 is valid
{
m_log.WarnFormat("[ESTATE LOADER FILE SYSTEM]: Estate name {0} is too large, ignoring", estateName);
continue;
}
string ownerString = config.GetString("Owner", string.Empty);
if (string.IsNullOrWhiteSpace(ownerString))
continue;
if (!UUID.TryParse(ownerString, out UUID estateOwner) || estateOwner.IsZero())
continue;
// Check If Estate Exists (Skip If So)
existingEstates = m_application.EstateDataService.GetEstates(estateName);
if (existingEstates.Count > 0)
continue;
//### Should check Estate Owner ID but no Scene object available at this point
// Does Config Specify EstateID (0 Defaults To AutoIncrement)
int EstateID = config.GetInt("EstateID", 0);
if (EstateID > 0)
{
if (EstateID < 100)
{
// EstateID Cannot be less than 100
m_log.WarnFormat("[ESTATE LOADER FILE SYSTEM]: Estate name {0} specified estateID that is less that 100, ignoring", estateName);
continue;
}
else if(existingEstateIDs.Contains(EstateID))
{
// Specified EstateID Exists
m_log.WarnFormat("[ESTATE LOADER FILE SYSTEM]: Estate name {0} specified estateID that is already in use, ignoring", estateName);
continue;
}
}
// Create a new estate with the name provided
EstateSettings estateSettings = m_application.EstateDataService.CreateNewEstate(EstateID);
estateSettings.EstateName = estateName;
estateSettings.EstateOwner = estateOwner;
// Persistence does not seem to effect the need to save a new estate
m_application.EstateDataService.StoreEstateSettings(estateSettings);
m_log.InfoFormat("[ESTATE LOADER FILE SYSTEM]: Loaded config for estate {0}", estateName);
}
}
}
}
}

View File

@@ -32,17 +32,16 @@ using System.Threading;
using log4net;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.RegionLoader.Filesystem;
using OpenSim.Framework.RegionLoader.Web;
using OpenSim.Region.CoreModules.Agent.AssetTransaction;
using OpenSim.Region.CoreModules.Avatar.InstantMessage;
using OpenSim.Region.CoreModules.Scripting.DynamicTexture;
using OpenSim.Region.CoreModules.Scripting.LoadImageURL;
using OpenSim.Region.CoreModules.Scripting.XMLRPC;
using OpenSim.Services.Interfaces;
using Mono.Addins;
namespace OpenSim.ApplicationPlugins.LoadRegions
{
[Extension(Path="/OpenSim/Startup", Id="LoadRegions", NodeName="Plugin")]
public class LoadRegionsPlugin : IApplicationPlugin, IRegionCreator
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
@@ -84,14 +83,11 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
{
//m_log.Info("[LOADREGIONS]: Load Regions addin being initialised");
IEstateLoader estateLoader = null;
IRegionLoader regionLoader;
if (m_openSim.ConfigSource.Source.Configs["Startup"].GetString("region_info_source", "filesystem") == "filesystem")
{
m_log.Info("[LOAD REGIONS PLUGIN]: Loading region configurations from filesystem");
regionLoader = new RegionLoaderFileSystem();
estateLoader = new EstateLoaderFileSystem(m_openSim);
}
else
{
@@ -99,24 +95,16 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
regionLoader = new RegionLoaderWebServer();
}
// Load Estates Before Regions!
if(estateLoader != null)
{
estateLoader.SetIniConfigSource(m_openSim.ConfigSource.Source);
estateLoader.LoadEstates();
}
regionLoader.SetIniConfigSource(m_openSim.ConfigSource.Source);
RegionInfo[] regionsToLoad = regionLoader.LoadRegions();
m_log.Info("[LOAD REGIONS PLUGIN]: Loading specific shared modules...");
//m_log.Info("[LOAD REGIONS PLUGIN]: DynamicTextureModule...");
//m_openSim.ModuleLoader.LoadDefaultSharedModule(new DynamicTextureModule());
//m_log.Info("[LOAD REGIONS PLUGIN]: LoadImageURLModule...");
//m_openSim.ModuleLoader.LoadDefaultSharedModule(new LoadImageURLModule());
//m_log.Info("[LOAD REGIONS PLUGIN]: XMLRPCModule...");
//m_openSim.ModuleLoader.LoadDefaultSharedModule(new XMLRPCModule());
m_log.Info("[LOAD REGIONS PLUGIN]: DynamicTextureModule...");
m_openSim.ModuleLoader.LoadDefaultSharedModule(new DynamicTextureModule());
m_log.Info("[LOAD REGIONS PLUGIN]: LoadImageURLModule...");
m_openSim.ModuleLoader.LoadDefaultSharedModule(new LoadImageURLModule());
m_log.Info("[LOAD REGIONS PLUGIN]: XMLRPCModule...");
m_openSim.ModuleLoader.LoadDefaultSharedModule(new XMLRPCModule());
// m_log.Info("[LOADREGIONSPLUGIN]: AssetTransactionModule...");
// m_openSim.ModuleLoader.LoadDefaultSharedModule(new AssetTransactionModule());
m_log.Info("[LOAD REGIONS PLUGIN]: Done.");
@@ -127,34 +115,29 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
Environment.Exit(1);
}
List<IScene> createdScenes = new List<IScene>();
for (int i = 0; i < regionsToLoad.Length; i++)
{
IScene scene;
m_log.Debug("[LOAD REGIONS PLUGIN]: Creating Region: " + regionsToLoad[i].RegionName + " (ThreadID: " +
Thread.CurrentThread.ManagedThreadId.ToString() +
")");
bool changed = m_openSim.PopulateRegionEstateInfo(regionsToLoad[i]);
m_openSim.PopulateRegionEstateInfo(regionsToLoad[i]);
m_openSim.CreateRegion(regionsToLoad[i], true, out scene);
createdScenes.Add(scene);
if (changed)
m_openSim.EstateDataService.StoreEstateSettings(regionsToLoad[i].EstateSettings);
}
foreach (IScene scene in createdScenes)
{
scene.Start();
m_newRegionCreatedHandler = OnNewRegionCreated;
if (m_newRegionCreatedHandler != null)
regionsToLoad[i].EstateSettings.Save();
if (scene != null)
{
m_newRegionCreatedHandler(scene);
m_newRegionCreatedHandler = OnNewRegionCreated;
if (m_newRegionCreatedHandler != null)
{
m_newRegionCreatedHandler(scene);
}
}
}
m_openSim.ModuleLoader.PostInitialise();
m_openSim.ModuleLoader.ClearCache();
}
public void Dispose()
@@ -175,7 +158,7 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
foreach (RegionInfo region in regions)
{
if (region.RegionID.IsZero())
if (region.RegionID == UUID.Zero)
{
m_log.ErrorFormat(
"[LOAD REGIONS PLUGIN]: Region {0} has invalid UUID {1}",

View File

@@ -27,17 +27,16 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Mono.Addins;
// General information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly : AssemblyTitle("OpenSim.ApplicationPlugins.LoadRegions")]
[assembly : AssemblyTitle("OpenSim.Addin")]
[assembly : AssemblyDescription("")]
[assembly : AssemblyConfiguration("")]
[assembly : AssemblyCompany("http://opensimulator.org")]
[assembly : AssemblyProduct("OpenSim")]
[assembly : AssemblyProduct("OpenSim.Addin")]
[assembly : AssemblyCopyright("Copyright © OpenSimulator.org Developers 2007-2009")]
[assembly : AssemblyTrademark("")]
[assembly : AssemblyCulture("")]
@@ -61,8 +60,7 @@ using Mono.Addins;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("0.6.5.*")]
[assembly : AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
[assembly: Addin("OpenSim.ApplicationPlugins.LoadRegions", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
[assembly : AssemblyVersion("0.6.5.*")]
[assembly : AssemblyFileVersion("0.6.5.0")]

View File

@@ -1,145 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Xml;
using log4net;
using Nini.Config;
using OpenSim.Framework;
namespace OpenSim.ApplicationPlugins.LoadRegions
{
public class RegionLoaderWebServer : IRegionLoader
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IConfigSource m_configSource;
public void SetIniConfigSource(IConfigSource configSource)
{
m_configSource = configSource;
}
public RegionInfo[] LoadRegions()
{
int tries = 3;
int wait = 2000;
if (m_configSource == null)
{
m_log.Error("[WEBLOADER]: Unable to load configuration source!");
return null;
}
else
{
IConfig startupConfig = (IConfig)m_configSource.Configs["Startup"];
string url = startupConfig.GetString("regionload_webserver_url", String.Empty).Trim();
bool allowRegionless = startupConfig.GetBoolean("allow_regionless", false);
if (url.Length == 0)
{
m_log.Error("[WEBLOADER]: Unable to load webserver URL - URL was empty.");
return null;
}
else
{
while (tries > 0)
{
RegionInfo[] regionInfos = Array.Empty<RegionInfo>();
int regionCount = 0;
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Timeout = 30000; //30 Second Timeout
m_log.DebugFormat("[WEBLOADER]: Sending download request to {0}", url);
try
{
string xmlSource = String.Empty;
m_log.Debug("[WEBLOADER]: Downloading region information...");
using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
{
string tempStr;
while ((tempStr = reader.ReadLine()) != null)
{
xmlSource += tempStr;
}
}
m_log.Debug("[WEBLOADER]: Done downloading region information from server. Total Bytes: " +
xmlSource.Length);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlSource);
if (xmlDoc.FirstChild.Name == "Nini")
{
regionCount = xmlDoc.FirstChild.ChildNodes.Count;
if (regionCount > 0)
{
regionInfos = new RegionInfo[regionCount];
int i;
for (i = 0; i < xmlDoc.FirstChild.ChildNodes.Count; i++)
{
m_log.Debug(xmlDoc.FirstChild.ChildNodes[i].OuterXml);
regionInfos[i] =
new RegionInfo("REGION CONFIG #" + (i + 1), xmlDoc.FirstChild.ChildNodes[i], false, m_configSource);
}
}
}
}
catch (WebException ex)
{
if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound)
{
if (!allowRegionless)
throw;
}
else
throw;
}
if (regionCount > 0 || allowRegionless)
return regionInfos;
m_log.Debug("[WEBLOADER]: Request yielded no regions.");
tries--;
if (tries > 0)
{
m_log.Debug("[WEBLOADER]: Retrying");
System.Threading.Thread.Sleep(wait);
}
}
m_log.Error("[WEBLOADER]: No region configs were available.");
return null;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
<Addin id="OpenSim.ApplicationPlugins.LoadRegions" version="0.1">
<Runtime>
<Import assembly="OpenSim.ApplicationPlugins.LoadRegions.dll"/>
</Runtime>
<Dependencies>
<Addin id="OpenSim" version="0.5" />
</Dependencies>
<Extension path = "/OpenSim/Startup">
<Plugin id="LoadRegions" type="OpenSim.ApplicationPlugins.LoadRegions.LoadRegionsPlugin" />
</Extension>
</Addin>

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Mono.Addins;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenSim.ApplicationPlugins.RegionModulesController")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("http://opensimulator.org")]
[assembly: AssemblyProduct("OpenSim")]
[assembly: AssemblyCopyright("OpenSimulator developers")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c023816d-194e-40c1-9195-a0f281d4ac5d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
[assembly: Addin("OpenSim.ApplicationPlugins.RegionModulesController", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]

View File

@@ -32,24 +32,18 @@ using log4net;
using Mono.Addins;
using Nini.Config;
using OpenSim;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
namespace OpenSim.ApplicationPlugins.RegionModulesController
{
[Extension(Path = "/OpenSim/Startup", Id = "LoadRegions", NodeName = "Plugin")]
public class RegionModulesControllerPlugin : IRegionModulesController,
IApplicationPlugin
{
// Logger
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Controls whether we load modules from Mono.Addins.
/// </summary>
/// <remarks>For debug purposes. Defaults to true.</remarks>
public bool LoadModulesFromAddins { get; set; }
private static readonly ILog m_log =
LogManager.GetLogger(
MethodBase.GetCurrentMethod().DeclaringType);
// Config access
private OpenSimBase m_openSim;
@@ -58,28 +52,23 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
private string m_name;
// Internal lists to collect information about modules present
private List<TypeExtensionNode> m_nonSharedModules = new List<TypeExtensionNode>();
private List<TypeExtensionNode> m_sharedModules = new List<TypeExtensionNode>();
private List<TypeExtensionNode> m_nonSharedModules =
new List<TypeExtensionNode>();
private List<TypeExtensionNode> m_sharedModules =
new List<TypeExtensionNode>();
// List of shared module instances, for adding to Scenes
private List<ISharedRegionModule> m_sharedInstances = new List<ISharedRegionModule>();
public RegionModulesControllerPlugin()
{
LoadModulesFromAddins = true;
}
private List<ISharedRegionModule> m_sharedInstances =
new List<ISharedRegionModule>();
#region IApplicationPlugin implementation
public void Initialise (OpenSimBase openSim)
{
m_openSim = openSim;
m_openSim.ApplicationRegistry.RegisterInterface<IRegionModulesController>(this);
m_log.DebugFormat("[REGIONMODULES]: Initializing...");
if (!LoadModulesFromAddins)
return;
// Who we are
string id = AddinManager.CurrentAddin.Id;
@@ -91,24 +80,35 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
m_name = id.Substring(pos + 1);
// The [Modules] section in the ini file
IConfig modulesConfig = m_openSim.ConfigSource.Source.Configs["Modules"];
IConfig modulesConfig =
m_openSim.ConfigSource.Source.Configs["Modules"];
if (modulesConfig == null)
modulesConfig = m_openSim.ConfigSource.Source.AddConfig("Modules");
Dictionary<RuntimeAddin, IList<int>> loadedModules = new Dictionary<RuntimeAddin, IList<int>>();
// Scan modules and load all that aren't disabled
foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes("/OpenSim/RegionModules"))
AddNode(node, modulesConfig, loadedModules);
foreach (KeyValuePair<RuntimeAddin, IList<int>> loadedModuleData in loadedModules)
foreach (TypeExtensionNode node in
AddinManager.GetExtensionNodes("/OpenSim/RegionModules"))
{
m_log.InfoFormat(
"[REGIONMODULES]: From plugin {0}, (version {1}), loaded {2} modules, {3} shared, {4} non-shared {5} unknown",
loadedModuleData.Key.Id,
loadedModuleData.Key.Version,
loadedModuleData.Value[0] + loadedModuleData.Value[1] + loadedModuleData.Value[2],
loadedModuleData.Value[0], loadedModuleData.Value[1], loadedModuleData.Value[2]);
if (node.Type.GetInterface(typeof(ISharedRegionModule).ToString()) != null)
{
if (CheckModuleEnabled(node, modulesConfig))
{
m_log.DebugFormat("[REGIONMODULES]: Found shared region module {0}, class {1}", node.Id, node.Type);
m_sharedModules.Add(node);
}
}
else if (node.Type.GetInterface(typeof(INonSharedRegionModule).ToString()) != null)
{
if (CheckModuleEnabled(node, modulesConfig))
{
m_log.DebugFormat("[REGIONMODULES]: Found non-shared region module {0}, class {1}", node.Id, node.Type);
m_nonSharedModules.Add(node);
}
}
else
{
m_log.DebugFormat("[REGIONMODULES]: Found unknown type of module {0}, class {1}", node.Id, node.Type);
}
}
// Load and init the module. We try a constructor with a port
@@ -120,19 +120,18 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
//
foreach (TypeExtensionNode node in m_sharedModules)
{
object[] ctorArgs = new object[] { (uint)0 };
Object[] ctorArgs = new Object[] { (uint)0 };
// Read the config again
string moduleString = modulesConfig.GetString("Setup_" + node.Id, string.Empty);
// Test to see if we want this module
if (moduleString == "disabled")
continue;
string moduleString =
modulesConfig.GetString("Setup_" + node.Id, String.Empty);
// Get the port number, if there is one
if (moduleString != string.Empty)
if (moduleString != String.Empty)
{
// Get the port number from the string
string[] moduleParts = moduleString.Split(new char[] { '/' }, 2);
string[] moduleParts = moduleString.Split(new char[] { '/' },
2);
if (moduleParts.Length > 1)
ctorArgs[0] = Convert.ToUInt32(moduleParts[0]);
}
@@ -143,11 +142,13 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
try
{
module = (ISharedRegionModule)Activator.CreateInstance(node.Type, ctorArgs);
module = (ISharedRegionModule)Activator.CreateInstance(
node.Type, ctorArgs);
}
catch
{
module = (ISharedRegionModule)Activator.CreateInstance(node.Type);
module = (ISharedRegionModule)Activator.CreateInstance(
node.Type);
}
// OK, we're up and running
@@ -171,40 +172,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
#region IPlugin implementation
private void AddNode(TypeExtensionNode node, IConfig modulesConfig, Dictionary<RuntimeAddin, IList<int>> loadedModules)
{
IList<int> loadedModuleData;
if (!loadedModules.ContainsKey(node.Addin))
loadedModules.Add(node.Addin, new List<int> { 0, 0, 0 });
loadedModuleData = loadedModules[node.Addin];
if (node.Type.GetInterface(typeof(ISharedRegionModule).ToString()) != null)
{
if (CheckModuleEnabled(node, modulesConfig))
{
m_log.DebugFormat("[REGIONMODULES]: Found shared region module {0}, class {1}", node.Id, node.Type);
m_sharedModules.Add(node);
loadedModuleData[0]++;
}
}
else if (node.Type.GetInterface(typeof(INonSharedRegionModule).ToString()) != null)
{
if (CheckModuleEnabled(node, modulesConfig))
{
m_log.DebugFormat("[REGIONMODULES]: Found non-shared region module {0}, class {1}", node.Id, node.Type);
m_nonSharedModules.Add(node);
loadedModuleData[1]++;
}
}
else
{
m_log.WarnFormat("[REGIONMODULES]: Found unknown type of module {0}, class {1}", node.Id, node.Type);
loadedModuleData[2]++;
}
}
// We don't do that here
//
public void Initialise ()
@@ -226,7 +193,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
m_sharedInstances[0].Close();
m_sharedInstances.RemoveAt(0);
}
m_sharedModules.Clear();
m_nonSharedModules.Clear();
}
@@ -249,8 +215,8 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
}
}
#region Region Module interfacesController implementation
#region IRegionModulesController implementation
/// <summary>
/// Check that the given module is no disabled in the [Modules] section of the config files.
/// </summary>
@@ -260,10 +226,11 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
protected bool CheckModuleEnabled(TypeExtensionNode node, IConfig modulesConfig)
{
// Get the config string
string moduleString = modulesConfig.GetString("Setup_" + node.Id, string.Empty);
string moduleString =
modulesConfig.GetString("Setup_" + node.Id, String.Empty);
// We have a selector
if (!string.IsNullOrEmpty(moduleString))
if (moduleString != String.Empty)
{
// Allow disabling modules even if they don't have
// support for it
@@ -281,10 +248,10 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
if (className != String.Empty &&
node.Type.ToString() != className)
return false;
}
}
return true;
}
}
// The root of all evil.
// This is where we handle adding the modules to scenes when they
@@ -293,8 +260,10 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
//
public void AddRegionToModules (Scene scene)
{
Dictionary<Type, ISharedRegionModule> deferredSharedModules = new Dictionary<Type, ISharedRegionModule>();
Dictionary<Type, INonSharedRegionModule> deferredNonSharedModules = new Dictionary<Type, INonSharedRegionModule>();
Dictionary<Type, ISharedRegionModule> deferredSharedModules =
new Dictionary<Type, ISharedRegionModule>();
Dictionary<Type, INonSharedRegionModule> deferredNonSharedModules =
new Dictionary<Type, INonSharedRegionModule>();
// We need this to see if a module has already been loaded and
// has defined a replaceable interface. It's a generic call,
@@ -303,7 +272,8 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
MethodInfo mi = s.GetMethod("RequestModuleInterface");
// This will hold the shared modules we actually load
List<ISharedRegionModule> sharedlist = new List<ISharedRegionModule>();
List<ISharedRegionModule> sharedlist =
new List<ISharedRegionModule>();
// Iterate over the shared modules that have been loaded
// Add them to the new Scene
@@ -340,26 +310,25 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
sharedlist.Add(module);
}
IConfig modulesConfig = m_openSim.ConfigSource.Source.Configs["Modules"];
IConfig modulesConfig =
m_openSim.ConfigSource.Source.Configs["Modules"];
// Scan for, and load, nonshared modules
List<INonSharedRegionModule> list = new List<INonSharedRegionModule>();
foreach (TypeExtensionNode node in m_nonSharedModules)
{
object[] ctorArgs = new object[] {0};
Object[] ctorArgs = new Object[] {0};
// Read the config
string moduleString = modulesConfig.GetString("Setup_" + node.Id, string.Empty);
// We may not want to load this at all
if (moduleString == "disabled")
continue;
string moduleString =
modulesConfig.GetString("Setup_" + node.Id, String.Empty);
// Get the port number, if there is one
if (!string.IsNullOrEmpty(moduleString))
if (moduleString != String.Empty)
{
// Get the port number from the string
string[] moduleParts = moduleString.Split(new char[] {'/'}, 2);
string[] moduleParts = moduleString.Split(new char[] {'/'},
2);
if (moduleParts.Length > 1)
ctorArgs[0] = Convert.ToUInt32(moduleParts[0]);
}
@@ -438,7 +407,8 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
}
// Same thing for nonshared modules, load them unless overridden
List<INonSharedRegionModule> deferredlist = new List<INonSharedRegionModule>();
List<INonSharedRegionModule> deferredlist =
new List<INonSharedRegionModule>();
foreach (INonSharedRegionModule module in deferredNonSharedModules.Values)
{
@@ -490,8 +460,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
{
module.RegionLoaded(scene);
}
scene.AllModulesLoaded();
}
public void RemoveRegionFromModules (Scene scene)

View File

@@ -0,0 +1,13 @@
<Addin id="OpenSim.ApplicationPlugins.RegionModulesController" version="0.1">
<Runtime>
<Import assembly="OpenSim.ApplicationPlugins.RegionModulesController.dll"/>
</Runtime>
<Dependencies>
<Addin id="OpenSim" version="0.5" />
</Dependencies>
<Extension path = "/OpenSim/Startup">
<Plugin id="RegionModulesController" type="OpenSim.ApplicationPlugins.RegionModulesController.RegionModulesControllerPlugin" />
</Extension>
</Addin>

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Mono.Addins;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenSim.ApplicationPlugins.RemoteController")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("http://opensimulator.org")]
[assembly: AssemblyProduct("OpenSim")]
[assembly: AssemblyCopyright("Copyright OpenSimulator developers © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("efec6e69-fc4a-4e21-86e6-4a261c12d4db")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
[assembly: Addin("OpenSim.ApplicationPlugins.RemoteController", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]

View File

@@ -0,0 +1,11 @@
<Addin id="OpenSim.ApplicationPlugins.RemoteController" version="0.1">
<Runtime>
<Import assembly="OpenSim.ApplicationPlugins.RemoteController.dll"/>
</Runtime>
<Dependencies>
<Addin id="OpenSim" version="0.5" />
</Dependencies>
<Extension path = "/OpenSim/Startup">
<Plugin id="RemoteController" type="OpenSim.ApplicationPlugins.RemoteController.RemoteAdminPlugin" />
</Extension>
</Addin>

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
/// <summary>
/// This interface represents the boundary between the general purpose
/// REST plugin handling, and the functionally specific handlers. The
/// handler knows only to initialize and terminate all such handlers
/// that it finds. Implementing this interface identifies the class as
/// a REST handler implementation.
/// </summary>
internal interface IRest
{
void Initialize();
void Close();
}
}

View File

@@ -25,30 +25,35 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Threading.Tasks;
using OpenSim.Framework.Servers.HttpServer;
using OMV = OpenMetaverse;
namespace osWebRtcVoice
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
/// <remarks>
/// The handler delegates are not noteworthy. The allocator allows
/// a given handler to optionally subclass the base RequestData
/// structure to carry any locally required per-request state
/// needed.
/// </remarks>
public delegate void RestMethodHandler(RequestData rdata);
public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path);
/// <summary>
/// This is the interface for the viewer session. It is used to store the
/// state of the viewer session and to disconnect the session when needed.
/// This interface exports the generic plugin-handling services
/// available to each loaded REST services module (IRest implementation)
/// </summary>
public interface IVoiceViewerSession
internal interface IRestHandler
{
// This ID is passed to and from the viewer to identify the session
public string ViewerSessionID { get; set; }
public IWebRtcVoiceService VoiceService { get; set; }
// THis ID is passed between us and the voice service to idetify the session
public string VoiceServiceSessionId { get; set; }
// The UUID of the region that is being connected to
public OMV.UUID RegionId { get; set; }
// The simulator has a GUID to identify the user
public OMV.UUID AgentId { get; set; }
string MsgId { get; }
string RequestId { get; }
void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ma);
void AddStreamHandler(string httpMethod, string path, RestMethod method);
// Disconnect the connection to the voice service for this session
public Task Shutdown();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
<Addin id="OpenSim.ApplicationPlugins.Rest.Inventory" version="0.1">
<Runtime>
<Import assembly="OpenSim.ApplicationPlugins.Rest.Inventory.dll"/>
</Runtime>
<Dependencies>
<Addin id="OpenSim" version="0.5" />
</Dependencies>
<Extension path = "/OpenSim/Startup">
<Plugin id="RestInventory" type="OpenSim.ApplicationPlugins.Rest.Inventory.RestHandler" />
</Extension>
</Addin>

View File

@@ -0,0 +1,551 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using log4net;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Framework.Communications;
using OpenSim.Services.Interfaces;
using IAvatarService = OpenSim.Services.Interfaces.IAvatarService;
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
public class Rest
{
internal static readonly ILog Log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
internal static bool DEBUG = Log.IsDebugEnabled;
/// <summary>
/// Supported authentication schemes
/// </summary>
public const string AS_BASIC = "Basic"; // simple user/password verification
public const string AS_DIGEST = "Digest"; // password safe authentication
/// Supported Digest algorithms
public const string Digest_MD5 = "MD5"; // assumed default if omitted
public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST?
public const string Qop_Auth = "auth"; // authentication only
public const string Qop_Int = "auth-int"; // TODO
/// <summary>
/// These values have a single value for the whole
/// domain and lifetime of the plugin handler. We
/// make them static for ease of reference within
/// the assembly. These are initialized by the
/// RestHandler class during start-up.
/// </summary>
internal static IRestHandler Plugin = null;
internal static OpenSimBase main = null;
internal static string Prefix = null;
internal static IConfig Config = null;
internal static string GodKey = null;
internal static bool Authenticate = true;
internal static bool Secure = true;
internal static bool ExtendedEscape = true;
internal static bool DumpAsset = false;
internal static bool Fill = true;
internal static bool FlushEnabled = true;
internal static string Realm = "OpenSim REST";
internal static string Scheme = AS_BASIC;
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
/// <summary>
/// These are all dependent upon the Comms manager
/// being initialized. So they have to be properties
/// because the comms manager is now a module and is
/// not guaranteed to be there when the rest handler
/// initializes.
/// </summary>
internal static IInventoryService InventoryServices
{
get { return main.SceneManager.CurrentOrFirstScene.InventoryService; }
}
internal static IUserAccountService UserServices
{
get { return main.SceneManager.CurrentOrFirstScene.UserAccountService; }
}
internal static IAuthenticationService AuthServices
{
get { return main.SceneManager.CurrentOrFirstScene.AuthenticationService; }
}
internal static IAvatarService AvatarServices
{
get { return main.SceneManager.CurrentOrFirstScene.AvatarService; }
}
internal static IAssetService AssetServices
{
get { return main.SceneManager.CurrentOrFirstScene.AssetService; }
}
/// <summary>
/// HTTP requires that status information be generated for PUT
/// and POST opertaions. This is in support of that. The
/// operation verb gets substituted into the first string,
/// and the completion code is inserted into the tail. The
/// strings are put here to encourage consistency.
/// </summary>
internal static string statusHead = "<html><body><title>{0} status</title><break>";
internal static string statusTail = "</body></html>";
internal static Dictionary<int,string> HttpStatusDesc;
static Rest()
{
HttpStatusDesc = new Dictionary<int,string>();
if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length)
{
Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match");
throw new Exception("HTTP Status array discrepancy");
}
// Repackage the data into something more tractable. The sparse
// nature of HTTP return codes makes an array a bad choice.
for (int i=0; i<HttpStatusCodeArray.Length; i++)
{
HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]);
}
}
internal static int CreationDate
{
get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; }
}
internal static string MsgId
{
get { return Plugin.MsgId; }
}
internal static string RequestId
{
get { return Plugin.RequestId; }
}
internal static Encoding Encoding = Util.UTF8;
/// <summary>
/// Version control for REST implementation. This
/// refers to the overall infrastructure represented
/// by the following classes
/// RequestData
/// RequestInventoryPlugin
/// Rest
/// It does no describe implementation classes such as
/// RestInventoryServices, which may morph much more
/// often. Such classes ARE dependent upon this however
/// and should check it in their Initialize method.
/// </summary>
public static readonly float Version = 1.0F;
public const string Name = "REST 1.0";
/// <summary>
/// Currently defined HTTP methods.
/// Only GET and HEAD are required to be
/// supported by all servers. See Respond
/// to see how these are handled.
/// </summary>
// REST AGENT 1.0 interpretations
public const string GET = "get"; // information retrieval - server state unchanged
public const string HEAD = "head"; // same as get except only the headers are returned.
public const string POST = "post"; // Replace the URI designated resource with the entity.
public const string PUT = "put"; // Add the entity to the context represented by the URI
public const string DELETE = "delete"; // Remove the URI designated resource from the server.
public const string OPTIONS = "options"; //
public const string TRACE = "trace"; //
public const string CONNECT = "connect"; //
// Define this in one place...
public const string UrlPathSeparator = "/";
public const string UrlMethodSeparator = ":";
// Redirection qualifications
public const bool PERMANENT = false;
public const bool TEMPORARY = true;
// Constant arrays used by String.Split
public static readonly char C_SPACE = ' ';
public static readonly char C_SLASH = '/';
public static readonly char C_PATHSEP = '/';
public static readonly char C_COLON = ':';
public static readonly char C_PLUS = '+';
public static readonly char C_PERIOD = '.';
public static readonly char C_COMMA = ',';
public static readonly char C_DQUOTE = '"';
public static readonly string CS_SPACE = " ";
public static readonly string CS_SLASH = "/";
public static readonly string CS_PATHSEP = "/";
public static readonly string CS_COLON = ":";
public static readonly string CS_PLUS = "+";
public static readonly string CS_PERIOD = ".";
public static readonly string CS_COMMA = ",";
public static readonly string CS_DQUOTE = "\"";
public static readonly char[] CA_SPACE = { C_SPACE };
public static readonly char[] CA_SLASH = { C_SLASH };
public static readonly char[] CA_PATHSEP = { C_PATHSEP };
public static readonly char[] CA_COLON = { C_COLON };
public static readonly char[] CA_PERIOD = { C_PERIOD };
public static readonly char[] CA_PLUS = { C_PLUS };
public static readonly char[] CA_COMMA = { C_COMMA };
public static readonly char[] CA_DQUOTE = { C_DQUOTE };
// HTTP Code Values (in value order)
public const int HttpStatusCodeContinue = 100;
public const int HttpStatusCodeSwitchingProtocols = 101;
public const int HttpStatusCodeOK = 200;
public const int HttpStatusCodeCreated = 201;
public const int HttpStatusCodeAccepted = 202;
public const int HttpStatusCodeNonAuthoritative = 203;
public const int HttpStatusCodeNoContent = 204;
public const int HttpStatusCodeResetContent = 205;
public const int HttpStatusCodePartialContent = 206;
public const int HttpStatusCodeMultipleChoices = 300;
public const int HttpStatusCodePermanentRedirect = 301;
public const int HttpStatusCodeFound = 302;
public const int HttpStatusCodeSeeOther = 303;
public const int HttpStatusCodeNotModified = 304;
public const int HttpStatusCodeUseProxy = 305;
public const int HttpStatusCodeReserved306 = 306;
public const int HttpStatusCodeTemporaryRedirect = 307;
public const int HttpStatusCodeBadRequest = 400;
public const int HttpStatusCodeNotAuthorized = 401;
public const int HttpStatusCodePaymentRequired = 402;
public const int HttpStatusCodeForbidden = 403;
public const int HttpStatusCodeNotFound = 404;
public const int HttpStatusCodeMethodNotAllowed = 405;
public const int HttpStatusCodeNotAcceptable = 406;
public const int HttpStatusCodeProxyAuthenticate = 407;
public const int HttpStatusCodeTimeOut = 408;
public const int HttpStatusCodeConflict = 409;
public const int HttpStatusCodeGone = 410;
public const int HttpStatusCodeLengthRequired = 411;
public const int HttpStatusCodePreconditionFailed = 412;
public const int HttpStatusCodeEntityTooLarge = 413;
public const int HttpStatusCodeUriTooLarge = 414;
public const int HttpStatusCodeUnsupportedMedia = 415;
public const int HttpStatusCodeRangeNotSatsified = 416;
public const int HttpStatusCodeExpectationFailed = 417;
public const int HttpStatusCodeServerError = 500;
public const int HttpStatusCodeNotImplemented = 501;
public const int HttpStatusCodeBadGateway = 502;
public const int HttpStatusCodeServiceUnavailable = 503;
public const int HttpStatusCodeGatewayTimeout = 504;
public const int HttpStatusCodeHttpVersionError = 505;
public static readonly int[] HttpStatusCodeArray = {
HttpStatusCodeContinue,
HttpStatusCodeSwitchingProtocols,
HttpStatusCodeOK,
HttpStatusCodeCreated,
HttpStatusCodeAccepted,
HttpStatusCodeNonAuthoritative,
HttpStatusCodeNoContent,
HttpStatusCodeResetContent,
HttpStatusCodePartialContent,
HttpStatusCodeMultipleChoices,
HttpStatusCodePermanentRedirect,
HttpStatusCodeFound,
HttpStatusCodeSeeOther,
HttpStatusCodeNotModified,
HttpStatusCodeUseProxy,
HttpStatusCodeReserved306,
HttpStatusCodeTemporaryRedirect,
HttpStatusCodeBadRequest,
HttpStatusCodeNotAuthorized,
HttpStatusCodePaymentRequired,
HttpStatusCodeForbidden,
HttpStatusCodeNotFound,
HttpStatusCodeMethodNotAllowed,
HttpStatusCodeNotAcceptable,
HttpStatusCodeProxyAuthenticate,
HttpStatusCodeTimeOut,
HttpStatusCodeConflict,
HttpStatusCodeGone,
HttpStatusCodeLengthRequired,
HttpStatusCodePreconditionFailed,
HttpStatusCodeEntityTooLarge,
HttpStatusCodeUriTooLarge,
HttpStatusCodeUnsupportedMedia,
HttpStatusCodeRangeNotSatsified,
HttpStatusCodeExpectationFailed,
HttpStatusCodeServerError,
HttpStatusCodeNotImplemented,
HttpStatusCodeBadGateway,
HttpStatusCodeServiceUnavailable,
HttpStatusCodeGatewayTimeout,
HttpStatusCodeHttpVersionError
};
// HTTP Status Descriptions (in status code order)
// This array must be kept strictly consistent with respect
// to the status code array above.
public static readonly string[] HttpStatusDescArray = {
"Continue Request",
"Switching Protocols",
"OK",
"CREATED",
"ACCEPTED",
"NON-AUTHORITATIVE INFORMATION",
"NO CONTENT",
"RESET CONTENT",
"PARTIAL CONTENT",
"MULTIPLE CHOICES",
"PERMANENT REDIRECT",
"FOUND",
"SEE OTHER",
"NOT MODIFIED",
"USE PROXY",
"RESERVED CODE 306",
"TEMPORARY REDIRECT",
"BAD REQUEST",
"NOT AUTHORIZED",
"PAYMENT REQUIRED",
"FORBIDDEN",
"NOT FOUND",
"METHOD NOT ALLOWED",
"NOT ACCEPTABLE",
"PROXY AUTHENTICATION REQUIRED",
"TIMEOUT",
"CONFLICT",
"GONE",
"LENGTH REQUIRED",
"PRECONDITION FAILED",
"ENTITY TOO LARGE",
"URI TOO LARGE",
"UNSUPPORTED MEDIA",
"RANGE NOT SATISFIED",
"EXPECTATION FAILED",
"SERVER ERROR",
"NOT IMPLEMENTED",
"BAD GATEWAY",
"SERVICE UNAVAILABLE",
"GATEWAY TIMEOUT",
"HTTP VERSION NOT SUPPORTED"
};
// HTTP Headers
public const string HttpHeaderAccept = "Accept";
public const string HttpHeaderAcceptCharset = "Accept-Charset";
public const string HttpHeaderAcceptEncoding = "Accept-Encoding";
public const string HttpHeaderAcceptLanguage = "Accept-Language";
public const string HttpHeaderAcceptRanges = "Accept-Ranges";
public const string HttpHeaderAge = "Age";
public const string HttpHeaderAllow = "Allow";
public const string HttpHeaderAuthorization = "Authorization";
public const string HttpHeaderCacheControl = "Cache-Control";
public const string HttpHeaderConnection = "Connection";
public const string HttpHeaderContentEncoding = "Content-Encoding";
public const string HttpHeaderContentLanguage = "Content-Language";
public const string HttpHeaderContentLength = "Content-Length";
public const string HttpHeaderContentLocation = "Content-Location";
public const string HttpHeaderContentMD5 = "Content-MD5";
public const string HttpHeaderContentRange = "Content-Range";
public const string HttpHeaderContentType = "Content-Type";
public const string HttpHeaderDate = "Date";
public const string HttpHeaderETag = "ETag";
public const string HttpHeaderExpect = "Expect";
public const string HttpHeaderExpires = "Expires";
public const string HttpHeaderFrom = "From";
public const string HttpHeaderHost = "Host";
public const string HttpHeaderIfMatch = "If-Match";
public const string HttpHeaderIfModifiedSince = "If-Modified-Since";
public const string HttpHeaderIfNoneMatch = "If-None-Match";
public const string HttpHeaderIfRange = "If-Range";
public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since";
public const string HttpHeaderLastModified = "Last-Modified";
public const string HttpHeaderLocation = "Location";
public const string HttpHeaderMaxForwards = "Max-Forwards";
public const string HttpHeaderPragma = "Pragma";
public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate";
public const string HttpHeaderProxyAuthorization = "Proxy-Authorization";
public const string HttpHeaderRange = "Range";
public const string HttpHeaderReferer = "Referer";
public const string HttpHeaderRetryAfter = "Retry-After";
public const string HttpHeaderServer = "Server";
public const string HttpHeaderTE = "TE";
public const string HttpHeaderTrailer = "Trailer";
public const string HttpHeaderTransferEncoding = "Transfer-Encoding";
public const string HttpHeaderUpgrade = "Upgrade";
public const string HttpHeaderUserAgent = "User-Agent";
public const string HttpHeaderVary = "Vary";
public const string HttpHeaderVia = "Via";
public const string HttpHeaderWarning = "Warning";
public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
/// Utility routines
public static string StringToBase64(string str)
{
try
{
byte[] encData_byte = new byte[str.Length];
encData_byte = Util.UTF8.GetBytes(str);
return Convert.ToBase64String(encData_byte);
}
catch
{
return String.Empty;
}
}
public static string Base64ToString(string str)
{
try
{
return Util.Base64ToString(str);
}
catch
{
return String.Empty;
}
}
private const string hvals = "0123456789abcdef";
public static int Hex2Int(string hex)
{
int val = 0;
int sum = 0;
string tmp = null;
if (hex != null)
{
tmp = hex.ToLower();
for (int i = 0; i < tmp.Length; i++)
{
val = hvals.IndexOf(tmp[i]);
if (val == -1)
break;
sum *= 16;
sum += val;
}
}
return sum;
}
// Nonce management
public static string NonceGenerator()
{
return StringToBase64(CreationDate + Guid.NewGuid().ToString());
}
// Dump the specified data stream
public static void Dump(byte[] data)
{
char[] buffer = new char[DumpLineSize];
int cc = 0;
for (int i = 0; i < data.Length; i++)
{
if (i % DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8"));
if (i % 4 == 0) Console.Write(" ");
Console.Write("{0}",data[i].ToString("x2"));
if (data[i] < 127 && data[i] > 31)
buffer[i % DumpLineSize] = (char) data[i];
else
buffer[i % DumpLineSize] = '.';
cc++;
if (i != 0 && (i + 1) % DumpLineSize == 0)
{
Console.Write(" |"+(new String(buffer))+"|");
cc = 0;
}
}
// Finish off any incomplete line
if (cc != 0)
{
for (int i = cc ; i < DumpLineSize; i++)
{
if (i % 4 == 0) Console.Write(" ");
Console.Write(" ");
buffer[i % DumpLineSize] = ' ';
}
Console.WriteLine(" |"+(new String(buffer))+"|");
}
else
{
Console.Write("\n");
}
}
}
// Local exception type
public class RestException : Exception
{
internal int statusCode;
internal string statusDesc;
internal string httpmethod;
internal string httppath;
public RestException(string msg) : base(msg)
{
}
}
}

View File

@@ -0,0 +1,860 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
public class RestAppearanceServices : IRest
{
// private static readonly int PARM_USERID = 0;
// private static readonly int PARM_PATH = 1;
// private bool enabled = false;
private string qPrefix = "appearance";
/// <summary>
/// The constructor makes sure that the service prefix is absolute
/// and the registers the service handler and the allocator.
/// </summary>
public RestAppearanceServices()
{
Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// If a relative path was specified for the handler's domain,
// add the standard prefix to make it absolute, e.g. /admin
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{
Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
}
// Register interface using the absolute URI.
Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate);
// Activate if everything went OK
// enabled = true;
Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId);
}
/// <summary>
/// Post-construction, pre-enabled initialization opportunity
/// Not currently exploited.
/// </summary>
public void Initialize()
{
}
/// <summary>
/// Called by the plug-in to halt service processing. Local processing is
/// disabled.
/// </summary>
public void Close()
{
// enabled = false;
Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId);
}
/// <summary>
/// This property is declared locally because it is used a lot and
/// brevity is nice.
/// </summary>
internal string MsgId
{
get { return Rest.MsgId; }
}
#region Interface
/// <summary>
/// The plugin (RestHandler) calls this method to allocate the request
/// state carrier for a new request. It is destroyed when the request
/// completes. All request-instance specific state is kept here. This
/// is registered when this service provider is registered.
/// </summary>
/// <param name=request>Inbound HTTP request information</param>
/// <param name=response>Outbound HTTP request information</param>
/// <param name=qPrefix>REST service domain prefix</param>
/// <returns>A RequestData instance suitable for this service</returns>
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
{
return (RequestData) new AppearanceRequestData(request, response, prefix);
}
/// <summary>
/// This method is registered with the handler when this service provider
/// is initialized. It is called whenever the plug-in identifies this service
/// provider as the best match for a given request.
/// It handles all aspects of inventory REST processing, i.e. /admin/inventory
/// </summary>
/// <param name=hdata>A consolidated HTTP request work area</param>
private void DoAppearance(RequestData hdata)
{
// !!! REFACTORIMG PROBLEM. This needs rewriting for 0.7
//AppearanceRequestData rdata = (AppearanceRequestData) hdata;
//Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId);
//// If we're disabled, do nothing.
//if (!enabled)
//{
// return;
//}
//// Now that we know this is a serious attempt to
//// access inventory data, we should find out who
//// is asking, and make sure they are authorized
//// to do so. We need to validate the caller's
//// identity before revealing anything about the
//// status quo. Authenticate throws an exception
//// via Fail if no identity information is present.
////
//// With the present HTTP server we can't use the
//// builtin authentication mechanisms because they
//// would be enforced for all in-bound requests.
//// Instead we look at the headers ourselves and
//// handle authentication directly.
//try
//{
// if (!rdata.IsAuthenticated)
// {
// rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
// }
//}
//catch (RestException e)
//{
// if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
// {
// Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
// Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
// }
// else
// {
// Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
// Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
// }
// throw (e);
//}
//Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
//// We can only get here if we are authorized
////
//// The requestor may have specified an UUID or
//// a conjoined FirstName LastName string. We'll
//// try both. If we fail with the first, UUID,
//// attempt, we try the other. As an example, the
//// URI for a valid inventory request might be:
////
//// http://<host>:<port>/admin/inventory/Arthur Dent
////
//// Indicating that this is an inventory request for
//// an avatar named Arthur Dent. This is ALL that is
//// required to designate a GET for an entire
//// inventory.
////
//// Do we have at least a user agent name?
//if (rdata.Parameters.Length < 1)
//{
// Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId);
// rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
//}
//// The first parameter MUST be the agent identification, either an UUID
//// or a space-separated First-name Last-Name specification. We check for
//// an UUID first, if anyone names their character using a valid UUID
//// that identifies another existing avatar will cause this a problem...
//try
//{
// rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]);
// Rest.Log.DebugFormat("{0} UUID supplied", MsgId);
// rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
//}
//catch
//{
// string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
// if (names.Length == 2)
// {
// Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
// rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
// }
// else
// {
// Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
// }
//}
//// If the user profile is null then either the server is broken, or the
//// user is not known. We always assume the latter case.
//if (rdata.userProfile != null)
//{
// Rest.Log.DebugFormat("{0} User profile obtained for agent {1} {2}",
// MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
//}
//else
//{
// Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path);
// rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
//}
//// If we get to here, then we have effectively validated the user's
//switch (rdata.method)
//{
// case Rest.HEAD : // Do the processing, set the status code, suppress entity
// DoGet(rdata);
// rdata.buffer = null;
// break;
// case Rest.GET : // Do the processing, set the status code, return entity
// DoGet(rdata);
// break;
// case Rest.PUT : // Update named element
// DoUpdate(rdata);
// break;
// case Rest.POST : // Add new information to identified context.
// DoExtend(rdata);
// break;
// case Rest.DELETE : // Delete information
// DoDelete(rdata);
// break;
// default :
// Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
// MsgId, rdata.method, rdata.path);
// rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
// String.Format("{0} not supported", rdata.method));
// break;
//}
}
#endregion Interface
#region method-specific processing
/// <summary>
/// This method implements GET processing for user's appearance.
/// </summary>
/// <param name=rdata>HTTP service request work area</param>
// private void DoGet(AppearanceRequestData rdata)
// {
// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID);
//
// if (adata == null)
// {
// rdata.Fail(Rest.HttpStatusCodeNoContent,
// String.Format("appearance data not found for user {0} {1}",
// rdata.userProfile.FirstName, rdata.userProfile.SurName));
// }
// rdata.userAppearance = adata.ToAvatarAppearance(rdata.userProfile.ID);
//
// rdata.initXmlWriter();
//
// FormatUserAppearance(rdata);
//
// // Indicate a successful request
//
// rdata.Complete();
//
// // Send the response to the user. The body will be implicitly
// // constructed from the result of the XML writer.
//
// rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method));
// }
/// <summary>
/// POST adds NEW information to the user profile database.
/// This effectively resets the appearance before applying those
/// characteristics supplied in the request.
/// </summary>
// private void DoExtend(AppearanceRequestData rdata)
// {
//
// bool created = false;
// bool modified = false;
// string newnode = String.Empty;
//
// Rest.Log.DebugFormat("{0} POST ENTRY", MsgId);
//
// //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);
//
// rdata.userAppearance = new AvatarAppearance();
//
// // Although the following behavior is admitted by HTTP I am becoming
// // increasingly doubtful that it is appropriate for REST. If I attempt to
// // add a new record, and it already exists, then it seems to me that the
// // attempt should fail, rather than update the existing record.
// AvatarData adata = null;
// if (GetUserAppearance(rdata))
// {
// modified = rdata.userAppearance != null;
// created = !modified;
// adata = new AvatarData(rdata.userAppearance);
// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
// }
// else
// {
// created = true;
// adata = new AvatarData(rdata.userAppearance);
// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
// }
//
// if (created)
// {
// newnode = String.Format("{0} {1}", rdata.userProfile.FirstName,
// rdata.userProfile.SurName);
// // Must include a location header with a URI that identifies the new resource.
//
// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}",
// rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode));
// rdata.Complete(Rest.HttpStatusCodeCreated);
//
// }
// else
// {
// if (modified)
// {
// rdata.Complete(Rest.HttpStatusCodeOK);
// }
// else
// {
// rdata.Complete(Rest.HttpStatusCodeNoContent);
// }
// }
//
// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
//
// }
/// <summary>
/// This updates the user's appearance. not all aspects need to be provided,
/// only those supplied will be changed.
/// </summary>
// private void DoUpdate(AppearanceRequestData rdata)
// {
//
// // REFACTORING PROBLEM This was commented out. It doesn't work for 0.7
//
// //bool created = false;
// //bool modified = false;
//
//
// //rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);
//
// //// If the user exists then this is considered a modification regardless
// //// of what may, or may not be, specified in the payload.
//
// //if (rdata.userAppearance != null)
// //{
// // modified = true;
// // Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);
// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
// //}
//
// //if (created)
// //{
// // rdata.Complete(Rest.HttpStatusCodeCreated);
// //}
// //else
// //{
// // if (modified)
// // {
// // rdata.Complete(Rest.HttpStatusCodeOK);
// // }
// // else
// // {
// // rdata.Complete(Rest.HttpStatusCodeNoContent);
// // }
// //}
//
// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
//
// }
/// <summary>
/// Delete the specified user's appearance. This actually performs a reset
/// to the default avatar appearance, if the info is already there.
/// Existing ownership is preserved. All prior updates are lost and can not
/// be recovered.
/// </summary>
// private void DoDelete(AppearanceRequestData rdata)
// {
// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID);
//
// if (adata != null)
// {
// AvatarAppearance old = adata.ToAvatarAppearance(rdata.userProfile.ID);
// rdata.userAppearance = new AvatarAppearance();
// rdata.userAppearance.Owner = old.Owner;
// adata = new AvatarData(rdata.userAppearance);
//
// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
//
// rdata.Complete();
// }
// else
// {
//
// rdata.Complete(Rest.HttpStatusCodeNoContent);
// }
//
// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
// }
#endregion method-specific processing
private bool GetUserAppearance(AppearanceRequestData rdata)
{
XmlReader xml;
bool indata = false;
rdata.initXmlReader();
xml = rdata.reader;
while (xml.Read())
{
switch (xml.NodeType)
{
case XmlNodeType.Element :
switch (xml.Name)
{
case "Appearance" :
if (xml.MoveToAttribute("Height"))
{
rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value);
indata = true;
}
// if (xml.MoveToAttribute("Owner"))
// {
// rdata.userAppearance.Owner = (UUID)xml.Value;
// indata = true;
// }
if (xml.MoveToAttribute("Serial"))
{
rdata.userAppearance.Serial = Convert.ToInt32(xml.Value);
indata = true;
}
break;
/*
case "Body" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.BodyItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.BodyAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Skin" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.SkinItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.SkinAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Hair" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.HairItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.HairAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Eyes" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.EyesItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.EyesAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Shirt" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.ShirtItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.ShirtAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Pants" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.PantsItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.PantsAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Shoes" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.ShoesItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.ShoesAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Socks" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.SocksItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.SocksAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Jacket" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.JacketItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.JacketAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Gloves" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.GlovesItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.GlovesAsset = (UUID)xml.Value;
indata = true;
}
break;
case "UnderShirt" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.UnderShirtItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value;
indata = true;
}
break;
case "UnderPants" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.UnderPantsItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value;
indata = true;
}
break;
case "Skirt" :
if (xml.MoveToAttribute("Item"))
{
rdata.userAppearance.SkirtItem = (UUID)xml.Value;
indata = true;
}
if (xml.MoveToAttribute("Asset"))
{
rdata.userAppearance.SkirtAsset = (UUID)xml.Value;
indata = true;
}
break;
*/
case "Attachment" :
{
int ap;
UUID asset;
UUID item;
if (xml.MoveToAttribute("AtPoint"))
{
ap = Convert.ToInt32(xml.Value);
if (xml.MoveToAttribute("Asset"))
{
asset = new UUID(xml.Value);
if (xml.MoveToAttribute("Asset"))
{
item = new UUID(xml.Value);
rdata.userAppearance.SetAttachment(ap, item, asset);
indata = true;
}
}
}
}
break;
case "Texture" :
if (xml.MoveToAttribute("Default"))
{
rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value));
indata = true;
}
break;
case "Face" :
{
uint index;
if (xml.MoveToAttribute("Index"))
{
index = Convert.ToUInt32(xml.Value);
if (xml.MoveToAttribute("Id"))
{
rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value);
indata = true;
}
}
}
break;
case "VisualParameters" :
{
xml.ReadContentAsBase64(rdata.userAppearance.VisualParams,
0, rdata.userAppearance.VisualParams.Length);
indata = true;
}
break;
}
break;
}
}
return indata;
}
private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset)
{
if (item != UUID.Zero || asset != UUID.Zero)
{
rdata.writer.WriteStartElement(part);
if (item != UUID.Zero)
{
rdata.writer.WriteAttributeString("Item",item.ToString());
}
if (asset != UUID.Zero)
{
rdata.writer.WriteAttributeString("Asset",asset.ToString());
}
rdata.writer.WriteEndElement();
}
}
private void FormatUserAppearance(AppearanceRequestData rdata)
{
Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId);
if (rdata.userAppearance != null)
{
Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId);
rdata.writer.WriteStartElement("Appearance");
rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString());
// if (rdata.userAppearance.Owner != UUID.Zero)
// rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString());
rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString());
/*
FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset);
FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset);
FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset);
FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset);
FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset);
FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset);
FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset);
FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset);
FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset);
FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset);
FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset);
FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset);
FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset);
*/
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId);
rdata.writer.WriteStartElement("Attachments");
List<AvatarAttachment> attachments = rdata.userAppearance.GetAttachments();
foreach (AvatarAttachment attach in attachments)
{
rdata.writer.WriteStartElement("Attachment");
rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString());
rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString());
rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString());
rdata.writer.WriteEndElement();
}
rdata.writer.WriteEndElement();
Primitive.TextureEntry texture = rdata.userAppearance.Texture;
if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null))
{
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId);
rdata.writer.WriteStartElement("Texture");
if (texture.DefaultTexture != null)
{
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId);
rdata.writer.WriteAttributeString("Default",
texture.DefaultTexture.TextureID.ToString());
}
if (texture.FaceTextures != null)
{
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId);
for (int i=0; i<texture.FaceTextures.Length;i++)
{
if (texture.FaceTextures[i] != null)
{
rdata.writer.WriteStartElement("Face");
rdata.writer.WriteAttributeString("Index", i.ToString());
rdata.writer.WriteAttributeString("Id",
texture.FaceTextures[i].TextureID.ToString());
rdata.writer.WriteEndElement();
}
}
}
rdata.writer.WriteEndElement();
}
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting visual parameters", MsgId);
rdata.writer.WriteStartElement("VisualParameters");
rdata.writer.WriteBase64(rdata.userAppearance.VisualParams,0,
rdata.userAppearance.VisualParams.Length);
rdata.writer.WriteEndElement();
rdata.writer.WriteFullEndElement();
}
Rest.Log.DebugFormat("{0} FormatUserAppearance: completed", MsgId);
return;
}
#region appearance RequestData extension
internal class AppearanceRequestData : RequestData
{
/// <summary>
/// These are the inventory specific request/response state
/// extensions.
/// </summary>
internal UUID uuid = UUID.Zero;
internal UserProfileData userProfile = null;
internal AvatarAppearance userAppearance = null;
internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
: base(request, response, prefix)
{
}
}
#endregion Appearance RequestData extension
}
}

View File

@@ -0,0 +1,383 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Xml;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
public class RestAssetServices : IRest
{
private bool enabled = false;
private string qPrefix = "assets";
// A simple constructor is used to handle any once-only
// initialization of working classes.
public RestAssetServices()
{
Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// If the handler specifies a relative path for its domain
// then we must add the standard absolute prefix, e.g. /admin
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{
Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
}
// Register interface using the fully-qualified prefix
Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate);
// Activate if all went OK
enabled = true;
Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId);
}
// Post-construction, pre-enabled initialization opportunity
// Not currently exploited.
public void Initialize()
{
}
// Called by the plug-in to halt REST processing. Local processing is
// disabled, and control blocks until all current processing has
// completed. No new processing will be started
public void Close()
{
enabled = false;
Rest.Log.InfoFormat("{0} Asset services ({1}) closing down", MsgId, qPrefix);
}
// Properties
internal string MsgId
{
get { return Rest.MsgId; }
}
#region Interface
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
{
return (RequestData) new AssetRequestData(request, response, prefix);
}
// Asset Handler
private void DoAsset(RequestData rparm)
{
if (!enabled) return;
AssetRequestData rdata = (AssetRequestData) rparm;
Rest.Log.DebugFormat("{0} REST Asset handler ({1}) ENTRY", MsgId, qPrefix);
// Now that we know this is a serious attempt to
// access inventory data, we should find out who
// is asking, and make sure they are authorized
// to do so. We need to validate the caller's
// identity before revealing anything about the
// status quo. Authenticate throws an exception
// via Fail if no identity information is present.
//
// With the present HTTP server we can't use the
// builtin authentication mechanisms because they
// would be enforced for all in-bound requests.
// Instead we look at the headers ourselves and
// handle authentication directly.
try
{
if (!rdata.IsAuthenticated)
{
rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
}
}
catch (RestException e)
{
if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
{
Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
rdata.request.Headers.Get("Authorization"));
}
else
{
Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
rdata.request.Headers.Get("Authorization"));
}
throw (e);
}
// Remove the prefix and what's left are the parameters. If we don't have
// the parameters we need, fail the request. Parameters do NOT include
// any supplied query values.
if (rdata.Parameters.Length > 0)
{
switch (rdata.method)
{
case "get" :
DoGet(rdata);
break;
case "put" :
DoPut(rdata);
break;
case "post" :
DoPost(rdata);
break;
case "delete" :
default :
Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}",
MsgId, rdata.method);
rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
break;
}
}
else
{
Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId);
rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
}
Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId);
}
#endregion Interface
/// <summary>
/// The only parameter we recognize is a UUID.If an asset with this identification is
/// found, it's content, base-64 encoded, is returned to the client.
/// </summary>
private void DoGet(AssetRequestData rdata)
{
Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length == 1)
{
UUID uuid = new UUID(rdata.Parameters[0]);
AssetBase asset = Rest.AssetServices.Get(uuid.ToString());
if (asset != null)
{
Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]);
rdata.initXmlWriter();
rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty);
rdata.writer.WriteAttributeString("id", asset.ID);
rdata.writer.WriteAttributeString("name", asset.Name);
rdata.writer.WriteAttributeString("desc", asset.Description);
rdata.writer.WriteAttributeString("type", asset.Type.ToString());
rdata.writer.WriteAttributeString("local", asset.Local.ToString());
rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString());
rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length);
rdata.writer.WriteFullEndElement();
}
else
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
}
}
rdata.Complete();
rdata.Respond(String.Format("Asset <{0}> : Normal completion", rdata.method));
}
/// <summary>
/// UPDATE existing item, if it exists. URI identifies the item in question.
/// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
/// is decoded and stored in the database, identified by the supplied UUID.
/// </summary>
private void DoPut(AssetRequestData rdata)
{
bool modified = false;
bool created = false;
AssetBase asset = null;
Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length == 1)
{
rdata.initXmlReader();
XmlReader xml = rdata.reader;
if (!xml.ReadToFollowing("Asset"))
{
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
}
UUID uuid = new UUID(rdata.Parameters[0]);
asset = Rest.AssetServices.Get(uuid.ToString());
modified = (asset != null);
created = !modified;
asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString());
asset.Description = xml.GetAttribute("desc");
asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0;
asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0;
asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", ""));
if (asset.ID != rdata.Parameters[0])
{
Rest.Log.WarnFormat("{0} URI and payload disagree on UUID U:{1} vs P:{2}",
MsgId, rdata.Parameters[0], asset.ID);
}
Rest.AssetServices.Store(asset);
}
else
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
}
if (created)
{
rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
rdata.Complete(Rest.HttpStatusCodeCreated);
}
else
{
if (modified)
{
rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
rdata.Complete(Rest.HttpStatusCodeOK);
}
else
{
rdata.Complete(Rest.HttpStatusCodeNoContent);
}
}
rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method));
}
/// <summary>
/// CREATE new item, replace if it exists. URI identifies the context for the item in question.
/// No parameters are required for POST, just thepayload.
/// </summary>
private void DoPost(AssetRequestData rdata)
{
bool modified = false;
bool created = false;
Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length != 0)
{
Rest.Log.WarnFormat("{0} Parameters ignored <{1}>", MsgId, rdata.path);
Rest.Log.InfoFormat("{0} POST of an asset has no parameters", MsgId, rdata.path);
}
rdata.initXmlReader();
XmlReader xml = rdata.reader;
if (!xml.ReadToFollowing("Asset"))
{
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
}
UUID uuid = new UUID(xml.GetAttribute("id"));
AssetBase asset = Rest.AssetServices.Get(uuid.ToString());
modified = (asset != null);
created = !modified;
asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString());
asset.Description = xml.GetAttribute("desc");
asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0;
asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0;
asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", ""));
Rest.AssetServices.Store(asset);
if (created)
{
rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
rdata.Complete(Rest.HttpStatusCodeCreated);
}
else
{
if (modified)
{
rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
rdata.Complete(Rest.HttpStatusCodeOK);
}
else
{
rdata.Complete(Rest.HttpStatusCodeNoContent);
}
}
rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method));
}
/// <summary>
/// Asset processing has no special data area requirements.
/// </summary>
internal class AssetRequestData : RequestData
{
internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
: base(request, response, prefix)
{
}
}
}
}

View File

@@ -0,0 +1,448 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Xml;
using System.IO;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
public class RestFileServices : IRest
{
private bool enabled = false;
private string qPrefix = "files";
// A simple constructor is used to handle any once-only
// initialization of working classes.
public RestFileServices()
{
Rest.Log.InfoFormat("{0} File services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// If the handler specifies a relative path for its domain
// then we must add the standard absolute prefix, e.g. /admin
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{
Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
}
// Register interface using the fully-qualified prefix
Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate);
// Activate if all went OK
enabled = true;
Rest.Log.InfoFormat("{0} File services initialization complete", MsgId);
}
// Post-construction, pre-enabled initialization opportunity
// Not currently exploited.
public void Initialize()
{
}
// Called by the plug-in to halt REST processing. Local processing is
// disabled, and control blocks until all current processing has
// completed. No new processing will be started
public void Close()
{
enabled = false;
Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix);
}
// Properties
internal string MsgId
{
get { return Rest.MsgId; }
}
#region Interface
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
{
return (RequestData) new FileRequestData(request, response, prefix);
}
// Asset Handler
private void DoFile(RequestData rparm)
{
if (!enabled) return;
FileRequestData rdata = (FileRequestData) rparm;
Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix);
// Now that we know this is a serious attempt to
// access file data, we should find out who
// is asking, and make sure they are authorized
// to do so. We need to validate the caller's
// identity before revealing anything about the
// status quo. Authenticate throws an exception
// via Fail if no identity information is present.
//
// With the present HTTP server we can't use the
// builtin authentication mechanisms because they
// would be enforced for all in-bound requests.
// Instead we look at the headers ourselves and
// handle authentication directly.
try
{
if (!rdata.IsAuthenticated)
{
rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
}
}
catch (RestException e)
{
if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
{
Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
rdata.request.Headers.Get("Authorization"));
}
else
{
Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
rdata.request.Headers.Get("Authorization"));
}
throw (e);
}
// Remove the prefix and what's left are the parameters. If we don't have
// the parameters we need, fail the request. Parameters do NOT include
// any supplied query values.
if (rdata.Parameters.Length > 0)
{
switch (rdata.method)
{
case "get" :
DoGet(rdata);
break;
case "put" :
DoPut(rdata);
break;
case "post" :
DoPost(rdata);
break;
case "delete" :
DoDelete(rdata);
break;
default :
Rest.Log.WarnFormat("{0} File: Method not supported: {1}",
MsgId, rdata.method);
rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
break;
}
}
else
{
Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId);
rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
}
Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId);
}
#endregion Interface
/// <summary>
/// The only parameter we recognize is a UUID.If an asset with this identification is
/// found, it's content, base-64 encoded, is returned to the client.
/// </summary>
private void DoGet(FileRequestData rdata)
{
string path = String.Empty;
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length > 1)
{
try
{
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
if (File.Exists(path))
{
Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path);
Byte[] data = File.ReadAllBytes(path);
rdata.initXmlWriter();
rdata.writer.WriteStartElement(String.Empty,"File",String.Empty);
rdata.writer.WriteAttributeString("name", path);
rdata.writer.WriteBase64(data,0,data.Length);
rdata.writer.WriteFullEndElement();
}
else
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path);
rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path));
}
}
catch (Exception e)
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message);
rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
path, e.Message));
}
}
rdata.Complete();
rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method));
}
/// <summary>
/// UPDATE existing item, if it exists. URI identifies the item in question.
/// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
/// is decoded and stored in the database, identified by the supplied UUID.
/// </summary>
private void DoPut(FileRequestData rdata)
{
bool modified = false;
bool created = false;
string path = String.Empty;
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length > 1)
{
try
{
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
bool maymod = File.Exists(path);
rdata.initXmlReader();
XmlReader xml = rdata.reader;
if (!xml.ReadToFollowing("File"))
{
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
}
Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
File.WriteAllBytes(path,data);
modified = maymod;
created = ! maymod;
}
catch (Exception e)
{
Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
e.Message);
}
}
else
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
}
if (created)
{
rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
rdata.Complete(Rest.HttpStatusCodeCreated);
}
else
{
if (modified)
{
rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
rdata.Complete(Rest.HttpStatusCodeOK);
}
else
{
rdata.Complete(Rest.HttpStatusCodeNoContent);
}
}
rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
}
/// <summary>
/// CREATE new item, replace if it exists. URI identifies the context for the item in question.
/// No parameters are required for POST, just thepayload.
/// </summary>
private void DoPost(FileRequestData rdata)
{
bool modified = false;
bool created = false;
string path = String.Empty;
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length > 1)
{
try
{
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
bool maymod = File.Exists(path);
rdata.initXmlReader();
XmlReader xml = rdata.reader;
if (!xml.ReadToFollowing("File"))
{
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
}
Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
File.WriteAllBytes(path,data);
modified = maymod;
created = ! maymod;
}
catch (Exception e)
{
Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
e.Message);
}
}
else
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
}
if (created)
{
rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
rdata.Complete(Rest.HttpStatusCodeCreated);
}
else
{
if (modified)
{
rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
rdata.Complete(Rest.HttpStatusCodeOK);
}
else
{
rdata.Complete(Rest.HttpStatusCodeNoContent);
}
}
rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
}
/// <summary>
/// CREATE new item, replace if it exists. URI identifies the context for the item in question.
/// No parameters are required for POST, just thepayload.
/// </summary>
private void DoDelete(FileRequestData rdata)
{
bool modified = false;
bool created = false;
string path = String.Empty;
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
if (rdata.Parameters.Length > 1)
{
try
{
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
if (File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception e)
{
Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
e.Message);
rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
path, e.Message));
}
}
else
{
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
}
if (created)
{
rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
rdata.Complete(Rest.HttpStatusCodeCreated);
}
else
{
if (modified)
{
rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
rdata.Complete(Rest.HttpStatusCodeOK);
}
else
{
rdata.Complete(Rest.HttpStatusCodeNoContent);
}
}
rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
}
/// <summary>
/// File processing has no special data area requirements.
/// </summary>
internal class FileRequestData : RequestData
{
internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
: base(request, response, prefix)
{
}
}
}
}

View File

@@ -0,0 +1,659 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
/// <remarks>
/// The class signature reveals the roles that RestHandler plays.
///
/// [1] It is a sub-class of RestPlugin. It inherits and extends
/// the functionality of this class, constraining it to the
/// specific needs of this REST implementation. This relates
/// to the plug-in mechanism supported by OpenSim, the specifics
/// of which are mostly hidden by RestPlugin.
/// [2] IRestHandler describes the interface that this class
/// exports to service implementations. This is the services
/// management interface.
/// [3] IHttpAgentHandler describes the interface that is exported
/// to the BaseHttpServer in support of this particular HTTP
/// processing model. This is the request interface of the
/// handler.
/// </remarks>
public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler
{
// Handler tables: both stream and REST are supported. The path handlers and their
// respective allocators are stored in separate tables.
internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
#region local static state
private static bool handlersLoaded = false;
private static List<Type> classes = new List<Type>();
private static List<IRest> handlers = new List<IRest>();
private static Type[] parms = new Type[0];
private static Object[] args = new Object[0];
/// <summary>
/// This static initializer scans the ASSEMBLY for classes that
/// export the IRest interface and builds a list of them. These
/// are later activated by the handler. To add a new handler it
/// is only necessary to create a new services class that implements
/// the IRest interface, and recompile the handler. This gives
/// all of the build-time flexibility of a modular approach
/// while not introducing yet-another module loader. Note that
/// multiple assembles can still be built, each with its own set
/// of handlers. Examples of services classes are RestInventoryServices
/// and RestSkeleton.
/// </summary>
static RestHandler()
{
Module[] mods = Assembly.GetExecutingAssembly().GetModules();
foreach (Module m in mods)
{
Type[] types = m.GetTypes();
foreach (Type t in types)
{
try
{
if (t.GetInterface("IRest") != null)
{
classes.Add(t);
}
}
catch (Exception)
{
Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t);
Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t);
}
}
}
}
#endregion local static state
#region local instance state
/// <summary>
/// This routine loads all of the handlers discovered during
/// instance initialization.
/// A table of all loaded and successfully constructed handlers
/// is built, and this table is then used by the constructor to
/// initialize each of the handlers in turn.
/// NOTE: The loading process does not automatically imply that
/// the handler has registered any kind of an interface, that
/// may be (optionally) done by the handler either during
/// construction, or during initialization.
///
/// I was not able to make this code work within a constructor
/// so it is isolated within this method.
/// </summary>
private void LoadHandlers()
{
lock (handlers)
{
if (!handlersLoaded)
{
ConstructorInfo ci;
Object ht;
foreach (Type t in classes)
{
try
{
ci = t.GetConstructor(parms);
ht = ci.Invoke(args);
handlers.Add((IRest)ht);
}
catch (Exception e)
{
Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message);
}
}
handlersLoaded = true;
}
}
}
#endregion local instance state
#region overriding properties
// These properties override definitions
// in the base class.
// Name is used to differentiate the message header.
public override string Name
{
get { return "HANDLER"; }
}
// Used to partition the .ini configuration space.
public override string ConfigName
{
get { return "RestHandler"; }
}
// We have to rename these because we want
// to be able to share the values with other
// classes in our assembly and the base
// names are protected.
public string MsgId
{
get { return base.MsgID; }
}
public string RequestId
{
get { return base.RequestID; }
}
#endregion overriding properties
#region overriding methods
/// <summary>
/// This method is called by OpenSimMain immediately after loading the
/// plugin and after basic server setup, but before running any server commands.
/// </summary>
/// <remarks>
/// Note that entries MUST be added to the active configuration files before
/// the plugin can be enabled.
/// </remarks>
public override void Initialise(OpenSimBase openSim)
{
try
{
// This plugin will only be enabled if the broader
// REST plugin mechanism is enabled.
//Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId);
base.Initialise(openSim);
// IsEnabled is implemented by the base class and
// reflects an overall RestPlugin status
if (!IsEnabled)
{
//Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
return;
}
Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name);
Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName);
// These are stored in static variables to make
// them easy to reach from anywhere in the assembly.
Rest.main = openSim;
if (Rest.main == null)
throw new Exception("OpenSim base pointer is null");
Rest.Plugin = this;
Rest.Config = Config;
Rest.Prefix = Prefix;
Rest.GodKey = GodKey;
Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate);
Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme);
Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure);
Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape);
Rest.Realm = Rest.Config.GetString("realm", Rest.Realm);
Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset);
Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill);
Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize);
Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled);
// Note: Odd spacing is required in the following strings
Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
(Rest.Authenticate ? "" : "not "));
Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
(Rest.Secure ? "" : "not "));
Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
(Rest.ExtendedEscape ? "" : "not "));
Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
(Rest.DumpAsset ? "" : "not "));
// The supplied prefix MUST be absolute
if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator)
{
Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix);
Rest.Log.InfoFormat("{0} Prefix changed to </{1}>", MsgId, Rest.Prefix);
Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix);
}
// If data dumping is requested, report on the chosen line
// length.
if (Rest.DumpAsset)
{
Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize);
}
// Load all of the handlers present in the
// assembly
// In principle, as we're an application plug-in,
// most of what needs to be done could be done using
// static resources, however the Open Sim plug-in
// model makes this an instance, so that's what we
// need to be.
// There is only one Communications manager per
// server, and by inference, only one each of the
// user, asset, and inventory servers. So we can cache
// those using a static initializer.
// We move all of this processing off to another
// services class to minimize overlap between function
// and infrastructure.
LoadHandlers();
// The intention of a post construction initializer
// is to allow for setup that is dependent upon other
// activities outside of the agency.
foreach (IRest handler in handlers)
{
try
{
handler.Initialize();
}
catch (Exception e)
{
Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message);
}
}
// Now that everything is setup we can proceed to
// add THIS agent to the HTTP server's handler list
if (!AddAgentHandler(Rest.Name,this))
{
Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
foreach (IRest handler in handlers)
{
handler.Close();
}
}
}
catch (Exception e)
{
Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
}
}
/// <summary>
/// In the interests of efficiency, and because we cannot determine whether
/// or not this instance will actually be harvested, we clobber the only
/// anchoring reference to the working state for this plug-in. What the
/// call to close does is irrelevant to this class beyond knowing that it
/// can nullify the reference when it returns.
/// To make sure everything is copacetic we make sure the primary interface
/// is disabled by deleting the handler from the HTTP server tables.
/// </summary>
public override void Close()
{
Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
try
{
RemoveAgentHandler(Rest.Name, this);
}
catch (KeyNotFoundException){}
foreach (IRest handler in handlers)
{
handler.Close();
}
}
#endregion overriding methods
#region interface methods
/// <summary>
/// This method is called by the HTTP server to match an incoming
/// request. It scans all of the strings registered by the
/// underlying handlers and looks for the best match. It returns
/// true if a match is found.
/// The matching process could be made arbitrarily complex.
/// Note: The match is case-insensitive.
/// </summary>
public bool Match(OSHttpRequest request, OSHttpResponse response)
{
string path = request.RawUrl.ToLower();
// Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
try
{
foreach (string key in pathHandlers.Keys)
{
// Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
// Note that Match will not necessarily find the handler that will
// actually be used - it does no test for the "closest" fit. It
// simply reflects that at least one possible handler exists.
if (path.StartsWith(key))
{
// Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// This apparently odd evaluation is needed to prevent a match
// on anything other than a URI token boundary. Otherwise we
// may match on URL's that were not intended for this handler.
return (path.Length == key.Length ||
path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
}
}
path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
foreach (string key in streamHandlers.Keys)
{
// Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
// Note that Match will not necessarily find the handler that will
// actually be used - it does no test for the "closest" fit. It
// simply reflects that at least one possible handler exists.
if (path.StartsWith(key))
{
// Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// This apparently odd evaluation is needed to prevent a match
// on anything other than a URI token boundary. Otherwise we
// may match on URL's that were not intended for this handler.
return (path.Length == key.Length ||
path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
}
}
}
catch (Exception e)
{
Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message);
}
return false;
}
/// <summary>
/// This is called by the HTTP server once the handler has indicated
/// that it is able to handle the request.
/// Preconditions:
/// [1] request != null and is a valid request object
/// [2] response != null and is a valid response object
/// Behavior is undefined if preconditions are not satisfied.
/// </summary>
public bool Handle(OSHttpRequest request, OSHttpResponse response)
{
bool handled;
base.MsgID = base.RequestID;
// Debug only
if (Rest.DEBUG)
{
Rest.Log.DebugFormat("{0} ENTRY", MsgId);
Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
for (int i = 0; i < request.Headers.Count; i++)
{
Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
}
Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
}
// If a path handler worked we're done, otherwise try any
// available stream handlers too.
try
{
handled = (FindPathHandler(request, response) ||
FindStreamHandler(request, response));
}
catch (Exception e)
{
// A raw exception indicates that something we weren't expecting has
// happened. This should always reflect a shortcoming in the plugin,
// or a failure to satisfy the preconditions. It should not reflect
// an error in the request itself. Under such circumstances the state
// of the request cannot be determined and we are obliged to mark it
// as 'handled'.
Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
handled = true;
}
Rest.Log.DebugFormat("{0} EXIT", MsgId);
return handled;
}
#endregion interface methods
/// <summary>
/// If there is a stream handler registered that can handle the
/// request, then fine. If the request is not matched, do
/// nothing.
/// Note: The selection is case-insensitive
/// </summary>
private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
{
RequestData rdata = new RequestData(request, response, String.Empty);
string bestMatch = String.Empty;
string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower();
Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
if (!IsEnabled)
{
return false;
}
foreach (string pattern in streamHandlers.Keys)
{
if (path.StartsWith(pattern))
{
if (pattern.Length > bestMatch.Length)
{
bestMatch = pattern;
}
}
}
// Handle using the best match available
if (bestMatch.Length > 0)
{
Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
RestStreamHandler handler = streamHandlers[bestMatch];
rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
rdata.Respond("FindStreamHandler Completion");
}
return rdata.handled;
}
/// <summary>
/// Add a stream handler for the designated HTTP method and path prefix.
/// If the handler is not enabled, the request is ignored. If the path
/// does not start with the REST prefix, it is added. If method-qualified
/// path has not already been registered, the method is added to the active
/// handler table.
/// </summary>
public void AddStreamHandler(string httpMethod, string path, RestMethod method)
{
if (!IsEnabled)
{
return;
}
if (!path.StartsWith(Rest.Prefix))
{
path = String.Format("{0}{1}", Rest.Prefix, path);
}
path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
// Conditionally add to the list
if (!streamHandlers.ContainsKey(path))
{
streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path);
}
else
{
Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
}
}
/// <summary>
/// Given the supplied request/response, if the handler is enabled, the inbound
/// information is used to match an entry in the active path handler tables, using
/// the method-qualified path information. If a match is found, then the handler is
/// invoked. The result is the boolean result of the handler, or false if no
/// handler was located. The boolean indicates whether or not the request has been
/// handled, not whether or not the request was successful - that information is in
/// the response.
/// Note: The selection process is case-insensitive
/// </summary>
internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
{
RequestData rdata = null;
string bestMatch = null;
if (!IsEnabled)
{
return false;
}
// Conditionally add to the list
Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
foreach (string pattern in pathHandlers.Keys)
{
if (request.RawUrl.ToLower().StartsWith(pattern))
{
if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
{
bestMatch = pattern;
}
}
}
if (!String.IsNullOrEmpty(bestMatch))
{
rdata = pathAllocators[bestMatch](request, response, bestMatch);
Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
try
{
pathHandlers[bestMatch](rdata);
}
// A plugin generated error indicates a request-related error
// that has been handled by the plugin.
catch (RestException r)
{
Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
}
}
return (rdata == null) ? false : rdata.handled;
}
/// <summary>
/// A method handler and a request allocator are stored using the designated
/// path as a key. If an entry already exists, it is replaced by the new one.
/// </summary>
public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
{
if (!IsEnabled)
{
return;
}
if (pathHandlers.ContainsKey(path))
{
Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
pathHandlers.Remove(path);
}
if (pathAllocators.ContainsKey(path))
{
Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
pathAllocators.Remove(path);
}
Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
pathHandlers.Add(path, mh);
pathAllocators.Add(path, ra);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
public class RestTestServices : IRest
{
private bool enabled = false;
private string qPrefix = "test";
// A simple constructor is used to handle any once-only
// initialization of working classes.
public RestTestServices()
{
Rest.Log.InfoFormat("{0} Test services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// If a relative path was specified, make it absolute by adding
// the standard prefix, e.g. /admin
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{
Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
}
// Load test cases
loadTests();
foreach (ITest test in tests)
{
test.Initialize();
}
// Register interface
Rest.Plugin.AddPathHandler(DoTests,qPrefix,Allocate);
// Activate
enabled = true;
Rest.Log.InfoFormat("{0} Test services initialization complete", MsgId);
}
// Post-construction, pre-enabled initialization opportunity
// Not currently exploited.
public void Initialize()
{
}
// Called by the plug-in to halt REST processing. Local processing is
// disabled, and control blocks until all current processing has
// completed. No new processing will be started
public void Close()
{
enabled = false;
foreach (ITest test in tests)
{
test.Close();
}
Rest.Log.InfoFormat("{0} Test services closing down", MsgId);
}
// Properties
internal string MsgId
{
get { return Rest.MsgId; }
}
#region Interface
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
{
return new RequestData(request, response, prefix);
}
// Inventory Handler
private void DoTests(RequestData rdata)
{
if (!enabled)
return;
// Now that we know this is a serious attempt to
// access inventory data, we should find out who
// is asking, and make sure they are authorized
// to do so. We need to validate the caller's
// identity before revealing anything about the
// status quo. Authenticate throws an exception
// via Fail if no identity information is present.
//
// With the present HTTP server we can't use the
// builtin authentication mechanisms because they
// would be enforced for all in-bound requests.
// Instead we look at the headers ourselves and
// handle authentication directly.
try
{
if (!rdata.IsAuthenticated)
{
rdata.Fail(Rest.HttpStatusCodeNotAuthorized,
String.Format("user \"{0}\" could not be authenticated", rdata.userName));
}
}
catch (RestException e)
{
if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
{
Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
}
else
{
Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
}
throw (e);
}
// Check that a test was specified
if (rdata.Parameters.Length < 1)
{
Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId);
rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters");
}
// Select the test
foreach (ITest test in tests)
{
if (!rdata.handled)
test.Execute(rdata);
}
}
#endregion Interface
private static bool testsLoaded = false;
private static List<Type> classes = new List<Type>();
private static List<ITest> tests = new List<ITest>();
private static Type[] parms = new Type[0];
private static Object[] args = new Object[0];
static RestTestServices()
{
Module[] mods = Assembly.GetExecutingAssembly().GetModules();
foreach (Module m in mods)
{
Type[] types = m.GetTypes();
foreach (Type t in types)
{
try
{
if (t.GetInterface("ITest") != null)
{
classes.Add(t);
}
}
catch (Exception e)
{
Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message);
}
}
}
}
/// <summary>
/// This routine loads all of the handlers discovered during
/// instance initialization. Each handler is responsible for
/// registering itself with this handler.
/// I was not able to make this code work in a constructor.
/// </summary>
private void loadTests()
{
lock (tests)
{
if (!testsLoaded)
{
ConstructorInfo ci;
Object ht;
foreach (Type t in classes)
{
try
{
if (t.GetInterface("ITest") != null)
{
ci = t.GetConstructor(parms);
ht = ci.Invoke(args);
tests.Add((ITest)ht);
Rest.Log.InfoFormat("{0} Test {1} added", MsgId, t);
}
}
catch (Exception e)
{
Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message);
}
}
testsLoaded = true;
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
/// <summary>
/// This interface represents the boundary between the general purpose
/// REST plugin handling, and the functionally specific handlers. The
/// handler knows only to initialzie and terminate all such handlers
/// that it finds.
/// </summary>
internal interface ITest
{
void Initialize();
void Execute(RequestData rdata);
void Close();
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using OpenMetaverse;
using OpenSim.Region.Framework.Scenes;
namespace OpenSim.ApplicationPlugins.Rest.Inventory.Tests
{
public class Remote : ITest
{
private static readonly int PARM_TESTID = 0;
private static readonly int PARM_COMMAND = 1;
private static readonly int PARM_MOVE_AVATAR = 2;
private static readonly int PARM_MOVE_X = 3;
private static readonly int PARM_MOVE_Y = 4;
private static readonly int PARM_MOVE_Z = 5;
private bool enabled = false;
// No constructor code is required.
public Remote()
{
Rest.Log.InfoFormat("{0} Remote services constructor", MsgId);
}
// Post-construction, pre-enabled initialization opportunity
// Not currently exploited.
public void Initialize()
{
enabled = true;
Rest.Log.InfoFormat("{0} Remote services initialized", MsgId);
}
// Called by the plug-in to halt REST processing. Local processing is
// disabled, and control blocks until all current processing has
// completed. No new processing will be started
public void Close()
{
enabled = false;
Rest.Log.InfoFormat("{0} Remote services closing down", MsgId);
}
// Properties
internal string MsgId
{
get { return Rest.MsgId; }
}
// Remote Handler
// Key information of interest here is the Parameters array, each
// entry represents an element of the URI, with element zero being
// the
public void Execute(RequestData rdata)
{
if (!enabled) return;
// If we can't relate to what's there, leave it for others.
if (rdata.Parameters.Length == 0 || rdata.Parameters[PARM_TESTID] != "remote")
return;
Rest.Log.DebugFormat("{0} REST Remote handler ENTRY", MsgId);
// Remove the prefix and what's left are the parameters. If we don't have
// the parameters we need, fail the request. Parameters do NOT include
// any supplied query values.
if (rdata.Parameters.Length > 1)
{
switch (rdata.Parameters[PARM_COMMAND].ToLower())
{
case "move" :
DoMove(rdata);
break;
default :
DoHelp(rdata);
break;
}
}
else
{
DoHelp(rdata);
}
}
private void DoHelp(RequestData rdata)
{
rdata.body = Help;
rdata.Complete();
rdata.Respond("Help");
}
private void DoMove(RequestData rdata)
{
if (rdata.Parameters.Length < 6)
{
Rest.Log.WarnFormat("{0} Move: No movement information provided", MsgId);
rdata.Fail(Rest.HttpStatusCodeBadRequest, "no movement information provided");
}
else
{
string[] names = rdata.Parameters[PARM_MOVE_AVATAR].Split(Rest.CA_SPACE);
ScenePresence presence = null;
Scene scene = null;
if (names.Length != 2)
{
rdata.Fail(Rest.HttpStatusCodeBadRequest,
String.Format("invalid avatar name: <{0}>",rdata.Parameters[PARM_MOVE_AVATAR]));
}
Rest.Log.WarnFormat("{0} '{1}' command received for {2} {3}",
MsgId, rdata.Parameters[0], names[0], names[1]);
// The first parameter should be an avatar name, look for the
// avatar in the known regions first.
Rest.main.SceneManager.ForEachScene(delegate(Scene s)
{
s.ForEachRootScenePresence(delegate(ScenePresence sp)
{
if (sp.Firstname == names[0] && sp.Lastname == names[1])
{
scene = s;
presence = sp;
}
});
});
if (presence != null)
{
Rest.Log.DebugFormat("{0} Move : Avatar {1} located in region {2}",
MsgId, rdata.Parameters[PARM_MOVE_AVATAR], scene.RegionInfo.RegionName);
try
{
float x = Convert.ToSingle(rdata.Parameters[PARM_MOVE_X]);
float y = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Y]);
float z = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Z]);
Vector3 vector = new Vector3(x, y, z);
presence.MoveToTarget(vector, false, false);
}
catch (Exception e)
{
rdata.Fail(Rest.HttpStatusCodeBadRequest,
String.Format("invalid parameters: {0}", e.Message));
}
}
else
{
rdata.Fail(Rest.HttpStatusCodeBadRequest,
String.Format("avatar {0} not present", rdata.Parameters[PARM_MOVE_AVATAR]));
}
rdata.Complete();
rdata.Respond("OK");
}
}
private static readonly string Help =
"<html>"
+ "<head><title>Remote Command Usage</title></head>"
+ "<body>"
+ "<p>Supported commands are:</p>"
+ "<dl>"
+ "<dt>move/avatar-name/x/y/z</dt>"
+ "<dd>moves the specified avatar to another location</dd>"
+ "</dl>"
+ "</body>"
+ "</html>"
;
}
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Xml.Serialization;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
namespace OpenSim.ApplicationPlugins.Rest.Regions
{
public partial class RestRegionPlugin : RestPlugin
{
#region GET methods
public string GetHandler(string request, string path, string param,
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
// foreach (string h in httpRequest.Headers.AllKeys)
// foreach (string v in httpRequest.Headers.GetValues(h))
// m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v);
MsgID = RequestID;
m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param);
try
{
// param empty: regions list
if (String.IsNullOrEmpty(param)) return GetHandlerRegions(httpResponse);
// param not empty: specific region
return GetHandlerRegion(httpResponse, param);
}
catch (Exception e)
{
return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e);
}
}
public string GetHandlerRegions(IOSHttpResponse httpResponse)
{
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
rxw.WriteStartElement(String.Empty, "regions", String.Empty);
foreach (Scene s in App.SceneManager.Scenes)
{
rxw.WriteStartElement(String.Empty, "uuid", String.Empty);
rxw.WriteString(s.RegionInfo.RegionID.ToString());
rxw.WriteEndElement();
}
rxw.WriteEndElement();
return rxw.ToString();
}
protected string ShortRegionInfo(string key, string value)
{
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
if (String.IsNullOrEmpty(value) ||
String.IsNullOrEmpty(key)) return null;
rxw.WriteStartElement(String.Empty, "region", String.Empty);
rxw.WriteStartElement(String.Empty, key, String.Empty);
rxw.WriteString(value);
rxw.WriteEndDocument();
return rxw.ToString();
}
public string GetHandlerRegion(IOSHttpResponse httpResponse, string param)
{
// be resilient and don't get confused by a terminating '/'
param = param.TrimEnd(new char[]{'/'});
string[] comps = param.Split('/');
UUID regionID = (UUID)comps[0];
m_log.DebugFormat("{0} GET region UUID {1}", MsgID, regionID.ToString());
if (UUID.Zero == regionID) throw new Exception("missing region ID");
Scene scene = null;
App.SceneManager.TryGetScene(regionID, out scene);
if (null == scene) return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound,
"GET", "cannot find region {0}", regionID.ToString());
RegionDetails details = new RegionDetails(scene.RegionInfo);
// m_log.DebugFormat("{0} GET comps {1}", MsgID, comps.Length);
// for (int i = 0; i < comps.Length; i++) m_log.DebugFormat("{0} GET comps[{1}] >{2}<", MsgID, i, comps[i]);
if (1 == comps.Length)
{
// complete region details requested
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
XmlSerializer xs = new XmlSerializer(typeof(RegionDetails));
xs.Serialize(rxw, details, _xmlNs);
return rxw.ToString();
}
if (2 == comps.Length)
{
string resp = ShortRegionInfo(comps[1], details[comps[1]]);
if (null != resp) return resp;
// m_log.DebugFormat("{0} GET comps advanced: >{1}<", MsgID, comps[1]);
// check for {terrain,stats,prims}
switch (comps[1].ToLower())
{
case "terrain":
return RegionTerrain(httpResponse, scene);
case "stats":
return RegionStats(httpResponse, scene);
case "prims":
return RegionPrims(httpResponse, scene, Vector3.Zero, Vector3.Zero);
}
}
if (3 == comps.Length)
{
switch (comps[1].ToLower())
{
case "prims":
string[] subregion = comps[2].Split(',');
if (subregion.Length == 6)
{
Vector3 min, max;
try
{
min = new Vector3((float)Double.Parse(subregion[0], Culture.NumberFormatInfo), (float)Double.Parse(subregion[1], Culture.NumberFormatInfo), (float)Double.Parse(subregion[2], Culture.NumberFormatInfo));
max = new Vector3((float)Double.Parse(subregion[3], Culture.NumberFormatInfo), (float)Double.Parse(subregion[4], Culture.NumberFormatInfo), (float)Double.Parse(subregion[5], Culture.NumberFormatInfo));
}
catch (Exception)
{
return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest,
"GET", "invalid subregion parameter");
}
return RegionPrims(httpResponse, scene, min, max);
}
else
{
return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest,
"GET", "invalid subregion parameter");
}
}
}
return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest,
"GET", "too many parameters {0}", param);
}
#endregion GET methods
protected string RegionTerrain(IOSHttpResponse httpResponse, Scene scene)
{
httpResponse.SendChunked = true;
httpResponse.ContentType = "text/xml";
return scene.Heightmap.SaveToXmlString();
//return Failure(httpResponse, OSHttpStatusCode.ServerErrorNotImplemented,
// "GET", "terrain not implemented");
}
protected string RegionStats(IOSHttpResponse httpResponse, Scene scene)
{
int users = scene.GetRootAgentCount();
int objects = scene.Entities.Count - users;
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
rxw.WriteStartElement(String.Empty, "region", String.Empty);
rxw.WriteStartElement(String.Empty, "stats", String.Empty);
rxw.WriteStartElement(String.Empty, "users", String.Empty);
rxw.WriteString(users.ToString());
rxw.WriteEndElement();
rxw.WriteStartElement(String.Empty, "objects", String.Empty);
rxw.WriteString(objects.ToString());
rxw.WriteEndElement();
rxw.WriteEndDocument();
return rxw.ToString();
}
protected string RegionPrims(IOSHttpResponse httpResponse, Scene scene, Vector3 min, Vector3 max)
{
httpResponse.SendChunked = true;
httpResponse.ContentType = "text/xml";
IRegionSerialiserModule serialiser = scene.RequestModuleInterface<IRegionSerialiserModule>();
if (serialiser != null)
serialiser.SavePrimsToXml2(scene, new StreamWriter(httpResponse.OutputStream), min, max);
return "";
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Xml.Serialization;
using OpenMetaverse;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
namespace OpenSim.ApplicationPlugins.Rest.Regions
{
public partial class RestRegionPlugin : RestPlugin
{
#region GET methods
public string GetRegionInfoHandler(string request, string path, string param,
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
// foreach (string h in httpRequest.Headers.AllKeys)
// foreach (string v in httpRequest.Headers.GetValues(h))
// m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v);
MsgID = RequestID;
m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param);
try
{
// param empty: regions list
// if (String.IsNullOrEmpty(param))
return GetRegionInfoHandlerRegions(httpResponse);
// // param not empty: specific region
// return GetRegionInfoHandlerRegion(httpResponse, param);
}
catch (Exception e)
{
return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e);
}
}
public string GetRegionInfoHandlerRegions(IOSHttpResponse httpResponse)
{
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
// regions info
rxw.WriteStartElement(String.Empty, "regions", String.Empty);
{
// regions info: number of regions
rxw.WriteStartAttribute(String.Empty, "number", String.Empty);
rxw.WriteValue(App.SceneManager.Scenes.Count);
rxw.WriteEndAttribute();
// regions info: max number of regions
rxw.WriteStartAttribute(String.Empty, "max", String.Empty);
if (App.ConfigSource.Source.Configs["RemoteAdmin"] != null)
{
rxw.WriteValue(App.ConfigSource.Source.Configs["RemoteAdmin"].GetInt("region_limit", -1));
}
else
{
rxw.WriteValue(-1);
}
rxw.WriteEndAttribute();
// regions info: region
foreach (Scene s in App.SceneManager.Scenes)
{
rxw.WriteStartElement(String.Empty, "region", String.Empty);
rxw.WriteStartAttribute(String.Empty, "uuid", String.Empty);
rxw.WriteString(s.RegionInfo.RegionID.ToString());
rxw.WriteEndAttribute();
rxw.WriteStartAttribute(String.Empty, "name", String.Empty);
rxw.WriteString(s.RegionInfo.RegionName);
rxw.WriteEndAttribute();
rxw.WriteStartAttribute(String.Empty, "x", String.Empty);
rxw.WriteValue(s.RegionInfo.RegionLocX);
rxw.WriteEndAttribute();
rxw.WriteStartAttribute(String.Empty, "y", String.Empty);
rxw.WriteValue(s.RegionInfo.RegionLocY);
rxw.WriteEndAttribute();
rxw.WriteStartAttribute(String.Empty, "external_hostname", String.Empty);
rxw.WriteString(s.RegionInfo.ExternalHostName);
rxw.WriteEndAttribute();
rxw.WriteStartAttribute(String.Empty, "ip", String.Empty);
rxw.WriteString(s.RegionInfo.InternalEndPoint.ToString());
rxw.WriteEndAttribute();
int users = s.GetRootAgentCount();
rxw.WriteStartAttribute(String.Empty, "avatars", String.Empty);
rxw.WriteValue(users);
rxw.WriteEndAttribute();
rxw.WriteStartAttribute(String.Empty, "objects", String.Empty);
rxw.WriteValue(s.Entities.Count - users);
rxw.WriteEndAttribute();
rxw.WriteEndElement();
}
}
return rxw.ToString();
}
#endregion GET methods
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using OpenMetaverse;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
namespace OpenSim.ApplicationPlugins.Rest.Regions
{
public partial class RestRegionPlugin : RestPlugin
{
#region POST methods
public string PostHandler(string request, string path, string param,
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
// foreach (string h in httpRequest.Headers.AllKeys)
// foreach (string v in httpRequest.Headers.GetValues(h))
// m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v);
MsgID = RequestID;
m_log.DebugFormat("{0} POST path {1} param {2}", MsgID, path, param);
try
{
// param empty: new region post
if (!IsGod(httpRequest))
// XXX: this needs to be turned into a FailureUnauthorized(...)
return Failure(httpResponse, OSHttpStatusCode.ClientErrorUnauthorized,
"GET", "you are not god");
if (String.IsNullOrEmpty(param)) return CreateRegion(httpRequest, httpResponse);
// Parse region ID and other parameters
param = param.TrimEnd(new char[] {'/'});
string[] comps = param.Split('/');
UUID regionID = (UUID) comps[0];
m_log.DebugFormat("{0} POST region UUID {1}", MsgID, regionID.ToString());
if (UUID.Zero == regionID) throw new Exception("missing region ID");
Scene scene = null;
App.SceneManager.TryGetScene(regionID, out scene);
if (null == scene)
return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound,
"POST", "cannot find region {0}", regionID.ToString());
if (2 == comps.Length)
{
// check for {prims}
switch (comps[1].ToLower())
{
case "prims":
return LoadPrims(request, httpRequest, httpResponse, scene);
}
}
return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound,
"POST", "url {0} not supported", param);
}
catch (Exception e)
{
return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "POST", e);
}
}
public string CreateRegion(IOSHttpRequest request, IOSHttpResponse response)
{
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
rxw.WriteStartElement(String.Empty, "regions", String.Empty);
foreach (Scene s in App.SceneManager.Scenes)
{
rxw.WriteStartElement(String.Empty, "uuid", String.Empty);
rxw.WriteString(s.RegionInfo.RegionID.ToString());
rxw.WriteEndElement();
}
rxw.WriteEndElement();
return rxw.ToString();
}
public string LoadPrims(string requestBody, IOSHttpRequest request, IOSHttpResponse response, Scene scene)
{
IRegionSerialiserModule serialiser = scene.RequestModuleInterface<IRegionSerialiserModule>();
if (serialiser != null)
serialiser.LoadPrimsFromXml2(scene, new StringReader(requestBody), true);
return "";
}
#endregion POST methods
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Xml.Serialization;
using OpenMetaverse;
using OpenSim.Framework;
namespace OpenSim.ApplicationPlugins.Rest.Regions
{
[XmlRoot(ElementName="region", IsNullable = false)]
public class RegionDetails
{
public string region_name;
public string region_id;
public uint region_x;
public uint region_y;
public string region_owner;
public string region_owner_id;
public uint region_http_port;
public uint region_port;
public string region_server_uri;
public string region_external_hostname;
public RegionDetails()
{
}
public RegionDetails(RegionInfo regInfo)
{
region_name = regInfo.RegionName;
region_id = regInfo.RegionID.ToString();
region_x = regInfo.RegionLocX;
region_y = regInfo.RegionLocY;
region_owner_id = regInfo.EstateSettings.EstateOwner.ToString();
region_http_port = regInfo.HttpPort;
region_server_uri = regInfo.ServerURI;
region_external_hostname = regInfo.ExternalHostName;
Uri uri = new Uri(region_server_uri);
region_port = (uint)uri.Port;
}
public string this[string idx]
{
get
{
switch (idx.ToLower())
{
case "name":
return region_name;
case "id":
return region_id;
case "location":
return String.Format("<x>{0}</x><y>{1}</y>", region_x, region_y);
case "owner":
return region_owner;
case "owner_id":
return region_owner_id;
case "http_port":
return region_http_port.ToString();
case "server_uri":
return region_server_uri;
case "external_hostname":
case "hostname":
return region_external_hostname;
default:
return null;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
<Addin id="OpenSim.ApplicationPlugins.Rest.Regions" version="0.1">
<Runtime>
<Import assembly="OpenSim.ApplicationPlugins.Rest.Regions.dll"/>
</Runtime>
<Dependencies>
<Addin id="OpenSim" version="0.5" />
</Dependencies>
<Extension path = "/OpenSim/Startup">
<Plugin id="RestRegions" type="OpenSim.ApplicationPlugins.Rest.Regions.RestRegionPlugin" />
</Extension>
</Addin>

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Xml.Serialization;
namespace OpenSim.ApplicationPlugins.Rest.Regions
{
public partial class RestRegionPlugin : RestPlugin
{
private static XmlSerializerNamespaces _xmlNs;
static RestRegionPlugin()
{
_xmlNs = new XmlSerializerNamespaces();
_xmlNs.Add(String.Empty, String.Empty);
}
#region overriding properties
public override string Name
{
get { return "REGION"; }
}
public override string ConfigName
{
get { return "RestRegionPlugin"; }
}
#endregion overriding properties
#region overriding methods
/// <summary>
/// This method is called by OpenSimMain immediately after loading the
/// plugin and after basic server setup, but before running any server commands.
/// </summary>
/// <remarks>
/// Note that entries MUST be added to the active configuration files before
/// the plugin can be enabled.
/// </remarks>
public override void Initialise(OpenSimBase openSim)
{
try
{
base.Initialise(openSim);
if (!IsEnabled)
{
//m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID);
return;
}
m_log.InfoFormat("{0} REST region plugin enabled", MsgID);
// add REST method handlers
AddRestStreamHandler("GET", "/regions/", GetHandler);
AddRestStreamHandler("POST", "/regions/", PostHandler);
AddRestStreamHandler("GET", "/regioninfo/", GetRegionInfoHandler);
}
catch (Exception e)
{
m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message);
m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString());
}
}
public override void Close()
{
}
#endregion overriding methods
}
}

View File

@@ -0,0 +1,415 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
namespace OpenSim.ApplicationPlugins.Rest
{
public abstract class RestPlugin : IApplicationPlugin
{
#region properties
protected static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IConfig _config; // Configuration source: Rest Plugins
private IConfig _pluginConfig; // Configuration source: Plugin specific
private OpenSimBase _app; // The 'server'
private BaseHttpServer _httpd; // The server's RPC interface
private string _prefix; // URL prefix below
// which all REST URLs
// are living
// private StringWriter _sw = null;
// private RestXmlWriter _xw = null;
private string _godkey;
private int _reqk;
[ThreadStatic]
private static string _threadRequestID = String.Empty;
/// <summary>
/// Return an ever increasing request ID for logging
/// </summary>
protected string RequestID
{
get { return _reqk++.ToString(); }
set { _reqk = Convert.ToInt32(value); }
}
/// <summary>
/// Thread-constant message IDs for logging.
/// </summary>
protected string MsgID
{
get { return String.Format("[REST-{0}] #{1}", Name, _threadRequestID); }
set { _threadRequestID = value; }
}
/// <summary>
/// Returns true if Rest Plugins are enabled.
/// </summary>
public bool PluginsAreEnabled
{
get { return null != _config; }
}
/// <summary>
/// Returns true if specific Rest Plugin is enabled.
/// </summary>
public bool IsEnabled
{
get
{
return (null != _pluginConfig) && _pluginConfig.GetBoolean("enabled", false);
}
}
/// <summary>
/// OpenSimMain application
/// </summary>
public OpenSimBase App
{
get { return _app; }
}
/// <summary>
/// RPC server
/// </summary>
public BaseHttpServer HttpServer
{
get { return _httpd; }
}
/// <summary>
/// URL prefix to use for all REST handlers
/// </summary>
public string Prefix
{
get { return _prefix; }
}
/// <summary>
/// Access to GOD password string
/// </summary>
protected string GodKey
{
get { return _godkey; }
}
/// <summary>
/// Configuration of the plugin
/// </summary>
public IConfig Config
{
get { return _pluginConfig; }
}
/// <summary>
/// Name of the plugin
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Return the config section name
/// </summary>
public abstract string ConfigName { get; }
// public XmlTextWriter XmlWriter
// {
// get
// {
// if (null == _xw)
// {
// _sw = new StringWriter();
// _xw = new RestXmlWriter(_sw);
// _xw.Formatting = Formatting.Indented;
// }
// return _xw;
// }
// }
// public string XmlWriterResult
// {
// get
// {
// _xw.Flush();
// _xw.Close();
// _xw = null;
// return _sw.ToString();
// }
// }
#endregion properties
#region methods
// TODO: required by IPlugin, but likely not at all right
private string m_version = "0.0";
public string Version
{
get { return m_version; }
}
public void Initialise()
{
m_log.Info("[RESTPLUGIN]: " + Name + " cannot be default-initialized!");
throw new PluginNotInitialisedException(Name);
}
/// <summary>
/// This method is called by OpenSimMain immediately after loading the
/// plugin and after basic server setup, but before running any server commands.
/// </summary>
/// <remarks>
/// Note that entries MUST be added to the active configuration files before
/// the plugin can be enabled.
/// </remarks>
public virtual void Initialise(OpenSimBase openSim)
{
RequestID = "0";
MsgID = RequestID;
try
{
if ((_config = openSim.ConfigSource.Source.Configs["RestPlugins"]) == null)
{
m_log.WarnFormat("{0} Rest Plugins not configured", MsgID);
return;
}
if (!_config.GetBoolean("enabled", false))
{
//m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID);
return;
}
_app = openSim;
_httpd = openSim.HttpServer;
// Retrieve GOD key value, if any.
_godkey = _config.GetString("god_key", String.Empty);
// Retrive prefix if any.
_prefix = _config.GetString("prefix", "/admin");
// Get plugin specific config
_pluginConfig = openSim.ConfigSource.Source.Configs[ConfigName];
m_log.InfoFormat("{0} Rest Plugins Enabled", MsgID);
}
catch (Exception e)
{
// we can safely ignore this, as it just means that
// the key lookup in Configs failed, which signals to
// us that noone is interested in our services...they
// don't know what they are missing out on...
// NOTE: Under the present OpenSimulator implementation it is
// not possible for the openSimulator pointer to be null. However
// were the implementation to be changed, this could
// result in a silent initialization failure. Harmless
// except for lack of function and lack of any
// diagnostic indication as to why. The same is true if
// the HTTP server reference is bad.
// We should at least issue a message...
m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message);
m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString());
}
}
public virtual void PostInitialise()
{
}
private List<RestStreamHandler> _handlers = new List<RestStreamHandler>();
private Dictionary<string, IHttpAgentHandler> _agents = new Dictionary<string, IHttpAgentHandler>();
/// <summary>
/// Add a REST stream handler to the underlying HTTP server.
/// </summary>
/// <param name="httpMethod">GET/PUT/POST/DELETE or
/// similar</param>
/// <param name="path">URL prefix</param>
/// <param name="method">RestMethod handler doing the actual work</param>
public virtual void AddRestStreamHandler(string httpMethod, string path, RestMethod method)
{
if (!IsEnabled) return;
if (!path.StartsWith(_prefix))
{
path = String.Format("{0}{1}", _prefix, path);
}
RestStreamHandler h = new RestStreamHandler(httpMethod, path, method);
_httpd.AddStreamHandler(h);
_handlers.Add(h);
m_log.DebugFormat("{0} Added REST handler {1} {2}", MsgID, httpMethod, path);
}
/// <summary>
/// Add a powerful Agent handler to the underlying HTTP
/// server.
/// </summary>
/// <param name="agentName">name of agent handler</param>
/// <param name="handler">agent handler method</param>
/// <returns>false when the plugin is disabled or the agent
/// handler could not be added. Any generated exceptions are
/// allowed to drop through to the caller, i.e. ArgumentException.
/// </returns>
public bool AddAgentHandler(string agentName, IHttpAgentHandler handler)
{
if (!IsEnabled) return false;
_agents.Add(agentName, handler);
return _httpd.AddAgentHandler(agentName, handler);
}
/// <summary>
/// Remove a powerful Agent handler from the underlying HTTP
/// server.
/// </summary>
/// <param name="agentName">name of agent handler</param>
/// <param name="handler">agent handler method</param>
/// <returns>false when the plugin is disabled or the agent
/// handler could not be removed. Any generated exceptions are
/// allowed to drop through to the caller, i.e. KeyNotFound.
/// </returns>
public bool RemoveAgentHandler(string agentName, IHttpAgentHandler handler)
{
if (!IsEnabled) return false;
if (_agents[agentName] == handler)
{
_agents.Remove(agentName);
return _httpd.RemoveAgentHandler(agentName, handler);
}
return false;
}
/// <summary>
/// Check whether the HTTP request came from god; that is, is
/// the god_key as configured in the config section supplied
/// via X-OpenSim-Godkey?
/// </summary>
/// <param name="request">HTTP request header</param>
/// <returns>true when the HTTP request came from god.</returns>
protected bool IsGod(IOSHttpRequest request)
{
string[] keys = request.Headers.GetValues("X-OpenSim-Godkey");
if (null == keys) return false;
// we take the last key supplied
return keys[keys.Length - 1] == _godkey;
}
/// <summary>
/// Checks wether the X-OpenSim-Password value provided in the
/// HTTP header is indeed the password on file for the avatar
/// specified by the UUID
/// </summary>
protected bool IsVerifiedUser(IOSHttpRequest request, UUID uuid)
{
// XXX under construction
return false;
}
/// <summary>
/// Clean up and remove all handlers that were added earlier.
/// </summary>
public virtual void Close()
{
foreach (RestStreamHandler h in _handlers)
{
_httpd.RemoveStreamHandler(h.HttpMethod, h.Path);
}
_handlers = null;
foreach (KeyValuePair<string, IHttpAgentHandler> h in _agents)
{
_httpd.RemoveAgentHandler(h.Key, h.Value);
}
_agents = null;
}
public virtual void Dispose()
{
Close();
}
/// <summary>
/// Return a failure message.
/// </summary>
/// <param name="method">origin of the failure message</param>
/// <param name="message">failure message</param>
/// <remarks>This should probably set a return code as
/// well. (?)</remarks>
protected string Failure(IOSHttpResponse response, OSHttpStatusCode status,
string method, string format, params string[] msg)
{
string m = String.Format(format, msg);
response.StatusCode = (int) status;
response.StatusDescription = m;
m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, m);
return String.Format("<error>{0}</error>", m);
}
/// <summary>
/// Return a failure message.
/// </summary>
/// <param name="method">origin of the failure message</param>
/// <param name="e">exception causing the failure message</param>
/// <remarks>This should probably set a return code as
/// well. (?)</remarks>
public string Failure(IOSHttpResponse response, OSHttpStatusCode status,
string method, Exception e)
{
string m = String.Format("exception occurred: {0}", e.Message);
response.StatusCode = (int) status;
response.StatusDescription = m;
m_log.DebugFormat("{0} {1} failed: {2}", MsgID, method, e.ToString());
m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, e.Message);
return String.Format("<error>{0}</error>", e.Message);
}
#endregion methods
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.IO;
using System.Text;
using System.Xml;
namespace OpenSim.ApplicationPlugins.Rest
{
public class RestXmlWriter: XmlTextWriter
{
private StringWriter m_sw = null;
public RestXmlWriter(StringWriter sw) : base(sw)
{
m_sw = sw;
Formatting = Formatting.Indented;
}
public RestXmlWriter(TextWriter textWriter) : base(textWriter)
{
}
public RestXmlWriter(Stream stream)
: this(stream, Encoding.UTF8)
{
}
public RestXmlWriter(Stream stream, Encoding enc) : base(stream, enc)
{
}
public override void WriteStartDocument()
{
}
public override void WriteStartDocument(bool standalone)
{
}
public override string ToString()
{
Flush();
Close();
return m_sw.ToString();
}
}
}

View File

@@ -0,0 +1,276 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Open Simulator Export/Import XML schema
August 2008
</xsd:documentation>
</xsd:annotation>
<!-- WARNING!!!
This is currently a draft, it does not reflect
what is exported, nor what will be understood
on import. It is included as a working document
and this comment will be removed at such time as
the schema corresponds to reality.
-->
<!--
REST-related information
Inventory data is always framed by an
inventory element. Consists of zero or
more elements representing either folders
or items within those folders. The inventory
element represents the "real" root folder.
-->
<xsd:element name="inventory" type="inventory_ct" />
<!--
The inventory complex type is just an arbitrary
sequence of folders and items. In reality it is
typically just folders. Both item and folder
have corresponding complex types. It is distinct
from folders insofar as it has no other defining
attributes.
-->
<xsd:complexType name="inventory_ct">
<xsd:element name="folder" type="folder_ct" maxOccurs="unbounded"/>
<xsd:element name="item" type="item_ct" maxOccurs="unbounded" />
</xsd:complexType>
<xsd:complexType name="folder_ct">
<xsd:attribute name="UUID" type="uuid_st" />
<xsd:attribute name="name" type="name_st" />
<xsd:attribute name="type" type="folder_type_st" />
<xsd:attribute name="description" type="xsd:string" /> <!-- added -->
<xsd:attribute name="version" type="unsignedShort" />
<xsd:attribute name="owner" type="uuid_st" />
<xsd:attribute name="creator" type="uuid_st" /> <!-- added -->
<xsd:attribute name="creationdate" type="date_st" /> <!-- added -->
<xsd:attribute name="parent" type="uuid_st" />
<xsd:element name="permissions" type="permissions_ct" maxOccurs="unbounded" /> <!-- added -->
<xsd:element name="folder" type="folder_ct" maxOccurs="unbounded" />
<xsd:element name="item" type="item_ct" maxOccurs="unbounded" />
</xsd:complexType>
<xsd:complexType name="item_ct">
<xsd:attribute name="UUID" type="uuid_st" />
<xsd:attribute name="name" type="name_st" />
<xsd:attribute name="type" type="inventory_type_st" />
<xsd:attribute name="description" type="xsd:string" />
<xsd:attribute name="version" type="unsignedShort" /> <!-- added -->
<xsd:attribute name="owner" type="uuid_st" />
<xsd:attribute name="creator" type="uuid_st" />
<xsd:attribute name="creationdate" type="date_st" />
<xsd:attribute name="folder" type="uuid_st" />
<xsd:attribute name="groupid" type="uuid_st" />
<xsd:attribute name="groupowned" type="xsd:boolean" />
<xsd:attribute name="saletype" type="sale_st" />
<xsd:attribute name="saleprice" type="xsd:decimal" />
<xsd:element name="permissions" type="permissions_ct" maxOccurs="unbounded" />
</xsd:complexType>
<xsd:complexType name="asset_ct">
<xsd:attribute name="UUID" type="uuid_st" />
<xsd:attribute name="name" type="name_st" />
<xsd:attribute name="type" type="asset_type_st" />
<xsd:attribute name="description" type="xsd:string" />
<xsd:attribute name="version" type="unsignedShort" /> <!-- added -->
<xsd:attribute name="owner" type="uuid_st" />
<xsd:attribute name="creator" type="uuid_st" />
<xsd:attribute name="creationdate" type="date_st" />
<xsd:attribute name="temporary" type="xsd:boolean" />
<xsd:attribute name="local" type="xsd:boolean" />
<xsd:attribute name="inline" type="xsd:boolean" />
</xsd:complexType>
<!-- Constrained Simple Data types -->
<!--
We need to specify name as a simple type because on
some platforms it is constrained by a certain length
limitation. For completeness we indicate that whitespace
should be preserved exactly as specified.
-->
<xsd:simpleType name="name_st">
<xsd:restriction base="xsd:string">
<whiteSpace value="preserve" />
<minLength value="0" />
<maxLength value="64" />
</xsd:restriction>
</xsd:simpleType>
<!--
Type information in the folder is meant to indicate
the preferred asset type for this folder. As such, that
currently corresponds to the type values allowed for
assets, however that is not mandated, so for
now at least I'll represent this as a distinct
enumeration.
This seems inappropriate; it seems like the folder's
content should reflect the InventoryType classifications
rather than the asset types.
-->
<xsd:simpleType name="folder_type_st">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Texture" />
<xsd:enumeration value="Sound" />
<xsd:enumeration value="CallingCard" />
<xsd:enumeration value="Landmark" />
<xsd:enumeration value="Script" />
<xsd:enumeration value="Clothing" />
<xsd:enumeration value="Object" />
<xsd:enumeration value="Notecard" />
<xsd:enumeration value="LSLText" />
<xsd:enumeration value="LSLByteCode" />
<xsd:enumeration value="TextureTGA" />
<xsd:enumeration value="BodyPart" />
<xsd:enumeration value="SoundWAV" />
<xsd:enumeration value="ImageTGA" />
<xsd:enumeration value="ImageJPEG" />
<xsd:enumeration value="Animation" />
<xsd:enumeration value="Gesture" />
<xsd:enumeration value="Simstate" />
<xsd:enumeration value="Unknown" />
<xsd:enumeration value="LostAndFoundFolder" />
<xsd:enumeration value="SnapshotFolder" />
<xsd:enumeration value="TrashFolder" />
<xsd:enumeration value="Folder" />
<xsd:enumeration value="RootFolder" />
</xsd:restriction>
</xsd:simpleType>
<!--
Inventory item type designates an asset class, rather
than a specific asset type. For example, "SnapShot"
might include a number of asset types such as JPEG,
TGA, etc.. This is not a consistent interpretation,
classifications such as LostAndFound are meta-types
relative to asset classes.
These types should be abstract and not be tied to a
specific platform. A world's import facility should be
responsible for mapping these to meaningful internal
representations.
These types were based on information in:
libsecondlife/InventoryManager.cs
-->
<xsd:simpleType name="inventory_type_st">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Texture" />
<xsd:enumeration value="Sound" />
<xsd:enumeration value="CallingCard" />
<xsd:enumeration value="Landmark" />
<xsd:enumeration value="Script" />
<xsd:enumeration value="Clothing" />
<xsd:enumeration value="Object" />
<xsd:enumeration value="Notecard" />
<xsd:enumeration value="LSL" />
<xsd:enumeration value="LSLBytecode" />
<xsd:enumeration value="TextureTGA" />
<xsd:enumeration value="BodyPart" />
<xsd:enumeration value="Snapshot" />
<xsd:enumeration value="Attachment" />
<xsd:enumeration value="Wearable" />
<xsd:enumeration value="Animation" />
<xsd:enumeration value="Gesture" />
<xsd:enumeration value="Folder" />
<xsd:enumeration value="Unknown" />
<xsd:enumeration value="LostAndFound" />
<xsd:enumeration value="Trash" />
<xsd:enumeration value="Root" />
</xsd:restriction>
</xsd:simpleType>
<!--
The asset types seem to be even more disarrayed than
the inventory types. It seems to be little more than
a reiteration of the inventory type information,
which adds little or nothing to the overall data
model.
Of course, given that these are drawn from the
libsecondlife definitions, we aren't at liberty to
simply redefine them in place. But the XML definitions
here could be made more useful.
These types were based on information in:
libsecondlife/AssetManager.cs
-->
<xsd:simpleType name="asset_type_st">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Texture" />
<xsd:enumeration value="Sound" />
<xsd:enumeration value="CallingCard" />
<xsd:enumeration value="Landmark" />
<xsd:enumeration value="Script" />
<xsd:enumeration value="Clothing" />
<xsd:enumeration value="Object" />
<xsd:enumeration value="Notecard" />
<xsd:enumeration value="LSLText" />
<xsd:enumeration value="LSLByteCode" />
<xsd:enumeration value="TextureTGA" />
<xsd:enumeration value="BodyPart" />
<xsd:enumeration value="SoundWAV" />
<xsd:enumeration value="ImageTGA" />
<xsd:enumeration value="ImageJPEG" />
<xsd:enumeration value="Animation" />
<xsd:enumeration value="Gesture" />
<xsd:enumeration value="Simstate" />
<xsd:enumeration value="Unknown" />
<xsd:enumeration value="LostAndFoundFolder" />
<xsd:enumeration value="SnapshotFolder" />
<xsd:enumeration value="TrashFolder" />
<xsd:enumeration value="Folder" />
<xsd:enumeration value="RootFolder" />
</xsd:restriction>
</xsd:simpleType>
<!-- This is describing the apparent form of a UUID. If
we ever want a more metaphysical definition we'll
need to add to it.
-->
<xsd:simpleType name="uuid_st">
<xsd:restriction base="xsd:string">
<xsd:pattern value="[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"/>
</xsd:restriction>
</xsd:simpleType>
<!-- This constrains the date representation. Currently
it is simply an integer representing the elapsed
?? since ??.
-->
<xsd:simpleType name="date_st">
<xsd:restriction base="xsd:positiveInteger">
</xsd:restriction>
</xsd:simpleType>
<!-- This constrains the representation of sale price.
Currently it is a simple decimal with no unit
specified.
Issues: interoperability.
-->
<xsd:simpleType name="sale_st">
<xsd:restriction base="xsd:decimal">
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -28,14 +28,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Reflection;
using System.Threading;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
// using OpenSim.Region.Framework.Interfaces;
@@ -48,28 +48,26 @@ namespace OpenSim.Framework.Capabilities
/// </summary>
public delegate IClientAPI GetClientDelegate(UUID agentID);
public class Caps : IDisposable
public class Caps
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// private static readonly ILog m_log =
// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly string m_httpListenerHostName;
private readonly uint m_httpListenPort;
private string m_httpListenerHostName;
private uint m_httpListenPort;
/// <summary>
/// This is the uuid portion of every CAPS path. It is used to make capability urls private to the requester.
/// </summary>
private readonly string m_capsObjectPath;
private string m_capsObjectPath;
public string CapsObjectPath { get { return m_capsObjectPath; } }
private readonly CapsHandlers m_capsHandlers;
private readonly ConcurrentDictionary<string, PollServiceEventArgs> m_pollServiceHandlers = new ();
private readonly Dictionary<string, string> m_externalCapsHandlers = new ();
private CapsHandlers m_capsHandlers;
private Dictionary<string, string> m_externalCapsHandlers;
private readonly IHttpServer m_httpListener;
private readonly UUID m_agentID;
private readonly string m_regionName;
private ManualResetEvent m_capsActive = new(false);
private readonly string m_baseCapsURL;
private IHttpServer m_httpListener;
private UUID m_agentID;
private string m_regionName;
public UUID AgentID
{
@@ -116,22 +114,6 @@ namespace OpenSim.Framework.Capabilities
get { return m_externalCapsHandlers; }
}
[Flags]
public enum CapsFlags: uint
{
None = 0,
SentSeeds = 1,
ObjectAnim = 0x100,
WLEnv = 0x200,
AdvEnv = 0x400,
PBR = 0x800,
ViewerBenefits = 0x1000,
TPBR = 0x2000
}
public CapsFlags Flags { get; set;}
public Caps(IHttpServer httpServer, string httpListen, uint httpPort, string capsPath,
UUID agent, string regionName)
{
@@ -141,7 +123,7 @@ namespace OpenSim.Framework.Capabilities
m_httpListenPort = httpPort;
if (httpServer is not null && httpServer.UseSSL)
if (httpServer != null && httpServer.UseSSL)
{
m_httpListenPort = httpServer.SSLPort;
httpListen = httpServer.SSLCommonName;
@@ -149,41 +131,9 @@ namespace OpenSim.Framework.Capabilities
}
m_agentID = agent;
m_capsHandlers = new CapsHandlers(httpServer, httpListen, httpPort);
m_capsHandlers = new CapsHandlers(httpServer, httpListen, httpPort, (httpServer == null) ? false : httpServer.UseSSL);
m_externalCapsHandlers = new Dictionary<string, string>();
m_regionName = regionName;
Flags = CapsFlags.None;
m_capsActive.Reset();
if (MainServer.Instance.UseSSL)
m_baseCapsURL = $"https://{MainServer.Instance.SSLCommonName}:{MainServer.Instance.SSLPort}";
else
{
if (MainServer.Instance is null)
m_baseCapsURL = $"http://{m_httpListenerHostName}:0";
else
m_baseCapsURL = $"http://{m_httpListenerHostName}:{MainServer.Instance.Port}";
}
}
~Caps()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
Flags = CapsFlags.None;
if (m_capsActive != null)
{
DeregisterHandlers();
m_capsActive.Dispose();
m_capsActive = null;
}
}
/// <summary>
@@ -193,45 +143,8 @@ namespace OpenSim.Framework.Capabilities
/// <param name="handler"></param>
public void RegisterHandler(string capName, IRequestHandler handler)
{
//m_log.DebugFormat("[CAPS]: Registering handler for \"{0}\": path {1}", capName, handler.Path);
m_capsHandlers[capName] = handler;
}
public void RegisterSimpleHandler(string capName, ISimpleStreamHandler handler, bool addToListener = true)
{
//m_log.DebugFormat("[CAPS]: Registering handler for \"{0}\": path {1}", capName, handler.Path);
m_capsHandlers.AddSimpleHandler(capName, handler, addToListener);
}
public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler)
{
//m_log.DebugFormat(
// "[CAPS]: Registering handler with name {0}, url {1} for {2}",
// capName, pollServiceHandler.Url, m_agentID, m_regionName);
if(!m_pollServiceHandlers.TryAdd(capName, pollServiceHandler))
{
m_log.ErrorFormat(
"[CAPS]: Handler with name {0} already registered (ulr {1}, agent {2}, region {3}",
capName, pollServiceHandler.Url, m_agentID, m_regionName);
return;
}
m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler);
//uint port = (MainServer.Instance == null) ? 0 : MainServer.Instance.Port;
//string protocol = "http";
//string hostName = m_httpListenerHostName;
//if (MainServer.Instance.UseSSL)
//{
// hostName = MainServer.Instance.SSLCommonName;
// port = MainServer.Instance.SSLPort;
// protocol = "https";
//}
//RegisterHandler(
// capName, String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, pollServiceHandler.Url));
}
/// <summary>
@@ -250,101 +163,13 @@ namespace OpenSim.Framework.Capabilities
/// </summary>
public void DeregisterHandlers()
{
foreach (string capsName in m_capsHandlers.Caps)
if (m_capsHandlers != null)
{
m_capsHandlers.Remove(capsName);
}
foreach (PollServiceEventArgs handler in m_pollServiceHandlers.Values)
{
m_httpListener.RemovePollServiceHTTPHandler(handler.Url);
}
m_pollServiceHandlers.Clear();
}
public bool TryGetPollHandler(string name, out PollServiceEventArgs pollHandler)
{
return m_pollServiceHandlers.TryGetValue(name, out pollHandler);
}
public Dictionary<string, PollServiceEventArgs> GetPollHandlers()
{
return new Dictionary<string, PollServiceEventArgs>(m_pollServiceHandlers);
}
/// <summary>
/// Return an LLSD-serializable Hashtable describing the
/// capabilities and their handler details.
/// </summary>
/// <param name="excludeSeed">If true, then exclude the seed cap.</param>
public Hashtable GetCapsDetails(bool excludeSeed, List<string> requestedCaps)
{
Hashtable caps = CapsHandlers.GetCapsDetails(excludeSeed, requestedCaps);
lock (m_pollServiceHandlers)
{
foreach (KeyValuePair<string, PollServiceEventArgs> kvp in m_pollServiceHandlers)
foreach (string capsName in m_capsHandlers.Caps)
{
if (!requestedCaps.Contains(kvp.Key))
continue;
string hostName = m_httpListenerHostName;
uint port = (MainServer.Instance is null) ? 0 : MainServer.Instance.Port;
string protocol = "http";
if (MainServer.Instance.UseSSL)
{
hostName = MainServer.Instance.SSLCommonName;
port = MainServer.Instance.SSLPort;
protocol = "https";
}
caps[kvp.Key] = string.Format("{0}://{1}:{2}{3}", protocol, hostName, port, kvp.Value.Url);
m_capsHandlers.Remove(capsName);
}
}
// Add the external too
foreach (KeyValuePair<string, string> kvp in ExternalCapsHandlers)
{
if (!requestedCaps.Contains(kvp.Key))
continue;
caps[kvp.Key] = kvp.Value;
}
return caps;
}
public void GetCapsDetailsLLSDxml(HashSet<string> requestedCaps, osUTF8 sb)
{
CapsHandlers.GetCapsDetailsLLSDxml(requestedCaps, sb);
lock (m_pollServiceHandlers)
{
foreach (KeyValuePair<string, PollServiceEventArgs> kvp in m_pollServiceHandlers)
{
if (requestedCaps.Contains(kvp.Key))
LLSDxmlEncode2.AddElem(kvp.Key, m_baseCapsURL + kvp.Value.Url, sb);
}
}
// Add the external too
foreach (KeyValuePair<string, string> kvp in ExternalCapsHandlers)
{
if (requestedCaps.Contains(kvp.Key))
LLSDxmlEncode2.AddElem(kvp.Key, kvp.Value, sb);
}
}
public void Activate()
{
m_capsActive.Set();
}
public bool WaitForActivation()
{
// Wait for 30s. If that elapses, return false and run without caps
return m_capsActive.WaitOne(120000);
}
}
}
}

View File

@@ -27,11 +27,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
namespace OpenSim.Framework.Capabilities
{
@@ -42,13 +39,11 @@ namespace OpenSim.Framework.Capabilities
/// </summary>
public class CapsHandlers
{
private readonly Dictionary<string, IRequestHandler> m_capsHandlers = new Dictionary<string, IRequestHandler>();
private readonly ConcurrentDictionary<string, ISimpleStreamHandler> m_capsSimpleHandlers = new ConcurrentDictionary<string, ISimpleStreamHandler>();
private Dictionary <string, IRequestHandler> m_capsHandlers = new Dictionary<string, IRequestHandler>();
private IHttpServer m_httpListener;
private string m_httpListenerHostName;
private uint m_httpListenerPort;
public readonly string BaseURL;
private bool m_useSSL = false;
/// <summary></summary>
/// CapsHandlers is a cap handler container but also takes
@@ -58,15 +53,31 @@ namespace OpenSim.Framework.Capabilities
/// <param name="httpListener">base HTTP server</param>
/// <param name="httpListenerHostname">host name of the HTTP server</param>
/// <param name="httpListenerPort">HTTP port</param>
public CapsHandlers(IHttpServer httpListener, string httpListenerHostname, uint httpListenerPort)
public CapsHandlers(BaseHttpServer httpListener, string httpListenerHostname, uint httpListenerPort)
: this(httpListener,httpListenerHostname,httpListenerPort, false)
{
}
/// <summary></summary>
/// CapsHandlers is a cap handler container but also takes
/// care of adding and removing cap handlers to and from the
/// supplied BaseHttpServer.
/// </summary>
/// <param name="httpListener">base HTTP server</param>
/// <param name="httpListenerHostname">host name of the HTTP
/// server</param>
/// <param name="httpListenerPort">HTTP port</param>
public CapsHandlers(IHttpServer httpListener, string httpListenerHostname, uint httpListenerPort, bool https)
{
m_httpListener = httpListener;
m_httpListenerHostName = httpListenerHostname;
m_httpListenerPort = httpListenerPort;
if (httpListener != null && httpListener.UseSSL)
BaseURL = $"https://{m_httpListenerHostName}:{m_httpListenerPort}";
else
BaseURL = $"http://{m_httpListenerHostName}:{m_httpListenerPort}";
m_useSSL = https;
if (httpListener != null && m_useSSL)
{
m_httpListenerHostName = httpListener.SSLCommonName;
m_httpListenerPort = httpListener.SSLPort;
}
}
/// <summary>
@@ -78,35 +89,16 @@ namespace OpenSim.Framework.Capabilities
{
lock (m_capsHandlers)
{
if(m_capsHandlers.ContainsKey(capsName))
{
m_httpListener.RemoveStreamHandler("POST", m_capsHandlers[capsName].Path);
m_httpListener.RemoveStreamHandler("PUT", m_capsHandlers[capsName].Path);
m_httpListener.RemoveStreamHandler("GET", m_capsHandlers[capsName].Path);
m_httpListener.RemoveStreamHandler("DELETE", m_capsHandlers[capsName].Path);
m_capsHandlers.Remove(capsName);
}
m_httpListener.RemoveStreamHandler("POST", m_capsHandlers[capsName].Path);
m_httpListener.RemoveStreamHandler("GET", m_capsHandlers[capsName].Path);
m_capsHandlers.Remove(capsName);
}
if(m_capsSimpleHandlers.TryRemove(capsName, out ISimpleStreamHandler hdr))
{
m_httpListener.RemoveSimpleStreamHandler(hdr.Path);
}
}
public void AddSimpleHandler(string capName, ISimpleStreamHandler handler, bool addToListener = true)
{
if(ContainsCap(capName))
Remove(capName);
if(m_capsSimpleHandlers.TryAdd(capName, handler) && addToListener)
m_httpListener.AddSimpleStreamHandler(handler);
}
public bool ContainsCap(string cap)
{
lock (m_capsHandlers)
if (m_capsHandlers.ContainsKey(cap))
return true;
return m_capsSimpleHandlers.ContainsKey(cap);
return m_capsHandlers.ContainsKey(cap);
}
/// <summary>
@@ -133,14 +125,11 @@ namespace OpenSim.Framework.Capabilities
if (m_capsHandlers.ContainsKey(idx))
{
m_httpListener.RemoveStreamHandler("POST", m_capsHandlers[idx].Path);
m_httpListener.RemoveStreamHandler("PUT", m_capsHandlers[idx].Path);
m_httpListener.RemoveStreamHandler("GET", m_capsHandlers[idx].Path);
m_httpListener.RemoveStreamHandler("DELETE", m_capsHandlers[idx].Path);
m_capsHandlers.Remove(idx);
}
if (null == value) return;
m_capsHandlers[idx] = value;
m_httpListener.AddStreamHandler(value);
}
@@ -157,9 +146,8 @@ namespace OpenSim.Framework.Capabilities
{
lock (m_capsHandlers)
{
string[] __keys = new string[m_capsHandlers.Keys.Count + m_capsSimpleHandlers.Keys.Count];
string[] __keys = new string[m_capsHandlers.Keys.Count];
m_capsHandlers.Keys.CopyTo(__keys, 0);
m_capsSimpleHandlers.Keys.CopyTo(__keys, m_capsHandlers.Keys.Count);
return __keys;
}
}
@@ -170,85 +158,28 @@ namespace OpenSim.Framework.Capabilities
/// capabilities and their handler details.
/// </summary>
/// <param name="excludeSeed">If true, then exclude the seed cap.</param>
public Hashtable GetCapsDetails(bool excludeSeed, List<string> requestedCaps)
public Hashtable GetCapsDetails(bool excludeSeed)
{
Hashtable caps = new Hashtable();
string protocol = "http://";
if (m_useSSL)
protocol = "https://";
if (requestedCaps == null)
{
lock (m_capsHandlers)
{
foreach (KeyValuePair<string, ISimpleStreamHandler> kvp in m_capsSimpleHandlers)
caps[kvp.Key] = BaseURL + kvp.Value.Path;
foreach (KeyValuePair<string, IRequestHandler> kvp in m_capsHandlers)
caps[kvp.Key] = BaseURL + kvp.Value.Path;
}
return caps;
}
string baseUrl = protocol + m_httpListenerHostName + ":" + m_httpListenerPort.ToString();
lock (m_capsHandlers)
{
for (int i = 0; i < requestedCaps.Count; ++i)
foreach (string capsName in m_capsHandlers.Keys)
{
string capsName = requestedCaps[i];
if (excludeSeed && "SEED" == capsName)
continue;
if (m_capsSimpleHandlers.TryGetValue(capsName, out ISimpleStreamHandler shdr))
{
caps[capsName] = BaseURL + shdr.Path;
continue;
}
if (m_capsHandlers.TryGetValue(capsName, out IRequestHandler chdr))
{
caps[capsName] = BaseURL + chdr.Path;
}
caps[capsName] = baseUrl + m_capsHandlers[capsName].Path;
}
}
return caps;
}
public Dictionary<string, string> GetCapsLocalPaths()
{
Dictionary<string, string> caps = new();
lock (m_capsHandlers)
{
foreach (KeyValuePair<string, ISimpleStreamHandler> kvp in m_capsSimpleHandlers)
caps[kvp.Key] = kvp.Value.Path;
foreach (KeyValuePair<string, IRequestHandler> kvp in m_capsHandlers)
caps[kvp.Key] = kvp.Value.Path;
}
return caps;
}
public void GetCapsDetailsLLSDxml(HashSet<string> requestedCaps, osUTF8 sb)
{
lock (m_capsHandlers)
{
if (requestedCaps is null)
return;
foreach (string capsName in requestedCaps)
{
if (m_capsSimpleHandlers.TryGetValue(capsName, out ISimpleStreamHandler shdr))
LLSDxmlEncode2.AddElem(capsName, BaseURL + shdr.Path, sb);
else if (m_capsHandlers.TryGetValue(capsName, out IRequestHandler chdr))
LLSDxmlEncode2.AddElem(capsName, BaseURL + chdr.Path, sb);
}
}
}
/// <summary>
/// Returns a copy of the dictionary of all the HTTP cap handlers
/// </summary>
/// <returns>
/// The dictionary copy. The key is the capability name, the value is the HTTP handler.
/// </returns>
public Dictionary<string, IRequestHandler> GetCapsHandlers()
{
lock (m_capsHandlers)
return new Dictionary<string, IRequestHandler>(m_capsHandlers);
}
}
}

View File

@@ -1,472 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Capabilities;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
namespace OpenSim.Capabilities.Handlers
{
public class FetchInvDescHandler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly byte[] EmptyResponse = Util.UTF8NBGetbytes("<llsd><map><key>folders</key><array /></map></llsd>");
private readonly IInventoryService m_InventoryService;
private readonly ILibraryService m_LibraryService;
private readonly UUID libOwner;
private readonly IScene m_Scene;
public FetchInvDescHandler(IInventoryService invService, ILibraryService libService, IScene s)
{
m_InventoryService = invService;
if(libService != null && libService.LibraryRootFolder != null)
{
m_LibraryService = libService;
libOwner = libService.LibraryRootFolder.Owner;
}
m_Scene = s;
}
public void FetchInventoryDescendentsRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, ExpiringKey<UUID> BadRequests)
{
//m_log.DebugFormat("[XXX]: FetchInventoryDescendentsRequest in {0}, {1}", (m_Scene == null) ? "none" : m_Scene.Name, request);
List<LLSDFetchInventoryDescendents> folders;
List<UUID> bad_folders = new();
try
{
OSDArray foldersrequested = null;
OSD tmp = OSDParser.DeserializeLLSDXml(httpRequest.InputStream);
httpRequest.InputStream.Dispose();
OSDMap map = (OSDMap)tmp;
if(map.TryGetValue("folders", out tmp) && tmp is OSDArray frtmp)
foldersrequested = frtmp;
if (foldersrequested is null || foldersrequested.Count == 0)
{
httpResponse.RawBuffer = EmptyResponse;
return;
}
folders = new List<LLSDFetchInventoryDescendents>(foldersrequested.Count);
for (int i = 0; i < foldersrequested.Count; i++)
{
OSDMap mfolder = foldersrequested[i] as OSDMap;
UUID id = mfolder["folder_id"].AsUUID();
if(BadRequests.ContainsKey(id))
{
bad_folders.Add(id);
}
else
{
LLSDFetchInventoryDescendents llsdRequest = new();
try
{
llsdRequest.folder_id = id;
llsdRequest.owner_id = mfolder["owner_id"].AsUUID();
llsdRequest.sort_order = mfolder["sort_order"].AsInteger();
llsdRequest.fetch_folders = mfolder["fetch_folders"].AsBoolean();
llsdRequest.fetch_items = mfolder["fetch_items"].AsBoolean();
}
catch (Exception e)
{
m_log.Debug("[WEB FETCH INV DESC HANDLER]: caught exception doing OSD deserialize" + e.Message);
continue;
}
folders.Add(llsdRequest);
}
}
foldersrequested = null;
map = null;
}
catch (Exception e)
{
m_log.Error("[FETCH INV DESC]: fail parsing request: " + e.Message);
httpResponse.RawBuffer = EmptyResponse;
return;
}
if (folders is null || folders.Count == 0)
{
if(bad_folders.Count == 0)
{
httpResponse.RawBuffer = EmptyResponse;
return;
}
osUTF8 osu = OSUTF8Cached.Acquire();
osu.AppendASCII("[WEB FETCH INV DESC HANDLER]: Unable to fetch folders owned by Unknown user:");
int limit = 5;
int count = 0;
foreach (UUID bad in bad_folders)
{
if (BadRequests.ContainsKey(bad))
continue;
osu.Append((byte)' ');
osu.AppendASCII(bad.ToString());
++count;
if (--limit < 0)
break;
}
if(count > 0)
{
if (limit < 0)
osu.AppendASCII(" ...");
m_log.Warn(osu.ToString());
}
osu.Clear();
osu.AppendASCII("<llsd><map><key>folders</key><array /></map><map><key>bad_folders</key><array>");
foreach (UUID bad in bad_folders)
{
osu.AppendASCII("<map><key>folder_id</key><uuid>");
osu.AppendASCII(bad.ToString());
osu.AppendASCII("</uuid><key>error</key><string>Unknown</string></map>");
}
osu.AppendASCII("</array></map></llsd>");
httpResponse.RawBuffer = OSUTF8Cached.GetArrayAndRelease(osu);
return;
}
UUID requester = folders[0].owner_id;
List<InventoryCollection> invcollSet = Fetch(folders, bad_folders);
//m_log.DebugFormat("[XXX]: Got {0} folders from a request of {1}", invcollSet.Count, folders.Count);
int invcollSetCount = 0;
if (invcollSet is not null)
invcollSetCount = invcollSet.Count;
osUTF8 lastresponse = LLSDxmlEncode2.Start();
if (invcollSetCount > 0)
{
lastresponse.AppendASCII("<map><key>folders</key><array>");
int i = 0;
InventoryCollection thiscoll;
for (i = 0; i < invcollSetCount; i++)
{
thiscoll = invcollSet[i];
invcollSet[i] = null;
LLSDxmlEncode2.AddMap(lastresponse);
LLSDxmlEncode2.AddElem_folder_id(thiscoll.FolderID, lastresponse);
LLSDxmlEncode2.AddElem_agent_id(thiscoll.OwnerID, lastresponse);
LLSDxmlEncode2.AddElem_owner_id(thiscoll.OwnerID, lastresponse);
LLSDxmlEncode2.AddElem("descendents", thiscoll.Descendents, lastresponse);
LLSDxmlEncode2.AddElem_version(thiscoll.Version, lastresponse);
if (thiscoll.Folders is null || thiscoll.Folders.Count == 0)
LLSDxmlEncode2.AddEmptyArray("categories", lastresponse);
else
{
LLSDxmlEncode2.AddArray("categories", lastresponse);
foreach (InventoryFolderBase invFolder in thiscoll.Folders)
{
LLSDxmlEncode2.AddMap(lastresponse);
LLSDxmlEncode2.AddElem_category_id(invFolder.ID, lastresponse);
LLSDxmlEncode2.AddElem_parent_id(invFolder.ParentID, lastresponse);
LLSDxmlEncode2.AddElem_name(invFolder.Name, lastresponse);
LLSDxmlEncode2.AddElem("type_default", invFolder.Type, lastresponse);
LLSDxmlEncode2.AddElem_version( invFolder.Version, lastresponse);
LLSDxmlEncode2.AddEndMap(lastresponse);
}
LLSDxmlEncode2.AddEndArray(lastresponse);
}
if (thiscoll.Items is null || thiscoll.Items.Count == 0)
LLSDxmlEncode2.AddEmptyArray("items", lastresponse);
else
{
LLSDxmlEncode2.AddArray("items", lastresponse);
foreach (InventoryItemBase invItem in thiscoll.Items)
{
invItem.ToLLSDxml(lastresponse);
}
LLSDxmlEncode2.AddEndArray(lastresponse);
}
LLSDxmlEncode2.AddEndMap(lastresponse);
invcollSet[i] = null;
}
LLSDxmlEncode2.AddEndArrayAndMap(lastresponse);
}
else
{
lastresponse.AppendASCII("<map><key>folders</key><array /></map>");
}
if (bad_folders.Count > 0)
{
lastresponse.AppendASCII("<map><key>bad_folders</key><array>");
foreach (UUID bad in bad_folders)
{
BadRequests.Add(bad);
lastresponse.AppendASCII("<map><key>folder_id</key><uuid>");
lastresponse.AppendASCII(bad.ToString());
lastresponse.AppendASCII("</uuid><key>error</key><string>Unknown</string></map>");
}
lastresponse.AppendASCII("</array></map>");
StringBuilder sb = osStringBuilderCache.Acquire();
sb.Append("[WEB FETCH INV DESC HANDLER]: Unable to fetch folders owned by ");
sb.Append(requester.ToString());
sb.Append(" :");
int limit = 9;
foreach (UUID bad in bad_folders)
{
sb.Append(' ');
sb.Append(bad.ToString());
if(--limit < 0)
break;
}
if(limit < 0)
sb.Append(" ...");
m_log.Warn(osStringBuilderCache.GetStringAndRelease(sb));
}
httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(lastresponse);
}
private void AddLibraryFolders(List<LLSDFetchInventoryDescendents> libFolders, List<InventoryCollection> result)
{
InventoryFolderImpl fold;
if (m_LibraryService is null || m_LibraryService.LibraryRootFolder is null)
return;
foreach (LLSDFetchInventoryDescendents f in libFolders)
{
if ((fold = m_LibraryService.LibraryRootFolder.FindFolder(f.folder_id)) is not null)
{
InventoryCollection Collection = new();
//ret.Collection.Folders = new List<InventoryFolderBase>();
Collection.Folders = fold.RequestListOfFolders();
Collection.Items = fold.RequestListOfItems();
Collection.OwnerID = m_LibraryService.LibraryRootFolder.Owner;
Collection.FolderID = f.folder_id;
Collection.Version = fold.Version;
Collection.Descendents = Collection.Items.Count + Collection.Folders.Count;
result.Add(Collection);
//m_log.DebugFormat("[XXX]: Added libfolder {0} ({1}) {2}", ret.Collection.FolderID, ret.Collection.OwnerID);
}
}
}
private List<InventoryCollection> Fetch(List<LLSDFetchInventoryDescendents> fetchFolders, List<UUID> bad_folders)
{
//m_log.DebugFormat(
// "[WEB FETCH INV DESC HANDLER]: Fetching {0} folders for owner {1}", fetchFolders.Count, fetchFolders[0].owner_id);
// FIXME MAYBE: We're not handling sortOrder!
List<InventoryCollection> result = new(32);
List<LLSDFetchInventoryDescendents> libFolders = new(32);
List<LLSDFetchInventoryDescendents> otherFolders = new(32);
HashSet<UUID> libIDs = new();
HashSet<UUID> otherIDs = new();
bool dolib = m_LibraryService != null;
// Filter folder Zero right here. Some viewers (Firestorm) send request for folder Zero, which doesn't make sense
// and can kill the sim (all root folders have parent_id Zero)
// send something.
bool doneZeroID = false;
foreach(LLSDFetchInventoryDescendents f in fetchFolders)
{
if (f.folder_id.IsZero())
{
if(doneZeroID)
continue;
doneZeroID = true;
InventoryCollection Collection = new InventoryCollection();
Collection.OwnerID = f.owner_id;
Collection.Version = 0;
Collection.FolderID = f.folder_id;
Collection.Descendents = 0;
result.Add(Collection);
continue;
}
if(dolib && f.owner_id.Equals(libOwner))
{
if(libIDs.Contains(f.folder_id))
continue;
libIDs.Add(f.folder_id);
libFolders.Add(f);
continue;
}
if(otherIDs.Contains(f.folder_id))
continue;
otherIDs.Add(f.folder_id);
otherFolders.Add(f);
}
fetchFolders.Clear();
if(otherFolders.Count > 0)
{
//m_log.DebugFormat("[XXX]: {0}", string.Join(",", fids));
InventoryCollection[] fetchedContents = m_InventoryService.GetMultipleFoldersContent(otherFolders[0].owner_id, otherIDs.ToArray());
if (fetchedContents is null)
return null;
if (fetchedContents.Length == 0)
{
foreach (LLSDFetchInventoryDescendents freq in otherFolders)
BadFolder(freq, null, bad_folders);
}
else
{
int i = 0;
// Do some post-processing. May need to fetch more from inv server for links
foreach (InventoryCollection contents in fetchedContents)
{
// Find the original request
LLSDFetchInventoryDescendents freq = otherFolders[i];
otherFolders[i]=null;
i++;
if (BadFolder(freq, contents, bad_folders))
continue;
if(!freq.fetch_folders)
contents.Folders.Clear();
if(!freq.fetch_items)
contents.Items.Clear();
contents.Descendents = contents.Items.Count + contents.Folders.Count;
// Next: link management
ProcessLinks(freq, contents);
result.Add(contents);
}
}
}
if(libFolders.Count > 0)
AddLibraryFolders(libFolders, result);
return result;
}
private bool BadFolder(LLSDFetchInventoryDescendents freq, InventoryCollection contents, List<UUID> bad_folders)
{
if (contents is null)
{
bad_folders.Add(freq.folder_id);
return true;
}
// The inventory server isn't sending FolderID in the collection...
// Must fetch it individually
if (contents.FolderID.IsZero())
{
InventoryFolderBase containingFolder = m_InventoryService.GetFolder(freq.owner_id, freq.folder_id);
if (containingFolder is null)
{
bad_folders.Add(freq.folder_id);
return true;
}
contents.FolderID = containingFolder.ID;
contents.OwnerID = containingFolder.Owner;
contents.Version = containingFolder.Version;
}
return false;
}
private void ProcessLinks(LLSDFetchInventoryDescendents freq, InventoryCollection contents)
{
if (contents.Items is null || contents.Items.Count == 0)
return;
// viewers are lasy and want a copy of the linked item sent before the link to it
// look for item links
List<UUID> itemIDs = new();
foreach (InventoryItemBase item in contents.Items)
{
//m_log.DebugFormat("[XXX]: {0} {1}", item.Name, item.AssetType);
if (item.AssetType == (int)AssetType.Link)
itemIDs.Add(item.AssetID);
}
// get the linked if any
if (itemIDs.Count > 0)
{
InventoryItemBase[] linked = m_InventoryService.GetMultipleItems(freq.owner_id, itemIDs.ToArray());
if (linked is not null)
{
List<InventoryItemBase> linkedItems = new List<InventoryItemBase>(linked.Length);
// check for broken
foreach (InventoryItemBase linkedItem in linked)
{
// Take care of genuinely broken links where the target doesn't exist
// HACK: Also, don't follow up links that just point to other links. In theory this is legitimate,
// but no viewer has been observed to set these up and this is the lazy way of avoiding cycles
// rather than having to keep track of every folder requested in the recursion.
if (linkedItem is not null && linkedItem.AssetType != (int)AssetType.Link)
{
linkedItems.Add(linkedItem);
//m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Added {0} {1} {2}", linkedItem.Name, linkedItem.AssetType, linkedItem.Folder);
}
}
// insert them
if(linkedItems.Count > 0)
contents.Items.InsertRange(0, linkedItems);
}
}
}
}
}

View File

@@ -1,165 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Net;
using System.Reflection;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
using log4net;
namespace OpenSim.Capabilities.Handlers
{
public class FetchInventory2Handler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IInventoryService m_inventoryService;
private UUID m_agentID;
public FetchInventory2Handler(IInventoryService invService, UUID agentId)
{
m_inventoryService = invService;
m_agentID = agentId;
}
public string FetchInventoryRequest(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
//m_log.DebugFormat("[FETCH INVENTORY HANDLER]: Received FetchInventory capability request {0}", request);
OSDMap requestmap = (OSDMap)OSDParser.DeserializeLLSDXml(Utils.StringToBytes(request));
OSDArray itemsRequested = (OSDArray)requestmap["items"];
UUID[] itemIDs = new UUID[itemsRequested.Count];
int i = 0;
foreach (OSDMap osdItemId in itemsRequested)
{
itemIDs[i++] = osdItemId["item_id"].AsUUID();
}
InventoryItemBase[] items;
if (m_agentID.IsZero())
{
items = new InventoryItemBase[itemsRequested.Count];
foreach (UUID id in itemIDs)
items[i++] = m_inventoryService.GetItem(UUID.Zero, id);
}
else
{
items = m_inventoryService.GetMultipleItems(m_agentID, itemIDs);
}
osUTF8 lsl = LLSDxmlEncode2.Start(4096);
LLSDxmlEncode2.AddMap(lsl);
if(m_agentID.IsZero() && items.Length > 0)
LLSDxmlEncode2.AddElem("agent_id", items[0].Owner, lsl);
else
LLSDxmlEncode2.AddElem("agent_id", m_agentID, lsl);
if(items is null || items.Length == 0)
{
LLSDxmlEncode2.AddEmptyArray("items", lsl);
}
else
{
LLSDxmlEncode2.AddArray("items", lsl);
foreach (InventoryItemBase item in items)
{
if (item is not null)
item.ToLLSDxml(lsl, 0xff);
}
LLSDxmlEncode2.AddEndArray(lsl);
}
LLSDxmlEncode2.AddEndMap(lsl);
return LLSDxmlEncode2.End(lsl);
}
public void FetchInventorySimpleRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, OSDMap requestmap, ExpiringKey<UUID> BadRequests)
{
//m_log.DebugFormat("[FETCH INVENTORY HANDLER]: Received FetchInventory capability request {0}", request);
if(BadRequests == null)
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
OSDArray itemsRequested = (OSDArray)requestmap["items"];
UUID[] itemIDs = new UUID[itemsRequested.Count];
int i = 0;
foreach (OSDMap osdItemId in itemsRequested)
{
UUID id = osdItemId["item_id"].AsUUID();
if(!BadRequests.ContainsKey(id))
itemIDs[i++] = id;
}
InventoryItemBase[] items = null;
try
{
// badrequests still not filled
items = m_inventoryService.GetMultipleItems(m_agentID, itemIDs);
}
catch{ }
osUTF8 lsl = LLSDxmlEncode2.Start(4096);
LLSDxmlEncode2.AddMap(lsl);
LLSDxmlEncode2.AddElem("agent_id", m_agentID, lsl);
if (items == null || items.Length == 0)
{
LLSDxmlEncode2.AddEmptyArray("items", lsl);
}
else
{
LLSDxmlEncode2.AddArray("items", lsl);
foreach (InventoryItemBase item in items)
{
if (item != null)
item.ToLLSDxml(lsl, 0xff);
}
LLSDxmlEncode2.AddEndArray(lsl);
}
LLSDxmlEncode2.AddEndMap(lsl);
httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(lsl);
httpResponse.StatusCode = (int)HttpStatusCode.OK;
}
}
}

View File

@@ -1,112 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Net;
using System.Reflection;
using System.Text;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
using log4net;
namespace OpenSim.Capabilities.Handlers
{
public class FetchLib2Handler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IInventoryService m_inventoryService;
private ILibraryService m_LibraryService;
private UUID m_agentID;
private UUID libOwner;
public FetchLib2Handler(IInventoryService invService, ILibraryService libraryService, UUID agentId)
{
m_inventoryService = invService;
m_agentID = agentId;
m_LibraryService = libraryService;
if(libraryService != null)
libOwner = m_LibraryService.LibraryRootFolder.Owner;
}
public void FetchLibSimpleRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, OSDMap requestmap, ExpiringKey<UUID> BadRequests)
{
//m_log.DebugFormat("[FETCH LIB INVENTORY HANDLER]: Received FetchInventory capability request {0}", request);
if (BadRequests == null)
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (m_LibraryService == null || m_agentID == UUID.Zero)
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
OSDArray itemsRequested = (OSDArray)requestmap["items"];
UUID[] itemIDs = new UUID[itemsRequested.Count];
int i = 0;
foreach (OSDMap osdItemId in itemsRequested)
{
UUID id = osdItemId["item_id"].AsUUID();
if (!BadRequests.ContainsKey(id))
itemIDs[i++] = id;
}
InventoryItemBase[] items = m_LibraryService.GetMultipleItems(itemIDs);
osUTF8 lsl = LLSDxmlEncode2.Start(4096);
LLSDxmlEncode2.AddMap(lsl);
LLSDxmlEncode2.AddElem("agent_id", m_agentID, lsl);
if(items is null || items.Length == 0)
{
LLSDxmlEncode2.AddEmptyArray("items", lsl);
}
else
{
LLSDxmlEncode2.AddArray("items", lsl);
foreach (InventoryItemBase item in items)
{
if (item != null)
item.ToLLSDxml(lsl);
}
LLSDxmlEncode2.AddEndArray(lsl);
}
LLSDxmlEncode2.AddEndMap(lsl);
httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(lsl);
httpResponse.StatusCode = (int)HttpStatusCode.OK;
}
}
}

View File

@@ -1,342 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text;
using log4net;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Capabilities;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
namespace OpenSim.Capabilities.Handlers
{
public class FetchLibDescHandler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly byte[] EmptyResponse = Util.UTF8NBGetbytes("<llsd><map><key>folders</key><array /></map></llsd>");
private readonly ILibraryService m_LibraryService;
private readonly UUID libOwner;
private readonly IScene m_Scene;
public FetchLibDescHandler(ILibraryService libService, IScene s)
{
m_LibraryService = libService;
libOwner = m_LibraryService.LibraryRootFolder.Owner;
m_Scene = s;
}
public void FetchRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, ExpiringKey<UUID> BadRequests, UUID agentID)
{
//m_log.DebugFormat("[XXX]: FetchLibDescendentsRequest in {0}, {1}", (m_Scene == null) ? "none" : m_Scene.Name, request);
if (m_LibraryService == null || m_LibraryService.LibraryRootFolder == null)
{
httpResponse.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
return;
}
httpResponse.StatusCode = (int)HttpStatusCode.OK;
List<LLSDFetchInventoryDescendents> folders;
List<UUID> bad_folders = new List<UUID>();
try
{
OSDArray foldersrequested = null;
OSD tmp = OSDParser.DeserializeLLSDXml(httpRequest.InputStream);
httpRequest.InputStream.Dispose();
OSDMap map = (OSDMap)tmp;
if(map.TryGetValue("folders", out tmp) && tmp is OSDArray frtmp)
foldersrequested = frtmp;
if (foldersrequested is null || foldersrequested.Count == 0)
{
httpResponse.RawBuffer = EmptyResponse;
return;
}
folders = new List<LLSDFetchInventoryDescendents>(foldersrequested.Count);
for (int i = 0; i < foldersrequested.Count; i++)
{
OSDMap mfolder = foldersrequested[i] as OSDMap;
UUID id = mfolder["folder_id"].AsUUID();
if(BadRequests.ContainsKey(id))
{
bad_folders.Add(id);
}
else
{
LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents();
try
{
llsdRequest.folder_id = id;
llsdRequest.owner_id = mfolder["owner_id"].AsUUID();
llsdRequest.sort_order = mfolder["sort_order"].AsInteger();
llsdRequest.fetch_folders = mfolder["fetch_folders"].AsBoolean();
llsdRequest.fetch_items = mfolder["fetch_items"].AsBoolean();
}
catch (Exception e)
{
m_log.Debug("[WEB FETCH INV DESC HANDLER]: caught exception doing OSD deserialize" + e.Message);
continue;
}
folders.Add(llsdRequest);
}
}
foldersrequested = null;
map.Clear();
map = null;
}
catch (Exception e)
{
m_log.ErrorFormat("[FETCH LIB DESC]: fail parsing request: {0}", e.Message);
httpResponse.RawBuffer = EmptyResponse;
return;
}
if (folders is null || folders.Count == 0)
{
if(bad_folders.Count == 0)
{
httpResponse.RawBuffer = EmptyResponse;
return;
}
osUTF8 osu = OSUTF8Cached.Acquire();
osu.AppendASCII("[FETCH LIB DESC HANDLER]: Unable to fetch folders:");
int limit = 5;
int count = 0;
foreach (UUID bad in bad_folders)
{
if (BadRequests.ContainsKey(bad))
continue;
osu.Append((byte)' ');
osu.AppendASCII(bad.ToString());
++count;
if (--limit < 0)
break;
}
if(count > 0)
{
if (limit < 0)
osu.AppendASCII(" ...");
m_log.Warn(osu.ToString());
}
osu.Clear();
osu.AppendASCII("<llsd><map><key>folders</key><array /></map><map><key>bad_folders</key><array>");
foreach (UUID bad in bad_folders)
{
osu.AppendASCII("<map><key>folder_id</key><uuid>");
osu.AppendASCII(bad.ToString());
osu.AppendASCII("</uuid><key>error</key><string>Unknown</string></map>");
}
osu.AppendASCII("</array></map></llsd>");
httpResponse.RawBuffer = OSUTF8Cached.GetArrayAndRelease(osu);
return;
}
UUID requester = folders[0].owner_id;
List<InventoryCollection> invcollSet = Fetch(folders, bad_folders);
//m_log.DebugFormat("[XXX]: Got {0} folders from a request of {1}", invcollSet.Count, folders.Count);
int invcollSetCount = 0;
if (invcollSet != null)
invcollSetCount = invcollSet.Count;
osUTF8 lastresponse = LLSDxmlEncode2.Start();
if (invcollSetCount > 0)
{
lastresponse.AppendASCII("<map><key>folders</key><array>");
int i = 0;
InventoryCollection thiscoll;
for (i = 0; i < invcollSetCount; i++)
{
thiscoll = invcollSet[i];
invcollSet[i] = null;
LLSDxmlEncode2.AddMap(lastresponse);
LLSDxmlEncode2.AddElem_folder_id(thiscoll.FolderID, lastresponse);
LLSDxmlEncode2.AddElem_agent_id(agentID, lastresponse);
LLSDxmlEncode2.AddElem_owner_id(thiscoll.OwnerID, lastresponse);
LLSDxmlEncode2.AddElem("descendents", thiscoll.Descendents, lastresponse);
LLSDxmlEncode2.AddElem_version(thiscoll.Version, lastresponse);
if (thiscoll.Folders == null || thiscoll.Folders.Count == 0)
LLSDxmlEncode2.AddEmptyArray("categories", lastresponse);
else
{
LLSDxmlEncode2.AddArray("categories", lastresponse);
foreach (InventoryFolderBase invFolder in thiscoll.Folders)
{
LLSDxmlEncode2.AddMap(lastresponse);
LLSDxmlEncode2.AddElem_category_id(invFolder.ID, lastresponse);
LLSDxmlEncode2.AddElem_parent_id(invFolder.ParentID, lastresponse);
LLSDxmlEncode2.AddElem_name(invFolder.Name, lastresponse);
LLSDxmlEncode2.AddElem("type_default", invFolder.Type, lastresponse);
LLSDxmlEncode2.AddElem_version( invFolder.Version, lastresponse);
LLSDxmlEncode2.AddEndMap(lastresponse);
}
LLSDxmlEncode2.AddEndArray(lastresponse);
}
if (thiscoll.Items == null || thiscoll.Items.Count == 0)
LLSDxmlEncode2.AddEmptyArray("items", lastresponse);
else
{
LLSDxmlEncode2.AddArray("items", lastresponse);
foreach (InventoryItemBase invItem in thiscoll.Items)
{
invItem.ToLLSDxml(lastresponse);
}
LLSDxmlEncode2.AddEndArray(lastresponse);
}
LLSDxmlEncode2.AddEndMap(lastresponse);
invcollSet[i] = null;
}
LLSDxmlEncode2.AddEndArrayAndMap(lastresponse);
}
else
{
lastresponse.AppendASCII("<map><key>folders</key><array /></map>");
}
if (bad_folders.Count > 0)
{
lastresponse.AppendASCII("<map><key>bad_folders</key><array>");
foreach (UUID bad in bad_folders)
{
BadRequests.Add(bad);
lastresponse.AppendASCII("<map><key>folder_id</key><uuid>");
lastresponse.AppendASCII(bad.ToString());
lastresponse.AppendASCII("</uuid><key>error</key><string>Unknown</string></map>");
}
lastresponse.AppendASCII("</array></map>");
StringBuilder sb = osStringBuilderCache.Acquire();
sb.Append("[WEB FETCH INV DESC HANDLER]: Unable to fetch folders owned by ");
sb.Append(requester.ToString());
sb.Append(" :");
int limit = 9;
foreach (UUID bad in bad_folders)
{
sb.Append(' ');
sb.Append(bad.ToString());
if(--limit < 0)
break;
}
if(limit < 0)
sb.Append(" ...");
m_log.Warn(osStringBuilderCache.GetStringAndRelease(sb));
}
httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(lastresponse);
}
private List<InventoryCollection> Fetch(List<LLSDFetchInventoryDescendents> fetchFolders, List<UUID> bad_folders)
{
//m_log.DebugFormat(
// "[FETCH LIB DESC HANDLER]: Fetching {0} folders", fetchFolders.Count);
// FIXME MAYBE: We're not handling sortOrder!
int cntr = fetchFolders.Count;
List<InventoryCollection> result = new List<InventoryCollection>(cntr);
List<LLSDFetchInventoryDescendents> libFolders = new List<LLSDFetchInventoryDescendents>(cntr);
HashSet<UUID> libIDs = new HashSet<UUID>();
// Filter folder Zero right here. Some viewers (Firestorm) send request for folder Zero, which doesn't make sense
// and can kill the sim (all root folders have parent_id Zero)
// send something.
bool doneZeroID = false;
foreach(LLSDFetchInventoryDescendents f in fetchFolders)
{
if (f.folder_id.IsZero())
{
if(doneZeroID)
continue;
doneZeroID = true;
InventoryCollection Collection = new InventoryCollection()
{
OwnerID = f.owner_id,
Version = -1,
FolderID = f.folder_id,
Descendents = 0
};
result.Add(Collection);
continue;
}
if(f.owner_id.Equals(libOwner))
{
if(libIDs.Contains(f.folder_id))
continue;
libIDs.Add(f.folder_id);
libFolders.Add(f);
continue;
}
}
if (libFolders.Count > 0)
{
foreach (LLSDFetchInventoryDescendents f in libFolders)
{
InventoryFolderImpl fold = m_LibraryService.LibraryRootFolder.FindFolder(f.folder_id);
if (fold != null)
{
InventoryCollection Collection = new InventoryCollection()
{
Folders = fold.RequestListOfFolders(),
Items = fold.RequestListOfItems(),
OwnerID = m_LibraryService.LibraryRootFolder.Owner,
FolderID = f.folder_id,
Version = fold.Version
};
Collection.Descendents = Collection.Items.Count + Collection.Folders.Count;
result.Add(Collection);
//m_log.DebugFormat("[XXX]: Added libfolder {0} ({1}) {2}", ret.Collection.FolderID, ret.Collection.OwnerID);
}
else
bad_folders.Add(f.folder_id);
}
}
return result;
}
}
}

View File

@@ -1,170 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using log4net;
using log4net.Config;
using NUnit.Framework;
using OpenMetaverse;
using OpenSim.Capabilities.Handlers;
using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
using OpenSim.Tests.Common;
namespace OpenSim.Capabilities.Handlers.FetchInventory.Tests
{
[TestFixture]
public class FetchInventory2HandlerTests : OpenSimTestCase
{
private UUID m_userID = UUID.Random();
private Scene m_scene;
private UUID m_rootFolderID;
private UUID m_notecardsFolder;
private UUID m_objectsFolder;
private void Init()
{
// Create an inventory that looks like this:
//
// /My Inventory
// <other system folders>
// /Objects
// Object 1
// Object 2
// Object 3
// /Notecards
// Notecard 1
// Notecard 2
// Notecard 3
// Notecard 4
// Notecard 5
m_scene = new SceneHelpers().SetupScene();
m_scene.InventoryService.CreateUserInventory(m_userID);
m_rootFolderID = m_scene.InventoryService.GetRootFolder(m_userID).ID;
InventoryFolderBase of = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object);
m_objectsFolder = of.ID;
// Add 3 objects
InventoryItemBase item;
for (int i = 1; i <= 3; i++)
{
item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-0000000000b" + i), m_userID);
item.AssetID = UUID.Random();
item.AssetType = (int)AssetType.Object;
item.Folder = m_objectsFolder;
item.Name = "Object " + i;
m_scene.InventoryService.AddItem(item);
}
InventoryFolderBase ncf = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Notecard);
m_notecardsFolder = ncf.ID;
// Add 5 notecards
for (int i = 1; i <= 5; i++)
{
item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-00000000000" + i), m_userID);
item.AssetID = UUID.Random();
item.AssetType = (int)AssetType.Notecard;
item.Folder = m_notecardsFolder;
item.Name = "Notecard " + i;
m_scene.InventoryService.AddItem(item);
}
}
[Test]
public void Test_001_RequestOne()
{
TestHelpers.InMethod();
Init();
FetchInventory2Handler handler = new FetchInventory2Handler(m_scene.InventoryService, m_userID);
TestOSHttpRequest req = new TestOSHttpRequest();
TestOSHttpResponse resp = new TestOSHttpResponse();
string request = "<llsd><map><key>items</key><array><map><key>item_id</key><uuid>";
request += "10000000-0000-0000-0000-000000000001"; // Notecard 1
request += "</uuid></map></array></map></llsd>";
string llsdresponse = handler.FetchInventoryRequest(request, "/FETCH", string.Empty, req, resp);
Assert.That(llsdresponse != null, Is.True, "Incorrect null response");
Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response");
Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID");
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Response does not contain item uuid");
Assert.That(llsdresponse.Contains("Notecard 1"), Is.True, "Response does not contain item Name");
Console.WriteLine(llsdresponse);
}
[Test]
public void Test_002_RequestMany()
{
TestHelpers.InMethod();
Init();
FetchInventory2Handler handler = new FetchInventory2Handler(m_scene.InventoryService, m_userID);
TestOSHttpRequest req = new TestOSHttpRequest();
TestOSHttpResponse resp = new TestOSHttpResponse();
string request = "<llsd><map><key>items</key><array>";
request += "<map><key>item_id</key><uuid>10000000-0000-0000-0000-000000000001</uuid></map>"; // Notecard 1
request += "<map><key>item_id</key><uuid>10000000-0000-0000-0000-000000000002</uuid></map>"; // Notecard 2
request += "<map><key>item_id</key><uuid>10000000-0000-0000-0000-000000000003</uuid></map>"; // Notecard 3
request += "<map><key>item_id</key><uuid>10000000-0000-0000-0000-000000000004</uuid></map>"; // Notecard 4
request += "<map><key>item_id</key><uuid>10000000-0000-0000-0000-000000000005</uuid></map>"; // Notecard 5
request += "</array></map></llsd>";
string llsdresponse = handler.FetchInventoryRequest(request, "/FETCH", string.Empty, req, resp);
Assert.That(llsdresponse != null, Is.True, "Incorrect null response");
Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response");
Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID");
Console.WriteLine(llsdresponse);
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Response does not contain notecard 1");
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000002"), Is.True, "Response does not contain notecard 2");
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000003"), Is.True, "Response does not contain notecard 3");
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000004"), Is.True, "Response does not contain notecard 4");
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000005"), Is.True, "Response does not contain notecard 5");
}
}
}

View File

@@ -1,303 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text.RegularExpressions;
using log4net;
using log4net.Config;
using NUnit.Framework;
using OpenMetaverse;
using OpenSim.Capabilities.Handlers;
using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
using OpenSim.Tests.Common;
namespace OpenSim.Capabilities.Handlers.FetchInventory.Tests
{
[TestFixture]
public class FetchInventoryDescendents2HandlerTests : OpenSimTestCase
{
private UUID m_userID = new UUID("00000000-0000-0000-0000-000000000001");
private Scene m_scene;
private UUID m_rootFolderID;
private int m_rootDescendents;
private UUID m_notecardsFolder;
private UUID m_objectsFolder;
private void Init()
{
// Create an inventory that looks like this:
//
// /My Inventory
// <other system folders>
// /Objects
// Some Object
// /Notecards
// Notecard 1
// Notecard 2
// /Test Folder
// Link to notecard -> /Notecards/Notecard 2
// Link to Objects folder -> /Objects
m_scene = new SceneHelpers().SetupScene();
m_scene.InventoryService.CreateUserInventory(m_userID);
m_rootFolderID = m_scene.InventoryService.GetRootFolder(m_userID).ID;
InventoryFolderBase of = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object);
m_objectsFolder = of.ID;
// Add an object
InventoryItemBase item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-00000000000b"), m_userID);
item.AssetID = UUID.Random();
item.AssetType = (int)AssetType.Object;
item.Folder = m_objectsFolder;
item.Name = "Some Object";
m_scene.InventoryService.AddItem(item);
InventoryFolderBase ncf = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Notecard);
m_notecardsFolder = ncf.ID;
// Add a notecard
item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-000000000001"), m_userID);
item.AssetID = UUID.Random();
item.AssetType = (int)AssetType.Notecard;
item.Folder = m_notecardsFolder;
item.Name = "Test Notecard 1";
m_scene.InventoryService.AddItem(item);
// Add another notecard
item.ID = new UUID("20000000-0000-0000-0000-000000000002");
item.AssetID = new UUID("a0000000-0000-0000-0000-00000000000a");
item.Name = "Test Notecard 2";
m_scene.InventoryService.AddItem(item);
// Add a folder
InventoryFolderBase folder = new InventoryFolderBase(new UUID("f0000000-0000-0000-0000-00000000000f"), "Test Folder", m_userID, m_rootFolderID);
folder.Type = (short)FolderType.None;
m_scene.InventoryService.AddFolder(folder);
// Add a link to notecard 2 in Test Folder
item.AssetID = item.ID; // use item ID of notecard 2
item.ID = new UUID("40000000-0000-0000-0000-000000000004");
item.AssetType = (int)AssetType.Link;
item.Folder = folder.ID;
item.Name = "Link to notecard";
m_scene.InventoryService.AddItem(item);
// Add a link to the Objects folder in Test Folder
item.AssetID = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object).ID; // use item ID of Objects folder
item.ID = new UUID("50000000-0000-0000-0000-000000000005");
item.AssetType = (int)AssetType.LinkFolder;
item.Folder = folder.ID;
item.Name = "Link to Objects folder";
m_scene.InventoryService.AddItem(item);
InventoryCollection coll = m_scene.InventoryService.GetFolderContent(m_userID, m_rootFolderID);
m_rootDescendents = coll.Items.Count + coll.Folders.Count;
Console.WriteLine("Number of descendents: " + m_rootDescendents);
}
private string dorequest(FetchInvDescHandler handler, string request)
{
TestOSHttpRequest req = new TestOSHttpRequest();
TestOSHttpResponse resp = new TestOSHttpResponse();
using(ExpiringKey<UUID> bad = new ExpiringKey<UUID>(5000)) // bad but this is test
using (MemoryStream ms = new MemoryStream(Utils.StringToBytes(request), false))
{
req.InputStream = ms;
handler.FetchInventoryDescendentsRequest(req, resp, bad);
}
return Util.UTF8.GetString(resp.RawBuffer);
}
[Test]
public void Test_001_SimpleFolder()
{
TestHelpers.InMethod();
Init();
FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene);
string request = "<llsd><map><key>folders</key><array><map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_rootFolderID;
request += "</uuid><key>owner_id</key><uuid>";
request += m_userID.ToString();
request += "</uuid><key>sort_order</key><integer>1</integer></map></array></map></llsd>";
string llsdresponse = dorequest(handler, request);
Assert.That(llsdresponse != null, Is.True, "Incorrect null response");
Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response");
Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID");
string descendents = "descendents</key><integer>" + m_rootDescendents + "</integer>";
Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents");
Console.WriteLine(llsdresponse);
}
[Test]
public void Test_002_MultipleFolders()
{
TestHelpers.InMethod();
FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene);
string request = "<llsd><map><key>folders</key><array>";
request += "<map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_rootFolderID;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000001</uuid><key>sort_order</key><integer>1</integer></map>";
request += "<map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_notecardsFolder;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000001</uuid><key>sort_order</key><integer>1</integer></map>";
request += "</array></map></llsd>";
string llsdresponse = dorequest(handler, request);
Console.WriteLine(llsdresponse);
string descendents = "descendents</key><integer>" + m_rootDescendents + "</integer>";
Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for root folder");
descendents = "descendents</key><integer>2</integer>";
Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for Notecard folder");
Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Notecard 1 is missing from response");
Assert.That(llsdresponse.Contains("20000000-0000-0000-0000-000000000002"), Is.True, "Notecard 2 is missing from response");
}
[Test]
public void Test_003_Links()
{
TestHelpers.InMethod();
FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene);
string request = "<llsd><map><key>folders</key><array><map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += "f0000000-0000-0000-0000-00000000000f";
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000001</uuid><key>sort_order</key><integer>1</integer></map></array></map></llsd>";
string llsdresponse = dorequest(handler, request);
Console.WriteLine(llsdresponse);
string descendents = "descendents</key><integer>2</integer>";
Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for Test Folder");
// Make sure that the note card link is included
Assert.That(llsdresponse.Contains("Link to notecard"), Is.True, "Link to notecard is missing");
//Make sure the notecard item itself is included
Assert.That(llsdresponse.Contains("Test Notecard 2"), Is.True, "Notecard 2 item (the source) is missing");
// Make sure that the source item is before the link item
int pos1 = llsdresponse.IndexOf("Test Notecard 2");
int pos2 = llsdresponse.IndexOf("Link to notecard");
Assert.Less(pos1, pos2, "Source of link is after link");
// Make sure the folder link is included
Assert.That(llsdresponse.Contains("Link to Objects folder"), Is.True, "Link to Objects folder is missing");
/* contents of link folder are not supposed to be listed
// Make sure the objects inside the Objects folder are included
// Note: I'm not entirely sure this is needed, but that's what I found in the implementation
Assert.That(llsdresponse.Contains("Some Object"), Is.True, "Some Object item (contents of the source) is missing");
*/
// Make sure that the source item is before the link item
pos1 = llsdresponse.IndexOf("Some Object");
pos2 = llsdresponse.IndexOf("Link to Objects folder");
Assert.Less(pos1, pos2, "Contents of source of folder link is after folder link");
}
[Test]
public void Test_004_DuplicateFolders()
{
TestHelpers.InMethod();
FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene);
string request = "<llsd><map><key>folders</key><array>";
request += "<map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_rootFolderID;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000000</uuid><key>sort_order</key><integer>1</integer></map>";
request += "<map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_notecardsFolder;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000000</uuid><key>sort_order</key><integer>1</integer></map>";
request += "<map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_rootFolderID;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000000</uuid><key>sort_order</key><integer>1</integer></map>";
request += "<map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += m_notecardsFolder;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000000</uuid><key>sort_order</key><integer>1</integer></map>";
request += "</array></map></llsd>";
string llsdresponse = dorequest(handler, request);
Console.WriteLine(llsdresponse);
string root_folder = "<key>folder_id</key><uuid>" + m_rootFolderID + "</uuid>";
string notecards_folder = "<key>folder_id</key><uuid>" + m_notecardsFolder + "</uuid>";
string notecards_category = "<key>category_id</key><uuid>" + m_notecardsFolder + "</uuid>";
Assert.That(llsdresponse.Contains(root_folder), "Missing root folder");
Assert.That(llsdresponse.Contains(notecards_folder), "Missing notecards folder");
int count = Regex.Matches(llsdresponse, root_folder).Count;
Assert.AreEqual(1, count, "More than 1 root folder in response");
count = Regex.Matches(llsdresponse, notecards_folder).Count;
Assert.AreEqual(1, count, "More than 1 notecards folder in response");
count = Regex.Matches(llsdresponse, notecards_category).Count;
Assert.AreEqual(1, count, "More than 1 notecards folder in response"); // Notecards will also be a category on root
}
[Test]
public void Test_005_FolderZero()
{
TestHelpers.InMethod();
Init();
FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene);
string request = "<llsd><map><key>folders</key><array><map><key>fetch_folders</key><integer>1</integer><key>fetch_items</key><boolean>1</boolean><key>folder_id</key><uuid>";
request += UUID.Zero;
request += "</uuid><key>owner_id</key><uuid>00000000-0000-0000-0000-000000000000</uuid><key>sort_order</key><integer>1</integer></map></array></map></llsd>";
string llsdresponse = dorequest(handler, request);
Assert.That(llsdresponse != null, Is.True, "Incorrect null response");
Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response");
// we do return a answer now
//Assert.That(llsdresponse.Contains("bad_folders</key><array><uuid>00000000-0000-0000-0000-000000000000"), Is.True, "Folder Zero should be a bad folder");
Console.WriteLine(llsdresponse);
}
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Capabilities;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using Caps = OpenSim.Framework.Capabilities.Caps;
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
namespace OpenSim.Capabilities.Handlers
{
public class FetchInventory2Handler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IInventoryService m_inventoryService;
public FetchInventory2Handler(IInventoryService invService)
{
m_inventoryService = invService;
}
public string FetchInventoryRequest(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
// m_log.DebugFormat("[FETCH INVENTORY HANDLER]: Received FetchInventory capabilty request");
OSDMap requestmap = (OSDMap)OSDParser.DeserializeLLSDXml(Utils.StringToBytes(request));
OSDArray itemsRequested = (OSDArray)requestmap["items"];
string reply;
LLSDFetchInventory llsdReply = new LLSDFetchInventory();
foreach (OSDMap osdItemId in itemsRequested)
{
UUID itemId = osdItemId["item_id"].AsUUID();
InventoryItemBase item = m_inventoryService.GetItem(new InventoryItemBase(itemId));
if (item != null)
{
// We don't know the agent that this request belongs to so we'll use the agent id of the item
// which will be the same for all items.
llsdReply.agent_id = item.Owner;
llsdReply.items.Array.Add(ConvertInventoryItem(item));
}
}
reply = LLSDHelpers.SerialiseLLSDReply(llsdReply);
return reply;
}
/// <summary>
/// Convert an internal inventory item object into an LLSD object.
/// </summary>
/// <param name="invItem"></param>
/// <returns></returns>
private LLSDInventoryItem ConvertInventoryItem(InventoryItemBase invItem)
{
LLSDInventoryItem llsdItem = new LLSDInventoryItem();
llsdItem.asset_id = invItem.AssetID;
llsdItem.created_at = invItem.CreationDate;
llsdItem.desc = invItem.Description;
llsdItem.flags = (int)invItem.Flags;
llsdItem.item_id = invItem.ID;
llsdItem.name = invItem.Name;
llsdItem.parent_id = invItem.Folder;
try
{
llsdItem.type = Utils.AssetTypeToString((AssetType)invItem.AssetType);
llsdItem.inv_type = Utils.InventoryTypeToString((InventoryType)invItem.InvType);
}
catch (Exception e)
{
m_log.ErrorFormat(
"[WEB FETCH INV DESC HANDLER]: Problem setting asset {0} inventory {1} types while converting inventory item {2}: {3}",
invItem.AssetType, invItem.InvType, invItem.Name, e.Message);
}
llsdItem.permissions = new LLSDPermissions();
llsdItem.permissions.creator_id = invItem.CreatorIdAsUuid;
llsdItem.permissions.base_mask = (int)invItem.CurrentPermissions;
llsdItem.permissions.everyone_mask = (int)invItem.EveryOnePermissions;
llsdItem.permissions.group_id = invItem.GroupID;
llsdItem.permissions.group_mask = (int)invItem.GroupPermissions;
llsdItem.permissions.is_owner_group = invItem.GroupOwned;
llsdItem.permissions.next_owner_mask = (int)invItem.NextPermissions;
llsdItem.permissions.owner_id = invItem.Owner;
llsdItem.permissions.owner_mask = (int)invItem.CurrentPermissions;
llsdItem.sale_info = new LLSDSaleInfo();
llsdItem.sale_info.sale_price = invItem.SalePrice;
switch (invItem.SaleType)
{
default:
llsdItem.sale_info.sale_type = "not";
break;
case 1:
llsdItem.sale_info.sale_type = "original";
break;
case 2:
llsdItem.sale_info.sale_type = "copy";
break;
case 3:
llsdItem.sale_info.sale_type = "contents";
break;
}
return llsdItem;
}
}
}

View File

@@ -52,7 +52,7 @@ namespace OpenSim.Capabilities.Handlers
string invService = serverConfig.GetString("InventoryService", String.Empty);
if (invService.Length == 0)
if (invService == String.Empty)
throw new Exception("No InventoryService in config file");
Object[] args = new Object[] { config };
@@ -61,7 +61,7 @@ namespace OpenSim.Capabilities.Handlers
if (m_InventoryService == null)
throw new Exception(String.Format("Failed to load InventoryService from {0}; config is {1}", invService, m_ConfigName));
FetchInventory2Handler fiHandler = new FetchInventory2Handler(m_InventoryService, UUID.Zero);
FetchInventory2Handler fiHandler = new FetchInventory2Handler(m_InventoryService);
IRequestHandler reqHandler
= new RestStreamHandler(
"POST", "/CAPS/FetchInventory/", fiHandler.FetchInventoryRequest, "FetchInventory", null);

View File

@@ -1,197 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Threading;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using Caps = OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Capabilities.Handlers
{
public class GetAssetsHandler
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly Dictionary<string, AssetType> queryTypes = new()
{
{"texture_id", AssetType.Texture},
{"sound_id", AssetType.Sound},
{"callcard_id", AssetType.CallingCard},
{"landmark_id", AssetType.Landmark},
{"script_id", AssetType.LSLText},
{"clothing_id", AssetType.Clothing},
{"object_id", AssetType.Object},
{"notecard_id", AssetType.Notecard},
{"lsltext_id", AssetType.LSLText},
{"lslbyte_id", AssetType.LSLBytecode},
{"txtr_tga_id", AssetType.TextureTGA},
{"bodypart_id", AssetType.Bodypart},
{"snd_wav_id", AssetType.SoundWAV},
{"img_tga_id", AssetType.ImageTGA},
{"jpeg_id", AssetType.ImageJPEG},
{"animatn_id", AssetType.Animation},
{"gesture_id", AssetType.Gesture},
{"mesh_id", AssetType.Mesh},
{"settings_id", AssetType.Settings},
{"material_id", AssetType.Material}
};
private IAssetService m_assetService;
public GetAssetsHandler(IAssetService assService)
{
m_assetService = assService;
}
public void Handle(OSHttpRequest req, OSHttpResponse response, string serviceURL = null)
{
response.ContentType = "text/plain";
if (m_assetService == null)
{
//m_log.Warn("[GETASSET]: no service");
response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
response.KeepAlive = false;
return;
}
response.StatusCode = (int)HttpStatusCode.BadRequest;
var queries = req.QueryAsDictionary;
if(queries.Count == 0)
return;
AssetType type = AssetType.Unknown;
string assetStr = string.Empty;
foreach (KeyValuePair<string,string> kvp in queries)
{
if (queryTypes.TryGetValue(kvp.Key, out type))
{
assetStr = kvp.Value;
break;
}
}
if(type == AssetType.Unknown)
{
//m_log.Warn("[GETASSET]: Unknown type: " + query);
m_log.Warn("[GETASSET]: Unknown type");
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (string.IsNullOrEmpty(assetStr))
return;
if(!UUID.TryParse(assetStr, out UUID assetID))
return;
ManualResetEventSlim done = new ManualResetEventSlim(false);
AssetBase asset = null;
m_assetService.Get(assetID.ToString(), serviceURL, false, (AssetBase a) =>
{
asset = a;
done.Set();
});
done.Wait();
done.Dispose();
done = null;
if (asset == null)
{
// m_log.Warn("[GETASSET]: not found: " + query + " " + assetStr);
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
int len = asset.Data.Length;
if (len == 0)
{
m_log.Warn("[GETASSET]: asset with empty data: " + assetStr + " type " + asset.Type.ToString());
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (asset.Type != (sbyte)type)
{
m_log.Warn("[GETASSET]: asset with wrong type: " + assetStr + " " + asset.Type.ToString() + " != " + ((sbyte)type).ToString());
//response.StatusCode = (int)HttpStatusCode.NotFound;
//return;
}
// range request
if (Util.TryParseHttpRange(req.Headers["range"], out int start, out int end))
{
// viewers do send broken start, then flag good assets as bad
if (start >= len)
{
//m_log.Warn("[GETASSET]: bad start: " + range);
response.StatusCode = (int)HttpStatusCode.OK;
}
else
{
if (end == -1)
end = len - 1;
else
end = Utils.Clamp(end, 0, len - 1);
start = Utils.Clamp(start, 0, end);
len = end - start + 1;
//m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", start, end, asset.Data.Length));
response.StatusCode = (int)HttpStatusCode.PartialContent;
response.RawBufferStart = start;
}
}
else
response.StatusCode = (int)HttpStatusCode.OK;
response.ContentType = asset.Metadata.ContentType;
response.RawBuffer = asset.Data;
response.RawBufferLen = len;
if (type == AssetType.Mesh || type == AssetType.Texture)
response.Priority = 2;
else
response.Priority = 1;
}
}
}

View File

@@ -43,112 +43,73 @@ using Caps = OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Capabilities.Handlers
{
public class GetMeshHandler
public class GetMeshHandler
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// private static readonly ILog m_log =
// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IAssetService m_assetService;
public const string DefaultFormat = "vnd.ll.mesh";
public GetMeshHandler(IAssetService assService)
{
m_assetService = assService;
}
public Hashtable Handle(Hashtable request)
{
return ProcessGetMesh(request, UUID.Zero, null);
}
public Hashtable ProcessGetMesh(Hashtable request, UUID AgentId, Caps cap)
{
Hashtable responsedata = new Hashtable();
if (m_assetService == null)
{
responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.ServiceUnavailable;
responsedata["str_response_string"] = "The asset service is unavailable";
responsedata["keepalive"] = false;
return responsedata;
}
responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.BadRequest;
responsedata["int_response_code"] = 400; //501; //410; //404;
responsedata["content_type"] = "text/plain";
responsedata["int_bytes"] = 0;
responsedata["keepalive"] = false;
responsedata["str_response_string"] = "Request wasn't what was expected";
string meshStr = string.Empty;
if (request.ContainsKey("mesh_id"))
meshStr = request["mesh_id"].ToString();
if (String.IsNullOrEmpty(meshStr))
return responsedata;
UUID meshID = UUID.Zero;
if(!UUID.TryParse(meshStr, out meshID))
return responsedata;
AssetBase mesh = m_assetService.Get(meshID.ToString());
if(mesh == null)
if (!String.IsNullOrEmpty(meshStr) && UUID.TryParse(meshStr, out meshID))
{
responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound;
responsedata["str_response_string"] = "Mesh not found.";
return responsedata;
}
if (mesh.Type != (SByte)AssetType.Mesh)
{
responsedata["str_response_string"] = "Asset isn't a mesh.";
return responsedata;
}
string range = String.Empty;
if (((Hashtable)request["headers"])["range"] != null)
range = (string)((Hashtable)request["headers"])["range"];
else if (((Hashtable)request["headers"])["Range"] != null)
range = (string)((Hashtable)request["headers"])["Range"];
responsedata["content_type"] = "application/vnd.ll.mesh";
if (String.IsNullOrEmpty(range))
{
// full mesh
responsedata["str_response_string"] = Convert.ToBase64String(mesh.Data);
responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.OK;
return responsedata;
}
// range request
int start, end;
if (Util.TryParseHttpRange(range, out start, out end))
{
// Before clamping start make sure we can satisfy it in order to avoid
// sending back the last byte instead of an error status
if (start >= mesh.Data.Length)
if (m_assetService == null)
{
responsedata["str_response_string"] = "This range doesnt exist.";
responsedata["int_response_code"] = 404; //501; //410; //404;
responsedata["content_type"] = "text/plain";
responsedata["keepalive"] = false;
responsedata["str_response_string"] = "The asset service is unavailable. So is your mesh.";
return responsedata;
}
end = Utils.Clamp(end, 0, mesh.Data.Length - 1);
start = Utils.Clamp(start, 0, end);
int len = end - start + 1;
AssetBase mesh = m_assetService.Get(meshID.ToString());
//m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
Hashtable headers = new Hashtable();
headers["Content-Range"] = String.Format("bytes {0}-{1}/{2}", start, end, mesh.Data.Length);
responsedata["headers"] = headers;
responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.PartialContent;
byte[] d = new byte[len];
Array.Copy(mesh.Data, start, d, 0, len);
responsedata["bin_response_data"] = d;
responsedata["int_bytes"] = len;
return responsedata;
if (mesh != null)
{
if (mesh.Type == (SByte)AssetType.Mesh)
{
responsedata["str_response_string"] = Convert.ToBase64String(mesh.Data);
responsedata["content_type"] = "application/vnd.ll.mesh";
responsedata["int_response_code"] = 200;
}
// Optionally add additional mesh types here
else
{
responsedata["int_response_code"] = 404; //501; //410; //404;
responsedata["content_type"] = "text/plain";
responsedata["keepalive"] = false;
responsedata["str_response_string"] = "Unfortunately, this asset isn't a mesh.";
return responsedata;
}
}
else
{
responsedata["int_response_code"] = 404; //501; //410; //404;
responsedata["content_type"] = "text/plain";
responsedata["keepalive"] = false;
responsedata["str_response_string"] = "Your Mesh wasn't found. Sorry!";
return responsedata;
}
}
m_log.Warn("[GETMESH]: Failed to parse a range from GetMesh request, sending full asset: " + (string)request["uri"]);
responsedata["str_response_string"] = Convert.ToBase64String(mesh.Data);
responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.OK;
return responsedata;
}
}

View File

@@ -25,13 +25,16 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using Nini.Config;
using OpenMetaverse;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Base;
using OpenSim.Server.Handlers.Base;
using OpenSim.Services.Interfaces;
using System;
using System.Collections;
using Nini.Config;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Handlers.Base;
using OpenSim.Framework.Servers;
using OpenMetaverse;
namespace OpenSim.Capabilities.Handlers
{
@@ -52,7 +55,7 @@ namespace OpenSim.Capabilities.Handlers
string assetService = serverConfig.GetString("AssetService", String.Empty);
if (assetService.Length == 0)
if (assetService == String.Empty)
throw new Exception("No AssetService in config file");
Object[] args = new Object[] { config };
@@ -62,13 +65,11 @@ namespace OpenSim.Capabilities.Handlers
if (m_AssetService == null)
throw new Exception(String.Format("Failed to load AssetService from {0}; config is {1}", assetService, m_ConfigName));
string rurl = serverConfig.GetString("GetMeshRedirectURL");
GetMeshHandler gmeshHandler = new GetMeshHandler(m_AssetService);
IRequestHandler reqHandler
= new RestHTTPHandler(
"GET",
"/" + UUID.Random(),
"/CAPS/" + UUID.Random(),
httpMethod => gmeshHandler.ProcessGetMesh(httpMethod, UUID.Zero, null),
"GetMesh",
null);

View File

@@ -47,92 +47,87 @@ using Caps = OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Capabilities.Handlers
{
public class GetTextureHandler
public class GetTextureHandler : BaseStreamHandler
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IAssetService m_assetService;
public const string DefaultFormat = "x-j2c";
public GetTextureHandler(IAssetService assService)
// TODO: Change this to a config option
const string REDIRECT_URL = null;
public GetTextureHandler(string path, IAssetService assService, string name, string description)
: base("GET", path, name, description)
{
m_assetService = assService;
}
public Hashtable Handle(Hashtable request)
public override byte[] Handle(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
Hashtable ret = new Hashtable();
ret["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound;
ret["content_type"] = "text/plain";
ret["int_bytes"] = 0;
string textureStr = (string)request["texture_id"];
string format = (string)request["format"];
// Try to parse the texture ID from the request URL
NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query);
string textureStr = query.GetOne("texture_id");
string format = query.GetOne("format");
//m_log.DebugFormat("[GETTEXTURE]: called {0}", textureStr);
if (m_assetService == null)
{
m_log.Error("[GETTEXTURE]: Cannot fetch texture " + textureStr + " without an asset service");
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return null;
}
UUID textureID;
if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID))
{
// m_log.DebugFormat("[GETTEXTURE]: Received request for texture id {0}", textureID);
string[] formats;
if (!string.IsNullOrEmpty(format))
if (format != null && format != string.Empty)
{
formats = new string[1] { format.ToLower() };
}
else
{
formats = new string[1] { DefaultFormat }; // default
if (((Hashtable)request["headers"])["Accept"] != null)
formats = WebUtil.GetPreferredImageTypes((string)((Hashtable)request["headers"])["Accept"]);
formats = WebUtil.GetPreferredImageTypes(httpRequest.Headers.Get("Accept"));
if (formats.Length == 0)
formats = new string[1] { DefaultFormat }; // default
}
// OK, we have an array with preferred formats, possibly with only one entry
bool foundtexture = false;
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
foreach (string f in formats)
{
foundtexture = FetchTexture(request, ret, textureID, f);
if (foundtexture)
if (FetchTexture(httpRequest, httpResponse, textureID, f))
break;
}
if (!foundtexture)
{
ret["int_response_code"] = 404;
ret["error_status_text"] = "not found";
ret["str_response_string"] = "not found";
ret["content_type"] = "text/plain";
ret["int_bytes"] = 0;
}
}
else
{
m_log.Warn("[GETTEXTURE]: Failed to parse a texture_id from GetTexture request: " + (string)request["uri"]);
m_log.Warn("[GETTEXTURE]: Failed to parse a texture_id from GetTexture request: " + httpRequest.Url);
}
// m_log.DebugFormat(
// "[GETTEXTURE]: For texture {0} sending back response {1}, data length {2}",
// textureID, httpResponse.StatusCode, httpResponse.ContentLength);
return ret;
httpResponse.Send();
return null;
}
/// <summary>
///
///
/// </summary>
/// <param name="httpRequest"></param>
/// <param name="httpResponse"></param>
/// <param name="textureID"></param>
/// <param name="format"></param>
/// <returns>False for "caller try another codec"; true otherwise</returns>
private bool FetchTexture(Hashtable request, Hashtable response, UUID textureID, string format)
private bool FetchTexture(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, UUID textureID, string format)
{
// m_log.DebugFormat("[GETTEXTURE]: {0} with requested format {1}", textureID, format);
AssetBase texture;
@@ -141,142 +136,135 @@ namespace OpenSim.Capabilities.Handlers
if (format != DefaultFormat)
fullID = fullID + "-" + format;
// try the cache
texture = m_assetService.GetCached(fullID);
if (texture == null)
if (!String.IsNullOrEmpty(REDIRECT_URL))
{
//m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
// Fetch locally or remotely. Misses return a 404
texture = m_assetService.Get(textureID.ToString());
// Only try to fetch locally cached textures. Misses are redirected
texture = m_assetService.GetCached(fullID);
if (texture != null)
{
if (texture.Type != (sbyte)AssetType.Texture)
return true;
if (format == DefaultFormat)
{
WriteTextureData(request, response, texture, format);
return true;
}
else
{
AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
newTexture.Data = ConvertTextureData(texture, format);
if (newTexture.Data.Length == 0)
return false; // !!! Caller try another codec, please!
newTexture.Flags = AssetFlags.Collectable;
newTexture.Temporary = true;
newTexture.Local = true;
m_assetService.Store(newTexture);
WriteTextureData(request, response, newTexture, format);
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return true;
}
WriteTextureData(httpRequest, httpResponse, texture, format);
}
}
else // it was on the cache
{
//m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
WriteTextureData(request, response, texture, format);
return true;
}
else
{
string textureUrl = REDIRECT_URL + textureID.ToString();
m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl);
httpResponse.RedirectLocation = textureUrl;
return true;
}
}
else // no redirect
{
// try the cache
texture = m_assetService.GetCached(fullID);
//response = new Hashtable();
if (texture == null)
{
//m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
// Fetch locally or remotely. Misses return a 404
texture = m_assetService.Get(textureID.ToString());
if (texture != null)
{
if (texture.Type != (sbyte)AssetType.Texture)
{
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return true;
}
if (format == DefaultFormat)
{
WriteTextureData(httpRequest, httpResponse, texture, format);
return true;
}
else
{
AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
newTexture.Data = ConvertTextureData(texture, format);
if (newTexture.Data.Length == 0)
return false; // !!! Caller try another codec, please!
newTexture.Flags = AssetFlags.Collectable;
newTexture.Temporary = true;
m_assetService.Store(newTexture);
WriteTextureData(httpRequest, httpResponse, newTexture, format);
return true;
}
}
}
else // it was on the cache
{
//m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
WriteTextureData(httpRequest, httpResponse, texture, format);
return true;
}
}
//WriteTextureData(request,response,null,format);
// not found
//m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
return false;
// m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return true;
}
private void WriteTextureData(Hashtable request, Hashtable response, AssetBase texture, string format)
private void WriteTextureData(IOSHttpRequest request, IOSHttpResponse response, AssetBase texture, string format)
{
Hashtable headers = new Hashtable();
response["headers"] = headers;
string range = String.Empty;
if (((Hashtable)request["headers"])["range"] != null)
range = (string)((Hashtable)request["headers"])["range"];
else if (((Hashtable)request["headers"])["Range"] != null)
range = (string)((Hashtable)request["headers"])["Range"];
string range = request.Headers.GetOne("Range");
if (!String.IsNullOrEmpty(range)) // JP2's only
{
// Range request
int start, end;
if (Util.TryParseHttpRange(range, out start, out end))
if (TryParseRange(range, out start, out end))
{
// Before clamping start make sure we can satisfy it in order to avoid
// sending back the last byte instead of an error status
if (start >= texture.Data.Length)
{
// m_log.DebugFormat(
// "[GETTEXTURE]: Client requested range for texture {0} starting at {1} but texture has end of {2}",
// texture.ID, start, texture.Data.Length);
// Stricly speaking, as per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, we should be sending back
// Requested Range Not Satisfiable (416) here. However, it appears that at least recent implementations
// of the Linden Lab viewer (3.2.1 and 3.3.4 and probably earlier), a viewer that has previously
// received a very small texture may attempt to fetch bytes from the server past the
// range of data that it received originally. Whether this happens appears to depend on whether
// the viewer's estimation of how large a request it needs to make for certain discard levels
// (http://wiki.secondlife.com/wiki/Image_System#Discard_Level_and_Mip_Mapping), chiefly discard
// level 2. If this estimate is greater than the total texture size, returning a RequestedRangeNotSatisfiable
// here will cause the viewer to treat the texture as bad and never display the full resolution
// However, if we return PartialContent (or OK) instead, the viewer will display that resolution.
// response.StatusCode = (int)System.Net.HttpStatusCode.RequestedRangeNotSatisfiable;
// viewers don't seem to handle RequestedRangeNotSatisfiable and keep retrying with same parameters
response["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound;
response.StatusCode = (int)System.Net.HttpStatusCode.RequestedRangeNotSatisfiable;
}
else
{
// Handle the case where no second range value was given. This is equivalent to requesting
// the rest of the entity.
if (end == -1)
end = int.MaxValue;
end = Utils.Clamp(end, 0, texture.Data.Length - 1);
start = Utils.Clamp(start, 0, end);
int len = end - start + 1;
// m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
//m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
response["content-type"] = texture.Metadata.ContentType;
response["int_response_code"] = (int)System.Net.HttpStatusCode.PartialContent;
headers["Content-Range"] = String.Format("bytes {0}-{1}/{2}", start, end, texture.Data.Length);
// Always return PartialContent, even if the range covered the entire data length
// We were accidentally sending back 404 before in this situation
// https://issues.apache.org/bugzilla/show_bug.cgi?id=51878 supports sending 206 even if the
// entire range is requested, and viewer 3.2.2 (and very probably earlier) seems fine with this.
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
byte[] d = new byte[len];
Array.Copy(texture.Data, start, d, 0, len);
response["bin_response_data"] = d;
response["int_bytes"] = len;
response.ContentLength = len;
response.ContentType = texture.Metadata.ContentType;
response.AddHeader("Content-Range", String.Format("bytes {0}-{1}/{2}", start, end, texture.Data.Length));
response.Body.Write(texture.Data, start, len);
}
}
else
{
m_log.Warn("[GETTEXTURE]: Malformed Range header: " + range);
response["int_response_code"] = (int)System.Net.HttpStatusCode.BadRequest;
response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
}
}
else // JP2's or other formats
{
// Full content request
response["int_response_code"] = (int)System.Net.HttpStatusCode.OK;
response.StatusCode = (int)System.Net.HttpStatusCode.OK;
response.ContentLength = texture.Data.Length;
if (format == DefaultFormat)
response["content_type"] = texture.Metadata.ContentType;
response.ContentType = texture.Metadata.ContentType;
else
response["content_type"] = "image/" + format;
response["bin_response_data"] = texture.Data;
response["int_bytes"] = texture.Data.Length;
// response.Body.Write(texture.Data, 0, texture.Data.Length);
response.ContentType = "image/" + format;
response.Body.Write(texture.Data, 0, texture.Data.Length);
}
// if (response.StatusCode < 200 || response.StatusCode > 299)
@@ -289,41 +277,58 @@ namespace OpenSim.Capabilities.Handlers
// texture.FullID, range, response.StatusCode, response.ContentLength, texture.Data.Length);
}
private bool TryParseRange(string header, out int start, out int end)
{
if (header.StartsWith("bytes="))
{
string[] rangeValues = header.Substring(6).Split('-');
if (rangeValues.Length == 2)
{
if (Int32.TryParse(rangeValues[0], out start) && Int32.TryParse(rangeValues[1], out end))
return true;
}
}
start = end = 0;
return false;
}
private byte[] ConvertTextureData(AssetBase texture, string format)
{
m_log.DebugFormat("[GETTEXTURE]: Converting texture {0} to {1}", texture.ID, format);
byte[] data = Array.Empty<byte>();
byte[] data = new byte[0];
MemoryStream imgstream = new MemoryStream();
Bitmap mTexture = null;
ManagedImage managedImage = null;
Image image = null;
Bitmap mTexture = new Bitmap(1, 1);
ManagedImage managedImage;
Image image = (Image)mTexture;
try
{
// Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular data
imgstream = new MemoryStream();
// Decode image to System.Drawing.Image
if (OpenJPEG.DecodeToImage(texture.Data, out managedImage, out image) && image != null)
if (OpenJPEG.DecodeToImage(texture.Data, out managedImage, out image))
{
// Save to bitmap
mTexture = new Bitmap(image);
using(EncoderParameters myEncoderParameters = new EncoderParameters())
{
myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality,95L);
EncoderParameters myEncoderParameters = new EncoderParameters();
myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L);
// Save bitmap to stream
ImageCodecInfo codec = GetEncoderInfo("image/" + format);
if (codec != null)
{
mTexture.Save(imgstream, codec, myEncoderParameters);
// Save bitmap to stream
ImageCodecInfo codec = GetEncoderInfo("image/" + format);
if (codec != null)
{
mTexture.Save(imgstream, codec, myEncoderParameters);
// Write the stream to a byte array for output
data = imgstream.ToArray();
}
else
m_log.WarnFormat("[GETTEXTURE]: No such codec {0}", format);
data = imgstream.ToArray();
}
else
m_log.WarnFormat("[GETTEXTURE]: No such codec {0}", format);
}
}
catch (Exception e)
@@ -340,10 +345,11 @@ namespace OpenSim.Capabilities.Handlers
if (image != null)
image.Dispose();
if(managedImage != null)
managedImage.Clear();
if (imgstream != null)
{
imgstream.Close();
imgstream.Dispose();
}
}
return data;
@@ -362,4 +368,4 @@ namespace OpenSim.Capabilities.Handlers
return null;
}
}
}
}

View File

@@ -1,393 +0,0 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.IO;
using System.Net;
using System.Web;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Imaging;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
using Caps = OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Capabilities.Handlers
{
public class GetTextureRobustHandler : BaseStreamHandler
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IAssetService m_assetService;
public const string DefaultFormat = "x-j2c";
// TODO: Change this to a config option
private string m_RedirectURL = null;
public GetTextureRobustHandler(string path, IAssetService assService, string name, string description, string redirectURL)
: base("GET", path, name, description)
{
m_assetService = assService;
m_RedirectURL = redirectURL;
if (m_RedirectURL != null && !m_RedirectURL.EndsWith("/"))
m_RedirectURL += "/";
}
protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
// Try to parse the texture ID from the request URL
NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query);
string textureStr = query.GetOne("texture_id");
string format = query.GetOne("format");
//m_log.DebugFormat("[GETTEXTURE]: called {0}", textureStr);
if (m_assetService == null)
{
m_log.Error("[GETTEXTURE]: Cannot fetch texture " + textureStr + " without an asset service");
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return null;
}
UUID textureID;
if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID))
{
// m_log.DebugFormat("[GETTEXTURE]: Received request for texture id {0}", textureID);
string[] formats;
if (!string.IsNullOrEmpty(format))
{
formats = new string[1] { format.ToLower() };
}
else
{
formats = WebUtil.GetPreferredImageTypes(httpRequest.Headers.Get("Accept"));
if (formats.Length == 0)
formats = new string[1] { DefaultFormat }; // default
}
// OK, we have an array with preferred formats, possibly with only one entry
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
foreach (string f in formats)
{
if (FetchTexture(httpRequest, httpResponse, textureID, f))
break;
}
}
else
{
m_log.Warn("[GETTEXTURE]: Failed to parse a texture_id from GetTexture request: " + httpRequest.Url);
}
// m_log.DebugFormat(
// "[GETTEXTURE]: For texture {0} sending back response {1}, data length {2}",
// textureID, httpResponse.StatusCode, httpResponse.ContentLength);
return null;
}
/// <summary>
///
/// </summary>
/// <param name="httpRequest"></param>
/// <param name="httpResponse"></param>
/// <param name="textureID"></param>
/// <param name="format"></param>
/// <returns>False for "caller try another codec"; true otherwise</returns>
private bool FetchTexture(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, UUID textureID, string format)
{
// m_log.DebugFormat("[GETTEXTURE]: {0} with requested format {1}", textureID, format);
if(!String.IsNullOrEmpty(m_RedirectURL))
{
string textureUrl = m_RedirectURL + "?texture_id=" + textureID.ToString();
httpResponse.Redirect(textureUrl, HttpStatusCode.Moved);
m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl);
return true;
}
// Fetch, Misses or invalid return a 404
AssetBase texture = m_assetService.Get(textureID.ToString());
if (texture != null)
{
if (texture.Type != (sbyte)AssetType.Texture)
{
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return true;
}
if (format == DefaultFormat)
{
WriteTextureData(httpRequest, httpResponse, texture, format);
return true;
}
// need to convert format
AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
newTexture.Data = ConvertTextureData(texture, format);
if (newTexture.Data.Length == 0)
return false; // !!! Caller try another codec, please!
newTexture.Flags = AssetFlags.Collectable;
newTexture.Temporary = true;
newTexture.Local = true;
WriteTextureData(httpRequest, httpResponse, newTexture, format);
return true;
}
// not found
// m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return true;
}
private void WriteTextureData(IOSHttpRequest request, IOSHttpResponse response, AssetBase texture, string format)
{
string range = request.Headers.GetOne("Range");
if (!String.IsNullOrEmpty(range)) // JP2's only
{
// Range request
int start, end;
if (TryParseRange(range, out start, out end))
{
// Before clamping start make sure we can satisfy it in order to avoid
// sending back the last byte instead of an error status
if (start >= texture.Data.Length)
{
// m_log.DebugFormat(
// "[GETTEXTURE]: Client requested range for texture {0} starting at {1} but texture has end of {2}",
// texture.ID, start, texture.Data.Length);
// Stricly speaking, as per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, we should be sending back
// Requested Range Not Satisfiable (416) here. However, it appears that at least recent implementations
// of the Linden Lab viewer (3.2.1 and 3.3.4 and probably earlier), a viewer that has previously
// received a very small texture may attempt to fetch bytes from the server past the
// range of data that it received originally. Whether this happens appears to depend on whether
// the viewer's estimation of how large a request it needs to make for certain discard levels
// (http://wiki.secondlife.com/wiki/Image_System#Discard_Level_and_Mip_Mapping), chiefly discard
// level 2. If this estimate is greater than the total texture size, returning a RequestedRangeNotSatisfiable
// here will cause the viewer to treat the texture as bad and never display the full resolution
// However, if we return PartialContent (or OK) instead, the viewer will display that resolution.
// response.StatusCode = (int)System.Net.HttpStatusCode.RequestedRangeNotSatisfiable;
// response.AddHeader("Content-Range", String.Format("bytes */{0}", texture.Data.Length));
// response.StatusCode = (int)System.Net.HttpStatusCode.OK;
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
response.ContentType = texture.Metadata.ContentType;
}
else
{
// Handle the case where no second range value was given. This is equivalent to requesting
// the rest of the entity.
if (end == -1)
end = int.MaxValue;
end = Utils.Clamp(end, 0, texture.Data.Length - 1);
start = Utils.Clamp(start, 0, end);
int len = end - start + 1;
// m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
// Always return PartialContent, even if the range covered the entire data length
// We were accidentally sending back 404 before in this situation
// https://issues.apache.org/bugzilla/show_bug.cgi?id=51878 supports sending 206 even if the
// entire range is requested, and viewer 3.2.2 (and very probably earlier) seems fine with this.
//
// We also do not want to send back OK even if the whole range was satisfiable since this causes
// HTTP textures on at least Imprudence 1.4.0-beta2 to never display the final texture quality.
// if (end > maxEnd)
// response.StatusCode = (int)System.Net.HttpStatusCode.OK;
// else
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
response.ContentLength = len;
response.ContentType = texture.Metadata.ContentType;
response.AddHeader("Content-Range", String.Format("bytes {0}-{1}/{2}", start, end, texture.Data.Length));
response.RawBuffer = texture.Data;
response.RawBufferStart = start;
response.RawBufferLen = len;
}
}
else
{
m_log.Warn("[GETTEXTURE]: Malformed Range header: " + range);
response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
}
}
else // JP2's or other formats
{
// Full content request
response.StatusCode = (int)System.Net.HttpStatusCode.OK;
response.ContentLength = texture.Data.Length;
if (format == DefaultFormat)
response.ContentType = texture.Metadata.ContentType;
else
response.ContentType = "image/" + format;
response.RawBuffer = texture.Data;
response.RawBufferStart = 0;
response.RawBufferLen = texture.Data.Length;
}
// if (response.StatusCode < 200 || response.StatusCode > 299)
// m_log.WarnFormat(
// "[GETTEXTURE]: For texture {0} requested range {1} responded {2} with content length {3} (actual {4})",
// texture.FullID, range, response.StatusCode, response.ContentLength, texture.Data.Length);
// else
// m_log.DebugFormat(
// "[GETTEXTURE]: For texture {0} requested range {1} responded {2} with content length {3} (actual {4})",
// texture.FullID, range, response.StatusCode, response.ContentLength, texture.Data.Length);
}
/// <summary>
/// Parse a range header.
/// </summary>
/// <remarks>
/// As per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html,
/// this obeys range headers with two values (e.g. 533-4165) and no second value (e.g. 533-).
/// Where there is no value, -1 is returned.
/// FIXME: Need to cover the case where only a second value is specified (e.g. -4165), probably by returning -1
/// for start.</remarks>
/// <returns></returns>
/// <param name='header'></param>
/// <param name='start'>Start of the range. Undefined if this was not a number.</param>
/// <param name='end'>End of the range. Will be -1 if no end specified. Undefined if there was a raw string but this was not a number.</param>
private bool TryParseRange(string header, out int start, out int end)
{
start = end = 0;
if (header.StartsWith("bytes="))
{
string[] rangeValues = header.Substring(6).Split('-');
if (rangeValues.Length == 2)
{
if (!Int32.TryParse(rangeValues[0], out start))
return false;
string rawEnd = rangeValues[1];
if (rawEnd.Length == 0)
{
end = -1;
return true;
}
else if (Int32.TryParse(rawEnd, out end))
{
return true;
}
}
}
start = end = 0;
return false;
}
private byte[] ConvertTextureData(AssetBase texture, string format)
{
m_log.DebugFormat("[GETTEXTURE]: Converting texture {0} to {1}", texture.ID, format);
byte[] data = Array.Empty<byte>();
MemoryStream imgstream = new MemoryStream();
Bitmap mTexture = null;
ManagedImage managedImage = null;
Image image = null;
try
{
// Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular data
// Decode image to System.Drawing.Image
if (OpenJPEG.DecodeToImage(texture.Data, out managedImage, out image) && image != null)
{
// Save to bitmap
mTexture = new Bitmap(image);
using(EncoderParameters myEncoderParameters = new EncoderParameters())
{
myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality,95L);
// Save bitmap to stream
ImageCodecInfo codec = GetEncoderInfo("image/" + format);
if (codec != null)
{
mTexture.Save(imgstream, codec, myEncoderParameters);
// Write the stream to a byte array for output
data = imgstream.ToArray();
}
else
m_log.WarnFormat("[GETTEXTURE]: No such codec {0}", format);
}
}
}
catch (Exception e)
{
m_log.WarnFormat("[GETTEXTURE]: Unable to convert texture {0} to {1}: {2}", texture.ID, format, e.Message);
}
finally
{
// Reclaim memory, these are unmanaged resources
// If we encountered an exception, one or more of these will be null
if (mTexture != null)
mTexture.Dispose();
if (image != null)
image.Dispose();
if(managedImage != null)
managedImage.Clear();
if (imgstream != null)
imgstream.Dispose();
}
return data;
}
// From msdn
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (int j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
}
}

View File

@@ -33,7 +33,6 @@ using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Handlers.Base;
using OpenMetaverse;
namespace OpenSim.Capabilities.Handlers
{
public class GetTextureServerConnector : ServiceConnector
@@ -53,7 +52,7 @@ namespace OpenSim.Capabilities.Handlers
string assetService = serverConfig.GetString("AssetService", String.Empty);
if (assetService.Length == 0)
if (assetService == String.Empty)
throw new Exception("No AssetService in config file");
Object[] args = new Object[] { config };
@@ -63,11 +62,8 @@ namespace OpenSim.Capabilities.Handlers
if (m_AssetService == null)
throw new Exception(String.Format("Failed to load AssetService from {0}; config is {1}", assetService, m_ConfigName));
string rurl = serverConfig.GetString("GetTextureRedirectURL");
server.AddStreamHandler(
new GetTextureRobustHandler("/CAPS/GetTexture", m_AssetService, "GetTexture", null, rurl));
new GetTextureHandler("/CAPS/GetTexture/" /*+ UUID.Random() */, m_AssetService, "GetTexture", null));
}
}
}
}

View File

@@ -37,12 +37,12 @@ using OpenSim.Framework;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Tests.Common;
using OpenSim.Tests.Common.Mock;
/*
namespace OpenSim.Capabilities.Handlers.GetTexture.Tests
{
[TestFixture]
public class GetTextureHandlerTests : OpenSimTestCase
public class GetTextureHandlerTests
{
[Test]
public void TestTextureNotFound()
@@ -52,7 +52,7 @@ namespace OpenSim.Capabilities.Handlers.GetTexture.Tests
// Overkill - we only really need the asset service, not a whole scene.
Scene scene = new SceneHelpers().SetupScene();
GetTextureHandler handler = new GetTextureHandler("/gettexture", scene.AssetService, "TestGetTexture", null, null);
GetTextureHandler handler = new GetTextureHandler(null, scene.AssetService, "TestGetTexture", null);
TestOSHttpRequest req = new TestOSHttpRequest();
TestOSHttpResponse resp = new TestOSHttpResponse();
req.Url = new Uri("http://localhost/?texture_id=00000000-0000-1111-9999-000000000012");
@@ -60,5 +60,4 @@ namespace OpenSim.Capabilities.Handlers.GetTexture.Tests
Assert.That(resp.StatusCode, Is.EqualTo((int)System.Net.HttpStatusCode.NotFound));
}
}
}
*/
}

View File

@@ -1,33 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenSim.Capabilities.Handlers")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("http://opensimulator.org")]
[assembly: AssemblyProduct("OpenSim")]
[assembly: AssemblyCopyright("OpenSimulator developers")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("32350823-e1df-45e3-b7fa-0a58b4372433")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]

View File

@@ -0,0 +1,181 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.IO;
using System.Web;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Imaging;
using OpenSim.Framework;
using OpenSim.Framework.Capabilities;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Services.Interfaces;
using Caps = OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Capabilities.Handlers
{
public class UploadBakedTextureHandler
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private Caps m_HostCapsObj;
private IAssetService m_assetService;
private bool m_persistBakedTextures;
public UploadBakedTextureHandler(Caps caps, IAssetService assetService, bool persistBakedTextures)
{
m_HostCapsObj = caps;
m_assetService = assetService;
m_persistBakedTextures = persistBakedTextures;
}
/// <summary>
/// Handle a request from the client for a Uri to upload a baked texture.
/// </summary>
/// <param name="request"></param>
/// <param name="path"></param>
/// <param name="param"></param>
/// <param name="httpRequest"></param>
/// <param name="httpResponse"></param>
/// <returns>The upload response if the request is successful, null otherwise.</returns>
public string UploadBakedTexture(
string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
try
{
string capsBase = "/CAPS/" + m_HostCapsObj.CapsObjectPath;
string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000");
BakedTextureUploader uploader =
new BakedTextureUploader(capsBase + uploaderPath, m_HostCapsObj.HttpListener);
uploader.OnUpLoad += BakedTextureUploaded;
m_HostCapsObj.HttpListener.AddStreamHandler(
new BinaryStreamHandler(
"POST", capsBase + uploaderPath, uploader.uploaderCaps, "UploadBakedTexture", null));
string protocol = "http://";
if (m_HostCapsObj.SSLCaps)
protocol = "https://";
string uploaderURL = protocol + m_HostCapsObj.HostName + ":" +
m_HostCapsObj.Port.ToString() + capsBase + uploaderPath;
LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse();
uploadResponse.uploader = uploaderURL;
uploadResponse.state = "upload";
return LLSDHelpers.SerialiseLLSDReply(uploadResponse);
}
catch (Exception e)
{
m_log.ErrorFormat("[UPLOAD BAKED TEXTURE HANDLER]: {0}{1}", e.Message, e.StackTrace);
}
return null;
}
/// <summary>
/// Called when a baked texture has been successfully uploaded by a client.
/// </summary>
/// <param name="assetID"></param>
/// <param name="data"></param>
private void BakedTextureUploaded(UUID assetID, byte[] data)
{
// m_log.DebugFormat("[UPLOAD BAKED TEXTURE HANDLER]: Received baked texture {0}", assetID.ToString());
AssetBase asset;
asset = new AssetBase(assetID, "Baked Texture", (sbyte)AssetType.Texture, m_HostCapsObj.AgentID.ToString());
asset.Data = data;
asset.Temporary = true;
asset.Local = !m_persistBakedTextures; // Local assets aren't persisted, non-local are
m_assetService.Store(asset);
}
}
class BakedTextureUploader
{
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public event Action<UUID, byte[]> OnUpLoad;
private string uploaderPath = String.Empty;
private UUID newAssetID;
private IHttpServer httpListener;
public BakedTextureUploader(string path, IHttpServer httpServer)
{
newAssetID = UUID.Random();
uploaderPath = path;
httpListener = httpServer;
// m_log.InfoFormat("[CAPS] baked texture upload starting for {0}",newAssetID);
}
/// <summary>
/// Handle raw uploaded baked texture data.
/// </summary>
/// <param name="data"></param>
/// <param name="path"></param>
/// <param name="param"></param>
/// <returns></returns>
public string uploaderCaps(byte[] data, string path, string param)
{
Action<UUID, byte[]> handlerUpLoad = OnUpLoad;
// Don't do this asynchronously, otherwise it's possible for the client to send set appearance information
// on another thread which might send out avatar updates before the asset has been put into the asset
// service.
if (handlerUpLoad != null)
handlerUpLoad(newAssetID, data);
string res = String.Empty;
LLSDAssetUploadComplete uploadComplete = new LLSDAssetUploadComplete();
uploadComplete.new_asset = newAssetID.ToString();
uploadComplete.new_inventory_item = UUID.Zero;
uploadComplete.state = "complete";
res = LLSDHelpers.SerialiseLLSDReply(uploadComplete);
httpListener.RemoveStreamHandler("POST", uploaderPath);
// m_log.DebugFormat("[BAKED TEXTURE UPLOADER]: baked texture upload completed for {0}", newAssetID);
return res;
}
}
}

View File

@@ -0,0 +1,412 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Capabilities;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces;
using Caps = OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Capabilities.Handlers
{
public class WebFetchInvDescHandler
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IInventoryService m_InventoryService;
private ILibraryService m_LibraryService;
// private object m_fetchLock = new Object();
public WebFetchInvDescHandler(IInventoryService invService, ILibraryService libService)
{
m_InventoryService = invService;
m_LibraryService = libService;
}
public string FetchInventoryDescendentsRequest(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
// lock (m_fetchLock)
// {
// m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Received request {0}", request);
// nasty temporary hack here, the linden client falsely
// identifies the uuid 00000000-0000-0000-0000-000000000000
// as a string which breaks us
//
// correctly mark it as a uuid
//
request = request.Replace("<string>00000000-0000-0000-0000-000000000000</string>", "<uuid>00000000-0000-0000-0000-000000000000</uuid>");
// another hack <integer>1</integer> results in a
// System.ArgumentException: Object type System.Int32 cannot
// be converted to target type: System.Boolean
//
request = request.Replace("<key>fetch_folders</key><integer>0</integer>", "<key>fetch_folders</key><boolean>0</boolean>");
request = request.Replace("<key>fetch_folders</key><integer>1</integer>", "<key>fetch_folders</key><boolean>1</boolean>");
Hashtable hash = new Hashtable();
try
{
hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request));
}
catch (LLSD.LLSDParseException e)
{
m_log.ErrorFormat("[WEB FETCH INV DESC HANDLER]: Fetch error: {0}{1}" + e.Message, e.StackTrace);
m_log.Error("Request: " + request);
}
ArrayList foldersrequested = (ArrayList)hash["folders"];
string response = "";
for (int i = 0; i < foldersrequested.Count; i++)
{
string inventoryitemstr = "";
Hashtable inventoryhash = (Hashtable)foldersrequested[i];
LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents();
try
{
LLSDHelpers.DeserialiseOSDMap(inventoryhash, llsdRequest);
}
catch (Exception e)
{
m_log.Debug("[WEB FETCH INV DESC HANDLER]: caught exception doing OSD deserialize" + e);
}
LLSDInventoryDescendents reply = FetchInventoryReply(llsdRequest);
inventoryitemstr = LLSDHelpers.SerialiseLLSDReply(reply);
inventoryitemstr = inventoryitemstr.Replace("<llsd><map><key>folders</key><array>", "");
inventoryitemstr = inventoryitemstr.Replace("</array></map></llsd>", "");
response += inventoryitemstr;
}
if (response.Length == 0)
{
// Ter-guess: If requests fail a lot, the client seems to stop requesting descendants.
// Therefore, I'm concluding that the client only has so many threads available to do requests
// and when a thread stalls.. is stays stalled.
// Therefore we need to return something valid
response = "<llsd><map><key>folders</key><array /></map></llsd>";
}
else
{
response = "<llsd><map><key>folders</key><array>" + response + "</array></map></llsd>";
}
// m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Replying to CAPS fetch inventory request");
//m_log.Debug("[WEB FETCH INV DESC HANDLER] "+response);
return response;
// }
}
/// <summary>
/// Construct an LLSD reply packet to a CAPS inventory request
/// </summary>
/// <param name="invFetch"></param>
/// <returns></returns>
private LLSDInventoryDescendents FetchInventoryReply(LLSDFetchInventoryDescendents invFetch)
{
LLSDInventoryDescendents reply = new LLSDInventoryDescendents();
LLSDInventoryFolderContents contents = new LLSDInventoryFolderContents();
contents.agent_id = invFetch.owner_id;
contents.owner_id = invFetch.owner_id;
contents.folder_id = invFetch.folder_id;
reply.folders.Array.Add(contents);
InventoryCollection inv = new InventoryCollection();
inv.Folders = new List<InventoryFolderBase>();
inv.Items = new List<InventoryItemBase>();
int version = 0;
inv
= Fetch(
invFetch.owner_id, invFetch.folder_id, invFetch.owner_id,
invFetch.fetch_folders, invFetch.fetch_items, invFetch.sort_order, out version);
if (inv.Folders != null)
{
foreach (InventoryFolderBase invFolder in inv.Folders)
{
contents.categories.Array.Add(ConvertInventoryFolder(invFolder));
}
}
if (inv.Items != null)
{
foreach (InventoryItemBase invItem in inv.Items)
{
contents.items.Array.Add(ConvertInventoryItem(invItem));
}
}
contents.descendents = contents.items.Array.Count + contents.categories.Array.Count;
contents.version = version;
// m_log.DebugFormat(
// "[WEB FETCH INV DESC HANDLER]: Replying to request for folder {0} (fetch items {1}, fetch folders {2}) with {3} items and {4} folders for agent {5}",
// invFetch.folder_id,
// invFetch.fetch_items,
// invFetch.fetch_folders,
// contents.items.Array.Count,
// contents.categories.Array.Count,
// invFetch.owner_id);
return reply;
}
/// <summary>
/// Handle the caps inventory descendents fetch.
/// </summary>
/// <param name="agentID"></param>
/// <param name="folderID"></param>
/// <param name="ownerID"></param>
/// <param name="fetchFolders"></param>
/// <param name="fetchItems"></param>
/// <param name="sortOrder"></param>
/// <param name="version"></param>
/// <returns>An empty InventoryCollection if the inventory look up failed</returns>
private InventoryCollection Fetch(
UUID agentID, UUID folderID, UUID ownerID,
bool fetchFolders, bool fetchItems, int sortOrder, out int version)
{
// m_log.DebugFormat(
// "[WEB FETCH INV DESC HANDLER]: Fetching folders ({0}), items ({1}) from {2} for agent {3}",
// fetchFolders, fetchItems, folderID, agentID);
// FIXME MAYBE: We're not handling sortOrder!
version = 0;
InventoryFolderImpl fold;
if (m_LibraryService != null && m_LibraryService.LibraryRootFolder != null && agentID == m_LibraryService.LibraryRootFolder.Owner)
{
if ((fold = m_LibraryService.LibraryRootFolder.FindFolder(folderID)) != null)
{
InventoryCollection ret = new InventoryCollection();
ret.Folders = new List<InventoryFolderBase>();
ret.Items = fold.RequestListOfItems();
return ret;
}
}
InventoryCollection contents = new InventoryCollection();
if (folderID != UUID.Zero)
{
contents = m_InventoryService.GetFolderContent(agentID, folderID);
InventoryFolderBase containingFolder = new InventoryFolderBase();
containingFolder.ID = folderID;
containingFolder.Owner = agentID;
containingFolder = m_InventoryService.GetFolder(containingFolder);
if (containingFolder != null)
{
// m_log.DebugFormat(
// "[WEB FETCH INV DESC HANDLER]: Retrieved folder {0} {1} for agent id {2}",
// containingFolder.Name, containingFolder.ID, agentID);
version = containingFolder.Version;
// if (fetchItems)
// {
// List<InventoryItemBase> linkedItemsToAdd = new List<InventoryItemBase>();
//
// foreach (InventoryItemBase item in contents.Items)
// {
// if (item.AssetType == (int)AssetType.Link)
// {
// InventoryItemBase linkedItem = m_InventoryService.GetItem(new InventoryItemBase(item.AssetID));
//
// // Take care of genuinely broken links where the target doesn't exist
// // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate,
// // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles
// // rather than having to keep track of every folder requested in the recursion.
// if (linkedItem != null && linkedItem.AssetType != (int)AssetType.Link)
// linkedItemsToAdd.Insert(0, linkedItem);
// }
// }
//
// foreach (InventoryItemBase linkedItem in linkedItemsToAdd)
// {
// m_log.DebugFormat(
// "[WEB FETCH INV DESC HANDLER]: Inserted linked item {0} for link in folder {1} for agent {2}",
// linkedItem.Name, folderID, agentID);
//
// contents.Items.Add(linkedItem);
// }
//
// // If the folder requested contains links, then we need to send those folders first, otherwise the links
// // will be broken in the viewer.
// HashSet<UUID> linkedItemFolderIdsToSend = new HashSet<UUID>();
// foreach (InventoryItemBase item in contents.Items)
// {
// if (item.AssetType == (int)AssetType.Link)
// {
// InventoryItemBase linkedItem = m_InventoryService.GetItem(new InventoryItemBase(item.AssetID));
//
// // Take care of genuinely broken links where the target doesn't exist
// // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate,
// // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles
// // rather than having to keep track of every folder requested in the recursion.
// if (linkedItem != null && linkedItem.AssetType != (int)AssetType.Link)
// {
// // We don't need to send the folder if source and destination of the link are in the same
// // folder.
// if (linkedItem.Folder != containingFolder.ID)
// linkedItemFolderIdsToSend.Add(linkedItem.Folder);
// }
// }
// }
//
// foreach (UUID linkedItemFolderId in linkedItemFolderIdsToSend)
// {
// m_log.DebugFormat(
// "[WEB FETCH INV DESC HANDLER]: Recursively fetching folder {0} linked by item in folder {1} for agent {2}",
// linkedItemFolderId, folderID, agentID);
//
// int dummyVersion;
// InventoryCollection linkedCollection
// = Fetch(
// agentID, linkedItemFolderId, ownerID, fetchFolders, fetchItems, sortOrder, out dummyVersion);
//
// InventoryFolderBase linkedFolder = new InventoryFolderBase(linkedItemFolderId);
// linkedFolder.Owner = agentID;
// linkedFolder = m_InventoryService.GetFolder(linkedFolder);
//
//// contents.Folders.AddRange(linkedCollection.Folders);
//
// contents.Folders.Add(linkedFolder);
// contents.Items.AddRange(linkedCollection.Items);
// }
// }
}
}
else
{
// Lost items don't really need a version
version = 1;
}
return contents;
}
/// <summary>
/// Convert an internal inventory folder object into an LLSD object.
/// </summary>
/// <param name="invFolder"></param>
/// <returns></returns>
private LLSDInventoryFolder ConvertInventoryFolder(InventoryFolderBase invFolder)
{
LLSDInventoryFolder llsdFolder = new LLSDInventoryFolder();
llsdFolder.folder_id = invFolder.ID;
llsdFolder.parent_id = invFolder.ParentID;
llsdFolder.name = invFolder.Name;
if (invFolder.Type == (short)AssetType.Unknown || !Enum.IsDefined(typeof(AssetType), (sbyte)invFolder.Type))
llsdFolder.type = "-1";
else
llsdFolder.type = Utils.AssetTypeToString((AssetType)invFolder.Type);
llsdFolder.preferred_type = "-1";
return llsdFolder;
}
/// <summary>
/// Convert an internal inventory item object into an LLSD object.
/// </summary>
/// <param name="invItem"></param>
/// <returns></returns>
private LLSDInventoryItem ConvertInventoryItem(InventoryItemBase invItem)
{
LLSDInventoryItem llsdItem = new LLSDInventoryItem();
llsdItem.asset_id = invItem.AssetID;
llsdItem.created_at = invItem.CreationDate;
llsdItem.desc = invItem.Description;
llsdItem.flags = (int)invItem.Flags;
llsdItem.item_id = invItem.ID;
llsdItem.name = invItem.Name;
llsdItem.parent_id = invItem.Folder;
try
{
llsdItem.type = Utils.AssetTypeToString((AssetType)invItem.AssetType);
llsdItem.inv_type = Utils.InventoryTypeToString((InventoryType)invItem.InvType);
}
catch (Exception e)
{
m_log.ErrorFormat(
"[WEB FETCH INV DESC HANDLER]: Problem setting asset {0} inventory {1} types while converting inventory item {2}: {3}",
invItem.AssetType, invItem.InvType, invItem.Name, e.Message);
}
llsdItem.permissions = new LLSDPermissions();
llsdItem.permissions.creator_id = invItem.CreatorIdAsUuid;
llsdItem.permissions.base_mask = (int)invItem.CurrentPermissions;
llsdItem.permissions.everyone_mask = (int)invItem.EveryOnePermissions;
llsdItem.permissions.group_id = invItem.GroupID;
llsdItem.permissions.group_mask = (int)invItem.GroupPermissions;
llsdItem.permissions.is_owner_group = invItem.GroupOwned;
llsdItem.permissions.next_owner_mask = (int)invItem.NextPermissions;
llsdItem.permissions.owner_id = invItem.Owner;
llsdItem.permissions.owner_mask = (int)invItem.CurrentPermissions;
llsdItem.sale_info = new LLSDSaleInfo();
llsdItem.sale_info.sale_price = invItem.SalePrice;
switch (invItem.SaleType)
{
default:
llsdItem.sale_info.sale_type = "not";
break;
case 1:
llsdItem.sale_info.sale_type = "original";
break;
case 2:
llsdItem.sale_info.sale_type = "copy";
break;
case 3:
llsdItem.sale_info.sale_type = "contents";
break;
}
return llsdItem;
}
}
}

Some files were not shown because too many files have changed in this diff Show More