Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
442036773d | ||
|
|
bb9be982d8 | ||
|
|
8eca98da2e | ||
|
|
960c02f17f | ||
|
|
75dc363717 | ||
|
|
65b04f0f88 | ||
|
|
fb8daa59a9 | ||
|
|
83eaeb878b | ||
|
|
1c19e6dcb2 | ||
|
|
394f7cd842 | ||
|
|
86c1a39bac | ||
|
|
d9c1e6465a | ||
|
|
53d8caa978 | ||
|
|
6ac9c46ac2 | ||
|
|
53db460194 | ||
|
|
cca087cba4 | ||
|
|
209af09b28 | ||
|
|
d77c24ab98 | ||
|
|
cc72963cac | ||
|
|
a680dfc384 | ||
|
|
05bb9105ce | ||
|
|
7888ebc046 | ||
|
|
20b2a89e18 | ||
|
|
0a43d0391e | ||
|
|
0b3ba897c0 | ||
|
|
e0b9b410ee | ||
|
|
7cc03d0a0f | ||
|
|
e55a8b78e2 | ||
|
|
e29b80cd3d | ||
|
|
d926f5b757 | ||
|
|
4663412837 | ||
|
|
2cef09e9b6 | ||
|
|
07cb6268d9 | ||
|
|
60cf1d14cd | ||
|
|
fce63e1cfa | ||
|
|
712cee6967 | ||
|
|
c9825a646b | ||
|
|
7e5e5d7eda | ||
|
|
3f2c01c522 | ||
|
|
82685c4117 | ||
|
|
e07ed7b569 | ||
|
|
5b2a0e74ed | ||
|
|
565e72deda | ||
|
|
57f9c35e77 | ||
|
|
91281b2b0b | ||
|
|
acce61c48f | ||
|
|
8b096d9ad8 | ||
|
|
a3eb5bd8ad | ||
|
|
e8272ab138 | ||
|
|
98cef2a961 | ||
|
|
2d065bd327 | ||
|
|
81c484d713 | ||
|
|
335687c846 | ||
|
|
cdbf39ddba | ||
|
|
269e20ca11 | ||
|
|
0bcd3860cf | ||
|
|
94b5ea1b5a | ||
|
|
9106d82382 | ||
|
|
5d3d600b2e | ||
|
|
31a43c7fbc | ||
|
|
82fd8c46ea | ||
|
|
0ce8a5f76a | ||
|
|
34114c4e28 | ||
|
|
f839385af9 | ||
|
|
a6bce45692 | ||
|
|
7615d12148 | ||
|
|
0365fec926 | ||
|
|
53d21bcb50 | ||
|
|
c45392c17e | ||
|
|
7ac437987a | ||
|
|
9e17b07e8b | ||
|
|
8600ea2763 | ||
|
|
cd6fe5c4e6 | ||
|
|
64884bf65c | ||
|
|
c01db95f48 | ||
|
|
3fbadb79ba | ||
|
|
6eeba80d2a | ||
|
|
04431fcb01 | ||
|
|
bbe9af5576 | ||
|
|
6b4e4da89e | ||
|
|
b6e963c830 | ||
|
|
8ea08cf4b4 | ||
|
|
d92fc1dfb3 | ||
|
|
83c018ebcd | ||
|
|
cf763b8306 | ||
|
|
5cda6ab754 | ||
|
|
6f6e05d775 | ||
|
|
c07fdc6ac4 | ||
|
|
2e6f5f6bcd | ||
|
|
705b3fe414 | ||
|
|
55aaea8b91 | ||
|
|
f4e32350ed | ||
|
|
58f5e4b47a | ||
|
|
ddacc87617 | ||
|
|
c5d1d6c47b | ||
|
|
23ebe65add | ||
|
|
c611966828 | ||
|
|
b9fa9a801e | ||
|
|
ec38593674 | ||
|
|
f018047460 | ||
|
|
60a0395837 | ||
|
|
d27ed42897 | ||
|
|
8bdc238ca4 | ||
|
|
6903b60a0f | ||
|
|
46dec8666c | ||
|
|
352774d5bf | ||
|
|
34c0bd212a | ||
|
|
3032b49ec4 |
23
.gitattributes
vendored
23
.gitattributes
vendored
@@ -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
|
||||
62
.github/workflows/msbuildnet.yml
vendored
62
.github/workflows/msbuildnet.yml
vendored
@@ -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 }}
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -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,8 +23,6 @@
|
||||
*/*/*/*/*/bin
|
||||
*/*/*/*/*/*/bin
|
||||
*/*/*/*/*/*/*/bin
|
||||
.vs/
|
||||
addon-modules/
|
||||
bin/Debug/*.dll
|
||||
bin/*.dll.mdb
|
||||
bin/*.db
|
||||
@@ -51,7 +38,6 @@ bin/ScriptEngines/*-*-*-*-*
|
||||
bin/ScriptEngines/*.dll
|
||||
bin/ScriptEngines/*/*.dll
|
||||
bin/ScriptEngines/*/*.state
|
||||
bin/ScriptEngines/Yengine/*
|
||||
bin/*.maddin
|
||||
bin/*.exe
|
||||
bin/*.ini
|
||||
@@ -62,8 +48,6 @@ 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,17 +60,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.suo
|
||||
OpenSim.userprefs
|
||||
Prebuild/Prebuild.build
|
||||
Prebuild/Prebuild.sln
|
||||
@@ -99,6 +78,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/
|
||||
@@ -117,6 +97,3 @@ OpenSim/Tests/test-results/
|
||||
test-results/
|
||||
doc/html
|
||||
doc/doxygen.error.log
|
||||
bin/OpenSim.ini
|
||||
|
||||
*.patch
|
||||
|
||||
23
.hgignore
Normal file
23
.hgignore
Normal 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)$
|
||||
.+~$
|
||||
274
.nant/local.include
Normal file
274
.nant/local.include
Normal file
@@ -0,0 +1,274 @@
|
||||
<!-- -*- 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 dir="${distbindir}/ThirdParty"/>
|
||||
<delete>
|
||||
<fileset basedir="${distbindir}">
|
||||
<include name="compile.bat"/>
|
||||
<include name="BUILDING.md"/>
|
||||
<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}" />
|
||||
|
||||
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.server.handlers.tests">
|
||||
<arg value="./bin/OpenSim.Server.Handlers.Tests.dll" />
|
||||
</exec>
|
||||
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.server.handlers.tests)==0}" />
|
||||
|
||||
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.services.inventoryservice.tests">
|
||||
<arg value="./bin/OpenSim.Services.InventoryService.Tests.dll" />
|
||||
</exec>
|
||||
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.services.inventoryservice.tests)==0}" />
|
||||
|
||||
<delete dir="%temp%"/>
|
||||
</target>
|
||||
|
||||
<target name="test-stress" depends="build, find-nunit">
|
||||
<setenv name="MONO_THREADS_PER_CPU" value="100" />
|
||||
|
||||
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.tests.stress">
|
||||
<arg value="./bin/OpenSim.Tests.Stress.dll" />
|
||||
</exec>
|
||||
|
||||
<fail message="Failures reported in stress tests." unless="${int::parse(testresult.opensim.tests.stress)==0}" />
|
||||
<delete dir="%temp%"/>
|
||||
</target>
|
||||
|
||||
<target name="test-perf" depends="build, find-nunit">
|
||||
<setenv name="MONO_THREADS_PER_CPU" value="100" />
|
||||
|
||||
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.tests.performance">
|
||||
<arg value="./bin/OpenSim.Tests.Performance.dll" />
|
||||
</exec>
|
||||
|
||||
<fail message="Failures reported in performance tests." unless="${int::parse(testresult.opensim.tests.performance)==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>
|
||||
|
||||
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.server.handlers.tests">
|
||||
<arg value="./bin/OpenSim.Server.Handlers.Tests.dll" />
|
||||
<arg value="-xml=test-results/OpenSim.Server.Handlers.Tests.dll-Results.xml" />
|
||||
</exec>
|
||||
|
||||
<exec program="${nunitcmd}" failonerror="false" resultproperty="testresult.opensim.services.inventoryservice.tests">
|
||||
<arg value="./bin/OpenSim.Services.InventoryService.Tests.dll" />
|
||||
<arg value="-xml=test-results/OpenSim.Services.InventoryService.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}" />
|
||||
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.opensim.services.inventoryservice.tests)==0}" />
|
||||
</target>
|
||||
|
||||
<target name="doxygen">
|
||||
<exec program="doxygen" workingdir="doc" commandline="doxygen.conf" />
|
||||
</target>
|
||||
105
BUILDING.md
105
BUILDING.md
@@ -1,89 +1,36 @@
|
||||
# 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:
|
||||
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
|
||||
|
||||
* [dotnet 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
# Building on Linux
|
||||
|
||||
optionally also
|
||||
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
|
||||
|
||||
* [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
|
||||
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
|
||||
* http://opensimulator.org/wiki/Configuration
|
||||
102
CONTRIBUTORS.txt
102
CONTRIBUTORS.txt
@@ -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
|
||||
* Robert 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,65 +55,36 @@ 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
|
||||
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
|
||||
* dslake (Intel)
|
||||
* FredoChaplin
|
||||
* FreakyTech
|
||||
* Garmin Kawaguichi
|
||||
* Gavin Hird
|
||||
* Gerhard
|
||||
* Godfrey
|
||||
* Greg C.
|
||||
@@ -106,18 +94,12 @@ what it is today.
|
||||
* 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)
|
||||
@@ -125,36 +107,25 @@ what it is today.
|
||||
* 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,42 +134,32 @@ 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)
|
||||
@@ -210,18 +171,23 @@ what it is today.
|
||||
* 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)
|
||||
|
||||
7241
OpenSim.FxCop
Normal file
7241
OpenSim.FxCop
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,6 @@ using System.Collections.Generic;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace OpenSim.Groups
|
||||
public string AccessToken;
|
||||
}
|
||||
|
||||
public class ExtendedGroupMembersData
|
||||
public class ExtendedGroupMembersData
|
||||
{
|
||||
// This is the only difference: this is a string
|
||||
public string AgentID;
|
||||
@@ -65,7 +65,7 @@ namespace OpenSim.Groups
|
||||
public UUID RoleID;
|
||||
// This is the only difference: this is a string
|
||||
public string MemberID;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public struct ExtendedGroupNoticeData
|
||||
@@ -174,7 +174,7 @@ namespace OpenSim.Groups
|
||||
if (dict.ContainsKey("ServiceLocation") && dict["ServiceLocation"] != null)
|
||||
grec.ServiceLocation = dict["ServiceLocation"].ToString();
|
||||
else
|
||||
grec.ServiceLocation = string.Empty;
|
||||
grec.GroupName = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("ShownInList") && dict["ShownInList"] != null)
|
||||
grec.ShowInList = bool.Parse(dict["ShownInList"].ToString());
|
||||
|
||||
185
OpenSim/Addons/Groups/GroupsMessagingModule.cs
Executable file → Normal file
185
OpenSim/Addons/Groups/GroupsMessagingModule.cs
Executable file → Normal file
@@ -56,8 +56,8 @@ namespace OpenSim.Groups
|
||||
private IGroupsServicesConnector m_groupData = null;
|
||||
|
||||
// Config Options
|
||||
private bool m_groupMessagingEnabled;
|
||||
private bool m_debugEnabled;
|
||||
private bool m_groupMessagingEnabled = false;
|
||||
private bool m_debugEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, module only tries to send group IMs to online users by querying cached presence information.
|
||||
@@ -83,6 +83,7 @@ namespace OpenSim.Groups
|
||||
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)
|
||||
@@ -110,17 +111,9 @@ namespace OpenSim.Groups
|
||||
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_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true);
|
||||
|
||||
m_log.InfoFormat(
|
||||
"[Groups.Messaging]: GroupsMessagingModule enabled with MessageOnlineOnly = {0}, DebugEnabled = {1}",
|
||||
@@ -131,7 +124,7 @@ namespace OpenSim.Groups
|
||||
{
|
||||
if (!m_groupMessagingEnabled)
|
||||
return;
|
||||
|
||||
|
||||
scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
|
||||
m_sceneList.Add(scene);
|
||||
|
||||
@@ -140,14 +133,6 @@ namespace OpenSim.Groups
|
||||
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)
|
||||
@@ -163,7 +148,7 @@ namespace OpenSim.Groups
|
||||
if (m_groupData == null)
|
||||
{
|
||||
m_log.Error("[Groups.Messaging]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
|
||||
RemoveRegion(scene);
|
||||
RemoveRegion(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,8 +172,10 @@ namespace OpenSim.Groups
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (m_presenceService == null)
|
||||
m_presenceService = scene.PresenceService;
|
||||
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
@@ -218,7 +205,7 @@ namespace OpenSim.Groups
|
||||
m_msgTransferModule = null;
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
@@ -235,25 +222,6 @@ namespace OpenSim.Groups
|
||||
|
||||
#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.
|
||||
@@ -262,7 +230,7 @@ namespace OpenSim.Groups
|
||||
{
|
||||
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)
|
||||
@@ -274,35 +242,23 @@ namespace OpenSim.Groups
|
||||
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.
|
||||
string[] t1 = groupMembers.ConvertAll<string>(gmd => gmd.AgentID.ToString()).ToArray();
|
||||
|
||||
// We cache in order not to overwhelm the presence service on large grids with many groups. This does
|
||||
// We cache in order not to overwhlem 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);
|
||||
}
|
||||
@@ -317,6 +273,8 @@ namespace OpenSim.Groups
|
||||
// "[Groups.Messaging]: SendMessageToGroup called for group {0} with {1} visible members, {2} online",
|
||||
// groupID, groupMembersCount, groupMembers.Count());
|
||||
|
||||
int requestStartTick = Environment.TickCount;
|
||||
|
||||
im.imSessionID = groupID.Guid;
|
||||
im.fromGroup = true;
|
||||
IClientAPI thisClient = GetActiveClient(fromAgentID);
|
||||
@@ -325,13 +283,6 @@ namespace OpenSim.Groups
|
||||
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;
|
||||
@@ -348,27 +299,12 @@ namespace OpenSim.Groups
|
||||
|
||||
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))
|
||||
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);
|
||||
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} has dropped session, not delivering to them", member.AgentID);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -414,7 +350,7 @@ namespace OpenSim.Groups
|
||||
"[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)
|
||||
@@ -445,13 +381,13 @@ namespace OpenSim.Groups
|
||||
// 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
|
||||
// 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}",
|
||||
m_log.DebugFormat("[Groups.Messaging]: {0} called, IM from region {1}",
|
||||
System.Reflection.MethodBase.GetCurrentMethod().Name, regionID);
|
||||
|
||||
DebugGridInstantMessage(msg);
|
||||
@@ -487,7 +423,7 @@ namespace OpenSim.Groups
|
||||
{
|
||||
return gmd.AgentID == sp.UUID;
|
||||
});
|
||||
if (m.AgentID.IsZero())
|
||||
if (m.AgentID == UUID.Zero)
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he is not a member of the group", sp.UUID);
|
||||
@@ -508,7 +444,7 @@ namespace OpenSim.Groups
|
||||
m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he has an agent in region of origin", sp.UUID);
|
||||
return;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: not skipping agent {0}", sp.UUID);
|
||||
@@ -531,7 +467,7 @@ namespace OpenSim.Groups
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,7 +491,7 @@ namespace OpenSim.Groups
|
||||
break;
|
||||
|
||||
case (byte)InstantMessageDialog.SessionSend:
|
||||
// User hasn't dropped, so they're in the session,
|
||||
// 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)
|
||||
@@ -599,34 +535,35 @@ namespace OpenSim.Groups
|
||||
{
|
||||
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)
|
||||
);
|
||||
eq.ChatterboxInvitation(
|
||||
GroupID
|
||||
, groupInfo.GroupName
|
||||
, new UUID(msg.fromAgentID)
|
||||
, 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);
|
||||
}
|
||||
eq.ChatterBoxSessionAgentListUpdates(
|
||||
new UUID(GroupID)
|
||||
, AgentID
|
||||
, new UUID(msg.toAgentID)
|
||||
, false //canVoiceChat
|
||||
, false //isModerator
|
||||
, false //text mute
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,7 +590,7 @@ namespace OpenSim.Groups
|
||||
UUID AgentID = new UUID(im.fromAgentID);
|
||||
|
||||
GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
|
||||
|
||||
|
||||
if (groupInfo != null)
|
||||
{
|
||||
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
|
||||
@@ -661,12 +598,14 @@ namespace OpenSim.Groups
|
||||
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);
|
||||
}
|
||||
queue.ChatterBoxSessionAgentListUpdates(
|
||||
GroupID
|
||||
, AgentID
|
||||
, new UUID(im.toAgentID)
|
||||
, false //canVoiceChat
|
||||
, false //isModerator
|
||||
, false //text mute
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,7 +615,7 @@ namespace OpenSim.Groups
|
||||
UUID GroupID = new UUID(im.imSessionID);
|
||||
UUID AgentID = new UUID(im.fromAgentID);
|
||||
|
||||
if (m_debugEnabled)
|
||||
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
|
||||
@@ -708,7 +647,11 @@ namespace OpenSim.Groups
|
||||
bodyMap.Add("session_info", sessionMap);
|
||||
|
||||
IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
|
||||
queue?.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
|
||||
|
||||
if (queue != null)
|
||||
{
|
||||
queue.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
|
||||
}
|
||||
}
|
||||
|
||||
private void DebugGridInstantMessage(GridInstantMessage im)
|
||||
|
||||
794
OpenSim/Addons/Groups/GroupsModule.cs
Executable file → Normal file
794
OpenSim/Addons/Groups/GroupsModule.cs
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -95,13 +95,13 @@ namespace OpenSim.Groups
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, string token)
|
||||
{
|
||||
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
if (GroupID == UUID.Zero && (GroupName == null || (GroupName != null && GroupName == string.Empty)))
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
if (!GroupID.IsZero())
|
||||
if (GroupID != UUID.Zero)
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
if (!string.IsNullOrEmpty(GroupName))
|
||||
if (GroupName != null && GroupName != string.Empty)
|
||||
sendData["Name"] = GroupsDataUtils.Sanitize(GroupName);
|
||||
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
@@ -268,24 +268,18 @@ namespace OpenSim.Groups
|
||||
sendData["METHOD"] = method;
|
||||
|
||||
string reply = string.Empty;
|
||||
try
|
||||
{
|
||||
lock (m_Lock)
|
||||
reply = SynchronousRestFormsRequester.MakeRequest("POST",
|
||||
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))
|
||||
if (reply == string.Empty || reply == null)
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply);
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(
|
||||
reply);
|
||||
|
||||
return replyData;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -32,7 +32,6 @@ 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;
|
||||
@@ -103,7 +102,7 @@ namespace OpenSim.Groups
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Add(scene);
|
||||
|
||||
@@ -135,7 +134,7 @@ namespace OpenSim.Groups
|
||||
{
|
||||
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,
|
||||
new HGGroupsServiceRobustConnector(m_Config, MainServer.Instance, string.Empty,
|
||||
scene.RequestModuleInterface<IOfflineIMService>(), scene.RequestModuleInterface<IUserAccountService>());
|
||||
|
||||
}
|
||||
@@ -164,12 +163,13 @@ namespace OpenSim.Groups
|
||||
|
||||
void OnCompleteMovementToRegion(IClientAPI client, bool arg2)
|
||||
{
|
||||
if (client.SceneAgent is ScenePresence sp)
|
||||
object sp = null;
|
||||
if (client.Scene.TryGetScenePresence(client.AgentId, out sp))
|
||||
{
|
||||
if (sp.PresenceType != PresenceType.Npc)
|
||||
if (sp is ScenePresence && ((ScenePresence)sp).PresenceType != PresenceType.Npc)
|
||||
{
|
||||
AgentCircuitData aCircuit = sp.Scene.AuthenticateHandler.GetAgentCircuitData(client.AgentId);
|
||||
if (aCircuit != null && (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0 &&
|
||||
AgentCircuitData aCircuit = ((ScenePresence)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);
|
||||
@@ -183,12 +183,12 @@ namespace OpenSim.Groups
|
||||
|
||||
#region IGroupsServicesConnector
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
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,
|
||||
return m_LocalGroupsConnector.CreateGroup(RequestingAgentID, name, charter, showInList, insigniaID,
|
||||
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
|
||||
else
|
||||
{
|
||||
@@ -197,14 +197,14 @@ namespace OpenSim.Groups
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
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,
|
||||
return m_LocalGroupsConnector.UpdateGroup(AgentUUI(RequestingAgentID), groupID, charter, showInList, insigniaID, membershipFee,
|
||||
openEnrollment, allowPublish, maturePublish, out reason);
|
||||
else
|
||||
{
|
||||
@@ -245,9 +245,9 @@ namespace OpenSim.Groups
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentID, string search)
|
||||
{
|
||||
return m_LocalGroupsConnector.FindGroups(RequestingAgentIDstr, search);
|
||||
return m_LocalGroupsConnector.FindGroups(AgentUUI(RequestingAgentID), search);
|
||||
}
|
||||
|
||||
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
|
||||
@@ -373,7 +373,7 @@ namespace OpenSim.Groups
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new List<GroupRoleMembersData>();
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ namespace OpenSim.Groups
|
||||
// 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)
|
||||
if (url == string.Empty)
|
||||
{
|
||||
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;
|
||||
@@ -459,7 +459,7 @@ namespace OpenSim.Groups
|
||||
|
||||
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
return m_LocalGroupsConnector.GetAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID);
|
||||
return m_LocalGroupsConnector.GetAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID); ;
|
||||
}
|
||||
|
||||
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
@@ -560,7 +560,7 @@ namespace OpenSim.Groups
|
||||
|
||||
// so we have the list of urls to send the notice to
|
||||
// this may take a long time...
|
||||
WorkManager.RunInThread(delegate
|
||||
Util.FireAndForget(delegate
|
||||
{
|
||||
foreach (string u in urls)
|
||||
{
|
||||
@@ -571,7 +571,7 @@ namespace OpenSim.Groups
|
||||
hasAttachment, attType, attName, attItemID, AgentUUIForOutside(attOwnerID));
|
||||
}
|
||||
}
|
||||
}, null, string.Format("AddGroupNotice (agent {0}, group {1})", RequestingAgentID, groupID));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -604,8 +604,14 @@ namespace OpenSim.Groups
|
||||
private string AgentUUI(string AgentIDStr)
|
||||
{
|
||||
UUID AgentID = UUID.Zero;
|
||||
if (!UUID.TryParse(AgentIDStr, out AgentID) || AgentID.IsZero())
|
||||
return UUID.Zero.ToString();
|
||||
try
|
||||
{
|
||||
AgentID = new UUID(AgentIDStr);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return AgentID.ToString();
|
||||
}
|
||||
|
||||
if (m_UserManagement.IsLocalGridUser(AgentID))
|
||||
return AgentID.ToString();
|
||||
@@ -619,7 +625,7 @@ namespace OpenSim.Groups
|
||||
}
|
||||
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);
|
||||
@@ -629,8 +635,14 @@ namespace OpenSim.Groups
|
||||
private string AgentUUIForOutside(string AgentIDStr)
|
||||
{
|
||||
UUID AgentID = UUID.Zero;
|
||||
if (!UUID.TryParse(AgentIDStr, out AgentID) || AgentID.IsZero())
|
||||
return UUID.ZeroString;
|
||||
try
|
||||
{
|
||||
AgentID = new UUID(AgentIDStr);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return AgentID.ToString();
|
||||
}
|
||||
|
||||
AgentCircuitData agent = null;
|
||||
foreach (Scene scene in m_Scenes)
|
||||
@@ -651,7 +663,7 @@ namespace OpenSim.Groups
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -671,7 +683,7 @@ namespace OpenSim.Groups
|
||||
|
||||
serviceLocation = group.ServiceLocation;
|
||||
name = group.GroupName;
|
||||
bool isLocal = (group.ServiceLocation.Length == 0);
|
||||
bool isLocal = (group.ServiceLocation == string.Empty);
|
||||
//m_log.DebugFormat("[XXX]: IsLocal? {0}", isLocal);
|
||||
return isLocal;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -64,9 +64,9 @@ namespace OpenSim.Groups
|
||||
|
||||
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)
|
||||
string homeURI = Util.GetConfigVarFromSections<string>(config, "HomeURI",
|
||||
new string[] { "Startup", "Hypergrid", m_ConfigName}, string.Empty);
|
||||
if (homeURI == string.Empty)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide the HomeURI [Startup] or in section {0}", m_ConfigName));
|
||||
|
||||
IConfig cnf = config.Configs[m_ConfigName];
|
||||
@@ -76,7 +76,7 @@ namespace OpenSim.Groups
|
||||
if (im == null)
|
||||
{
|
||||
string imDll = cnf.GetString("OfflineIMService", string.Empty);
|
||||
if (imDll.Length == 0)
|
||||
if (imDll == string.Empty)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide OfflineIMService in section {0}", m_ConfigName));
|
||||
|
||||
Object[] args = new Object[] { config };
|
||||
@@ -86,7 +86,7 @@ namespace OpenSim.Groups
|
||||
if (users == null)
|
||||
{
|
||||
string usersDll = cnf.GetString("UserAccountService", string.Empty);
|
||||
if (usersDll.Length == 0)
|
||||
if (usersDll == string.Empty)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide UserAccountService in section {0}", m_ConfigName));
|
||||
|
||||
Object[] args = new Object[] { config };
|
||||
@@ -115,10 +115,9 @@ namespace OpenSim.Groups
|
||||
protected override byte[] ProcessRequest(string path, Stream requestData,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
string body;
|
||||
using(StreamReader sr = new StreamReader(requestData))
|
||||
body = sr.ReadToEnd();
|
||||
|
||||
StreamReader sr = new StreamReader(requestData);
|
||||
string body = sr.ReadToEnd();
|
||||
sr.Close();
|
||||
body = body.Trim();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: query String: {0}", body);
|
||||
@@ -159,7 +158,7 @@ namespace OpenSim.Groups
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error(string.Format("[Groups.RobustHGConnector]: Exception {0} ", e.Message), e);
|
||||
m_log.DebugFormat("[Groups.RobustHGConnector]: Exception {0}", e.StackTrace);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
@@ -210,13 +209,11 @@ namespace OpenSim.Groups
|
||||
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_GroupsService.RemoveAgentFromGroup(agentID, agentID, groupID, token);
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
result["RESULT"] = "true";
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ namespace OpenSim.Groups
|
||||
{
|
||||
public interface IGroupsServicesConnector
|
||||
{
|
||||
UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
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 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<DirGroupsReplyData> FindGroups(string RequestingAgentID, 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);
|
||||
@@ -75,7 +75,7 @@ namespace OpenSim.Groups
|
||||
/// 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>
|
||||
@@ -87,7 +87,7 @@ namespace OpenSim.Groups
|
||||
/// </returns>
|
||||
List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID);
|
||||
|
||||
bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
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);
|
||||
|
||||
@@ -34,7 +34,6 @@ 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;
|
||||
@@ -108,7 +107,7 @@ namespace OpenSim.Groups
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Add(scene);
|
||||
}
|
||||
@@ -146,16 +145,16 @@ namespace OpenSim.Groups
|
||||
|
||||
#region IGroupsServicesConnector
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
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,
|
||||
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,
|
||||
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;
|
||||
@@ -165,7 +164,7 @@ namespace OpenSim.Groups
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
if (!GroupID.IsZero())
|
||||
if (GroupID != UUID.Zero)
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID);
|
||||
else if (GroupName != null)
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupName);
|
||||
@@ -173,9 +172,9 @@ namespace OpenSim.Groups
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentID, string search)
|
||||
{
|
||||
return m_GroupsService.FindGroups(RequestingAgentIDstr, search);
|
||||
return m_GroupsService.FindGroups(RequestingAgentID, search);
|
||||
}
|
||||
|
||||
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
|
||||
@@ -240,7 +239,7 @@ namespace OpenSim.Groups
|
||||
|
||||
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID);
|
||||
return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID); ;
|
||||
}
|
||||
|
||||
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
@@ -285,7 +284,7 @@ namespace OpenSim.Groups
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
return m_GroupsService.GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID);
|
||||
return m_GroupsService.GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID); ;
|
||||
}
|
||||
|
||||
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
|
||||
@@ -296,7 +295,7 @@ namespace OpenSim.Groups
|
||||
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,
|
||||
return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message,
|
||||
hasAttachment, attType, attName, attItemID, attOwnerID);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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")]
|
||||
@@ -15,8 +15,8 @@ using Mono.Addins;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -26,11 +26,11 @@ using Mono.Addins;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
[assembly: AssemblyVersion("0.8.0.*")]
|
||||
|
||||
[assembly: Addin("OpenSim.Groups", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: Addin("OpenSim.Groups", "0.1")]
|
||||
[assembly: AddinDependency("OpenSim", "0.5")]
|
||||
|
||||
@@ -32,12 +32,10 @@ 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
|
||||
{
|
||||
@@ -46,33 +44,15 @@ namespace OpenSim.Groups
|
||||
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)
|
||||
public GroupsServiceRemoteConnector(string url)
|
||||
{
|
||||
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()));
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Groups server at {0}", m_ServerURI);
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord CreateGroup(string RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
@@ -126,7 +106,7 @@ namespace OpenSim.Groups
|
||||
sendData["OP"] = "UPDATE";
|
||||
Dictionary<string, object> ret = MakeRequest("PUTGROUP", sendData);
|
||||
|
||||
if (ret == null || (ret != null && (!ret.ContainsKey("RESULT") || ret["RESULT"].ToString() == "NULL")))
|
||||
if (ret == null || (ret != null && ret["RESULT"].ToString() == "NULL"))
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
|
||||
@@ -134,26 +114,26 @@ namespace OpenSim.Groups
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
if (GroupID == UUID.Zero && (GroupName == null || (GroupName != null && GroupName == string.Empty)))
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
if (!GroupID.IsZero())
|
||||
if (GroupID != UUID.Zero)
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
if (!string.IsNullOrEmpty(GroupName))
|
||||
if (GroupName != null && GroupName != string.Empty)
|
||||
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")))
|
||||
if (ret == null || (ret != null && ret["RESULT"].ToString() == "NULL"))
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string query)
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentID, string query)
|
||||
{
|
||||
List<DirGroupsReplyData> hits = new List<DirGroupsReplyData>();
|
||||
if (string.IsNullOrEmpty(query))
|
||||
@@ -161,7 +141,7 @@ namespace OpenSim.Groups
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["Query"] = query;
|
||||
sendData["RequestingAgentID"] = RequestingAgentIDstr;
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("FINDGROUPS", sendData);
|
||||
|
||||
@@ -224,7 +204,7 @@ namespace OpenSim.Groups
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID;
|
||||
if (!GroupID.IsZero())
|
||||
if (GroupID != UUID.Zero)
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETMEMBERSHIP", sendData);
|
||||
@@ -287,7 +267,6 @@ namespace OpenSim.Groups
|
||||
|
||||
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);
|
||||
@@ -378,7 +357,6 @@ namespace OpenSim.Groups
|
||||
|
||||
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);
|
||||
@@ -679,10 +657,9 @@ namespace OpenSim.Groups
|
||||
lock (m_Lock)
|
||||
reply = SynchronousRestFormsRequester.MakeRequest("POST",
|
||||
m_ServerURI + "groups",
|
||||
ServerUtils.BuildQueryString(sendData),
|
||||
m_Auth);
|
||||
ServerUtils.BuildQueryString(sendData));
|
||||
|
||||
if (reply.Length == 0)
|
||||
if (reply == string.Empty)
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(
|
||||
@@ -690,7 +667,7 @@ namespace OpenSim.Groups
|
||||
|
||||
return replyData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ 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;
|
||||
@@ -73,7 +72,12 @@ namespace OpenSim.Groups
|
||||
|
||||
private void Init(IConfigSource config)
|
||||
{
|
||||
m_GroupsService = new GroupsServiceRemoteConnector(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_GroupsService = new GroupsServiceRemoteConnector(url);
|
||||
m_Scenes = new List<Scene>();
|
||||
|
||||
}
|
||||
@@ -113,7 +117,7 @@ namespace OpenSim.Groups
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Add(scene);
|
||||
}
|
||||
@@ -151,7 +155,7 @@ namespace OpenSim.Groups
|
||||
|
||||
#region IGroupsServicesConnector
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
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);
|
||||
@@ -167,7 +171,7 @@ namespace OpenSim.Groups
|
||||
return groupID;
|
||||
}
|
||||
|
||||
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
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;
|
||||
@@ -183,19 +187,19 @@ namespace OpenSim.Groups
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
if (GroupID == UUID.Zero && (GroupName == null || GroupName != null && GroupName == string.Empty))
|
||||
return null;
|
||||
|
||||
return m_CacheWrapper.GetGroupRecord(RequestingAgentID,GroupID,GroupName, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
|
||||
return m_CacheWrapper.GetGroupRecord(RequestingAgentID,GroupID,GroupName, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
|
||||
});
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentID, string search)
|
||||
{
|
||||
// TODO!
|
||||
return m_GroupsService.FindGroups(RequestingAgentIDstr, search);
|
||||
return m_GroupsService.FindGroups(RequestingAgentID, search);
|
||||
}
|
||||
|
||||
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
|
||||
@@ -362,7 +366,7 @@ namespace OpenSim.Groups
|
||||
m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID);
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
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();
|
||||
|
||||
@@ -36,7 +36,6 @@ 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;
|
||||
@@ -53,26 +52,14 @@ namespace OpenSim.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));
|
||||
server.AddStreamHandler(new GroupsServicePostHandler(m_GroupsService));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +69,8 @@ namespace OpenSim.Groups
|
||||
|
||||
private GroupsService m_GroupsService;
|
||||
|
||||
public GroupsServicePostHandler(GroupsService service, IServiceAuth auth) :
|
||||
base("POST", "/groups", auth)
|
||||
public GroupsServicePostHandler(GroupsService service) :
|
||||
base("POST", "/groups")
|
||||
{
|
||||
m_GroupsService = service;
|
||||
}
|
||||
@@ -91,10 +78,9 @@ namespace OpenSim.Groups
|
||||
protected override byte[] ProcessRequest(string path, Stream requestData,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
string body;
|
||||
using(StreamReader sr = new StreamReader(requestData))
|
||||
body = sr.ReadToEnd();
|
||||
|
||||
StreamReader sr = new StreamReader(requestData);
|
||||
string body = sr.ReadToEnd();
|
||||
sr.Close();
|
||||
body = body.Trim();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: query String: {0}", body);
|
||||
@@ -110,7 +96,7 @@ namespace OpenSim.Groups
|
||||
string method = request["METHOD"].ToString();
|
||||
request.Remove("METHOD");
|
||||
|
||||
// m_log.DebugFormat("[Groups.Handler]: {0}", method);
|
||||
m_log.DebugFormat("[Groups.Handler]: {0}", method);
|
||||
switch (method)
|
||||
{
|
||||
case "PUTGROUP":
|
||||
@@ -154,7 +140,7 @@ namespace OpenSim.Groups
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error(string.Format("[GROUPS HANDLER]: Exception {0} ", e.Message), e);
|
||||
m_log.DebugFormat("[GROUPS HANDLER]: Exception {0}", e.StackTrace);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
@@ -186,7 +172,7 @@ namespace OpenSim.Groups
|
||||
|
||||
}
|
||||
|
||||
if (!grec.GroupID.IsZero())
|
||||
if (grec.GroupID != UUID.Zero)
|
||||
{
|
||||
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, grec.GroupID);
|
||||
if (grec == null)
|
||||
@@ -286,13 +272,11 @@ namespace OpenSim.Groups
|
||||
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_GroupsService.RemoveAgentFromGroup(requestingAgentID, agentID, groupID);
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
result["RESULT"] = "true";
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
@@ -314,7 +298,7 @@ namespace OpenSim.Groups
|
||||
if (!all)
|
||||
{
|
||||
ExtendedGroupMembershipData membership = null;
|
||||
if (groupID.IsZero())
|
||||
if (groupID == UUID.Zero)
|
||||
{
|
||||
membership = m_GroupsService.GetAgentActiveMembership(requestingAgentID, agentID);
|
||||
}
|
||||
@@ -394,7 +378,7 @@ namespace OpenSim.Groups
|
||||
|
||||
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"))
|
||||
!request.ContainsKey("Powers") || !request.ContainsKey("OP"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
@@ -520,11 +504,11 @@ namespace OpenSim.Groups
|
||||
|
||||
bool success = false;
|
||||
if (op == "ADD")
|
||||
success = m_GroupsService.AddAgentToGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
|
||||
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(),
|
||||
success = m_GroupsService.RemoveAgentFromGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
|
||||
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
@@ -648,8 +632,8 @@ namespace OpenSim.Groups
|
||||
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(),
|
||||
{
|
||||
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());
|
||||
|
||||
@@ -665,14 +649,10 @@ namespace OpenSim.Groups
|
||||
}
|
||||
else if (op == "GET")
|
||||
{
|
||||
GroupInviteInfo invite = m_GroupsService.GetAgentToGroupInvite(request["RequestingAgentID"].ToString(),
|
||||
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";
|
||||
|
||||
result["RESULT"] = GroupsDataUtils.GroupInviteInfo(invite);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
@@ -804,14 +784,6 @@ namespace OpenSim.Groups
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
using OpenSim.Framework;
|
||||
//using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
|
||||
using OpenMetaverse;
|
||||
|
||||
@@ -51,12 +50,12 @@ namespace OpenSim.Groups
|
||||
|
||||
public class RemoteConnectorCacheWrapper
|
||||
{
|
||||
private ForeignImporter m_ForeignImporter;
|
||||
|
||||
private Dictionary<string, bool> m_ActiveRequests = new Dictionary<string, bool>();
|
||||
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:
|
||||
// This all important cache cahces objects of different types:
|
||||
// group-<GroupID> or group-<Name> => ExtendedGroupRecord
|
||||
// active-<AgentID> => GroupMembershipData
|
||||
// membership-<AgentID>-<GroupID> => GroupMembershipData
|
||||
@@ -68,7 +67,7 @@ namespace OpenSim.Groups
|
||||
// rolemembers-<RequestingAgentID>-<GroupID> => List<ExtendedGroupRoleMembersData>
|
||||
// notice-<noticeID> => GroupNoticeInfo
|
||||
// notices-<GroupID> => List<ExtendedGroupNoticeData>
|
||||
private ExpiringCacheOS<string, object> m_Cache = new ExpiringCacheOS<string, object>(30000);
|
||||
private ExpiringCache<string, object> m_Cache = new ExpiringCache<string, object>();
|
||||
|
||||
public RemoteConnectorCacheWrapper(IUserManagement uman)
|
||||
{
|
||||
@@ -87,11 +86,14 @@ namespace OpenSim.Groups
|
||||
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());
|
||||
}
|
||||
if (group.GroupID != UUID.Zero)
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.Add("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT);
|
||||
if (m_Cache.Contains("memberships-" + RequestingAgentID.ToString()))
|
||||
m_Cache.Remove("memberships-" + RequestingAgentID.ToString());
|
||||
}
|
||||
|
||||
return group.GroupID;
|
||||
}
|
||||
|
||||
@@ -101,24 +103,24 @@ namespace OpenSim.Groups
|
||||
//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);
|
||||
|
||||
if (group != null && group.GroupID != UUID.Zero)
|
||||
lock (m_Cache)
|
||||
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))
|
||||
//if (GroupID == UUID.Zero && (GroupName == null || GroupName != null && GroupName == string.Empty))
|
||||
// return null;
|
||||
|
||||
object group = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "group-";
|
||||
if (GroupID.IsZero())
|
||||
cacheKey += GroupName;
|
||||
else
|
||||
if (GroupID != UUID.Zero)
|
||||
cacheKey += GroupID.ToString();
|
||||
else
|
||||
cacheKey += GroupName;
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetGroupRecord {0}", cacheKey);
|
||||
|
||||
@@ -133,29 +135,23 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
//group = m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
|
||||
group = d();
|
||||
//group = m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
|
||||
group = d();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, group, GROUPS_CACHE_TIMEOUT);
|
||||
return (ExtendedGroupRecord)group;
|
||||
}
|
||||
}
|
||||
finally
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, group, GROUPS_CACHE_TIMEOUT);
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (ExtendedGroupRecord)group;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -177,6 +173,8 @@ namespace OpenSim.Groups
|
||||
m_Cache.AddOrUpdate("active-" + AgentID.ToString(), membership, GROUPS_CACHE_TIMEOUT);
|
||||
m_Cache.AddOrUpdate("membership-" + AgentID.ToString() + "-" + GroupID.ToString(), membership, GROUPS_CACHE_TIMEOUT);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -184,29 +182,37 @@ namespace OpenSim.Groups
|
||||
{
|
||||
d();
|
||||
|
||||
string AgentIDToString = AgentID.ToString();
|
||||
string cacheKey = "active-" + AgentIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
lock (m_Cache)
|
||||
{
|
||||
string cacheKey = "active-" + AgentID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "memberships-" + AgentIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
cacheKey = "memberships-" + AgentID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
string GroupIDToString = GroupID.ToString();
|
||||
cacheKey = "membership-" + AgentIDToString + "-" + GroupIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "roles-" + "-" + GroupIDToString + "-" + AgentIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
cacheKey = "roles-" + "-" + GroupID.ToString() + "-" + AgentID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
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);
|
||||
lock (m_Cache)
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.AddOrUpdate(cacheKey, activeGroup, GROUPS_CACHE_TIMEOUT);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentActiveMembership(string AgentID, GroupMembershipDelegate d)
|
||||
@@ -228,24 +234,22 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
membership = d();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
membership = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -273,24 +277,21 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
membership = d();
|
||||
lock (m_Cache)
|
||||
{
|
||||
membership = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -317,24 +318,21 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
memberships = d();
|
||||
lock (m_Cache)
|
||||
{
|
||||
memberships = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, memberships, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupMembershipData>)memberships;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (List<GroupMembershipData>)memberships;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -362,30 +360,29 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
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>();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
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);
|
||||
m_Cache.AddOrUpdate(cacheKey, _members, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupMembersData>)members;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
|
||||
return (List<GroupMembersData>)members;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -405,10 +402,16 @@ namespace OpenSim.Groups
|
||||
role.RoleID = roleID;
|
||||
role.Title = title;
|
||||
|
||||
m_Cache.AddOrUpdate("role-" + roleID.ToString(), role, GROUPS_CACHE_TIMEOUT);
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate("role-" + roleID.ToString(), role, GROUPS_CACHE_TIMEOUT);
|
||||
|
||||
// also remove this list
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
if (m_Cache.Contains("roles-" + groupID.ToString()))
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -429,15 +432,22 @@ namespace OpenSim.Groups
|
||||
r.Powers = powers;
|
||||
r.Title = title;
|
||||
|
||||
m_Cache.AddOrUpdate("role-" + roleID.ToString(), r, GROUPS_CACHE_TIMEOUT);
|
||||
m_Cache.Update("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());
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.Contains("role-" + roleID.ToString()))
|
||||
m_Cache.Remove("role-" + roleID.ToString());
|
||||
|
||||
// also remove these lists, because they will have an outdated role
|
||||
if (m_Cache.Contains("roles-" + groupID.ToString()))
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -449,11 +459,18 @@ namespace OpenSim.Groups
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.Remove("role-" + roleID.ToString());
|
||||
if (m_Cache.Contains("role-" + roleID.ToString()))
|
||||
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());
|
||||
if (m_Cache.Contains("roles-" + groupID.ToString()))
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
|
||||
if (m_Cache.Contains("roles-" + groupID.ToString() + "-" + RequestingAgentID.ToString()))
|
||||
m_Cache.Remove("roles-" + groupID.ToString() + "-" + RequestingAgentID.ToString());
|
||||
|
||||
if (m_Cache.Contains("rolemembers-" + RequestingAgentID.ToString() + "-" + groupID.ToString()))
|
||||
m_Cache.Remove("rolemembers-" + RequestingAgentID.ToString() + "-" + groupID.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,28 +488,25 @@ namespace OpenSim.Groups
|
||||
return (List<GroupRolesData>)roles;
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
roles = d();
|
||||
if (roles != null)
|
||||
{
|
||||
roles = d();
|
||||
if (roles != null)
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT);
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (List<GroupRolesData>)roles;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
@@ -518,35 +532,32 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
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>();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
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);
|
||||
return (List<GroupRoleMembersData>)rmembers;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -617,10 +628,12 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,24 +657,21 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
roles = d();
|
||||
lock (m_Cache)
|
||||
{
|
||||
roles = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupRolesData>)roles;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (List<GroupRolesData>)roles;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -677,10 +687,12 @@ namespace OpenSim.Groups
|
||||
{
|
||||
// Invalidate cached info, because it has ActiveRoleID and Powers
|
||||
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "memberships-" + AgentID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +707,8 @@ namespace OpenSim.Groups
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "memberships-" + AgentID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "active-" + AgentID.ToString();
|
||||
object m = null;
|
||||
@@ -712,9 +725,14 @@ namespace OpenSim.Groups
|
||||
{
|
||||
if (d())
|
||||
{
|
||||
m_Cache.AddOrUpdate("notice-" + noticeID.ToString(), notice, GROUPS_CACHE_TIMEOUT);
|
||||
string cacheKey = "notices-" + groupID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate("notice-" + noticeID.ToString(), notice, GROUPS_CACHE_TIMEOUT);
|
||||
string cacheKey = "notices-" + groupID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -740,25 +758,22 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
GroupNoticeInfo _notice = d();
|
||||
GroupNoticeInfo _notice = d();
|
||||
|
||||
m_Cache.AddOrUpdate(cacheKey, _notice, GROUPS_CACHE_TIMEOUT);
|
||||
return _notice;
|
||||
}
|
||||
finally
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, _notice, GROUPS_CACHE_TIMEOUT);
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return _notice;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -785,30 +800,29 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
if (!m_ActiveRequests.ContainsKey(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
m_ActiveRequests.Add(cacheKey, true);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
notices = d();
|
||||
notices = d();
|
||||
|
||||
m_Cache.AddOrUpdate(cacheKey, notices, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<ExtendedGroupNoticeData>)notices;
|
||||
}
|
||||
finally
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, notices, GROUPS_CACHE_TIMEOUT);
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
return (List<ExtendedGroupNoticeData>)notices;
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,63 +43,59 @@ namespace OpenSim.Groups
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public const GroupPowers DefaultEveryonePowers =
|
||||
GroupPowers.AllowSetHome |
|
||||
GroupPowers.Accountable |
|
||||
GroupPowers.JoinChat |
|
||||
GroupPowers.AllowVoiceChat |
|
||||
GroupPowers.ReceiveNotices |
|
||||
GroupPowers.StartProposal |
|
||||
GroupPowers.VoteOnProposal;
|
||||
public const GroupPowers DefaultEveryonePowers = GroupPowers.AllowSetHome |
|
||||
GroupPowers.Accountable |
|
||||
GroupPowers.JoinChat |
|
||||
GroupPowers.AllowVoiceChat |
|
||||
GroupPowers.ReceiveNotices |
|
||||
GroupPowers.StartProposal |
|
||||
GroupPowers.VoteOnProposal;
|
||||
|
||||
public const GroupPowers OfficersPowers = DefaultEveryonePowers |
|
||||
GroupPowers.AllowFly |
|
||||
GroupPowers.AllowLandmark |
|
||||
GroupPowers.AllowRez |
|
||||
GroupPowers.AssignMemberLimited |
|
||||
GroupPowers.ChangeIdentity |
|
||||
GroupPowers.ChangeMedia |
|
||||
GroupPowers.ChangeOptions |
|
||||
GroupPowers.DeedObject |
|
||||
GroupPowers.Eject |
|
||||
GroupPowers.FindPlaces |
|
||||
GroupPowers.Invite |
|
||||
GroupPowers.LandChangeIdentity |
|
||||
GroupPowers.LandDeed |
|
||||
GroupPowers.LandDivideJoin |
|
||||
GroupPowers.LandEdit |
|
||||
GroupPowers.AllowEnvironment |
|
||||
GroupPowers.LandEjectAndFreeze |
|
||||
GroupPowers.LandGardening |
|
||||
GroupPowers.LandManageAllowed |
|
||||
GroupPowers.LandManageBanned |
|
||||
GroupPowers.LandManagePasses |
|
||||
GroupPowers.LandOptions |
|
||||
GroupPowers.LandRelease |
|
||||
GroupPowers.LandSetSale |
|
||||
GroupPowers.MemberVisible |
|
||||
GroupPowers.ModerateChat |
|
||||
GroupPowers.ObjectManipulate |
|
||||
GroupPowers.ObjectSetForSale |
|
||||
GroupPowers.ReturnGroupOwned |
|
||||
GroupPowers.ReturnGroupSet |
|
||||
GroupPowers.ReturnNonGroup |
|
||||
GroupPowers.RoleProperties |
|
||||
GroupPowers.SendNotices |
|
||||
GroupPowers.SetLandingPoint;
|
||||
|
||||
public const GroupPowers OwnerPowers = OfficersPowers |
|
||||
GroupPowers.Accountable |
|
||||
GroupPowers.AllowEditLand |
|
||||
GroupPowers.AssignMember |
|
||||
GroupPowers.ChangeActions |
|
||||
GroupPowers.CreateRole |
|
||||
GroupPowers.DeleteRole |
|
||||
GroupPowers.ExperienceAdmin |
|
||||
GroupPowers.ExperienceCreator |
|
||||
GroupPowers.GroupBanAccess |
|
||||
GroupPowers.HostEvent |
|
||||
GroupPowers.RemoveMember;
|
||||
public const GroupPowers OwnerPowers = GroupPowers.Accountable |
|
||||
GroupPowers.AllowEditLand |
|
||||
GroupPowers.AllowFly |
|
||||
GroupPowers.AllowLandmark |
|
||||
GroupPowers.AllowRez |
|
||||
GroupPowers.AllowSetHome |
|
||||
GroupPowers.AllowVoiceChat |
|
||||
GroupPowers.AssignMember |
|
||||
GroupPowers.AssignMemberLimited |
|
||||
GroupPowers.ChangeActions |
|
||||
GroupPowers.ChangeIdentity |
|
||||
GroupPowers.ChangeMedia |
|
||||
GroupPowers.ChangeOptions |
|
||||
GroupPowers.CreateRole |
|
||||
GroupPowers.DeedObject |
|
||||
GroupPowers.DeleteRole |
|
||||
GroupPowers.Eject |
|
||||
GroupPowers.FindPlaces |
|
||||
GroupPowers.Invite |
|
||||
GroupPowers.JoinChat |
|
||||
GroupPowers.LandChangeIdentity |
|
||||
GroupPowers.LandDeed |
|
||||
GroupPowers.LandDivideJoin |
|
||||
GroupPowers.LandEdit |
|
||||
GroupPowers.LandEjectAndFreeze |
|
||||
GroupPowers.LandGardening |
|
||||
GroupPowers.LandManageAllowed |
|
||||
GroupPowers.LandManageBanned |
|
||||
GroupPowers.LandManagePasses |
|
||||
GroupPowers.LandOptions |
|
||||
GroupPowers.LandRelease |
|
||||
GroupPowers.LandSetSale |
|
||||
GroupPowers.ModerateChat |
|
||||
GroupPowers.ObjectManipulate |
|
||||
GroupPowers.ObjectSetForSale |
|
||||
GroupPowers.ReceiveNotices |
|
||||
GroupPowers.RemoveMember |
|
||||
GroupPowers.ReturnGroupOwned |
|
||||
GroupPowers.ReturnGroupSet |
|
||||
GroupPowers.ReturnNonGroup |
|
||||
GroupPowers.RoleProperties |
|
||||
GroupPowers.SendNotices |
|
||||
GroupPowers.SetLandingPoint |
|
||||
GroupPowers.StartProposal |
|
||||
GroupPowers.VoteOnProposal;
|
||||
|
||||
#region Daily Cleanup
|
||||
|
||||
@@ -129,7 +125,7 @@ namespace OpenSim.Groups
|
||||
|
||||
#endregion
|
||||
|
||||
public UUID CreateGroup(string RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
public UUID 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;
|
||||
@@ -154,25 +150,20 @@ namespace OpenSim.Groups
|
||||
data.Data["ShowInList"] = showInList ? "1" : "0";
|
||||
data.Data["AllowPublish"] = allowPublish ? "1" : "0";
|
||||
data.Data["MaturePublish"] = maturePublish ? "1" : "0";
|
||||
UUID ownerRoleID = UUID.Random();
|
||||
data.Data["OwnerRoleID"] = ownerRoleID.ToString();
|
||||
data.Data["OwnerRoleID"] = UUID.Random().ToString();
|
||||
|
||||
if (!m_Database.StoreGroup(data))
|
||||
return UUID.Zero;
|
||||
|
||||
// Create Everyone role
|
||||
_AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, UUID.Zero, "Everyone", "Everyone in the group is in the everyone role.", "Member of " + name, (ulong)DefaultEveryonePowers, true);
|
||||
|
||||
// Create Officers role
|
||||
UUID officersRoleID = UUID.Random();
|
||||
_AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, officersRoleID, "Officers", "The officers of the group, with more powers than regular members.", "Officer of " + name, (ulong)OfficersPowers, true);
|
||||
_AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, UUID.Zero, "Everyone", "Everyone in the group", "Member of " + name, (ulong)DefaultEveryonePowers, true);
|
||||
|
||||
// Create Owner role
|
||||
_AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, ownerRoleID, "Owners", "Owners of the group", "Owner of " + name, (ulong)OwnerPowers, true);
|
||||
UUID roleID = UUID.Random();
|
||||
_AddOrUpdateGroupRole(RequestingAgentID, data.GroupID, roleID, "Owners", "Owners of the group", "Owner of " + name, (ulong)OwnerPowers, true);
|
||||
|
||||
// Add founder to group
|
||||
_AddAgentToGroup(RequestingAgentID, founderID.ToString(), data.GroupID, ownerRoleID);
|
||||
_AddAgentToGroup(RequestingAgentID, founderID.ToString(), data.GroupID, officersRoleID);
|
||||
_AddAgentToGroup(RequestingAgentID, founderID.ToString(), data.GroupID, roleID);
|
||||
|
||||
return data.GroupID;
|
||||
}
|
||||
@@ -231,22 +222,15 @@ namespace OpenSim.Groups
|
||||
if (d.Data.ContainsKey("Location") && d.Data["Location"] != string.Empty)
|
||||
continue;
|
||||
|
||||
int nmembers = m_Database.MemberCount(d.GroupID);
|
||||
if(nmembers == 0)
|
||||
continue;
|
||||
|
||||
DirGroupsReplyData g = new DirGroupsReplyData();
|
||||
g.groupID = d.GroupID;
|
||||
|
||||
if (d.Data.ContainsKey("Name"))
|
||||
g.groupName = d.Data["Name"];
|
||||
else
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: Key Name not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
g.groupID = d.GroupID;
|
||||
g.members = nmembers;
|
||||
g.members = m_Database.MemberCount(d.GroupID);
|
||||
|
||||
groups.Add(g);
|
||||
}
|
||||
@@ -263,9 +247,6 @@ namespace OpenSim.Groups
|
||||
if (group == null)
|
||||
return members;
|
||||
|
||||
// Unfortunately this doesn't quite work on legacy group data because of a bug
|
||||
// that's also being fixed here on CreateGroup. The OwnerRoleID sent to the DB was wrong.
|
||||
// See how to find the ownerRoleID a few lines below.
|
||||
UUID ownerRoleID = new UUID(group.Data["OwnerRoleID"]);
|
||||
|
||||
RoleData[] roles = m_Database.RetrieveRoles(GroupID);
|
||||
@@ -274,12 +255,7 @@ namespace OpenSim.Groups
|
||||
return members;
|
||||
List<RoleData> rolesList = new List<RoleData>(roles);
|
||||
|
||||
// Let's find the "real" ownerRoleID
|
||||
RoleData ownerRole = rolesList.Find(r => r.Data["Powers"] == ((long)OwnerPowers).ToString());
|
||||
if (ownerRole != null)
|
||||
ownerRoleID = ownerRole.RoleID;
|
||||
|
||||
// Check visibility?
|
||||
// Check visibility?
|
||||
// When we don't want to check visibility, we pass it "all" as the requestingAgentID
|
||||
bool checkVisibility = !RequestingAgentID.Equals(UUID.Zero.ToString());
|
||||
|
||||
@@ -315,32 +291,17 @@ namespace OpenSim.Groups
|
||||
{
|
||||
m.Title = selected.Data["Title"];
|
||||
m.AgentPowers = UInt64.Parse(selected.Data["Powers"]);
|
||||
|
||||
m.AgentID = d.PrincipalID;
|
||||
m.AcceptNotices = d.Data["AcceptNotices"] == "1" ? true : false;
|
||||
m.Contribution = Int32.Parse(d.Data["Contribution"]);
|
||||
m.ListInProfile = d.Data["ListInProfile"] == "1" ? true : false;
|
||||
|
||||
// Is this person an owner of the group?
|
||||
m.IsOwner = (rolemembershipsList.Find(r => r.RoleID == ownerRoleID) != null) ? true : false;
|
||||
|
||||
members.Add(m);
|
||||
}
|
||||
|
||||
m.AgentID = d.PrincipalID;
|
||||
m.AcceptNotices = d.Data["AcceptNotices"] == "1" ? true : false;
|
||||
m.Contribution = Int32.Parse(d.Data["Contribution"]);
|
||||
m.ListInProfile = d.Data["ListInProfile"] == "1" ? true : false;
|
||||
|
||||
GridUserData gud = m_GridUserService.Get(d.PrincipalID);
|
||||
if (gud != null)
|
||||
{
|
||||
if (bool.Parse(gud.Data["Online"]))
|
||||
{
|
||||
m.OnlineStatus = @"Online";
|
||||
}
|
||||
else
|
||||
{
|
||||
int unixtime = int.Parse(gud.Data["Login"]);
|
||||
// The viewer is very picky about how these strings are formed. Eg. it will crash on malformed dates!
|
||||
m.OnlineStatus = (unixtime == 0) ? @"unknown" : Util.ToDateTime(unixtime).ToString("MM/dd/yyyy");
|
||||
}
|
||||
}
|
||||
|
||||
// Is this person an owner of the group?
|
||||
m.IsOwner = (rolemembershipsList.Find(r => r.RoleID == ownerRoleID) != null) ? true : false;
|
||||
|
||||
members.Add(m);
|
||||
}
|
||||
|
||||
return members;
|
||||
@@ -383,7 +344,7 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// Can't delete Everyone and Owners roles
|
||||
if (roleID.IsZero())
|
||||
if (roleID == UUID.Zero)
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: Attempt at deleting Everyone role from group {0} denied", groupID);
|
||||
return;
|
||||
@@ -432,15 +393,13 @@ namespace OpenSim.Groups
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
// check perms
|
||||
if (RequestingAgentID != AgentID && !HasPower(RequestingAgentID, GroupID, GroupPowers.Eject))
|
||||
return false;
|
||||
return;
|
||||
|
||||
_RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
|
||||
@@ -500,8 +459,8 @@ namespace OpenSim.Groups
|
||||
|
||||
// check permissions
|
||||
bool limited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMemberLimited);
|
||||
bool unlimited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMember) || IsOwner(RequestingAgentID, GroupID);
|
||||
if (!limited && !unlimited)
|
||||
bool unlimited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMember) | IsOwner(RequestingAgentID, GroupID);
|
||||
if (!limited || !unlimited)
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: ({0}) Attempt at assigning {1} to role {2} denied because of lack of permission", RequestingAgentID, AgentID, RoleID);
|
||||
return false;
|
||||
@@ -511,7 +470,7 @@ namespace OpenSim.Groups
|
||||
if (!unlimited && limited)
|
||||
{
|
||||
// check whether person's has this role
|
||||
RoleMembershipData rolemembership = m_Database.RetrieveRoleMember(GroupID, RoleID, RequestingAgentID);
|
||||
RoleMembershipData rolemembership = m_Database.RetrieveRoleMember(GroupID, RoleID, AgentID);
|
||||
if (rolemembership == null)
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: ({0}) Attempt at assigning {1} to role {2} denied because of limited permission", RequestingAgentID, AgentID, RoleID);
|
||||
@@ -527,30 +486,17 @@ namespace OpenSim.Groups
|
||||
public bool RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
// Don't remove from Everyone role!
|
||||
if (RoleID.IsZero())
|
||||
if (RoleID == UUID.Zero)
|
||||
return false;
|
||||
|
||||
// check permissions
|
||||
bool limited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMemberLimited);
|
||||
bool unlimited = HasPower(RequestingAgentID, GroupID, GroupPowers.AssignMember) || IsOwner(RequestingAgentID, GroupID);
|
||||
if (!limited && !unlimited)
|
||||
if (!unlimited)
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: ({0}) Attempt at removing {1} from role {2} denied because of lack of permission", RequestingAgentID, AgentID, RoleID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// AssignMemberLimited means that the person can assign another person to the same roles that she has in the group
|
||||
if (!unlimited && limited)
|
||||
{
|
||||
// check whether person's has this role
|
||||
RoleMembershipData rolemembership = m_Database.RetrieveRoleMember(GroupID, RoleID, RequestingAgentID);
|
||||
if (rolemembership == null)
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: ({0}) Attempt at removing {1} from role {2} denied because of limited permission", RequestingAgentID, AgentID, RoleID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RoleMembershipData rolemember = m_Database.RetrieveRoleMember(GroupID, RoleID, AgentID);
|
||||
|
||||
if (rolemember == null)
|
||||
@@ -564,12 +510,12 @@ namespace OpenSim.Groups
|
||||
if (rdata != null)
|
||||
foreach (RoleMembershipData r in rdata)
|
||||
{
|
||||
if (!r.RoleID.IsZero())
|
||||
if (r.RoleID != UUID.Zero)
|
||||
{
|
||||
newRoleID = r.RoleID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MembershipData member = m_Database.RetrieveMember(GroupID, AgentID);
|
||||
if (member != null)
|
||||
@@ -727,7 +673,7 @@ namespace OpenSim.Groups
|
||||
m_Database.StoreMember(membership);
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
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)
|
||||
{
|
||||
// Check perms
|
||||
@@ -837,10 +783,10 @@ namespace OpenSim.Groups
|
||||
_AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, UUID.Zero);
|
||||
|
||||
// Add principal to role, if different from everyone role
|
||||
if (!RoleID.IsZero())
|
||||
if (RoleID != UUID.Zero)
|
||||
_AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
|
||||
// Make this the active group
|
||||
// Make thit this active group
|
||||
PrincipalData pdata = new PrincipalData();
|
||||
pdata.PrincipalID = AgentID;
|
||||
pdata.ActiveGroupID = GroupID;
|
||||
@@ -858,7 +804,7 @@ namespace OpenSim.Groups
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!add && data == null) // it doesn't exist, can't update
|
||||
if (!add && data == null) // it deosn't exist, can't update
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: Group {0} doesn't exist. Can't update it", groupID);
|
||||
return false;
|
||||
@@ -1058,24 +1004,13 @@ namespace OpenSim.Groups
|
||||
private bool HasPower(string agentID, UUID groupID, GroupPowers power)
|
||||
{
|
||||
RoleMembershipData[] rmembership = m_Database.RetrieveMemberRoles(groupID, agentID);
|
||||
if (rmembership is null || rmembership.Length == 0)
|
||||
if (rmembership == null || (rmembership != null && rmembership.Length == 0))
|
||||
return false;
|
||||
|
||||
foreach (RoleMembershipData rdata in rmembership)
|
||||
{
|
||||
if(rdata is null)
|
||||
{
|
||||
m_log.Warn($"[GROUPSERVICE] null membership data entry in group {groupID} for agent {agentID}");
|
||||
continue;
|
||||
}
|
||||
RoleData role = m_Database.RetrieveRole(groupID, rdata.RoleID);
|
||||
if (role is null)
|
||||
{
|
||||
m_log.Warn($"[GROUPSERVICE] role with id {rdata.RoleID} is null");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((UInt64.Parse(role.Data["Powers"]) & (ulong)power) != 0)
|
||||
if ( (UInt64.Parse(role.Data["Powers"]) & (ulong)power) != 0 )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1084,14 +1019,14 @@ namespace OpenSim.Groups
|
||||
private bool IsOwner(string agentID, UUID groupID)
|
||||
{
|
||||
GroupData group = m_Database.RetrieveGroup(groupID);
|
||||
if (group is null)
|
||||
if (group == null)
|
||||
return false;
|
||||
|
||||
if(!UUID.TryParse(group.Data["OwnerRoleID"], out UUID ownerRoleID))
|
||||
RoleMembershipData rmembership = m_Database.RetrieveRoleMember(groupID, new UUID(group.Data["OwnerRoleID"]), agentID);
|
||||
if (rmembership == null)
|
||||
return false;
|
||||
|
||||
RoleMembershipData rmembership = m_Database.RetrieveRoleMember(groupID, ownerRoleID, agentID);
|
||||
return rmembership is not null;
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -38,7 +38,6 @@ 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)
|
||||
@@ -46,8 +45,7 @@ namespace OpenSim.Groups
|
||||
string dllName = String.Empty;
|
||||
string connString = String.Empty;
|
||||
string realm = "os_groups";
|
||||
string usersRealm = "GridUser";
|
||||
string configName = (cName.Length == 0) ? "Groups" : cName;
|
||||
string configName = (cName == string.Empty) ? "Groups" : cName;
|
||||
|
||||
//
|
||||
// Try reading the [DatabaseService] section, if it exists
|
||||
@@ -55,9 +53,9 @@ namespace OpenSim.Groups
|
||||
IConfig dbConfig = config.Configs["DatabaseService"];
|
||||
if (dbConfig != null)
|
||||
{
|
||||
if (dllName.Length == 0)
|
||||
if (dllName == String.Empty)
|
||||
dllName = dbConfig.GetString("StorageProvider", String.Empty);
|
||||
if (connString.Length == 0)
|
||||
if (connString == String.Empty)
|
||||
connString = dbConfig.GetString("ConnectionString", String.Empty);
|
||||
}
|
||||
|
||||
@@ -81,21 +79,6 @@ namespace OpenSim.Groups
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace OpenSim.Groups
|
||||
|
||||
// Check if it already exists
|
||||
GroupData grec = m_Database.RetrieveGroup(groupID);
|
||||
if (grec == null ||
|
||||
if (grec == null ||
|
||||
(grec != null && grec.Data["Location"] != string.Empty && grec.Data["Location"].ToLower() != serviceLocation.ToLower()))
|
||||
{
|
||||
// Create the group
|
||||
@@ -100,7 +100,7 @@ namespace OpenSim.Groups
|
||||
return false;
|
||||
}
|
||||
|
||||
if (grec.Data["Location"].Length == 0)
|
||||
if (grec.Data["Location"] == string.Empty)
|
||||
{
|
||||
reason = "Cannot add proxy membership to non-proxy group";
|
||||
return false;
|
||||
@@ -131,27 +131,19 @@ namespace OpenSim.Groups
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, string token)
|
||||
public void 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);
|
||||
}
|
||||
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)
|
||||
@@ -161,7 +153,7 @@ namespace OpenSim.Groups
|
||||
return null;
|
||||
|
||||
ExtendedGroupRecord grec;
|
||||
if (GroupID.IsZero())
|
||||
if (GroupID == UUID.Zero)
|
||||
grec = GetGroupRecord(RequestingAgentID, groupName);
|
||||
else
|
||||
grec = GetGroupRecord(RequestingAgentID, GroupID);
|
||||
@@ -234,7 +226,7 @@ namespace OpenSim.Groups
|
||||
}
|
||||
|
||||
// check that the group is remote
|
||||
if (grec.ServiceLocation.Length == 0)
|
||||
if (grec.ServiceLocation == string.Empty)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to local (non-proxy) group");
|
||||
return false;
|
||||
|
||||
@@ -63,10 +63,10 @@ namespace OpenSim.OfflineIM
|
||||
m_Enabled = true;
|
||||
|
||||
string serviceLocation = cnf.GetString("OfflineMessageURL", string.Empty);
|
||||
if (serviceLocation.Length == 0)
|
||||
if (serviceLocation == string.Empty)
|
||||
m_OfflineIMService = new OfflineIMService(config);
|
||||
else
|
||||
m_OfflineIMService = new OfflineIMServiceRemoteConnector(config);
|
||||
m_OfflineIMService = new OfflineIMServiceRemoteConnector(serviceLocation);
|
||||
|
||||
m_ForwardOfflineGroupMessages = cnf.GetBoolean("ForwardOfflineGroupMessages", m_ForwardOfflineGroupMessages);
|
||||
m_log.DebugFormat("[OfflineIM.V2]: Offline messages enabled by {0}", Name);
|
||||
@@ -114,6 +114,7 @@ namespace OpenSim.OfflineIM
|
||||
scene.ForEachClient(delegate(IClientAPI client)
|
||||
{
|
||||
client.OnRetrieveInstantMessages -= RetrieveInstantMessages;
|
||||
client.OnMuteListRequest -= OnMuteListRequest;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,6 +162,7 @@ namespace OpenSim.OfflineIM
|
||||
private void OnNewClient(IClientAPI client)
|
||||
{
|
||||
client.OnRetrieveInstantMessages += RetrieveInstantMessages;
|
||||
client.OnMuteListRequest += OnMuteListRequest;
|
||||
}
|
||||
|
||||
private void RetrieveInstantMessages(IClientAPI client)
|
||||
@@ -185,16 +187,27 @@ namespace OpenSim.OfflineIM
|
||||
// Needed for proper state management for stored group
|
||||
// invitations
|
||||
//
|
||||
Scene s = client.Scene as Scene;
|
||||
Scene s = FindScene(client.AgentId);
|
||||
if (s != null)
|
||||
{
|
||||
im.offline = 1;
|
||||
s.EventManager.TriggerIncomingInstantMessage(im);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apparently this is needed in order for the viewer to request the IMs.
|
||||
private void OnMuteListRequest(IClientAPI client, uint crc)
|
||||
{
|
||||
m_log.DebugFormat("[OfflineIM.V2] Got mute list request for crc {0}", crc);
|
||||
string filename = "mutes" + client.AgentId.ToString();
|
||||
|
||||
IXfer xfer = client.Scene.RequestModuleInterface<IXfer>();
|
||||
if (xfer != null)
|
||||
{
|
||||
xfer.AddNewFile(filename, new Byte[0]);
|
||||
client.SendMuteListUpdate(filename);
|
||||
}
|
||||
}
|
||||
|
||||
private void UndeliveredMessage(GridInstantMessage im)
|
||||
{
|
||||
if (im.dialog != (byte)InstantMessageDialog.MessageFromObject &&
|
||||
@@ -213,6 +226,10 @@ namespace OpenSim.OfflineIM
|
||||
return;
|
||||
}
|
||||
|
||||
Scene scene = FindScene(new UUID(im.fromAgentID));
|
||||
if (scene == null)
|
||||
scene = m_SceneList[0];
|
||||
|
||||
string reason = string.Empty;
|
||||
bool success = m_OfflineIMService.StoreMessage(im, out reason);
|
||||
|
||||
@@ -244,11 +261,6 @@ namespace OpenSim.OfflineIM
|
||||
return m_OfflineIMService.StoreMessage(im, out reason);
|
||||
}
|
||||
|
||||
public void DeleteMessages(UUID userID)
|
||||
{
|
||||
m_OfflineIMService.DeleteMessages(userID);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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")]
|
||||
@@ -15,8 +15,8 @@ using Mono.Addins;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -26,11 +26,11 @@ using Mono.Addins;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
[assembly: AssemblyVersion("0.8.0.*")]
|
||||
|
||||
[assembly: Addin("OpenSim.OfflineIM", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: Addin("OpenSim.OfflineIM", "0.1")]
|
||||
[assembly: AddinDependency("OpenSim", "0.5")]
|
||||
|
||||
@@ -32,7 +32,6 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.ServiceAuth;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
@@ -47,7 +46,6 @@ namespace OpenSim.OfflineIM
|
||||
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)
|
||||
@@ -67,18 +65,6 @@ namespace OpenSim.OfflineIM
|
||||
|
||||
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
|
||||
@@ -88,42 +74,31 @@ namespace OpenSim.OfflineIM
|
||||
|
||||
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))
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return ims;
|
||||
|
||||
if(resultobj is string result)
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return ims;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
GridInstantMessage m = OfflineIMDataUtils.GridInstantMessage((Dictionary<string, object>)v);
|
||||
ims.Add(m);
|
||||
}
|
||||
|
||||
return ims;
|
||||
}
|
||||
|
||||
public bool StoreMessage(GridInstantMessage im, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
Dictionary<string, object> sendData = OfflineIMDataUtils.GridInstantMessage(im);
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("STORE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
@@ -132,31 +107,16 @@ namespace OpenSim.OfflineIM
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ret.TryGetValue("RESULT", out object o))
|
||||
string result = ret["RESULT"].ToString();
|
||||
if (result == "NULL" || result.ToLower() == "false")
|
||||
{
|
||||
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 = ret["REASON"].ToString();
|
||||
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
|
||||
|
||||
|
||||
@@ -170,10 +130,10 @@ namespace OpenSim.OfflineIM
|
||||
lock (m_Lock)
|
||||
reply = SynchronousRestFormsRequester.MakeRequest("POST",
|
||||
m_ServerURI + "/offlineim",
|
||||
ServerUtils.BuildQueryString(sendData),
|
||||
m_Auth);
|
||||
ServerUtils.BuildQueryString(sendData));
|
||||
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply);
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(
|
||||
reply);
|
||||
|
||||
return replyData;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -36,7 +36,6 @@ 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;
|
||||
@@ -60,9 +59,7 @@ namespace OpenSim.OfflineIM
|
||||
|
||||
m_OfflineIMService = new OfflineIMService(config);
|
||||
|
||||
IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName);
|
||||
|
||||
server.AddStreamHandler(new OfflineIMServicePostHandler(m_OfflineIMService, auth));
|
||||
server.AddStreamHandler(new OfflineIMServicePostHandler(m_OfflineIMService));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +69,8 @@ namespace OpenSim.OfflineIM
|
||||
|
||||
private IOfflineIMService m_OfflineIMService;
|
||||
|
||||
public OfflineIMServicePostHandler(IOfflineIMService service, IServiceAuth auth) :
|
||||
base("POST", "/offlineim", auth)
|
||||
public OfflineIMServicePostHandler(IOfflineIMService service) :
|
||||
base("POST", "/offlineim")
|
||||
{
|
||||
m_OfflineIMService = service;
|
||||
}
|
||||
@@ -99,20 +96,19 @@ namespace OpenSim.OfflineIM
|
||||
string method = request["METHOD"].ToString();
|
||||
request.Remove("METHOD");
|
||||
|
||||
m_log.DebugFormat("[OfflineIM.V2.Handler]: {0}", 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);
|
||||
m_log.DebugFormat("[OFFLINE IM HANDLER]: Exception {0}", e.StackTrace);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
@@ -163,21 +159,6 @@ namespace OpenSim.OfflineIM
|
||||
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)
|
||||
@@ -215,7 +196,18 @@ namespace OpenSim.OfflineIM
|
||||
|
||||
rootElement.AppendChild(result);
|
||||
|
||||
return Util.DocToBytes(doc);
|
||||
return DocToBytes(doc);
|
||||
}
|
||||
|
||||
private byte[] DocToBytes(XmlDocument doc)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
XmlTextWriter xw = new XmlTextWriter(ms, null);
|
||||
xw.Formatting = Formatting.Indented;
|
||||
doc.WriteTo(xw);
|
||||
xw.Flush();
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -28,7 +28,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using log4net;
|
||||
@@ -37,59 +40,21 @@ 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
|
||||
public class OfflineIMService : OfflineIMServiceBase, IOfflineIMService
|
||||
{
|
||||
//private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private IOfflineIMData m_Database = null;
|
||||
private int m_MaxOfflineIMs = 25;
|
||||
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private const int MAX_IM = 25;
|
||||
|
||||
private XmlSerializer m_serializer;
|
||||
private static bool m_Initialized = false;
|
||||
|
||||
public OfflineIMService(IConfigSource config) : base(config)
|
||||
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)
|
||||
{
|
||||
@@ -103,7 +68,8 @@ namespace OpenSim.OfflineIM
|
||||
List<GridInstantMessage> ims = new List<GridInstantMessage>();
|
||||
|
||||
OfflineIMData[] messages = m_Database.Get("PrincipalID", principalID.ToString());
|
||||
if (messages is null || messages.Length == 0)
|
||||
|
||||
if (messages == null || (messages != null && messages.Length == 0))
|
||||
return ims;
|
||||
|
||||
foreach (OfflineIMData m in messages)
|
||||
@@ -124,44 +90,42 @@ namespace OpenSim.OfflineIM
|
||||
public bool StoreMessage(GridInstantMessage im, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
// Check limits
|
||||
|
||||
// TODO Check limits
|
||||
UUID principalID = new UUID(im.toAgentID);
|
||||
long count = m_Database.GetCount("PrincipalID", principalID.ToString());
|
||||
if (count >= m_MaxOfflineIMs)
|
||||
if (count >= MAX_IM)
|
||||
{
|
||||
reason = "Number of offline IMs has maxed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
string imXml;
|
||||
string imXml = string.Empty;
|
||||
using (MemoryStream mstream = new MemoryStream())
|
||||
{
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.Encoding = Util.UTF8NoBomEncoding;
|
||||
settings.Encoding = Encoding.UTF8;
|
||||
|
||||
using (XmlWriter writer = XmlWriter.Create(mstream, settings))
|
||||
{
|
||||
m_serializer.Serialize(writer, im);
|
||||
writer.Flush();
|
||||
imXml = Util.UTF8NoBomEncoding.GetString(mstream.ToArray());
|
||||
|
||||
mstream.Position = 0;
|
||||
using (StreamReader sreader = new StreamReader(mstream))
|
||||
{
|
||||
imXml = sreader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,50 +24,60 @@
|
||||
* (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 Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Data;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Services.Base;
|
||||
|
||||
namespace OpenSim.Services.UserAccountService
|
||||
namespace OpenSim.OfflineIM
|
||||
{
|
||||
public class AgentPreferencesServiceBase: ServiceBase
|
||||
public class OfflineIMServiceBase : ServiceBase
|
||||
{
|
||||
protected IAgentPreferencesData m_Database = null;
|
||||
protected IOfflineIMData m_Database = null;
|
||||
|
||||
public AgentPreferencesServiceBase(IConfigSource config) : base(config)
|
||||
public OfflineIMServiceBase(IConfigSource config)
|
||||
: base(config)
|
||||
{
|
||||
string dllName = String.Empty;
|
||||
string connString = String.Empty;
|
||||
string realm = "AgentPrefs";
|
||||
string realm = "im_offline";
|
||||
|
||||
//
|
||||
// Try reading the [DatabaseService] section, if it exists
|
||||
//
|
||||
IConfig dbConfig = config.Configs["DatabaseService"];
|
||||
if (dbConfig != null)
|
||||
{
|
||||
dllName = dbConfig.GetString("StorageProvider", String.Empty);
|
||||
connString = dbConfig.GetString("ConnectionString", String.Empty);
|
||||
if (dllName == String.Empty)
|
||||
dllName = dbConfig.GetString("StorageProvider", String.Empty);
|
||||
if (connString == String.Empty)
|
||||
connString = dbConfig.GetString("ConnectionString", String.Empty);
|
||||
}
|
||||
|
||||
IConfig userConfig = config.Configs["AgentPreferencesService"];
|
||||
if (userConfig == null)
|
||||
throw new Exception("No AgentPreferencesService configuration");
|
||||
//
|
||||
// [Messaging] section overrides [DatabaseService], if it exists
|
||||
//
|
||||
IConfig imConfig = config.Configs["Messaging"];
|
||||
if (imConfig != null)
|
||||
{
|
||||
dllName = imConfig.GetString("StorageProvider", dllName);
|
||||
connString = imConfig.GetString("ConnectionString", connString);
|
||||
realm = imConfig.GetString("Realm", realm);
|
||||
}
|
||||
|
||||
dllName = userConfig.GetString("StorageProvider", dllName);
|
||||
|
||||
if (dllName.Length == 0)
|
||||
//
|
||||
// We tried, but this doesn't exist. We can't proceed.
|
||||
//
|
||||
if (dllName.Equals(String.Empty))
|
||||
throw new Exception("No StorageProvider configured");
|
||||
|
||||
connString = userConfig.GetString("ConnectionString", connString);
|
||||
|
||||
realm = userConfig.GetString("Realm", realm);
|
||||
|
||||
m_Database = LoadPlugin<IAgentPreferencesData>(dllName, new Object[] {connString, realm});
|
||||
|
||||
m_Database = LoadPlugin<IOfflineIMData>(dllName, new Object[] { connString, realm });
|
||||
if (m_Database == null)
|
||||
throw new Exception("Could not find a storage interface in the given module");
|
||||
throw new Exception("Could not find a storage interface in the given module " + dllName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
// ==============================================================
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -1,54 +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.Threading.Tasks;
|
||||
|
||||
using OMV = OpenMetaverse;
|
||||
|
||||
namespace osWebRtcVoice
|
||||
{
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public interface IVoiceViewerSession
|
||||
{
|
||||
// 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; }
|
||||
|
||||
// Disconnect the connection to the voice service for this session
|
||||
public Task Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,14 +95,6 @@ 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();
|
||||
|
||||
@@ -135,14 +123,14 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
|
||||
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.CreateRegion(regionsToLoad[i], true, out scene);
|
||||
createdScenes.Add(scene);
|
||||
|
||||
if (changed)
|
||||
m_openSim.EstateDataService.StoreEstateSettings(regionsToLoad[i].EstateSettings);
|
||||
regionsToLoad[i].EstateSettings.Save();
|
||||
}
|
||||
|
||||
foreach (IScene scene in createdScenes)
|
||||
@@ -175,7 +163,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}",
|
||||
|
||||
@@ -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,6 @@ 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.7.6.*")]
|
||||
|
||||
[assembly : AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
[assembly: Addin("OpenSim.ApplicationPlugins.LoadRegions", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly : AssemblyVersion("0.8.0.*")]
|
||||
|
||||
@@ -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>
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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")]
|
||||
@@ -15,8 +14,8 @@ using Mono.Addins;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -26,11 +25,9 @@ using Mono.Addins;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
[assembly: AssemblyVersion("0.8.0.*")]
|
||||
|
||||
[assembly: Addin("OpenSim.ApplicationPlugins.RegionModulesController", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
|
||||
|
||||
@@ -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,21 +80,54 @@ 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 (TypeExtensionNode node in
|
||||
AddinManager.GetExtensionNodes("/OpenSim/RegionModules"))
|
||||
{
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<RuntimeAddin, IList<int>> loadedModuleData in loadedModules)
|
||||
{
|
||||
m_log.InfoFormat(
|
||||
"[REGIONMODULES]: From plugin {0}, (version {1}), loaded {2} modules, {3} shared, {4} non-shared {5} unknown",
|
||||
loadedModuleData.Key.Id,
|
||||
loadedModuleData.Key.Id,
|
||||
loadedModuleData.Key.Version,
|
||||
loadedModuleData.Value[0] + loadedModuleData.Value[1] + loadedModuleData.Value[2],
|
||||
loadedModuleData.Value[0], loadedModuleData.Value[1], loadedModuleData.Value[2]);
|
||||
@@ -120,19 +142,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 +164,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 +194,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 +215,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
m_sharedInstances[0].Close();
|
||||
m_sharedInstances.RemoveAt(0);
|
||||
}
|
||||
|
||||
m_sharedModules.Clear();
|
||||
m_nonSharedModules.Clear();
|
||||
}
|
||||
@@ -250,7 +238,7 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
}
|
||||
|
||||
#region Region Module interfacesController implementation
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check that the given module is no disabled in the [Modules] section of the config files.
|
||||
/// </summary>
|
||||
@@ -260,10 +248,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 +270,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 +282,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 +294,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 +332,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 +429,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 +482,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
{
|
||||
module.RegionLoaded(scene);
|
||||
}
|
||||
|
||||
scene.AllModulesLoaded();
|
||||
}
|
||||
|
||||
public void RemoveRegionFromModules (Scene scene)
|
||||
|
||||
@@ -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>
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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")]
|
||||
@@ -15,8 +14,8 @@ using Mono.Addins;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -26,11 +25,9 @@ using Mono.Addins;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
[assembly: AssemblyVersion("0.8.0.*")]
|
||||
|
||||
[assembly: Addin("OpenSim.ApplicationPlugins.RemoteController", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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,30 @@ 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 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 Dictionary<string, PollServiceEventArgs> m_pollServiceHandlers
|
||||
= new Dictionary<string, PollServiceEventArgs>();
|
||||
|
||||
private Dictionary<string, string> m_externalCapsHandlers = new Dictionary<string, string>();
|
||||
|
||||
private IHttpServer m_httpListener;
|
||||
private UUID m_agentID;
|
||||
private string m_regionName;
|
||||
|
||||
public UUID AgentID
|
||||
{
|
||||
@@ -116,22 +118,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 +127,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 +135,8 @@ 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_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>
|
||||
@@ -197,41 +150,25 @@ namespace OpenSim.Framework.Capabilities
|
||||
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);
|
||||
m_pollServiceHandlers.Add(capName, pollServiceHandler);
|
||||
|
||||
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.Url, pollServiceHandler);
|
||||
|
||||
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";
|
||||
// }
|
||||
|
||||
//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));
|
||||
// RegisterHandler(
|
||||
// capName, String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, pollServiceHandler.Url));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -257,10 +194,10 @@ namespace OpenSim.Framework.Capabilities
|
||||
|
||||
foreach (PollServiceEventArgs handler in m_pollServiceHandlers.Values)
|
||||
{
|
||||
m_httpListener.RemovePollServiceHTTPHandler(handler.Url);
|
||||
m_httpListener.RemovePollServiceHTTPHandler("", handler.Url);
|
||||
}
|
||||
m_pollServiceHandlers.Clear();
|
||||
}
|
||||
|
||||
public bool TryGetPollHandler(string name, out PollServiceEventArgs pollHandler)
|
||||
{
|
||||
return m_pollServiceHandlers.TryGetValue(name, out pollHandler);
|
||||
@@ -282,22 +219,25 @@ namespace OpenSim.Framework.Capabilities
|
||||
|
||||
lock (m_pollServiceHandlers)
|
||||
{
|
||||
foreach (KeyValuePair<string, PollServiceEventArgs> kvp in m_pollServiceHandlers)
|
||||
foreach (KeyValuePair <string, PollServiceEventArgs> kvp in m_pollServiceHandlers)
|
||||
{
|
||||
if (!requestedCaps.Contains(kvp.Key))
|
||||
continue;
|
||||
|
||||
string hostName = m_httpListenerHostName;
|
||||
uint port = (MainServer.Instance is null) ? 0 : MainServer.Instance.Port;
|
||||
string protocol = "http";
|
||||
string hostName = m_httpListenerHostName;
|
||||
uint port = (MainServer.Instance == null) ? 0 : MainServer.Instance.Port;
|
||||
string protocol = "http";
|
||||
|
||||
if (MainServer.Instance.UseSSL)
|
||||
{
|
||||
hostName = MainServer.Instance.SSLCommonName;
|
||||
port = MainServer.Instance.SSLPort;
|
||||
protocol = "https";
|
||||
}
|
||||
//
|
||||
// caps.RegisterHandler("FetchInventoryDescendents2", String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, capUrl));
|
||||
|
||||
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);
|
||||
caps[kvp.Key] = string.Format("{0}://{1}:{2}{3}", protocol, hostName, port, kvp.Value.Url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,41 +250,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -173,72 +161,30 @@ namespace OpenSim.Framework.Capabilities
|
||||
public Hashtable GetCapsDetails(bool excludeSeed, List<string> requestedCaps)
|
||||
{
|
||||
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;
|
||||
if (requestedCaps != null && !requestedCaps.Contains(capsName))
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
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 AvatarPickerSearchHandler : BaseStreamHandler
|
||||
{
|
||||
private static readonly ILog m_log =
|
||||
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private IPeople m_PeopleService;
|
||||
|
||||
public AvatarPickerSearchHandler(string path, IPeople peopleService, string name, string description)
|
||||
: base("GET", path, name, description)
|
||||
{
|
||||
m_PeopleService = peopleService;
|
||||
}
|
||||
|
||||
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 names = query.GetOne("names");
|
||||
string psize = query.GetOne("page_size");
|
||||
string pnumber = query.GetOne("page");
|
||||
|
||||
if (m_PeopleService == null)
|
||||
return FailureResponse(names, (int)System.Net.HttpStatusCode.InternalServerError, httpResponse);
|
||||
|
||||
if (string.IsNullOrEmpty(names) || names.Length < 3)
|
||||
return FailureResponse(names, (int)System.Net.HttpStatusCode.BadRequest, httpResponse);
|
||||
|
||||
m_log.DebugFormat("[AVATAR PICKER SEARCH]: search for {0}", names);
|
||||
|
||||
int page_size = (string.IsNullOrEmpty(psize) ? 500 : Int32.Parse(psize));
|
||||
int page_number = (string.IsNullOrEmpty(pnumber) ? 1 : Int32.Parse(pnumber));
|
||||
|
||||
// Full content request
|
||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.OK;
|
||||
//httpResponse.ContentLength = ??;
|
||||
httpResponse.ContentType = "application/llsd+xml";
|
||||
|
||||
List<UserData> users = m_PeopleService.GetUserData(names, page_size, page_number);
|
||||
|
||||
LLSDAvatarPicker osdReply = new LLSDAvatarPicker();
|
||||
osdReply.next_page_url = httpRequest.RawUrl;
|
||||
foreach (UserData u in users)
|
||||
osdReply.agents.Array.Add(ConvertUserData(u));
|
||||
|
||||
string reply = LLSDHelpers.SerialiseLLSDReply(osdReply);
|
||||
return System.Text.Encoding.UTF8.GetBytes(reply);
|
||||
}
|
||||
|
||||
private LLSDPerson ConvertUserData(UserData user)
|
||||
{
|
||||
LLSDPerson p = new LLSDPerson();
|
||||
p.legacy_first_name = user.FirstName;
|
||||
p.legacy_last_name = user.LastName;
|
||||
p.display_name = user.FirstName + " " + user.LastName;
|
||||
if (user.LastName.StartsWith("@"))
|
||||
p.username = user.FirstName.ToLower() + user.LastName.ToLower();
|
||||
else
|
||||
p.username = user.FirstName.ToLower() + "." + user.LastName.ToLower();
|
||||
p.id = user.Id;
|
||||
p.is_display_name_default = false;
|
||||
return p;
|
||||
}
|
||||
|
||||
private byte[] FailureResponse(string names, int statuscode, IOSHttpResponse httpResponse)
|
||||
{
|
||||
m_log.Error("[AVATAR PICKER SEARCH]: Error searching for " + names);
|
||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||
return System.Text.Encoding.UTF8.GetBytes(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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;
|
||||
llsdItem.type = invItem.AssetType;
|
||||
llsdItem.inv_type = invItem.InvType;
|
||||
|
||||
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;
|
||||
llsdItem.sale_info.sale_type = invItem.SaleType;
|
||||
|
||||
return llsdItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -47,92 +47,85 @@ 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)
|
||||
protected override byte[] ProcessRequest(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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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,76 +134,91 @@ 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;
|
||||
newTexture.Local = 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
|
||||
@@ -232,8 +240,10 @@ namespace OpenSim.Capabilities.Handlers
|
||||
// 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.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
|
||||
{
|
||||
@@ -248,35 +258,41 @@ namespace OpenSim.Capabilities.Handlers
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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;
|
||||
|
||||
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 +305,86 @@ namespace OpenSim.Capabilities.Handlers
|
||||
// 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 == "")
|
||||
{
|
||||
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>();
|
||||
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 +401,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 +424,4 @@ namespace OpenSim.Capabilities.Handlers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,8 +37,8 @@ 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]
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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")]
|
||||
@@ -14,8 +14,8 @@ using System.Runtime.InteropServices;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -25,9 +25,9 @@ using System.Runtime.InteropServices;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
[assembly: AssemblyVersion("0.8.0.*")]
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,40 +25,52 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using Nini.Config;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Framework.ServiceAuth;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Server.Handlers.Base;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Server.Handlers.AgentPreferences
|
||||
namespace OpenSim.Capabilities.Handlers
|
||||
{
|
||||
public class AgentPreferencesServiceConnector : ServiceConnector
|
||||
public class UploadBakedTextureServerConnector : ServiceConnector
|
||||
{
|
||||
private IAgentPreferencesService m_AgentPreferencesService;
|
||||
private string m_ConfigName = "AgentPreferencesService";
|
||||
private IAssetService m_AssetService;
|
||||
private string m_ConfigName = "CapsService";
|
||||
|
||||
public AgentPreferencesServiceConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
public UploadBakedTextureServerConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
base(config, server, configName)
|
||||
{
|
||||
if (configName != String.Empty)
|
||||
m_ConfigName = configName;
|
||||
|
||||
IConfig serverConfig = config.Configs[m_ConfigName];
|
||||
if (serverConfig == null)
|
||||
throw new Exception(String.Format("No section {0} in config file", m_ConfigName));
|
||||
throw new Exception(String.Format("No section '{0}' in config file", m_ConfigName));
|
||||
|
||||
string service = serverConfig.GetString("LocalServiceModule", String.Empty);
|
||||
string assetService = serverConfig.GetString("AssetService", String.Empty);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(service))
|
||||
throw new Exception("No LocalServiceModule in config file");
|
||||
if (assetService == String.Empty)
|
||||
throw new Exception("No AssetService in config file");
|
||||
|
||||
Object[] args = new Object[] { config };
|
||||
m_AgentPreferencesService = ServerUtils.LoadPlugin<IAgentPreferencesService>(service, args);
|
||||
m_AssetService =
|
||||
ServerUtils.LoadPlugin<IAssetService>(assetService, args);
|
||||
|
||||
IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName);
|
||||
if (m_AssetService == null)
|
||||
throw new Exception(String.Format("Failed to load AssetService from {0}; config is {1}", assetService, m_ConfigName));
|
||||
|
||||
server.AddStreamHandler(new AgentPreferencesServerPostHandler(m_AgentPreferencesService, auth));
|
||||
}
|
||||
// NEED TO FIX THIS
|
||||
OpenSim.Framework.Capabilities.Caps caps = new OpenSim.Framework.Capabilities.Caps(server, "", server.Port, "", UUID.Zero, "");
|
||||
server.AddStreamHandler(new RestStreamHandler(
|
||||
"POST",
|
||||
"/CAPS/UploadBakedTexture/",
|
||||
new UploadBakedTextureHandler(caps, m_AssetService, true).UploadBakedTexture,
|
||||
"UploadBakedTexture",
|
||||
"Upload Baked Texture Capability"));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 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;
|
||||
int descendents = 0;
|
||||
|
||||
inv
|
||||
= Fetch(
|
||||
invFetch.owner_id, invFetch.folder_id, invFetch.owner_id,
|
||||
invFetch.fetch_folders, invFetch.fetch_items, invFetch.sort_order, out version, out descendents);
|
||||
|
||||
if (inv != null && inv.Folders != null)
|
||||
{
|
||||
foreach (InventoryFolderBase invFolder in inv.Folders)
|
||||
{
|
||||
contents.categories.Array.Add(ConvertInventoryFolder(invFolder));
|
||||
}
|
||||
|
||||
descendents += inv.Folders.Count;
|
||||
}
|
||||
|
||||
if (inv != null && inv.Items != null)
|
||||
{
|
||||
foreach (InventoryItemBase invItem in inv.Items)
|
||||
{
|
||||
contents.items.Array.Add(ConvertInventoryItem(invItem));
|
||||
}
|
||||
}
|
||||
|
||||
contents.descendents = descendents;
|
||||
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, out int descendents)
|
||||
{
|
||||
// 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;
|
||||
descendents = 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();
|
||||
descendents = ret.Folders.Count + ret.Items.Count;
|
||||
|
||||
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> itemsToReturn = contents.Items;
|
||||
List<InventoryItemBase> originalItems = new List<InventoryItemBase>(itemsToReturn);
|
||||
|
||||
// descendents must only include the links, not the linked items we add
|
||||
descendents = originalItems.Count;
|
||||
|
||||
// Add target items for links in this folder before the links themselves.
|
||||
foreach (InventoryItemBase item in originalItems)
|
||||
{
|
||||
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)
|
||||
itemsToReturn.Insert(0, linkedItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Now scan for folder links and insert the items they target and those links at the head of the return data
|
||||
foreach (InventoryItemBase item in originalItems)
|
||||
{
|
||||
if (item.AssetType == (int)AssetType.LinkFolder)
|
||||
{
|
||||
InventoryCollection linkedFolderContents = m_InventoryService.GetFolderContent(ownerID, item.AssetID);
|
||||
List<InventoryItemBase> links = linkedFolderContents.Items;
|
||||
|
||||
itemsToReturn.InsertRange(0, links);
|
||||
|
||||
foreach (InventoryItemBase link in linkedFolderContents.Items)
|
||||
{
|
||||
// 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 (link != null)
|
||||
{
|
||||
// m_log.DebugFormat(
|
||||
// "[WEB FETCH INV DESC HANDLER]: Adding item {0} {1} from folder {2} linked from {3}",
|
||||
// link.Name, (AssetType)link.AssetType, item.AssetID, containingFolder.Name);
|
||||
|
||||
InventoryItemBase linkedItem
|
||||
= m_InventoryService.GetItem(new InventoryItemBase(link.AssetID));
|
||||
|
||||
if (linkedItem != null)
|
||||
itemsToReturn.Insert(0, linkedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// foreach (InventoryItemBase item in contents.Items)
|
||||
// {
|
||||
// m_log.DebugFormat(
|
||||
// "[WEB FETCH INV DESC HANDLER]: Returning item {0}, type {1}, parent {2} in {3} {4}",
|
||||
// item.Name, (AssetType)item.AssetType, item.Folder, containingFolder.Name, containingFolder.ID);
|
||||
// }
|
||||
|
||||
// =====
|
||||
|
||||
//
|
||||
// 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;
|
||||
llsdFolder.type = 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;
|
||||
llsdItem.type = invItem.AssetType;
|
||||
llsdItem.inv_type = invItem.InvType;
|
||||
|
||||
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;
|
||||
llsdItem.sale_info.sale_type = invItem.SaleType;
|
||||
|
||||
return llsdItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,22 +29,19 @@ using System;
|
||||
using Nini.Config;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Server.Handlers.Base;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
|
||||
namespace OpenSim.Capabilities.Handlers
|
||||
{
|
||||
public class FetchInvDescServerConnector : ServiceConnector
|
||||
public class WebFetchInvDescServerConnector : ServiceConnector
|
||||
{
|
||||
private IInventoryService m_InventoryService;
|
||||
private ILibraryService m_LibraryService;
|
||||
private string m_ConfigName = "CapsService";
|
||||
|
||||
public FetchInvDescServerConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
public WebFetchInvDescServerConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
base(config, server, configName)
|
||||
{
|
||||
if (configName != String.Empty)
|
||||
@@ -56,7 +53,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 };
|
||||
@@ -70,16 +67,16 @@ namespace OpenSim.Capabilities.Handlers
|
||||
m_LibraryService =
|
||||
ServerUtils.LoadPlugin<ILibraryService>(libService, args);
|
||||
|
||||
ExpiringKey<UUID> m_badRequests = new ExpiringKey<UUID>(30000);
|
||||
|
||||
FetchInvDescHandler webFetchHandler = new FetchInvDescHandler(m_InventoryService, m_LibraryService, null);
|
||||
ISimpleStreamHandler reqHandler
|
||||
= new SimpleStreamHandler("/CAPS/WebFetchInvDesc/", delegate(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
webFetchHandler.FetchInventoryDescendentsRequest(httpRequest, httpResponse, m_badRequests);
|
||||
});
|
||||
|
||||
server.AddSimpleStreamHandler(reqHandler);
|
||||
WebFetchInvDescHandler webFetchHandler = new WebFetchInvDescHandler(m_InventoryService, m_LibraryService);
|
||||
IRequestHandler reqHandler
|
||||
= new RestStreamHandler(
|
||||
"POST",
|
||||
"/CAPS/WebFetchInvDesc/" /*+ UUID.Random()*/,
|
||||
webFetchHandler.FetchInventoryDescendentsRequest,
|
||||
"WebFetchInvDesc",
|
||||
null);
|
||||
server.AddStreamHandler(reqHandler);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
@@ -69,10 +68,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// <returns></returns>
|
||||
public static object LLSDDeserialize(byte[] b)
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream(b, false))
|
||||
{
|
||||
return LLSDDeserialize(ms);
|
||||
}
|
||||
return LLSDDeserialize(new MemoryStream(b, false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,25 +78,21 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// <returns></returns>
|
||||
public static object LLSDDeserialize(Stream st)
|
||||
{
|
||||
using (XmlTextReader reader = new XmlTextReader(st))
|
||||
{
|
||||
reader.DtdProcessing = DtdProcessing.Ignore;
|
||||
XmlTextReader reader = new XmlTextReader(st);
|
||||
reader.Read();
|
||||
SkipWS(reader);
|
||||
|
||||
reader.Read();
|
||||
SkipWS(reader);
|
||||
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd")
|
||||
throw new LLSDParseException("Expected <llsd>");
|
||||
|
||||
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd")
|
||||
throw new LLSDParseException("Expected <llsd>");
|
||||
reader.Read();
|
||||
object ret = LLSDParseOne(reader);
|
||||
SkipWS(reader);
|
||||
|
||||
reader.Read();
|
||||
object ret = LLSDParseOne(reader);
|
||||
SkipWS(reader);
|
||||
if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd")
|
||||
throw new LLSDParseException("Expected </llsd>");
|
||||
|
||||
if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd")
|
||||
throw new LLSDParseException("Expected </llsd>");
|
||||
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,17 +102,17 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// <returns></returns>
|
||||
public static byte[] LLSDSerialize(object obj)
|
||||
{
|
||||
using(StringWriter sw = new StringWriter())
|
||||
using(XmlTextWriter writer = new XmlTextWriter(sw))
|
||||
{
|
||||
writer.Formatting = Formatting.None;
|
||||
StringWriter sw = new StringWriter();
|
||||
XmlTextWriter writer = new XmlTextWriter(sw);
|
||||
writer.Formatting = Formatting.None;
|
||||
|
||||
writer.WriteStartElement(string.Empty, "llsd", string.Empty);
|
||||
LLSDWriteOne(writer, obj);
|
||||
writer.WriteEndElement();
|
||||
writer.Flush();
|
||||
return Util.UTF8.GetBytes(sw.ToString());
|
||||
}
|
||||
writer.WriteStartElement(String.Empty, "llsd", String.Empty);
|
||||
LLSDWriteOne(writer, obj);
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.Close();
|
||||
|
||||
return Util.UTF8.GetBytes(sw.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -132,32 +124,33 @@ namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "undef", string.Empty);
|
||||
writer.WriteStartElement(String.Empty, "undef", String.Empty);
|
||||
writer.WriteEndElement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is string s)
|
||||
if (obj is string)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "string", string.Empty);
|
||||
writer.WriteString(s);
|
||||
writer.WriteStartElement(String.Empty, "string", String.Empty);
|
||||
writer.WriteString((string) obj);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is int)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "integer", string.Empty);
|
||||
writer.WriteStartElement(String.Empty, "integer", String.Empty);
|
||||
writer.WriteString(obj.ToString());
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is double || obj is float)
|
||||
else if (obj is double)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "real", string.Empty);
|
||||
writer.WriteStartElement(String.Empty, "real", String.Empty);
|
||||
writer.WriteString(obj.ToString());
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is bool b)
|
||||
else if (obj is bool)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "boolean", string.Empty);
|
||||
bool b = (bool) obj;
|
||||
writer.WriteStartElement(String.Empty, "boolean", String.Empty);
|
||||
writer.WriteString(b ? "1" : "0");
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
@@ -165,63 +158,56 @@ namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
throw new Exception("ulong in LLSD is currently not implemented, fix me!");
|
||||
}
|
||||
else if (obj is UUID u)
|
||||
else if (obj is UUID)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "uuid", string.Empty);
|
||||
UUID u = (UUID) obj;
|
||||
writer.WriteStartElement(String.Empty, "uuid", String.Empty);
|
||||
writer.WriteString(u.ToString());
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is Hashtable h)
|
||||
else if (obj is Hashtable)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "map", string.Empty);
|
||||
foreach (DictionaryEntry de in h)
|
||||
Hashtable h = obj as Hashtable;
|
||||
writer.WriteStartElement(String.Empty, "map", String.Empty);
|
||||
foreach (string key in h.Keys)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "key", string.Empty);
|
||||
writer.WriteString((string)de.Key);
|
||||
writer.WriteStartElement(String.Empty, "key", String.Empty);
|
||||
writer.WriteString(key);
|
||||
writer.WriteEndElement();
|
||||
LLSDWriteOne(writer, de.Value);
|
||||
LLSDWriteOne(writer, h[key]);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is Dictionary<string,object> dict)
|
||||
else if (obj is ArrayList)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "map", string.Empty);
|
||||
foreach (KeyValuePair<string,object> kvp in dict)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "key", string.Empty);
|
||||
writer.WriteString(kvp.Key);
|
||||
writer.WriteEndElement();
|
||||
LLSDWriteOne(writer, kvp.Value);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is ArrayList a)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "array", string.Empty);
|
||||
ArrayList a = obj as ArrayList;
|
||||
writer.WriteStartElement(String.Empty, "array", String.Empty);
|
||||
foreach (object item in a)
|
||||
{
|
||||
LLSDWriteOne(writer, item);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is List<object> lsto)
|
||||
else if (obj is byte[])
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "array", string.Empty);
|
||||
foreach (object item in lsto)
|
||||
{
|
||||
LLSDWriteOne(writer, item);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else if (obj is byte[] bytes)
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "binary", string.Empty);
|
||||
byte[] b = obj as byte[];
|
||||
writer.WriteStartElement(String.Empty, "binary", String.Empty);
|
||||
|
||||
writer.WriteStartAttribute(String.Empty, "encoding", String.Empty);
|
||||
writer.WriteString("base64");
|
||||
writer.WriteEndAttribute();
|
||||
|
||||
writer.WriteString(Convert.ToBase64String(bytes));
|
||||
//// Calculate the length of the base64 output
|
||||
//long length = (long)(4.0d * b.Length / 3.0d);
|
||||
//if (length % 4 != 0) length += 4 - (length % 4);
|
||||
|
||||
//// Create the char[] for base64 output and fill it
|
||||
//char[] tmp = new char[length];
|
||||
//int i = Convert.ToBase64CharArray(b, 0, b.Length, tmp, 0);
|
||||
|
||||
//writer.WriteString(new String(tmp));
|
||||
|
||||
writer.WriteString(Convert.ToBase64String(b));
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
else
|
||||
@@ -462,7 +448,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
private static string GetSpaces(int count)
|
||||
{
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) b.Append(' ');
|
||||
for (int i = 0; i < count; i++) b.Append(" ");
|
||||
return b.ToString();
|
||||
}
|
||||
|
||||
@@ -472,7 +458,6 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="indent"></param>
|
||||
/// <returns></returns>
|
||||
/*
|
||||
public static String LLSDDump(object obj, int indent)
|
||||
{
|
||||
if (obj == null)
|
||||
@@ -576,7 +561,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
endPos = FindEnd(llsd, 1);
|
||||
|
||||
if (Double.TryParse(llsd.Substring(1, endPos - 1), NumberStyles.Float,
|
||||
Culture.NumberFormatInfo, out value))
|
||||
Utils.EnUsCulture.NumberFormat, out value))
|
||||
return value;
|
||||
else
|
||||
throw new LLSDParseException("Failed to parse double value type");
|
||||
@@ -667,7 +652,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
throw new Exception("Unknown value type");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private static int FindEnd(string llsd, int start)
|
||||
{
|
||||
int end = llsd.IndexOfAny(new char[] {',', ']', '}'});
|
||||
|
||||
@@ -30,20 +30,13 @@ using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
|
||||
[LLSDType("MAP")]
|
||||
public class LLSDAssetUploadComplete
|
||||
{
|
||||
public string new_asset = String.Empty;
|
||||
public UUID new_inventory_item = UUID.Zero;
|
||||
// public UUID new_texture_folder_id = UUID.Zero;
|
||||
public string state = String.Empty;
|
||||
public LLSDAssetUploadError error = null;
|
||||
//public bool success = false;
|
||||
public int new_next_owner_mask = 0;
|
||||
public int new_group_mask = 0;
|
||||
public int new_everyone_mask = 0;
|
||||
public int inventory_item_flags = 0;
|
||||
|
||||
public LLSDAssetUploadComplete()
|
||||
{
|
||||
|
||||
@@ -30,28 +30,15 @@ using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
[OSDMap]
|
||||
public class LLSDAssetResource
|
||||
{
|
||||
public OSDArray instance_list = new OSDArray();
|
||||
public OSDArray texture_list = new OSDArray();
|
||||
public OSDArray mesh_list = new OSDArray();
|
||||
public string metric = String.Empty;
|
||||
}
|
||||
|
||||
[OSDMap]
|
||||
public class LLSDAssetUploadRequest
|
||||
{
|
||||
public string asset_type = String.Empty;
|
||||
public string description = String.Empty;
|
||||
public UUID folder_id = UUID.Zero;
|
||||
public UUID texture_folder_id = UUID.Zero;
|
||||
public int next_owner_mask = 0;
|
||||
public int group_mask = 0;
|
||||
public int everyone_mask = 0;
|
||||
public string inventory_type = String.Empty;
|
||||
public string name = String.Empty;
|
||||
public LLSDAssetResource asset_resources = new LLSDAssetResource();
|
||||
|
||||
public LLSDAssetUploadRequest()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -26,51 +26,20 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
[OSDMap]
|
||||
public class LLSDAssetUploadError
|
||||
{
|
||||
public string message = String.Empty;
|
||||
public UUID identifier = UUID.Zero;
|
||||
}
|
||||
|
||||
[OSDMap]
|
||||
public class LLSDAssetUploadResponsePricebrkDown
|
||||
{
|
||||
public int mesh_streaming;
|
||||
public int mesh_physics;
|
||||
public int mesh_instance;
|
||||
public int texture;
|
||||
public int model;
|
||||
}
|
||||
|
||||
[OSDMap]
|
||||
public class LLSDAssetUploadResponseData
|
||||
{
|
||||
public double resource_cost;
|
||||
public double model_streaming_cost;
|
||||
public double simulation_cost;
|
||||
public double physics_cost;
|
||||
public LLSDAssetUploadResponsePricebrkDown upload_price_breakdown = new LLSDAssetUploadResponsePricebrkDown();
|
||||
}
|
||||
|
||||
[OSDMap]
|
||||
public class LLSDAssetUploadResponse
|
||||
{
|
||||
public string uploader = String.Empty;
|
||||
public string state = String.Empty;
|
||||
public int upload_price = 0;
|
||||
public LLSDAssetUploadResponseData data = null;
|
||||
public LLSDAssetUploadError error = null;
|
||||
|
||||
public LLSDAssetUploadResponse()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[OSDMap]
|
||||
public class LLSDNewFileAngentInventoryVariablePriceReplyResponse
|
||||
{
|
||||
@@ -78,7 +47,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
public string state;
|
||||
public int upload_price;
|
||||
public string rsvp;
|
||||
|
||||
|
||||
public LLSDNewFileAngentInventoryVariablePriceReplyResponse()
|
||||
{
|
||||
state = "confirm_upload";
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
public string username;
|
||||
public string display_name;
|
||||
//'display_name_next_update':d"1970-01-01T00:00:00Z"
|
||||
//'display_name_next_update':d"1970-01-01T00:00:00Z"
|
||||
public string legacy_first_name;
|
||||
public string legacy_last_name;
|
||||
public UUID id;
|
||||
|
||||
68
OpenSim/Capabilities/LLSDEnvironmentSettings.cs
Normal file
68
OpenSim/Capabilities/LLSDEnvironmentSettings.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
[OSDMap]
|
||||
public class LLSDEnvironmentRequest
|
||||
{
|
||||
public UUID messageID;
|
||||
public UUID regionID;
|
||||
}
|
||||
|
||||
[OSDMap]
|
||||
public class LLSDEnvironmentSetResponse
|
||||
{
|
||||
public UUID regionID;
|
||||
public UUID messageID;
|
||||
public Boolean success;
|
||||
public String fail_reason;
|
||||
}
|
||||
|
||||
public class EnvironmentSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// generates a empty llsd settings response for viewer
|
||||
/// </summary>
|
||||
/// <param name="messageID">the message UUID</param>
|
||||
/// <param name="regionID">the region UUID</param>
|
||||
public static string EmptySettings(UUID messageID, UUID regionID)
|
||||
{
|
||||
OSDArray arr = new OSDArray();
|
||||
LLSDEnvironmentRequest msg = new LLSDEnvironmentRequest();
|
||||
msg.messageID = messageID;
|
||||
msg.regionID = regionID;
|
||||
arr.Array.Add(msg);
|
||||
return LLSDHelpers.SerialiseLLSDReply(arr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,7 +30,6 @@ using System.Collections;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
@@ -41,32 +40,17 @@ namespace OpenSim.Framework.Capabilities
|
||||
|
||||
public static string SerialiseLLSDReply(object obj)
|
||||
{
|
||||
using(StringWriter sw = new StringWriter())
|
||||
using(XmlTextWriter writer = new XmlTextWriter(sw))
|
||||
{
|
||||
writer.Formatting = Formatting.None;
|
||||
writer.WriteStartElement(String.Empty, "llsd", String.Empty);
|
||||
SerializeOSDType(writer, obj);
|
||||
writer.WriteEndElement();
|
||||
writer.Flush();
|
||||
StringWriter sw = new StringWriter();
|
||||
XmlTextWriter writer = new XmlTextWriter(sw);
|
||||
writer.Formatting = Formatting.None;
|
||||
writer.WriteStartElement(String.Empty, "llsd", String.Empty);
|
||||
SerializeOSDType(writer, obj);
|
||||
writer.WriteEndElement();
|
||||
writer.Close();
|
||||
|
||||
//m_log.DebugFormat("[LLSD Helpers]: Generated serialized LLSD reply {0}", sw.ToString());
|
||||
|
||||
return sw.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerialiseLLSDReplyNoHeader(object obj)
|
||||
{
|
||||
using(StringWriter sw = new StringWriter())
|
||||
using(XmlTextWriter writer = new XmlTextWriter(sw))
|
||||
{
|
||||
writer.Formatting = Formatting.None;
|
||||
SerializeOSDType(writer, obj);
|
||||
writer.Flush();
|
||||
//m_log.DebugFormat("[LLSD Helpers]: Generated serialized LLSD reply {0}", sw.ToString());
|
||||
|
||||
return sw.ToString();
|
||||
}
|
||||
return sw.ToString();
|
||||
}
|
||||
|
||||
private static void SerializeOSDType(XmlTextWriter writer, object obj)
|
||||
@@ -173,22 +157,6 @@ namespace OpenSim.Framework.Capabilities
|
||||
// the LLSD map/array types in the array need to be deserialised
|
||||
// but first we need to know the right class to deserialise them into.
|
||||
}
|
||||
else if(enumerator.Value is Boolean && field.FieldType == typeof(int) )
|
||||
{
|
||||
int i = (bool)enumerator.Value ? 1 : 0;
|
||||
field.SetValue(obj, i);
|
||||
}
|
||||
else if(field.FieldType == typeof(bool) && enumerator.Value is int)
|
||||
{
|
||||
bool b = (int)enumerator.Value != 0;
|
||||
field.SetValue(obj, b);
|
||||
}
|
||||
else if(field.FieldType == typeof(UUID) && enumerator.Value is string)
|
||||
{
|
||||
UUID u;
|
||||
UUID.TryParse((string)enumerator.Value, out u);
|
||||
field.SetValue(obj, u);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.SetValue(obj, enumerator.Value);
|
||||
|
||||
@@ -37,6 +37,5 @@ namespace OpenSim.Framework.Capabilities
|
||||
public string name;
|
||||
public int type;
|
||||
public int preferred_type;
|
||||
public int version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ namespace OpenSim.Framework.Capabilities
|
||||
[OSDMap]
|
||||
public class LLSDInventoryFolderContents
|
||||
{
|
||||
public UUID agent_id;
|
||||
public UUID agent_id;
|
||||
public int descendents;
|
||||
public UUID folder_id;
|
||||
public UUID folder_id;
|
||||
public OSDArray categories = new OSDArray();
|
||||
public OSDArray items = new OSDArray();
|
||||
public UUID owner_id;
|
||||
public UUID owner_id;
|
||||
public int version;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,17 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Services.Interfaces
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
public interface IAttachmentsService
|
||||
[OSDMap]
|
||||
public class LLSDItemUpdate
|
||||
{
|
||||
string Get(string id);
|
||||
void Store(string id, string data);
|
||||
public UUID item_id;
|
||||
|
||||
public LLSDItemUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
51
OpenSim/Capabilities/LLSDParcelVoiceInfoResponse.cs
Normal file
51
OpenSim/Capabilities/LLSDParcelVoiceInfoResponse.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
[OSDMap]
|
||||
public class LLSDParcelVoiceInfoResponse
|
||||
{
|
||||
public int parcel_local_id;
|
||||
public string region_name;
|
||||
public Hashtable voice_credentials;
|
||||
|
||||
public LLSDParcelVoiceInfoResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public LLSDParcelVoiceInfoResponse(string region, int localID, Hashtable creds)
|
||||
{
|
||||
region_name = region;
|
||||
parcel_local_id = localID;
|
||||
voice_credentials = creds;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
OpenSim/Capabilities/LLSDRemoteParcelResponse.cs
Normal file
42
OpenSim/Capabilities/LLSDRemoteParcelResponse.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
[LLSDType("MAP")]
|
||||
public class LLSDRemoteParcelResponse
|
||||
{
|
||||
public UUID parcel_id;
|
||||
|
||||
public LLSDRemoteParcelResponse()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,10 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
@@ -60,9 +61,6 @@ namespace OpenSim.Framework.Capabilities
|
||||
// OpenMetaverse.StructuredData.LLSDParser.DeserializeXml(new XmlTextReader(request));
|
||||
|
||||
Hashtable hash = (Hashtable) LLSD.LLSDDeserialize(request);
|
||||
if(hash == null)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
TRequest llsdRequest = new TRequest();
|
||||
LLSDHelpers.DeserialiseOSDMap(hash, llsdRequest);
|
||||
|
||||
|
||||
50
OpenSim/Capabilities/LLSDTaskScriptUpdate.cs
Normal file
50
OpenSim/Capabilities/LLSDTaskScriptUpdate.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
[OSDMap]
|
||||
public class LLSDTaskScriptUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// The item containing the script to update
|
||||
/// </summary>
|
||||
public UUID item_id;
|
||||
|
||||
/// <summary>
|
||||
/// The task containing the script
|
||||
/// </summary>
|
||||
public UUID task_id;
|
||||
|
||||
/// <summary>
|
||||
/// Signals whether the script is currently active
|
||||
/// </summary>
|
||||
public int is_script_running;
|
||||
}
|
||||
}
|
||||
57
OpenSim/Capabilities/LLSDVoiceAccountResponse.cs
Normal file
57
OpenSim/Capabilities/LLSDVoiceAccountResponse.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.Framework.Capabilities
|
||||
{
|
||||
[OSDMap]
|
||||
public class LLSDVoiceAccountResponse
|
||||
{
|
||||
public string username;
|
||||
public string password;
|
||||
public string voice_sip_uri_hostname;
|
||||
public string voice_account_server_name;
|
||||
|
||||
public LLSDVoiceAccountResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public LLSDVoiceAccountResponse(string user, string pass)
|
||||
{
|
||||
username = user;
|
||||
password = pass;
|
||||
}
|
||||
|
||||
public LLSDVoiceAccountResponse(string user, string pass, string sipUriHost, string accountServer)
|
||||
{
|
||||
username = user;
|
||||
password = pass;
|
||||
voice_sip_uri_hostname = sipUriHost;
|
||||
voice_account_server_name = accountServer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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")]
|
||||
@@ -14,8 +14,8 @@ using System.Runtime.InteropServices;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -25,7 +25,7 @@ using System.Runtime.InteropServices;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
|
||||
@@ -75,15 +75,15 @@ namespace OpenSim.ConsoleClient
|
||||
|
||||
Requester.MakeRequest("http://"+m_Host+":"+m_Port.ToString()+"/StartSession/", String.Format("USER={0}&PASS={1}", m_User, m_Pass), LoginReply);
|
||||
|
||||
string pidFile = serverConfig.GetString("PIDFile", String.Empty);
|
||||
|
||||
while (m_Server.Running)
|
||||
{
|
||||
System.Threading.Thread.Sleep(500);
|
||||
MainConsole.Instance.Prompt();
|
||||
// MainConsole.Instance.Prompt();
|
||||
}
|
||||
|
||||
string pidFile = serverConfig.GetString("PIDFile", string.Empty);
|
||||
if (pidFile.Length > 0)
|
||||
if (pidFile != String.Empty)
|
||||
File.Delete(pidFile);
|
||||
|
||||
Environment.Exit(0);
|
||||
@@ -178,7 +178,7 @@ namespace OpenSim.ConsoleClient
|
||||
Requester.MakeRequest(requestUrl, requestData, ReadResponses);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
|
||||
foreach (XmlNode part in rootNodeL[0].ChildNodes)
|
||||
@@ -202,7 +202,7 @@ namespace OpenSim.ConsoleClient
|
||||
string[] parts = l.Split(new char[] {':'}, 3);
|
||||
if (parts.Length != 3)
|
||||
continue;
|
||||
|
||||
|
||||
if (parts[2].StartsWith("+++") || parts[2].StartsWith("-++"))
|
||||
prompt = parts[2];
|
||||
else
|
||||
@@ -212,10 +212,10 @@ namespace OpenSim.ConsoleClient
|
||||
|
||||
Requester.MakeRequest(requestUrl, requestData, ReadResponses);
|
||||
|
||||
// if (prompt.StartsWith("+++"))
|
||||
MainConsole.Instance.ReadLine(prompt.Substring(0), true, true);
|
||||
// else if (prompt.StartsWith("-++"))
|
||||
// SendCommand(String.Empty, new string[] { MainConsole.Instance.ReadLine(prompt.Substring(3), false, true) });
|
||||
if (prompt.StartsWith("+++"))
|
||||
MainConsole.Instance.ReadLine(prompt.Substring(3), true, true);
|
||||
else if (prompt.StartsWith("-++"))
|
||||
SendCommand(String.Empty, new string[] { MainConsole.Instance.ReadLine(prompt.Substring(3), false, true) });
|
||||
}
|
||||
|
||||
public static void CommandReply(string requestUrl, string requestData, string replyData)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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.ConsoleClient")]
|
||||
@@ -14,8 +14,8 @@ using System.Runtime.InteropServices;
|
||||
[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
|
||||
// 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)]
|
||||
|
||||
@@ -25,7 +25,7 @@ using System.Runtime.InteropServices;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
|
||||
@@ -67,8 +67,9 @@ namespace OpenSim.ConsoleClient
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamReader r = new StreamReader(response.GetResponseStream()))
|
||||
reply = r.ReadToEnd();
|
||||
using (Stream s = response.GetResponseStream())
|
||||
using (StreamReader r = new StreamReader(s))
|
||||
reply = r.ReadToEnd();
|
||||
|
||||
}
|
||||
catch (System.InvalidOperationException)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user