Compare commits
519 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1b49aaa99 | ||
|
|
b779ab041b | ||
|
|
a402571a60 | ||
|
|
7e75fd7dcb | ||
|
|
282123cb00 | ||
|
|
476ad0550f | ||
|
|
6e612937c7 | ||
|
|
c992037d50 | ||
|
|
fe06b49dd6 | ||
|
|
1df97c2385 | ||
|
|
37ab587f27 | ||
|
|
12201bf7f4 | ||
|
|
c7179ff454 | ||
|
|
841ca0fe38 | ||
|
|
1ee6822e39 | ||
|
|
3fdeb559f0 | ||
|
|
50811b02ab | ||
|
|
009178d7dd | ||
|
|
7cacf5f8a0 | ||
|
|
b1740e11de | ||
|
|
b6cbda61b3 | ||
|
|
914ebd7476 | ||
|
|
7d68559e15 | ||
|
|
5c75e43a70 | ||
|
|
79dd5f2692 | ||
|
|
216ef7522a | ||
|
|
ccd1bac994 | ||
|
|
182c66cea1 | ||
|
|
7fe768a98d | ||
|
|
4fd176f479 | ||
|
|
2be88cd46c | ||
|
|
5a2895977a | ||
|
|
7fc820b3fd | ||
|
|
72838a04d6 | ||
|
|
9ee6c06ec8 | ||
|
|
5393ecfa75 | ||
|
|
f6ddd20413 | ||
|
|
b46a9cf57f | ||
|
|
aab719dc18 | ||
|
|
90d71d423c | ||
|
|
3f6feec914 | ||
|
|
6874d56452 | ||
|
|
23df5768c3 | ||
|
|
f52cee9732 | ||
|
|
37e4186ad8 | ||
|
|
5be2483e93 | ||
|
|
6418b89fd6 | ||
|
|
c67f791097 | ||
|
|
001bbb0b16 | ||
|
|
365292e38f | ||
|
|
fb2de29f77 | ||
|
|
251a70338a | ||
|
|
e210dc188a | ||
|
|
e61117669e | ||
|
|
97fcc97902 | ||
|
|
eab1b1b9f8 | ||
|
|
87d50974f8 | ||
|
|
78814adf01 | ||
|
|
341dcbede6 | ||
|
|
901aa4153e | ||
|
|
f99ba6f506 | ||
|
|
85f32f184c | ||
|
|
982d3d3faa | ||
|
|
744ed1b313 | ||
|
|
69ca1498ef | ||
|
|
9a60039d36 | ||
|
|
f030f1dbfb | ||
|
|
16641e5bd0 | ||
|
|
10db078aa4 | ||
|
|
afc93d7ac4 | ||
|
|
fa5e1dde64 | ||
|
|
0810f5f449 | ||
|
|
56e245e5e9 | ||
|
|
89362dbc4e | ||
|
|
2f6ddc68b0 | ||
|
|
25b539e593 | ||
|
|
25a9ad1722 | ||
|
|
d3bbfcc97b | ||
|
|
c3cbba7778 | ||
|
|
648932458a | ||
|
|
f1b978cdf0 | ||
|
|
cb26e8a6c6 | ||
|
|
7b8dc102d2 | ||
|
|
cf04e09521 | ||
|
|
3f50de6445 | ||
|
|
18659dfaea | ||
|
|
ba175ee50b | ||
|
|
995976c6db | ||
|
|
cfc9270bae | ||
|
|
b2b6fd6aad | ||
|
|
96454ddee2 | ||
|
|
4a0b9c411e | ||
|
|
e67b84613d | ||
|
|
d4f9982936 | ||
|
|
873ce644d3 | ||
|
|
1f8e43dd93 | ||
|
|
7bfd369b24 | ||
|
|
1da5a1ab21 | ||
|
|
af30d9b07e | ||
|
|
b2c893a7be | ||
|
|
3f80ac23b6 | ||
|
|
701109c8c9 | ||
|
|
5cbda393cd | ||
|
|
98d0440c42 | ||
|
|
8148d7e204 | ||
|
|
b43a6b0199 | ||
|
|
d01943fa40 | ||
|
|
b6daf4d4c6 | ||
|
|
4495ecd158 | ||
|
|
85df66db51 | ||
|
|
3024d0e61c | ||
|
|
5ac0447e0f | ||
|
|
bb42862639 | ||
|
|
39b8f339ce | ||
|
|
af99e5e74a | ||
|
|
f79800246a | ||
|
|
4c1b1d89eb | ||
|
|
a473482413 | ||
|
|
535ec23646 | ||
|
|
245d6435e3 | ||
|
|
8728d4ce6a | ||
|
|
44901f0b31 | ||
|
|
5054a07be2 | ||
|
|
2f1cc6a06a | ||
|
|
ff7693a14b | ||
|
|
e89f93c78c | ||
|
|
d51aee876d | ||
|
|
3ad03d41e6 | ||
|
|
69104f38f9 | ||
|
|
c6bb0d9662 | ||
|
|
73717f2ce7 | ||
|
|
03bc92d112 | ||
|
|
fd015c1ed7 | ||
|
|
8e548e4c8a | ||
|
|
44adf909b0 | ||
|
|
d43863af78 | ||
|
|
83c70dc914 | ||
|
|
10c1b15f12 | ||
|
|
be5c6658bb | ||
|
|
f0a936832b | ||
|
|
ce5c2ee506 | ||
|
|
bd33953c60 | ||
|
|
ab22de03b8 | ||
|
|
61af272956 | ||
|
|
e43ddcd5ea | ||
|
|
4041cb54f3 | ||
|
|
87b4f335af | ||
|
|
f9bb9191cf | ||
|
|
4ab7697f00 | ||
|
|
d1ba3ea60d | ||
|
|
e597b33926 | ||
|
|
3bb3a8f39b | ||
|
|
561626fe1b | ||
|
|
86519bd407 | ||
|
|
35a1949fb8 | ||
|
|
586a331a95 | ||
|
|
9b0f784c63 | ||
|
|
ac0f5e75bd | ||
|
|
f283ff5949 | ||
|
|
04ee863c33 | ||
|
|
16349c1368 | ||
|
|
7caf21c8a8 | ||
|
|
c98d215242 | ||
|
|
76cfab35a5 | ||
|
|
2e02f49fd9 | ||
|
|
548deb9153 | ||
|
|
150c4faa79 | ||
|
|
6693ef8288 | ||
|
|
ba93e36fce | ||
|
|
9ab580d1ea | ||
|
|
7deb2d9646 | ||
|
|
c77728ebf8 | ||
|
|
83487bfbec | ||
|
|
e6f475735f | ||
|
|
48486b137a | ||
|
|
4490020197 | ||
|
|
770caad0ad | ||
|
|
13e29ae3f7 | ||
|
|
b52ac8c5d1 | ||
|
|
97a907ad84 | ||
|
|
7ced077821 | ||
|
|
fdbd448cc7 | ||
|
|
d8c88f4894 | ||
|
|
d558bbfa35 | ||
|
|
992dc1c2c7 | ||
|
|
ce1d3e9c96 | ||
|
|
a8a712e1f8 | ||
|
|
5a71cbe530 | ||
|
|
c2bdb36c11 | ||
|
|
6d03a5d01b | ||
|
|
ad75cb2682 | ||
|
|
f46478f1df | ||
|
|
7c811c39c8 | ||
|
|
e18d6c0956 | ||
|
|
a7af8345a5 | ||
|
|
b5ecc31096 | ||
|
|
3a297c7fe6 | ||
|
|
c3243ce0ce | ||
|
|
ace552ecc5 | ||
|
|
1d75570c59 | ||
|
|
70ccc63b83 | ||
|
|
31373ee099 | ||
|
|
afaf7b5b94 | ||
|
|
6d0f66ae87 | ||
|
|
207233335e | ||
|
|
e9fbfd0905 | ||
|
|
c35f974637 | ||
|
|
a4007cbe71 | ||
|
|
d70e0b1189 | ||
|
|
a9ce40a722 | ||
|
|
8d207fd8e6 | ||
|
|
2487adf0b1 | ||
|
|
4aa725f60b | ||
|
|
0686a2fba9 | ||
|
|
b6305011db | ||
|
|
9cdf5199df | ||
|
|
632dad337b | ||
|
|
08234d0097 | ||
|
|
d68ba391fc | ||
|
|
603a140eb7 | ||
|
|
fd31f05cf0 | ||
|
|
a8152c57b3 | ||
|
|
4753d14a19 | ||
|
|
ee5e61d448 | ||
|
|
41b76c4b9e | ||
|
|
314de0fc49 | ||
|
|
93f7e4fb9d | ||
|
|
f8adf4de2f | ||
|
|
08f0274b5a | ||
|
|
94748aab84 | ||
|
|
468d1cf03e | ||
|
|
e8e6bc0c6a | ||
|
|
c7b9d460e0 | ||
|
|
e263368656 | ||
|
|
4b6a9d107d | ||
|
|
ee5454fd4a | ||
|
|
38d2d6a20c | ||
|
|
7e493b9665 | ||
|
|
b7c1a37676 | ||
|
|
4fe042aa7f | ||
|
|
a83218fd08 | ||
|
|
221af2da70 | ||
|
|
c6c6ac0a3e | ||
|
|
a5fdfd6343 | ||
|
|
72e0c77f91 | ||
|
|
862f595e5d | ||
|
|
db045e69f9 | ||
|
|
6a54cb871b | ||
|
|
3619a83b28 | ||
|
|
e3927d2868 | ||
|
|
fe503de17a | ||
|
|
5498a64a9f | ||
|
|
3148a231db | ||
|
|
bffdf20721 | ||
|
|
a4b64d4854 | ||
|
|
fe195e0a9c | ||
|
|
9d70f48207 | ||
|
|
d83df8c1cf | ||
|
|
c7c6c12146 | ||
|
|
e83a4c24ee | ||
|
|
a1669be6c3 | ||
|
|
e36799f515 | ||
|
|
186fe5f7b0 | ||
|
|
27206eccef | ||
|
|
62c0d60d3e | ||
|
|
c39342df3e | ||
|
|
458a103529 | ||
|
|
c22a37e7a6 | ||
|
|
01a8a65d75 | ||
|
|
28ced402d8 | ||
|
|
95e35fe84a | ||
|
|
3692ff2bdb | ||
|
|
7235a5fed4 | ||
|
|
614e5b52b8 | ||
|
|
35ab31fb5f | ||
|
|
636994eea6 | ||
|
|
146ac5ceda | ||
|
|
5413bfec30 | ||
|
|
f39c2cd714 | ||
|
|
e0ef2bdf81 | ||
|
|
efc09e8022 | ||
|
|
1790355f3e | ||
|
|
d0d004c6ff | ||
|
|
ee4abb681e | ||
|
|
9e3605d952 | ||
|
|
0df4927710 | ||
|
|
9d4415429d | ||
|
|
c86ac36876 | ||
|
|
4a4d800523 | ||
|
|
01c148bbce | ||
|
|
1cbe4363a3 | ||
|
|
c0254d914f | ||
|
|
53112f1e80 | ||
|
|
0f312c58ca | ||
|
|
2ca520c1eb | ||
|
|
c9b7ac2ae5 | ||
|
|
6e2ffd7050 | ||
|
|
8726748e22 | ||
|
|
73c2db9e8f | ||
|
|
c1cab3e752 | ||
|
|
713fdda7f8 | ||
|
|
baa599e5b0 | ||
|
|
014c533ebe | ||
|
|
53d87510ce | ||
|
|
43aa74d139 | ||
|
|
ada9238907 | ||
|
|
4d98cdf829 | ||
|
|
547043047e | ||
|
|
9d3b68411a | ||
|
|
470f8d3c04 | ||
|
|
da00b57d4b | ||
|
|
caedac67e0 | ||
|
|
2b5dc4eba4 | ||
|
|
39b8d01e71 | ||
|
|
fca3154982 | ||
|
|
cef158c42d | ||
|
|
177c3bcfe6 | ||
|
|
35766f2c3a | ||
|
|
e175cf543c | ||
|
|
96b0d1276e | ||
|
|
301546289e | ||
|
|
813778b39e | ||
|
|
6c3eceb197 | ||
|
|
0afc43eed7 | ||
|
|
6eb260d4eb | ||
|
|
63f1efc414 | ||
|
|
e685f7ab68 | ||
|
|
ac8f420adb | ||
|
|
fa16f132e3 | ||
|
|
7a5782c3e3 | ||
|
|
cf98691a3d | ||
|
|
09aa87ba26 | ||
|
|
38fb0430bf | ||
|
|
ec82e2fde8 | ||
|
|
fba2864cbc | ||
|
|
60c6d3e108 | ||
|
|
92f9750ded | ||
|
|
23106bce76 | ||
|
|
15acba1972 | ||
|
|
909d4dca82 | ||
|
|
5676f1d037 | ||
|
|
d864d76254 | ||
|
|
9ff4d38cc9 | ||
|
|
8dadbf706d | ||
|
|
ff968cbe43 | ||
|
|
582540657f | ||
|
|
05eeee6ee8 | ||
|
|
cd6f3b147d | ||
|
|
86f519ba57 | ||
|
|
febc6bae30 | ||
|
|
48f818bf07 | ||
|
|
c2d21bb8cc | ||
|
|
39fe1ba028 | ||
|
|
7112e860dc | ||
|
|
4606137882 | ||
|
|
8eab6b7701 | ||
|
|
71cd68eec1 | ||
|
|
168c3b78f4 | ||
|
|
d175e49e15 | ||
|
|
79624d762e | ||
|
|
3ec9eec257 | ||
|
|
e5ac4a72b7 | ||
|
|
470ea6cf70 | ||
|
|
45dc7ac7d1 | ||
|
|
2905eaa8ff | ||
|
|
deb068050b | ||
|
|
8f803c5a7d | ||
|
|
601c7998eb | ||
|
|
5a686d40de | ||
|
|
57837a1e81 | ||
|
|
5162ebd2cc | ||
|
|
1f869ab36d | ||
|
|
e912e52e15 | ||
|
|
14b0c03d5b | ||
|
|
8414ec9429 | ||
|
|
ac03b1b82f | ||
|
|
d5c999553e | ||
|
|
371df42d3f | ||
|
|
a4a0396850 | ||
|
|
83ad75b997 | ||
|
|
3f45d4ba9d | ||
|
|
2fa8bc201a | ||
|
|
e6b99ec849 | ||
|
|
e5c665384c | ||
|
|
5bb1273b3d | ||
|
|
5629f5141e | ||
|
|
15aef01b34 | ||
|
|
67a010298f | ||
|
|
7c398a532b | ||
|
|
eefd39a0d5 | ||
|
|
176b1c85c0 | ||
|
|
2da6cfde80 | ||
|
|
8068c083f6 | ||
|
|
bbe04aab95 | ||
|
|
d31a951d94 | ||
|
|
c27a158961 | ||
|
|
068e97cf0e | ||
|
|
aa45d831f3 | ||
|
|
eff8448154 | ||
|
|
badd7f1578 | ||
|
|
e12cb7bca9 | ||
|
|
23fe2a2103 | ||
|
|
e684e426af | ||
|
|
e65959b6f7 | ||
|
|
8041af6d18 | ||
|
|
56873b319b | ||
|
|
b315fab771 | ||
|
|
05342ed677 | ||
|
|
487bf31e06 | ||
|
|
6f3b2ea632 | ||
|
|
307fdeff78 | ||
|
|
8df3edcc30 | ||
|
|
a43e282efa | ||
|
|
7374690957 | ||
|
|
0c69575670 | ||
|
|
148cad1976 | ||
|
|
7db4e00b14 | ||
|
|
a00f745eba | ||
|
|
9ae335fafc | ||
|
|
6bd7639d66 | ||
|
|
c336309234 | ||
|
|
41d217321d | ||
|
|
2226dd1efb | ||
|
|
44699b07e4 | ||
|
|
6739f0752b | ||
|
|
1ebde81d6a | ||
|
|
195625b5e7 | ||
|
|
951c2702ff | ||
|
|
02b90df6ff | ||
|
|
f7c01c8e90 | ||
|
|
e8c964efaf | ||
|
|
74baab6f0f | ||
|
|
bff0ea2fc3 | ||
|
|
e474c19c40 | ||
|
|
a2a81f1bb7 | ||
|
|
c291284629 | ||
|
|
7f7ad71def | ||
|
|
7dfc0f30cc | ||
|
|
c14e67a8bb | ||
|
|
75b7e6009a | ||
|
|
63df56d613 | ||
|
|
f2e1f3e659 | ||
|
|
b1b7d3b004 | ||
|
|
f2c12d4fba | ||
|
|
3a60c9c8c8 | ||
|
|
44cd864205 | ||
|
|
f70d7013a5 | ||
|
|
ac1e902d59 | ||
|
|
b67bdee3cf | ||
|
|
83d4fa98f0 | ||
|
|
70a968b342 | ||
|
|
8e576f7511 | ||
|
|
4526424436 | ||
|
|
fd43620e87 | ||
|
|
56b8d331cb | ||
|
|
14bfba8b3b | ||
|
|
af8d3ae790 | ||
|
|
e6bb7e99be | ||
|
|
97c56adb9d | ||
|
|
f8612b0d1b | ||
|
|
a2a46a18ae | ||
|
|
1cf888b71f | ||
|
|
0109603cdc | ||
|
|
c7c750d127 | ||
|
|
b738b50cd6 | ||
|
|
351daf90f1 | ||
|
|
19d9acf63a | ||
|
|
eb40d3b6fc | ||
|
|
f5da23d9db | ||
|
|
b412bce095 | ||
|
|
0b57ddd753 | ||
|
|
d0a6d82a23 | ||
|
|
ba2792bd1f | ||
|
|
3f8c09e006 | ||
|
|
8f39268761 | ||
|
|
cd8c8d78a9 | ||
|
|
980678846d | ||
|
|
9a92d8d57e | ||
|
|
b155c601d7 | ||
|
|
536f2c085a | ||
|
|
2602d4f16e | ||
|
|
174addc426 | ||
|
|
849053681e | ||
|
|
a8d8ea7990 | ||
|
|
10e6493f9f | ||
|
|
8c4f911935 | ||
|
|
62bc85b5c7 | ||
|
|
76eba917f9 | ||
|
|
84a046eaf5 | ||
|
|
8d1d314f49 | ||
|
|
9395f12584 | ||
|
|
b97f58d597 | ||
|
|
30c36a3960 | ||
|
|
206eccc2b6 | ||
|
|
2358a623e3 | ||
|
|
87850bd6dc | ||
|
|
d80eda202f | ||
|
|
3edfa585ec | ||
|
|
fa13a6a8da | ||
|
|
9d5e7c89d9 | ||
|
|
590bf0bcc0 | ||
|
|
b199a2dea3 | ||
|
|
f1d4b8d83e | ||
|
|
0088661356 | ||
|
|
1a7e3cabc0 | ||
|
|
132d701b3e | ||
|
|
3b97241716 | ||
|
|
f82d090df3 | ||
|
|
7399d3e953 | ||
|
|
cab546ecee | ||
|
|
7dc1c7d841 | ||
|
|
fbff51f387 | ||
|
|
b714fb0c39 | ||
|
|
f37038013d | ||
|
|
5e98e2b7c7 | ||
|
|
35f8f3ff79 | ||
|
|
7ec8e7e025 | ||
|
|
c6efebdd8c | ||
|
|
760047abc5 |
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>
|
||||
89
BUILDING.md
89
BUILDING.md
@@ -1,89 +0,0 @@
|
||||
# git clone
|
||||
|
||||
get or update source from git
|
||||
|
||||
`git clone git://opensimulator.org/git/opensim`
|
||||
|
||||
|
||||
|
||||
# Building on Windows
|
||||
|
||||
## Requirements
|
||||
To building under Windows, the following is required:
|
||||
|
||||
* [dotnet 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
|
||||
optionally also
|
||||
|
||||
* [Visual Studio .NET](https://visualstudio.microsoft.com/vs/features/net-development/), version 2022 or later
|
||||
|
||||
|
||||
### Building
|
||||
To create the project files, run
|
||||
|
||||
`runprebuild.bat`
|
||||
|
||||
run
|
||||
`compile.bat`
|
||||
|
||||
Or load the generated OpenSim.sln into Visual Studio and build the solution.
|
||||
|
||||
Configure, see below
|
||||
|
||||
Now just run `OpenSim.exe` from the `bin` folder, and set up the region.
|
||||
|
||||
# Building on Linux / Mac
|
||||
|
||||
## Requirements
|
||||
|
||||
* [dotnet 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
* libgdiplus
|
||||
|
||||
if you have mono 6.x complete, you already have libgdiplus, otherwise you need to install it
|
||||
using a package manager for your operating system, like apt, brew, macports, etc
|
||||
for example on debian:
|
||||
|
||||
`apt-get update && apt-get install -y apt-utils libgdiplus libc6-dev`
|
||||
|
||||
### Building
|
||||
To create the project files, run:
|
||||
|
||||
`./runprebuild.sh`
|
||||
|
||||
then run
|
||||
|
||||
`dotnet build --configuration Release OpenSim.sln`
|
||||
|
||||
|
||||
Configure. See below
|
||||
|
||||
run `./opensim.sh` from the `bin` folder, and set up the region
|
||||
|
||||
|
||||
|
||||
# Configure #
|
||||
## Standalone mode ##
|
||||
Copy `OpenSim.ini.example` to `OpenSim.ini` in the `bin/` directory, and verify the `[Const]` section, correcting for your case.
|
||||
|
||||
On `[Architecture]` section uncomment only the line with Standalone.ini if you do now want HG, or the line with StandaloneHypergrid.ini if you do
|
||||
|
||||
copy the `StandaloneCommon.ini.example` to `StandaloneCommon.ini` in the `bin/config-include` directory.
|
||||
|
||||
The StandaloneCommon.ini file describes the database and backend services that OpenSim will use, and is set to use sqlite by default, which requires no setup.
|
||||
|
||||
|
||||
## Grid mode ##
|
||||
Each grid may have its own requirements, so FOLLOW your Grid instructions!
|
||||
in general:
|
||||
Copy `OpenSim.ini.example` to `OpenSim.ini` in the `bin/` directory, and verify the `[Const]` section, correcting for your case
|
||||
|
||||
On `[Architecture]` section uncomment only the line with Grid.ini if you do now want HG, or the line with GridHypergrid.ini if you do
|
||||
|
||||
and copy the `GridCommon.ini.example` file to `GridCommon.ini` inside the `bin/config-include` directory and edit as necessary
|
||||
|
||||
|
||||
|
||||
# References
|
||||
|
||||
* http://opensimulator.org/wiki/Build_Instructions
|
||||
* http://opensimulator.org/wiki/Configuration
|
||||
39
BUILDING.txt
Normal file
39
BUILDING.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
==== Building OpenSim ====
|
||||
|
||||
=== Building on Windows ===
|
||||
|
||||
Steps:
|
||||
* runprebuild.bat
|
||||
* Load OpenSim.sln into Visual Studio .NET and build the solution.
|
||||
* chdir bin
|
||||
* copy OpenSim.ini.example to OpenSim.ini and other appropriate files in bin/config-include
|
||||
* run OpenSim.exe
|
||||
|
||||
=== Building on Linux ===
|
||||
|
||||
Prereqs:
|
||||
* Mono >= 2.4.3
|
||||
* Nant >= 0.85
|
||||
* On some Linux distributions you may need to install additional packages.
|
||||
See http://opensimulator.org/wiki/Dependencies for more information.
|
||||
|
||||
* May also use xbuild (included in mono distributions)
|
||||
* May use Monodevelop, a cross-platform IDE
|
||||
|
||||
From the distribution type:
|
||||
* ./runprebuild.sh
|
||||
* nant (or xbuild)
|
||||
* cd bin
|
||||
* copy OpenSim.ini.example to OpenSim.ini and other appropriate files in bin/config-include
|
||||
* run mono OpenSim.exe
|
||||
|
||||
=== Using Monodevelop ===
|
||||
|
||||
From the distribution type:
|
||||
* ./runprebuild.sh
|
||||
* type monodevelop OpenSim.sln
|
||||
|
||||
=== References ===
|
||||
|
||||
Helpful resources:
|
||||
* http://opensimulator.org/wiki/Build_Instructions
|
||||
122
CONTRIBUTORS.txt
122
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
|
||||
* Richard Adams (Intel)
|
||||
|
||||
= Core Developers Following the White Rabbit =
|
||||
Core developers who have temporarily (we hope) gone chasing the white rabbit.
|
||||
They are in all similar to the active core developers, except that they haven't
|
||||
been that active lately, so their voting rights are awaiting their come back.
|
||||
|
||||
* Nebadon Izumi (Michael Cerquoni, OSgrid)
|
||||
* Alicia Raven
|
||||
* MW (Tribal Media AB)
|
||||
* Adam Frisby (DeepThink Pty Ltd)
|
||||
* lbsa71 (Tribal Media AB)
|
||||
* Teravus (w3z)
|
||||
* Ckrinke (Charles Krinke)
|
||||
* Dr Scofield aka Dirk Husemann (IBM Research - Zurich)
|
||||
* mikem (3Di)
|
||||
* Homer_Horwitz
|
||||
* nlin (3Di)
|
||||
* Arthur Rodrigo S Valadares (IBM)
|
||||
* John Hurliman
|
||||
|
||||
= Past Open Sim Developers =
|
||||
These folks are alumns of the OpenSim core group, but are now
|
||||
@@ -38,123 +55,70 @@ where we are today.
|
||||
* adjohn (Genkii)
|
||||
* idb (Ian Brown)
|
||||
* Johan Berntsson (3Di)
|
||||
* MW (Tribal Media AB)
|
||||
* Adam Frisby (DeepThink Pty Ltd)
|
||||
* lbsa71 (Tribal Media AB)
|
||||
* Ckrinke (Charles Krinke)
|
||||
* Dr Scofield aka Dirk Husemann (IBM Research - Zurich)
|
||||
* mikem (3Di)
|
||||
* Homer_Horwitz
|
||||
* nlin (3Di)
|
||||
* John Hurliman
|
||||
* chi11ken (Genkii)
|
||||
* dahlia
|
||||
* justincc (OSVW Consulting, justincc.org)
|
||||
* Arthur Rodrigo S Valadares (IBM)
|
||||
* BlueWall (James Hughes)
|
||||
* Dan Lake
|
||||
* Marck
|
||||
* Mic Bowman
|
||||
* Oren Hurvitz (Kitely)
|
||||
* Snoopy Pfeffer
|
||||
* Teravus (w3z)
|
||||
|
||||
|
||||
= Additional OpenSim Contributors =
|
||||
These folks have contributed code patches or content to OpenSimulator to help make it
|
||||
These folks have contributed code patches to OpenSim to help make it
|
||||
what it is today.
|
||||
|
||||
* A_Biondi
|
||||
* aduffy70
|
||||
* Ai Austin
|
||||
* A_Biondi
|
||||
* alex_carnell
|
||||
* Alan Webb (IBM)
|
||||
* Aleric
|
||||
* Allen Kerensky
|
||||
* BigFootAg
|
||||
* Bill Blight
|
||||
* BlueWall Slade
|
||||
* bobshaffer2
|
||||
* brianw/Sir_Ahzz
|
||||
* CharlieO
|
||||
* ChrisDown
|
||||
* Chris Yeoh (IBM)
|
||||
* cinderblocks
|
||||
* controlbreak
|
||||
* coyled
|
||||
* ctrlaltdavid (David Rowe)
|
||||
* Daedius
|
||||
* daTwitch
|
||||
* Dev Random
|
||||
* devalnor-#708
|
||||
* dmiles (Daxtron Labs)
|
||||
* Dong Jun Lan (IBM)
|
||||
* DoranZemlja
|
||||
* Drake Arconis
|
||||
* dr0b3rts
|
||||
* dslake
|
||||
* eeyore
|
||||
* daTwitch
|
||||
* devalnor-#708
|
||||
* dmiles (Daxtron Labs)
|
||||
* dslake (Intel)
|
||||
* FredoChaplin
|
||||
* FreakyTech
|
||||
* Garmin Kawaguichi
|
||||
* Gavin Hird
|
||||
* Gerhard
|
||||
* Godfrey
|
||||
* Greg C.
|
||||
* Grumly57
|
||||
* GuduleLapointe
|
||||
* Ewe Loon
|
||||
* Fernando Oliveira
|
||||
* Fly-Man
|
||||
* Flyte Xevious
|
||||
* Freaky Tech
|
||||
* Garmin Kawaguichi
|
||||
* Geir Noklebye
|
||||
* Glenn Martin (MOSES)
|
||||
* Gryc Ueusp
|
||||
* H-H-H (ginge264)
|
||||
* Hiro Lecker
|
||||
* Iain Oliver
|
||||
* Imaze Rhiano
|
||||
* Intimidated
|
||||
* Jak Daniels
|
||||
* Jeff Kelly
|
||||
* Jeremy Bongio (IBM)
|
||||
* jhurliman
|
||||
* John R Sohn (XenReborn)
|
||||
* jonc
|
||||
* Jon Cundill
|
||||
* Junta Kohime
|
||||
* Kayne
|
||||
* Kevin Cozens
|
||||
* kinoc (Daxtron Labs)
|
||||
* Kira
|
||||
* Kitto Flora
|
||||
* KittyLiu
|
||||
* Kurt Taylor (IBM)
|
||||
* Lani Global
|
||||
* lickx
|
||||
* lillith_xue
|
||||
* lkalif
|
||||
* LuciusSirnah
|
||||
* lulurun
|
||||
* M.Igarashi
|
||||
* Magnuz Binder
|
||||
* maimedleech
|
||||
* Mana Janus
|
||||
* Mandarinka Tasty
|
||||
* MarcelEdward
|
||||
* Matt Lehmann
|
||||
* mewtwo0641
|
||||
* Mic Bowman
|
||||
* Michelle Argus
|
||||
* Michael Cortez (The Flotsam Project, http://osflotsam.org/)
|
||||
* Michael Heilmann (MOSES)
|
||||
* Micheil Merlin
|
||||
* Mike Osias (IBM)
|
||||
* Mike Pitman (IBM)
|
||||
* Mike Rieker (Dreamnation)
|
||||
* mikemig
|
||||
* mikkopa/_someone - RealXtend
|
||||
* Misterblue
|
||||
* Misterblue (Intel)
|
||||
* Mircea Kitsune
|
||||
* mpallari
|
||||
* MrMonkE
|
||||
@@ -163,75 +127,66 @@ what it is today.
|
||||
* nornalbion
|
||||
* Omar Vera Ustariz (IBM)
|
||||
* openlifegrid.com
|
||||
* Oren Hurvitz (Kitely)
|
||||
* otakup0pe
|
||||
* Pixel Tomsen
|
||||
* Quill Littlefeather
|
||||
* ralphos
|
||||
* RemedyTomm
|
||||
* Revolution
|
||||
* Richard Alimi (IBM)
|
||||
* Rick Alther (IBM)
|
||||
* Rob Smart (IBM)
|
||||
* Robert Louden (MOSES)
|
||||
* Roger Kirkman (zadark)
|
||||
* rtomita
|
||||
* Ruud Lathorp
|
||||
* SachaMagne
|
||||
* Salahzar Stenvaag
|
||||
* satguru p srivastava
|
||||
* sempuki
|
||||
* Shy Robbiani
|
||||
* SignpostMarv
|
||||
* SpotOn3D
|
||||
* Stefan_Boom / stoehr
|
||||
* Steven Zielinski (MOSES)
|
||||
* Stolen Ruby
|
||||
* Strawberry Fride
|
||||
* Talun
|
||||
* TechplexEngineer (Blake Bourque)
|
||||
* TBG Renfold
|
||||
* Terry Ford
|
||||
* tglion
|
||||
* tlaukkan/Tommil (Tommi S. E. Laukkanen, Bubble Cloud)
|
||||
* TomDataWorks
|
||||
* TomTheDragon (muckwaddle)
|
||||
* tyre
|
||||
* uriesk
|
||||
* Vegaslon <vegaslon@gmail.com>
|
||||
* Vincent Sylvester
|
||||
* VikingErik
|
||||
* Vytek
|
||||
* webmage (IBM)
|
||||
* Xantor
|
||||
* Y. Nitta
|
||||
* YoshikoFazuku
|
||||
* YZh
|
||||
* Zackary Geers aka Kunnis Basiat
|
||||
* Zha Ewry
|
||||
* ziah
|
||||
|
||||
|
||||
= LSL Devs =
|
||||
|
||||
* Alondria
|
||||
* CharlieO
|
||||
* Tedd
|
||||
* Melanie Thielker
|
||||
|
||||
|
||||
= Testers =
|
||||
|
||||
* Ai Austin
|
||||
* CharlieO (LSL)
|
||||
* Ckrinke
|
||||
* openlifegrid.com
|
||||
|
||||
|
||||
This software uses components from the following developers:
|
||||
* Sleepycat Software (Berkeley DB)
|
||||
* Aurora-Sim (http://aurora-sim.org)
|
||||
* SQLite (Public Domain)
|
||||
* XmlRpcCS (http://xmlrpccs.sf.net/)
|
||||
* MySQL, Inc. (MySQL Connector/NET)
|
||||
* NUnit (http://www.nunit.org)
|
||||
* AGEIA Inc. (PhysX)
|
||||
* Russel L. Smith (ODE)
|
||||
* Erwin Coumans (Bullet)
|
||||
* Prebuild (http://sourceforge.net/projects/dnpb/)
|
||||
* LibOpenMetaverse (http://lib.openmetaverse.org/)
|
||||
* DotNetOpenMail v0.5.8b (http://dotnetopenmail.sourceforge.net)
|
||||
@@ -252,3 +207,4 @@ In addition, we would like to thank:
|
||||
* The Mono Project
|
||||
* The NANT Developers
|
||||
* Microsoft (.NET, MSSQL-Adapters)
|
||||
*x
|
||||
|
||||
7241
OpenSim.FxCop
Normal file
7241
OpenSim.FxCop
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class ForeignImporter
|
||||
{
|
||||
IUserManagement m_UserManagement;
|
||||
public ForeignImporter(IUserManagement uman)
|
||||
{
|
||||
m_UserManagement = uman;
|
||||
}
|
||||
|
||||
public GroupMembersData ConvertGroupMembersData(ExtendedGroupMembersData _m)
|
||||
{
|
||||
GroupMembersData m = new GroupMembersData();
|
||||
m.AcceptNotices = _m.AcceptNotices;
|
||||
m.AgentPowers = _m.AgentPowers;
|
||||
m.Contribution = _m.Contribution;
|
||||
m.IsOwner = _m.IsOwner;
|
||||
m.ListInProfile = _m.ListInProfile;
|
||||
m.OnlineStatus = _m.OnlineStatus;
|
||||
m.Title = _m.Title;
|
||||
|
||||
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
|
||||
Util.ParseUniversalUserIdentifier(_m.AgentID, out m.AgentID, out url, out first, out last, out tmp);
|
||||
if (url != string.Empty)
|
||||
m_UserManagement.AddUser(m.AgentID, first, last, url);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public GroupRoleMembersData ConvertGroupRoleMembersData(ExtendedGroupRoleMembersData _rm)
|
||||
{
|
||||
GroupRoleMembersData rm = new GroupRoleMembersData();
|
||||
rm.RoleID = _rm.RoleID;
|
||||
|
||||
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
|
||||
Util.ParseUniversalUserIdentifier(_rm.MemberID, out rm.MemberID, out url, out first, out last, out tmp);
|
||||
if (url != string.Empty)
|
||||
m_UserManagement.AddUser(rm.MemberID, first, last, url);
|
||||
|
||||
return rm;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,533 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class ExtendedGroupRecord : GroupRecord
|
||||
{
|
||||
public int MemberCount;
|
||||
public int RoleCount;
|
||||
public string ServiceLocation;
|
||||
public string FounderUUI;
|
||||
}
|
||||
|
||||
public class ExtendedGroupMembershipData : GroupMembershipData
|
||||
{
|
||||
public string AccessToken;
|
||||
}
|
||||
|
||||
public class ExtendedGroupMembersData
|
||||
{
|
||||
// This is the only difference: this is a string
|
||||
public string AgentID;
|
||||
public int Contribution;
|
||||
public string OnlineStatus;
|
||||
public ulong AgentPowers;
|
||||
public string Title;
|
||||
public bool IsOwner;
|
||||
public bool ListInProfile;
|
||||
public bool AcceptNotices;
|
||||
public string AccessToken;
|
||||
}
|
||||
|
||||
public class ExtendedGroupRoleMembersData
|
||||
{
|
||||
public UUID RoleID;
|
||||
// This is the only difference: this is a string
|
||||
public string MemberID;
|
||||
|
||||
}
|
||||
|
||||
public struct ExtendedGroupNoticeData
|
||||
{
|
||||
public UUID NoticeID;
|
||||
public uint Timestamp;
|
||||
public string FromName;
|
||||
public string Subject;
|
||||
public bool HasAttachment;
|
||||
public byte AttachmentType;
|
||||
public string AttachmentName;
|
||||
public UUID AttachmentItemID;
|
||||
public string AttachmentOwnerID;
|
||||
|
||||
public GroupNoticeData ToGroupNoticeData()
|
||||
{
|
||||
GroupNoticeData n = new GroupNoticeData();
|
||||
n.FromName = this.FromName;
|
||||
n.AssetType = this.AttachmentType;
|
||||
n.HasAttachment = this.HasAttachment;
|
||||
n.NoticeID = this.NoticeID;
|
||||
n.Subject = this.Subject;
|
||||
n.Timestamp = this.Timestamp;
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupsDataUtils
|
||||
{
|
||||
public static string Sanitize(string s)
|
||||
{
|
||||
return s == null ? string.Empty : s;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupRecord(ExtendedGroupRecord grec)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
if (grec == null)
|
||||
return dict;
|
||||
|
||||
dict["AllowPublish"] = grec.AllowPublish.ToString();
|
||||
dict["Charter"] = Sanitize(grec.Charter);
|
||||
dict["FounderID"] = grec.FounderID.ToString();
|
||||
dict["FounderUUI"] = Sanitize(grec.FounderUUI);
|
||||
dict["GroupID"] = grec.GroupID.ToString();
|
||||
dict["GroupName"] = Sanitize(grec.GroupName);
|
||||
dict["InsigniaID"] = grec.GroupPicture.ToString();
|
||||
dict["MaturePublish"] = grec.MaturePublish.ToString();
|
||||
dict["MembershipFee"] = grec.MembershipFee.ToString();
|
||||
dict["OpenEnrollment"] = grec.OpenEnrollment.ToString();
|
||||
dict["OwnerRoleID"] = grec.OwnerRoleID.ToString();
|
||||
dict["ServiceLocation"] = Sanitize(grec.ServiceLocation);
|
||||
dict["ShownInList"] = grec.ShowInList.ToString();
|
||||
dict["MemberCount"] = grec.MemberCount.ToString();
|
||||
dict["RoleCount"] = grec.RoleCount.ToString();
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static ExtendedGroupRecord GroupRecord(Dictionary<string, object> dict)
|
||||
{
|
||||
if (dict == null)
|
||||
return null;
|
||||
|
||||
ExtendedGroupRecord grec = new ExtendedGroupRecord();
|
||||
if (dict.ContainsKey("AllowPublish") && dict["AllowPublish"] != null)
|
||||
grec.AllowPublish = bool.Parse(dict["AllowPublish"].ToString());
|
||||
|
||||
if (dict.ContainsKey("Charter") && dict["Charter"] != null)
|
||||
grec.Charter = dict["Charter"].ToString();
|
||||
else
|
||||
grec.Charter = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("FounderID") && dict["FounderID"] != null)
|
||||
grec.FounderID = UUID.Parse(dict["FounderID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("FounderUUI") && dict["FounderUUI"] != null)
|
||||
grec.FounderUUI = dict["FounderUUI"].ToString();
|
||||
else
|
||||
grec.FounderUUI = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("GroupID") && dict["GroupID"] != null)
|
||||
grec.GroupID = UUID.Parse(dict["GroupID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("GroupName") && dict["GroupName"] != null)
|
||||
grec.GroupName = dict["GroupName"].ToString();
|
||||
else
|
||||
grec.GroupName = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("InsigniaID") && dict["InsigniaID"] != null)
|
||||
grec.GroupPicture = UUID.Parse(dict["InsigniaID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("MaturePublish") && dict["MaturePublish"] != null)
|
||||
grec.MaturePublish = bool.Parse(dict["MaturePublish"].ToString());
|
||||
|
||||
if (dict.ContainsKey("MembershipFee") && dict["MembershipFee"] != null)
|
||||
grec.MembershipFee = Int32.Parse(dict["MembershipFee"].ToString());
|
||||
|
||||
if (dict.ContainsKey("OpenEnrollment") && dict["OpenEnrollment"] != null)
|
||||
grec.OpenEnrollment = bool.Parse(dict["OpenEnrollment"].ToString());
|
||||
|
||||
if (dict.ContainsKey("OwnerRoleID") && dict["OwnerRoleID"] != null)
|
||||
grec.OwnerRoleID = UUID.Parse(dict["OwnerRoleID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("ServiceLocation") && dict["ServiceLocation"] != null)
|
||||
grec.ServiceLocation = dict["ServiceLocation"].ToString();
|
||||
else
|
||||
grec.ServiceLocation = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("ShownInList") && dict["ShownInList"] != null)
|
||||
grec.ShowInList = bool.Parse(dict["ShownInList"].ToString());
|
||||
|
||||
if (dict.ContainsKey("MemberCount") && dict["MemberCount"] != null)
|
||||
grec.MemberCount = Int32.Parse(dict["MemberCount"].ToString());
|
||||
|
||||
if (dict.ContainsKey("RoleCount") && dict["RoleCount"] != null)
|
||||
grec.RoleCount = Int32.Parse(dict["RoleCount"].ToString());
|
||||
|
||||
return grec;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupMembershipData(ExtendedGroupMembershipData membership)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
if (membership == null)
|
||||
return dict;
|
||||
|
||||
dict["AcceptNotices"] = membership.AcceptNotices.ToString();
|
||||
dict["AccessToken"] = Sanitize(membership.AccessToken);
|
||||
dict["Active"] = membership.Active.ToString();
|
||||
dict["ActiveRole"] = membership.ActiveRole.ToString();
|
||||
dict["AllowPublish"] = membership.AllowPublish.ToString();
|
||||
dict["Charter"] = Sanitize(membership.Charter);
|
||||
dict["Contribution"] = membership.Contribution.ToString();
|
||||
dict["FounderID"] = membership.FounderID.ToString();
|
||||
dict["GroupID"] = membership.GroupID.ToString();
|
||||
dict["GroupName"] = Sanitize(membership.GroupName);
|
||||
dict["GroupPicture"] = membership.GroupPicture.ToString();
|
||||
dict["GroupPowers"] = membership.GroupPowers.ToString();
|
||||
dict["GroupTitle"] = Sanitize(membership.GroupTitle);
|
||||
dict["ListInProfile"] = membership.ListInProfile.ToString();
|
||||
dict["MaturePublish"] = membership.MaturePublish.ToString();
|
||||
dict["MembershipFee"] = membership.MembershipFee.ToString();
|
||||
dict["OpenEnrollment"] = membership.OpenEnrollment.ToString();
|
||||
dict["ShowInList"] = membership.ShowInList.ToString();
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static ExtendedGroupMembershipData GroupMembershipData(Dictionary<string, object> dict)
|
||||
{
|
||||
if (dict == null)
|
||||
return null;
|
||||
|
||||
ExtendedGroupMembershipData membership = new ExtendedGroupMembershipData();
|
||||
|
||||
if (dict.ContainsKey("AcceptNotices") && dict["AcceptNotices"] != null)
|
||||
membership.AcceptNotices = bool.Parse(dict["AcceptNotices"].ToString());
|
||||
|
||||
if (dict.ContainsKey("AccessToken") && dict["AccessToken"] != null)
|
||||
membership.AccessToken = dict["AccessToken"].ToString();
|
||||
else
|
||||
membership.AccessToken = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("Active") && dict["Active"] != null)
|
||||
membership.Active = bool.Parse(dict["Active"].ToString());
|
||||
|
||||
if (dict.ContainsKey("ActiveRole") && dict["ActiveRole"] != null)
|
||||
membership.ActiveRole = UUID.Parse(dict["ActiveRole"].ToString());
|
||||
|
||||
if (dict.ContainsKey("AllowPublish") && dict["AllowPublish"] != null)
|
||||
membership.AllowPublish = bool.Parse(dict["AllowPublish"].ToString());
|
||||
|
||||
if (dict.ContainsKey("Charter") && dict["Charter"] != null)
|
||||
membership.Charter = dict["Charter"].ToString();
|
||||
else
|
||||
membership.Charter = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("Contribution") && dict["Contribution"] != null)
|
||||
membership.Contribution = Int32.Parse(dict["Contribution"].ToString());
|
||||
|
||||
if (dict.ContainsKey("FounderID") && dict["FounderID"] != null)
|
||||
membership.FounderID = UUID.Parse(dict["FounderID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("GroupID") && dict["GroupID"] != null)
|
||||
membership.GroupID = UUID.Parse(dict["GroupID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("GroupName") && dict["GroupName"] != null)
|
||||
membership.GroupName = dict["GroupName"].ToString();
|
||||
else
|
||||
membership.GroupName = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("GroupPicture") && dict["GroupPicture"] != null)
|
||||
membership.GroupPicture = UUID.Parse(dict["GroupPicture"].ToString());
|
||||
|
||||
if (dict.ContainsKey("GroupPowers") && dict["GroupPowers"] != null)
|
||||
membership.GroupPowers = UInt64.Parse(dict["GroupPowers"].ToString());
|
||||
|
||||
if (dict.ContainsKey("GroupTitle") && dict["GroupTitle"] != null)
|
||||
membership.GroupTitle = dict["GroupTitle"].ToString();
|
||||
else
|
||||
membership.GroupTitle = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("ListInProfile") && dict["ListInProfile"] != null)
|
||||
membership.ListInProfile = bool.Parse(dict["ListInProfile"].ToString());
|
||||
|
||||
if (dict.ContainsKey("MaturePublish") && dict["MaturePublish"] != null)
|
||||
membership.MaturePublish = bool.Parse(dict["MaturePublish"].ToString());
|
||||
|
||||
if (dict.ContainsKey("MembershipFee") && dict["MembershipFee"] != null)
|
||||
membership.MembershipFee = Int32.Parse(dict["MembershipFee"].ToString());
|
||||
|
||||
if (dict.ContainsKey("OpenEnrollment") && dict["OpenEnrollment"] != null)
|
||||
membership.OpenEnrollment = bool.Parse(dict["OpenEnrollment"].ToString());
|
||||
|
||||
if (dict.ContainsKey("ShowInList") && dict["ShowInList"] != null)
|
||||
membership.ShowInList = bool.Parse(dict["ShowInList"].ToString());
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupMembersData(ExtendedGroupMembersData member)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
dict["AcceptNotices"] = member.AcceptNotices.ToString();
|
||||
dict["AccessToken"] = Sanitize(member.AccessToken);
|
||||
dict["AgentID"] = Sanitize(member.AgentID);
|
||||
dict["AgentPowers"] = member.AgentPowers.ToString();
|
||||
dict["Contribution"] = member.Contribution.ToString();
|
||||
dict["IsOwner"] = member.IsOwner.ToString();
|
||||
dict["ListInProfile"] = member.ListInProfile.ToString();
|
||||
dict["OnlineStatus"] = Sanitize(member.OnlineStatus);
|
||||
dict["Title"] = Sanitize(member.Title);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static ExtendedGroupMembersData GroupMembersData(Dictionary<string, object> dict)
|
||||
{
|
||||
ExtendedGroupMembersData member = new ExtendedGroupMembersData();
|
||||
|
||||
if (dict == null)
|
||||
return member;
|
||||
|
||||
if (dict.ContainsKey("AcceptNotices") && dict["AcceptNotices"] != null)
|
||||
member.AcceptNotices = bool.Parse(dict["AcceptNotices"].ToString());
|
||||
|
||||
if (dict.ContainsKey("AccessToken") && dict["AccessToken"] != null)
|
||||
member.AccessToken = Sanitize(dict["AccessToken"].ToString());
|
||||
else
|
||||
member.AccessToken = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("AgentID") && dict["AgentID"] != null)
|
||||
member.AgentID = Sanitize(dict["AgentID"].ToString());
|
||||
else
|
||||
member.AgentID = UUID.Zero.ToString();
|
||||
|
||||
if (dict.ContainsKey("AgentPowers") && dict["AgentPowers"] != null)
|
||||
member.AgentPowers = UInt64.Parse(dict["AgentPowers"].ToString());
|
||||
|
||||
if (dict.ContainsKey("Contribution") && dict["Contribution"] != null)
|
||||
member.Contribution = Int32.Parse(dict["Contribution"].ToString());
|
||||
|
||||
if (dict.ContainsKey("IsOwner") && dict["IsOwner"] != null)
|
||||
member.IsOwner = bool.Parse(dict["IsOwner"].ToString());
|
||||
|
||||
if (dict.ContainsKey("ListInProfile") && dict["ListInProfile"] != null)
|
||||
member.ListInProfile = bool.Parse(dict["ListInProfile"].ToString());
|
||||
|
||||
if (dict.ContainsKey("OnlineStatus") && dict["OnlineStatus"] != null)
|
||||
member.OnlineStatus = Sanitize(dict["OnlineStatus"].ToString());
|
||||
else
|
||||
member.OnlineStatus = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("Title") && dict["Title"] != null)
|
||||
member.Title = Sanitize(dict["Title"].ToString());
|
||||
else
|
||||
member.Title = string.Empty;
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupRolesData(GroupRolesData role)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
dict["Description"] = Sanitize(role.Description);
|
||||
dict["Members"] = role.Members.ToString();
|
||||
dict["Name"] = Sanitize(role.Name);
|
||||
dict["Powers"] = role.Powers.ToString();
|
||||
dict["RoleID"] = role.RoleID.ToString();
|
||||
dict["Title"] = Sanitize(role.Title);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static GroupRolesData GroupRolesData(Dictionary<string, object> dict)
|
||||
{
|
||||
GroupRolesData role = new GroupRolesData();
|
||||
|
||||
if (dict == null)
|
||||
return role;
|
||||
|
||||
if (dict.ContainsKey("Description") && dict["Description"] != null)
|
||||
role.Description = Sanitize(dict["Description"].ToString());
|
||||
else
|
||||
role.Description = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("Members") && dict["Members"] != null)
|
||||
role.Members = Int32.Parse(dict["Members"].ToString());
|
||||
|
||||
if (dict.ContainsKey("Name") && dict["Name"] != null)
|
||||
role.Name = Sanitize(dict["Name"].ToString());
|
||||
else
|
||||
role.Name = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("Powers") && dict["Powers"] != null)
|
||||
role.Powers = UInt64.Parse(dict["Powers"].ToString());
|
||||
|
||||
if (dict.ContainsKey("Title") && dict["Title"] != null)
|
||||
role.Title = Sanitize(dict["Title"].ToString());
|
||||
else
|
||||
role.Title = string.Empty;
|
||||
|
||||
if (dict.ContainsKey("RoleID") && dict["RoleID"] != null)
|
||||
role.RoleID = UUID.Parse(dict["RoleID"].ToString());
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupRoleMembersData(ExtendedGroupRoleMembersData rmember)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
dict["RoleID"] = rmember.RoleID.ToString();
|
||||
dict["MemberID"] = rmember.MemberID;
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static ExtendedGroupRoleMembersData GroupRoleMembersData(Dictionary<string, object> dict)
|
||||
{
|
||||
ExtendedGroupRoleMembersData rmember = new ExtendedGroupRoleMembersData();
|
||||
|
||||
if (dict.ContainsKey("RoleID") && dict["RoleID"] != null)
|
||||
rmember.RoleID = new UUID(dict["RoleID"].ToString());
|
||||
|
||||
if (dict.ContainsKey("MemberID") && dict["MemberID"] != null)
|
||||
rmember.MemberID = dict["MemberID"].ToString();
|
||||
|
||||
return rmember;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupInviteInfo(GroupInviteInfo invite)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
dict["InviteID"] = invite.InviteID.ToString();
|
||||
dict["GroupID"] = invite.GroupID.ToString();
|
||||
dict["RoleID"] = invite.RoleID.ToString();
|
||||
dict["AgentID"] = invite.AgentID;
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static GroupInviteInfo GroupInviteInfo(Dictionary<string, object> dict)
|
||||
{
|
||||
if (dict == null)
|
||||
return null;
|
||||
|
||||
GroupInviteInfo invite = new GroupInviteInfo();
|
||||
|
||||
invite.InviteID = new UUID(dict["InviteID"].ToString());
|
||||
invite.GroupID = new UUID(dict["GroupID"].ToString());
|
||||
invite.RoleID = new UUID(dict["RoleID"].ToString());
|
||||
invite.AgentID = Sanitize(dict["AgentID"].ToString());
|
||||
|
||||
return invite;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupNoticeData(ExtendedGroupNoticeData notice)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
dict["NoticeID"] = notice.NoticeID.ToString();
|
||||
dict["Timestamp"] = notice.Timestamp.ToString();
|
||||
dict["FromName"] = Sanitize(notice.FromName);
|
||||
dict["Subject"] = Sanitize(notice.Subject);
|
||||
dict["HasAttachment"] = notice.HasAttachment.ToString();
|
||||
dict["AttachmentItemID"] = notice.AttachmentItemID.ToString();
|
||||
dict["AttachmentName"] = Sanitize(notice.AttachmentName);
|
||||
dict["AttachmentType"] = notice.AttachmentType.ToString();
|
||||
dict["AttachmentOwnerID"] = Sanitize(notice.AttachmentOwnerID);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static ExtendedGroupNoticeData GroupNoticeData(Dictionary<string, object> dict)
|
||||
{
|
||||
ExtendedGroupNoticeData notice = new ExtendedGroupNoticeData();
|
||||
|
||||
if (dict == null)
|
||||
return notice;
|
||||
|
||||
notice.NoticeID = new UUID(dict["NoticeID"].ToString());
|
||||
notice.Timestamp = UInt32.Parse(dict["Timestamp"].ToString());
|
||||
notice.FromName = Sanitize(dict["FromName"].ToString());
|
||||
notice.Subject = Sanitize(dict["Subject"].ToString());
|
||||
notice.HasAttachment = bool.Parse(dict["HasAttachment"].ToString());
|
||||
notice.AttachmentItemID = new UUID(dict["AttachmentItemID"].ToString());
|
||||
notice.AttachmentName = dict["AttachmentName"].ToString();
|
||||
notice.AttachmentType = byte.Parse(dict["AttachmentType"].ToString());
|
||||
notice.AttachmentOwnerID = dict["AttachmentOwnerID"].ToString();
|
||||
|
||||
return notice;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> GroupNoticeInfo(GroupNoticeInfo notice)
|
||||
{
|
||||
Dictionary<string, object> dict = GroupNoticeData(notice.noticeData);
|
||||
|
||||
dict["GroupID"] = notice.GroupID.ToString();
|
||||
dict["Message"] = Sanitize(notice.Message);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static GroupNoticeInfo GroupNoticeInfo(Dictionary<string, object> dict)
|
||||
{
|
||||
GroupNoticeInfo notice = new GroupNoticeInfo();
|
||||
|
||||
notice.noticeData = GroupNoticeData(dict);
|
||||
notice.GroupID = new UUID(dict["GroupID"].ToString());
|
||||
notice.Message = Sanitize(dict["Message"].ToString());
|
||||
|
||||
return notice;
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> DirGroupsReplyData(DirGroupsReplyData g)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
dict["GroupID"] = g.groupID;
|
||||
dict["Name"] = g.groupName;
|
||||
dict["NMembers"] = g.members;
|
||||
dict["SearchOrder"] = g.searchOrder;
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static DirGroupsReplyData DirGroupsReplyData(Dictionary<string, object> dict)
|
||||
{
|
||||
DirGroupsReplyData g;
|
||||
|
||||
g.groupID = new UUID(dict["GroupID"].ToString());
|
||||
g.groupName = dict["Name"].ToString();
|
||||
Int32.TryParse(dict["NMembers"].ToString(), out g.members);
|
||||
float.TryParse(dict["SearchOrder"].ToString(), out g.searchOrder);
|
||||
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,841 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using log4net;
|
||||
using Mono.Addins;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
|
||||
using GridRegion = OpenSim.Services.Interfaces.GridRegion;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsMessagingModule")]
|
||||
public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private List<Scene> m_sceneList = new List<Scene>();
|
||||
private IPresenceService m_presenceService;
|
||||
|
||||
private IMessageTransferModule m_msgTransferModule = null;
|
||||
private IUserManagement m_UserManagement = null;
|
||||
private IGroupsServicesConnector m_groupData = null;
|
||||
|
||||
// Config Options
|
||||
private bool m_groupMessagingEnabled;
|
||||
private bool m_debugEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, module only tries to send group IMs to online users by querying cached presence information.
|
||||
/// </summary>
|
||||
private bool m_messageOnlineAgentsOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Cache for online users.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Group ID is key, presence information for online members is value.
|
||||
/// Will only be non-null if m_messageOnlineAgentsOnly = true
|
||||
/// We cache here so that group messages don't constantly have to re-request the online user list to avoid
|
||||
/// attempted expensive sending of messages to offline users.
|
||||
/// The tradeoff is that a user that comes online will not receive messages consistently from all other users
|
||||
/// until caches have updated.
|
||||
/// Therefore, we set the cache expiry to just 20 seconds.
|
||||
/// </remarks>
|
||||
private ExpiringCache<UUID, PresenceInfo[]> m_usersOnlineCache;
|
||||
|
||||
private int m_usersOnlineCacheExpirySeconds = 20;
|
||||
|
||||
private Dictionary<UUID, List<string>> m_groupsAgentsDroppedFromChatSession = new Dictionary<UUID, List<string>>();
|
||||
private Dictionary<UUID, List<string>> m_groupsAgentsInvitedToChatSession = new Dictionary<UUID, List<string>>();
|
||||
|
||||
#region Region Module interfaceBase Members
|
||||
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
IConfig groupsConfig = config.Configs["Groups"];
|
||||
|
||||
if (groupsConfig == null)
|
||||
// Do not run this module by default.
|
||||
return;
|
||||
|
||||
// if groups aren't enabled, we're not needed.
|
||||
// if we're not specified as the connector to use, then we're not wanted
|
||||
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|
||||
|| (groupsConfig.GetString("MessagingModule", "") != Name))
|
||||
{
|
||||
m_groupMessagingEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
|
||||
|
||||
if (!m_groupMessagingEnabled)
|
||||
return;
|
||||
|
||||
m_messageOnlineAgentsOnly = groupsConfig.GetBoolean("MessageOnlineUsersOnly", false);
|
||||
|
||||
if (m_messageOnlineAgentsOnly)
|
||||
{
|
||||
m_usersOnlineCache = new ExpiringCache<UUID, PresenceInfo[]>();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.Error("[Groups.Messaging]: GroupsMessagingModule V2 requires MessageOnlineUsersOnly = true");
|
||||
m_groupMessagingEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_debugEnabled = groupsConfig.GetBoolean("MessagingDebugEnabled", m_debugEnabled);
|
||||
|
||||
m_log.InfoFormat(
|
||||
"[Groups.Messaging]: GroupsMessagingModule enabled with MessageOnlineOnly = {0}, DebugEnabled = {1}",
|
||||
m_messageOnlineAgentsOnly, m_debugEnabled);
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
if (!m_groupMessagingEnabled)
|
||||
return;
|
||||
|
||||
scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
|
||||
m_sceneList.Add(scene);
|
||||
|
||||
scene.EventManager.OnNewClient += OnNewClient;
|
||||
scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
|
||||
scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
|
||||
scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
|
||||
scene.EventManager.OnClientLogin += OnClientLogin;
|
||||
|
||||
scene.AddCommand(
|
||||
"Debug",
|
||||
this,
|
||||
"debug groups messaging verbose",
|
||||
"debug groups messaging verbose <true|false>",
|
||||
"This setting turns on very verbose groups messaging debugging",
|
||||
HandleDebugGroupsMessagingVerbose);
|
||||
}
|
||||
|
||||
public void RegionLoaded(Scene scene)
|
||||
{
|
||||
if (!m_groupMessagingEnabled)
|
||||
return;
|
||||
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
|
||||
|
||||
// No groups module, no groups messaging
|
||||
if (m_groupData == null)
|
||||
{
|
||||
m_log.Error("[Groups.Messaging]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
|
||||
RemoveRegion(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
|
||||
|
||||
// No message transfer module, no groups messaging
|
||||
if (m_msgTransferModule == null)
|
||||
{
|
||||
m_log.Error("[Groups.Messaging]: Could not get MessageTransferModule");
|
||||
RemoveRegion(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
|
||||
|
||||
// No groups module, no groups messaging
|
||||
if (m_UserManagement == null)
|
||||
{
|
||||
m_log.Error("[Groups.Messaging]: Could not get IUserManagement, GroupsMessagingModule is now disabled.");
|
||||
RemoveRegion(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_presenceService == null)
|
||||
m_presenceService = scene.PresenceService;
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
{
|
||||
if (!m_groupMessagingEnabled)
|
||||
return;
|
||||
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
m_sceneList.Remove(scene);
|
||||
scene.EventManager.OnNewClient -= OnNewClient;
|
||||
scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
|
||||
scene.EventManager.OnClientLogin -= OnClientLogin;
|
||||
scene.UnregisterModuleInterface<IGroupsMessagingModule>(this);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!m_groupMessagingEnabled)
|
||||
return;
|
||||
|
||||
if (m_debugEnabled) m_log.Debug("[Groups.Messaging]: Shutting down GroupsMessagingModule module.");
|
||||
|
||||
m_sceneList.Clear();
|
||||
|
||||
m_groupData = null;
|
||||
m_msgTransferModule = null;
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Groups Messaging Module V2"; }
|
||||
}
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
// NoOp
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HandleDebugGroupsMessagingVerbose(object modules, string[] args)
|
||||
{
|
||||
if (args.Length < 5)
|
||||
{
|
||||
MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
|
||||
return;
|
||||
}
|
||||
|
||||
bool verbose = false;
|
||||
if (!bool.TryParse(args[4], out verbose))
|
||||
{
|
||||
MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
|
||||
return;
|
||||
}
|
||||
|
||||
m_debugEnabled = verbose;
|
||||
|
||||
MainConsole.Instance.Output("{0} verbose logging set to {1}", Name, m_debugEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not really needed, but does confirm that the group exists.
|
||||
/// </summary>
|
||||
public bool StartGroupChatSession(UUID agentID, UUID groupID)
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID.ToString(), groupID, null);
|
||||
|
||||
if (groupInfo != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendMessageToGroup(GridInstantMessage im, UUID groupID)
|
||||
{
|
||||
SendMessageToGroup(im, groupID, UUID.Zero, null);
|
||||
}
|
||||
|
||||
public void SendMessageToGroup(
|
||||
GridInstantMessage im, UUID groupID, UUID sendingAgentForGroupCalls, Func<GroupMembersData, bool> sendCondition)
|
||||
{
|
||||
int requestStartTick = Environment.TickCount;
|
||||
|
||||
UUID fromAgentID = new UUID(im.fromAgentID);
|
||||
|
||||
// Unlike current XmlRpcGroups, Groups V2 can accept UUID.Zero when a perms check for the requesting agent
|
||||
// is not necessary.
|
||||
List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(UUID.Zero.ToString(), groupID);
|
||||
|
||||
int groupMembersCount = groupMembers.Count;
|
||||
PresenceInfo[] onlineAgents = null;
|
||||
|
||||
// In V2 we always only send to online members.
|
||||
// Sending to offline members is not an option.
|
||||
|
||||
// We cache in order not to overwhelm the presence service on large grids with many groups. This does
|
||||
// mean that members coming online will not see all group members until after m_usersOnlineCacheExpirySeconds has elapsed.
|
||||
// (assuming this is the same across all grid simulators).
|
||||
if (!m_usersOnlineCache.TryGetValue(groupID, out onlineAgents))
|
||||
{
|
||||
string[] t1 = groupMembers.ConvertAll<string>(gmd => gmd.AgentID.ToString()).ToArray();
|
||||
onlineAgents = m_presenceService.GetAgents(t1);
|
||||
m_usersOnlineCache.Add(groupID, onlineAgents, m_usersOnlineCacheExpirySeconds);
|
||||
}
|
||||
|
||||
HashSet<string> onlineAgentsUuidSet = new HashSet<string>();
|
||||
Array.ForEach<PresenceInfo>(onlineAgents, pi => onlineAgentsUuidSet.Add(pi.UserID));
|
||||
|
||||
groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList();
|
||||
|
||||
// if (m_debugEnabled)
|
||||
// m_log.DebugFormat(
|
||||
// "[Groups.Messaging]: SendMessageToGroup called for group {0} with {1} visible members, {2} online",
|
||||
// groupID, groupMembersCount, groupMembers.Count());
|
||||
|
||||
im.imSessionID = groupID.Guid;
|
||||
im.fromGroup = true;
|
||||
IClientAPI thisClient = GetActiveClient(fromAgentID);
|
||||
if (thisClient != null)
|
||||
{
|
||||
im.RegionID = thisClient.Scene.RegionInfo.RegionID.Guid;
|
||||
}
|
||||
|
||||
if (im.binaryBucket is null || im.binaryBucket.Length == 0 || (im.binaryBucket.Length == 1 && im.binaryBucket[0] == 0))
|
||||
{
|
||||
ExtendedGroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), groupID, null);
|
||||
if (groupInfo != null)
|
||||
im.binaryBucket = Util.StringToBytes256(groupInfo.GroupName);
|
||||
}
|
||||
|
||||
// Send to self first of all
|
||||
im.toAgentID = im.fromAgentID;
|
||||
im.fromGroup = true;
|
||||
ProcessMessageFromGroupSession(im);
|
||||
|
||||
List<UUID> regions = new List<UUID>();
|
||||
List<UUID> clientsAlreadySent = new List<UUID>();
|
||||
|
||||
// Then send to everybody else
|
||||
foreach (GroupMembersData member in groupMembers)
|
||||
{
|
||||
if (member.AgentID.Guid == im.fromAgentID)
|
||||
continue;
|
||||
|
||||
if (clientsAlreadySent.Contains(member.AgentID))
|
||||
continue;
|
||||
|
||||
clientsAlreadySent.Add(member.AgentID);
|
||||
|
||||
if (sendCondition != null)
|
||||
{
|
||||
if (!sendCondition(member))
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat(
|
||||
"[Groups.Messaging]: Not sending to {0} as they do not fulfill send condition",
|
||||
member.AgentID);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (hasAgentDroppedGroupChatSession(member.AgentID.ToString(), groupID))
|
||||
{
|
||||
// Don't deliver messages to people who have dropped this session
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: {0} has dropped session, not delivering to them", member.AgentID);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
im.toAgentID = member.AgentID.Guid;
|
||||
|
||||
IClientAPI client = GetActiveClient(member.AgentID);
|
||||
if (client == null)
|
||||
{
|
||||
// If they're not local, forward across the grid
|
||||
// BUT do it only once per region, please! Sim would be even better!
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} via Grid", member.AgentID);
|
||||
|
||||
bool reallySend = true;
|
||||
if (onlineAgents != null)
|
||||
{
|
||||
PresenceInfo presence = onlineAgents.First(p => p.UserID == member.AgentID.ToString());
|
||||
if (regions.Contains(presence.RegionID))
|
||||
reallySend = false;
|
||||
else
|
||||
regions.Add(presence.RegionID);
|
||||
}
|
||||
|
||||
if (reallySend)
|
||||
{
|
||||
// We have to create a new IM structure because the transfer module
|
||||
// uses async send
|
||||
GridInstantMessage msg = new GridInstantMessage(im, true);
|
||||
m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Deliver locally, directly
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name);
|
||||
|
||||
ProcessMessageFromGroupSession(im);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat(
|
||||
"[Groups.Messaging]: SendMessageToGroup for group {0} with {1} visible members, {2} online took {3}ms",
|
||||
groupID, groupMembersCount, groupMembers.Count(), Environment.TickCount - requestStartTick);
|
||||
}
|
||||
|
||||
#region SimGridEventHandlers
|
||||
|
||||
void OnClientLogin(IClientAPI client)
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name);
|
||||
}
|
||||
|
||||
private void OnNewClient(IClientAPI client)
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name);
|
||||
|
||||
ResetAgentGroupChatSessions(client.AgentId.ToString());
|
||||
}
|
||||
|
||||
void OnMakeRootAgent(ScenePresence sp)
|
||||
{
|
||||
sp.ControllingClient.OnInstantMessage += OnInstantMessage;
|
||||
}
|
||||
|
||||
void OnMakeChildAgent(ScenePresence sp)
|
||||
{
|
||||
sp.ControllingClient.OnInstantMessage -= OnInstantMessage;
|
||||
}
|
||||
|
||||
|
||||
private void OnGridInstantMessage(GridInstantMessage msg)
|
||||
{
|
||||
// The instant message module will only deliver messages of dialog types:
|
||||
// MessageFromAgent, StartTyping, StopTyping, MessageFromObject
|
||||
//
|
||||
// Any other message type will not be delivered to a client by the
|
||||
// Instant Message Module
|
||||
|
||||
UUID regionID = new UUID(msg.RegionID);
|
||||
if (m_debugEnabled)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.Messaging]: {0} called, IM from region {1}",
|
||||
System.Reflection.MethodBase.GetCurrentMethod().Name, regionID);
|
||||
|
||||
DebugGridInstantMessage(msg);
|
||||
}
|
||||
|
||||
// Incoming message from a group
|
||||
if ((msg.fromGroup == true) && (msg.dialog == (byte)InstantMessageDialog.SessionSend))
|
||||
{
|
||||
// We have to redistribute the message across all members of the group who are here
|
||||
// on this sim
|
||||
|
||||
UUID GroupID = new UUID(msg.imSessionID);
|
||||
|
||||
Scene aScene = m_sceneList[0];
|
||||
GridRegion regionOfOrigin = aScene.GridService.GetRegionByUUID(aScene.RegionInfo.ScopeID, regionID);
|
||||
|
||||
List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(UUID.Zero.ToString(), GroupID);
|
||||
|
||||
//if (m_debugEnabled)
|
||||
// foreach (GroupMembersData m in groupMembers)
|
||||
// m_log.DebugFormat("[Groups.Messaging]: member {0}", m.AgentID);
|
||||
|
||||
foreach (Scene s in m_sceneList)
|
||||
{
|
||||
s.ForEachScenePresence(sp =>
|
||||
{
|
||||
// If we got this via grid messaging, it's because the caller thinks
|
||||
// that the root agent is here. We should only send the IM to root agents.
|
||||
if (sp.IsChildAgent)
|
||||
return;
|
||||
|
||||
GroupMembersData m = groupMembers.Find(gmd =>
|
||||
{
|
||||
return gmd.AgentID == sp.UUID;
|
||||
});
|
||||
if (m.AgentID.IsZero())
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he is not a member of the group", sp.UUID);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user has an agent in the region where
|
||||
// the IM came from, and if so, skip it, because the IM
|
||||
// was already sent via that agent
|
||||
if (regionOfOrigin != null)
|
||||
{
|
||||
AgentCircuitData aCircuit = s.AuthenticateHandler.GetAgentCircuitData(sp.UUID);
|
||||
if (aCircuit != null)
|
||||
{
|
||||
if (aCircuit.ChildrenCapSeeds.Keys.Contains(regionOfOrigin.RegionHandle))
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he has an agent in region of origin", sp.UUID);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: not skipping agent {0}", sp.UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UUID AgentID = sp.UUID;
|
||||
msg.toAgentID = AgentID.Guid;
|
||||
|
||||
if (!hasAgentDroppedGroupChatSession(AgentID.ToString(), GroupID))
|
||||
{
|
||||
if (!hasAgentBeenInvitedToGroupChatSession(AgentID.ToString(), GroupID))
|
||||
AddAgentToSession(AgentID, GroupID, msg);
|
||||
else
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", sp.Name);
|
||||
|
||||
ProcessMessageFromGroupSession(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMessageFromGroupSession(GridInstantMessage msg)
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID);
|
||||
|
||||
UUID AgentID = new UUID(msg.fromAgentID);
|
||||
UUID GroupID = new UUID(msg.imSessionID);
|
||||
UUID toAgentID = new UUID(msg.toAgentID);
|
||||
|
||||
switch (msg.dialog)
|
||||
{
|
||||
case (byte)InstantMessageDialog.SessionAdd:
|
||||
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
|
||||
break;
|
||||
|
||||
case (byte)InstantMessageDialog.SessionDrop:
|
||||
AgentDroppedFromGroupChatSession(AgentID.ToString(), GroupID);
|
||||
break;
|
||||
|
||||
case (byte)InstantMessageDialog.SessionSend:
|
||||
// User hasn't dropped, so they're in the session,
|
||||
// maybe we should deliver it.
|
||||
IClientAPI client = GetActiveClient(new UUID(msg.toAgentID));
|
||||
if (client != null)
|
||||
{
|
||||
// Deliver locally, directly
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} locally", client.Name);
|
||||
|
||||
if (!hasAgentDroppedGroupChatSession(toAgentID.ToString(), GroupID))
|
||||
{
|
||||
if (!hasAgentBeenInvitedToGroupChatSession(toAgentID.ToString(), GroupID))
|
||||
// This actually sends the message too, so no need to resend it
|
||||
// with client.SendInstantMessage
|
||||
AddAgentToSession(toAgentID, GroupID, msg);
|
||||
else
|
||||
client.SendInstantMessage(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.WarnFormat("[Groups.Messaging]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
m_log.WarnFormat("[Groups.Messaging]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAgentToSession(UUID AgentID, UUID GroupID, GridInstantMessage msg)
|
||||
{
|
||||
// Agent not in session and hasn't dropped from session
|
||||
// Add them to the session for now, and Invite them
|
||||
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
|
||||
|
||||
IClientAPI activeClient = GetActiveClient(AgentID);
|
||||
if (activeClient != null)
|
||||
{
|
||||
GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
|
||||
if (groupInfo != null)
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Sending chatterbox invite instant message");
|
||||
|
||||
UUID fromAgent = new UUID(msg.fromAgentID);
|
||||
// Force? open the group session dialog???
|
||||
// and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg);
|
||||
IEventQueue eq = activeClient.Scene.RequestModuleInterface<IEventQueue>();
|
||||
if (eq != null)
|
||||
{
|
||||
eq.ChatterboxInvitation(
|
||||
GroupID
|
||||
, groupInfo.GroupName
|
||||
, fromAgent
|
||||
, msg.message
|
||||
, AgentID
|
||||
, msg.fromAgentName
|
||||
, msg.dialog
|
||||
, msg.timestamp
|
||||
, msg.offline == 1
|
||||
, (int)msg.ParentEstateID
|
||||
, msg.Position
|
||||
, 1
|
||||
, new UUID(msg.imSessionID)
|
||||
, msg.fromGroup
|
||||
, OpenMetaverse.Utils.StringToBytes(groupInfo.GroupName)
|
||||
);
|
||||
|
||||
var update = new GroupChatListAgentUpdateData(AgentID);
|
||||
var updates = new List<GroupChatListAgentUpdateData> { update };
|
||||
eq.ChatterBoxSessionAgentListUpdates(GroupID, new UUID(msg.toAgentID), updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region ClientEvents
|
||||
private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
|
||||
{
|
||||
if (m_debugEnabled)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
DebugGridInstantMessage(im);
|
||||
}
|
||||
|
||||
// Start group IM session
|
||||
if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart))
|
||||
{
|
||||
if (m_debugEnabled) m_log.InfoFormat("[Groups.Messaging]: imSessionID({0}) toAgentID({1})", im.imSessionID, im.toAgentID);
|
||||
|
||||
UUID GroupID = new UUID(im.imSessionID);
|
||||
UUID AgentID = new UUID(im.fromAgentID);
|
||||
|
||||
GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
|
||||
|
||||
if (groupInfo != null)
|
||||
{
|
||||
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
|
||||
|
||||
ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, GroupID);
|
||||
|
||||
IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
|
||||
if (queue != null)
|
||||
{
|
||||
var update = new GroupChatListAgentUpdateData(AgentID);
|
||||
var updates = new List<GroupChatListAgentUpdateData> { update };
|
||||
queue.ChatterBoxSessionAgentListUpdates(GroupID, remoteClient.AgentId, updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message from locally connected client to a group
|
||||
if ((im.dialog == (byte)InstantMessageDialog.SessionSend))
|
||||
{
|
||||
UUID GroupID = new UUID(im.imSessionID);
|
||||
UUID AgentID = new UUID(im.fromAgentID);
|
||||
|
||||
if (m_debugEnabled)
|
||||
m_log.DebugFormat("[Groups.Messaging]: Send message to session for group {0} with session ID {1}", GroupID, im.imSessionID.ToString());
|
||||
|
||||
//If this agent is sending a message, then they want to be in the session
|
||||
AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
|
||||
|
||||
SendMessageToGroup(im, GroupID);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID)
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
OSDMap moderatedMap = new OSDMap(4);
|
||||
moderatedMap.Add("voice", OSD.FromBoolean(false));
|
||||
|
||||
OSDMap sessionMap = new OSDMap(4);
|
||||
sessionMap.Add("moderated_mode", moderatedMap);
|
||||
sessionMap.Add("session_name", OSD.FromString(groupName));
|
||||
sessionMap.Add("type", OSD.FromInteger(0));
|
||||
sessionMap.Add("voice_enabled", OSD.FromBoolean(false));
|
||||
|
||||
OSDMap bodyMap = new OSDMap(4);
|
||||
bodyMap.Add("session_id", OSD.FromUUID(groupID));
|
||||
bodyMap.Add("temp_session_id", OSD.FromUUID(groupID));
|
||||
bodyMap.Add("success", OSD.FromBoolean(true));
|
||||
bodyMap.Add("session_info", sessionMap);
|
||||
|
||||
IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
|
||||
queue?.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
|
||||
}
|
||||
|
||||
private void DebugGridInstantMessage(GridInstantMessage im)
|
||||
{
|
||||
// Don't log any normal IMs (privacy!)
|
||||
if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent)
|
||||
{
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: fromGroup({0})", im.fromGroup ? "True" : "False");
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentID({0})", im.fromAgentID.ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentName({0})", im.fromAgentName.ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: imSessionID({0})", im.imSessionID.ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: message({0})", im.message.ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: offline({0})", im.offline.ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: toAgentID({0})", im.toAgentID.ToString());
|
||||
m_log.WarnFormat("[Groups.Messaging]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket"));
|
||||
}
|
||||
}
|
||||
|
||||
#region Client Tools
|
||||
|
||||
/// <summary>
|
||||
/// Try to find an active IClientAPI reference for agentID giving preference to root connections
|
||||
/// </summary>
|
||||
private IClientAPI GetActiveClient(UUID agentID)
|
||||
{
|
||||
if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Looking for local client {0}", agentID);
|
||||
|
||||
IClientAPI child = null;
|
||||
|
||||
// Try root avatar first
|
||||
foreach (Scene scene in m_sceneList)
|
||||
{
|
||||
ScenePresence sp = scene.GetScenePresence(agentID);
|
||||
if (sp != null)
|
||||
{
|
||||
if (!sp.IsChildAgent)
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Found root agent for client : {0}", sp.ControllingClient.Name);
|
||||
return sp.ControllingClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Found child agent for client : {0}", sp.ControllingClient.Name);
|
||||
child = sp.ControllingClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a root, then just return whichever child we found, or null if none
|
||||
if (child == null)
|
||||
{
|
||||
if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Could not find local client for agent : {0}", agentID);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Returning child agent for client : {0}", child.Name);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GroupSessionTracking
|
||||
|
||||
public void ResetAgentGroupChatSessions(string agentID)
|
||||
{
|
||||
foreach (List<string> agentList in m_groupsAgentsDroppedFromChatSession.Values)
|
||||
agentList.Remove(agentID);
|
||||
|
||||
foreach (List<string> agentList in m_groupsAgentsInvitedToChatSession.Values)
|
||||
agentList.Remove(agentID);
|
||||
}
|
||||
|
||||
public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID)
|
||||
{
|
||||
// If we're tracking this group, and we can find them in the tracking, then they've been invited
|
||||
return m_groupsAgentsInvitedToChatSession.ContainsKey(groupID)
|
||||
&& m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID);
|
||||
}
|
||||
|
||||
public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID)
|
||||
{
|
||||
// If we're tracking drops for this group,
|
||||
// and we find them, well... then they've dropped
|
||||
return m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID)
|
||||
&& m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID);
|
||||
}
|
||||
|
||||
public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID)
|
||||
{
|
||||
if (m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
|
||||
{
|
||||
// If not in dropped list, add
|
||||
if (!m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
|
||||
{
|
||||
m_groupsAgentsDroppedFromChatSession[groupID].Add(agentID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AgentInvitedToGroupChatSession(string agentID, UUID groupID)
|
||||
{
|
||||
// Add Session Status if it doesn't exist for this session
|
||||
CreateGroupChatSessionTracking(groupID);
|
||||
|
||||
// If nessesary, remove from dropped list
|
||||
if (m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
|
||||
{
|
||||
m_groupsAgentsDroppedFromChatSession[groupID].Remove(agentID);
|
||||
}
|
||||
|
||||
// Add to invited
|
||||
if (!m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID))
|
||||
m_groupsAgentsInvitedToChatSession[groupID].Add(agentID);
|
||||
}
|
||||
|
||||
private void CreateGroupChatSessionTracking(UUID groupID)
|
||||
{
|
||||
if (!m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
|
||||
{
|
||||
m_groupsAgentsDroppedFromChatSession.Add(groupID, new List<string>());
|
||||
m_groupsAgentsInvitedToChatSession.Add(groupID, new List<string>());
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,295 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Server.Base;
|
||||
|
||||
using OpenMetaverse;
|
||||
using log4net;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class GroupsServiceHGConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private string m_ServerURI;
|
||||
private object m_Lock = new object();
|
||||
|
||||
public GroupsServiceHGConnector(string url)
|
||||
{
|
||||
m_ServerURI = url;
|
||||
if (!m_ServerURI.EndsWith("/"))
|
||||
m_ServerURI += "/";
|
||||
|
||||
m_log.DebugFormat("[Groups.HGConnector]: Groups server at {0}", m_ServerURI);
|
||||
}
|
||||
|
||||
public bool CreateProxy(string RequestingAgentID, string AgentID, string accessToken, UUID groupID, string url, string name, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string,object>();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["AccessToken"] = accessToken;
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["Location"] = url;
|
||||
sendData["Name"] = name;
|
||||
Dictionary<string, object> ret = MakeRequest("POSTGROUP", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
{
|
||||
reason = ret["REASON"].ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroup(string AgentID, UUID GroupID, string token)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID;
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
|
||||
MakeRequest("REMOVEAGENTFROMGROUP", sendData);
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, string token)
|
||||
{
|
||||
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
if (!GroupID.IsZero())
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
if (!string.IsNullOrEmpty(GroupName))
|
||||
sendData["Name"] = GroupsDataUtils.Sanitize(GroupName);
|
||||
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("GETGROUP", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public List<ExtendedGroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID, string token)
|
||||
{
|
||||
List<ExtendedGroupMembersData> members = new List<ExtendedGroupMembersData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
|
||||
Dictionary<string, object> ret = MakeRequest("GETGROUPMEMBERS", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return members;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return members;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return members;
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
ExtendedGroupMembersData m = GroupsDataUtils.GroupMembersData((Dictionary<string, object>)v);
|
||||
members.Add(m);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID, string token)
|
||||
{
|
||||
List<GroupRolesData> roles = new List<GroupRolesData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
|
||||
Dictionary<string, object> ret = MakeRequest("GETGROUPROLES", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return roles;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return roles;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return roles;
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary<string, object>)v);
|
||||
roles.Add(m);
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public List<ExtendedGroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, string token)
|
||||
{
|
||||
List<ExtendedGroupRoleMembersData> rmembers = new List<ExtendedGroupRoleMembersData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["AccessToken"] = GroupsDataUtils.Sanitize(token);
|
||||
Dictionary<string, object> ret = MakeRequest("GETROLEMEMBERS", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return rmembers;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return rmembers;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return rmembers;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
ExtendedGroupRoleMembersData m = GroupsDataUtils.GroupRoleMembersData((Dictionary<string, object>)v);
|
||||
rmembers.Add(m);
|
||||
}
|
||||
|
||||
return rmembers;
|
||||
}
|
||||
|
||||
public bool AddNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["NoticeID"] = noticeID.ToString();
|
||||
sendData["FromName"] = GroupsDataUtils.Sanitize(fromName);
|
||||
sendData["Subject"] = GroupsDataUtils.Sanitize(subject);
|
||||
sendData["Message"] = GroupsDataUtils.Sanitize(message);
|
||||
sendData["HasAttachment"] = hasAttachment.ToString();
|
||||
if (hasAttachment)
|
||||
{
|
||||
sendData["AttachmentType"] = attType.ToString();
|
||||
sendData["AttachmentName"] = attName.ToString();
|
||||
sendData["AttachmentItemID"] = attItemID.ToString();
|
||||
sendData["AttachmentOwnerID"] = attOwnerID;
|
||||
}
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("ADDNOTICE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool VerifyNotice(UUID noticeID, UUID groupID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["NoticeID"] = noticeID.ToString();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
Dictionary<string, object> ret = MakeRequest("VERIFYNOTICE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#region Make Request
|
||||
|
||||
private Dictionary<string, object> MakeRequest(string method, Dictionary<string, object> sendData)
|
||||
{
|
||||
sendData["METHOD"] = method;
|
||||
|
||||
string reply = string.Empty;
|
||||
try
|
||||
{
|
||||
lock (m_Lock)
|
||||
reply = SynchronousRestFormsRequester.MakeRequest("POST",
|
||||
m_ServerURI + "hg-groups",
|
||||
ServerUtils.BuildQueryString(sendData));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: reply was {0}", reply);
|
||||
|
||||
if (string.IsNullOrEmpty(reply))
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply);
|
||||
|
||||
return replyData;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,694 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Monitoring;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
using OpenMetaverse;
|
||||
using Mono.Addins;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceHGConnectorModule")]
|
||||
public class GroupsServiceHGConnectorModule : ISharedRegionModule, IGroupsServicesConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private bool m_Enabled = false;
|
||||
private IGroupsServicesConnector m_LocalGroupsConnector;
|
||||
private string m_LocalGroupsServiceLocation;
|
||||
private IUserManagement m_UserManagement;
|
||||
private IOfflineIMService m_OfflineIM;
|
||||
private IMessageTransferModule m_Messaging;
|
||||
private List<Scene> m_Scenes;
|
||||
private ForeignImporter m_ForeignImporter;
|
||||
private string m_ServiceLocation;
|
||||
private IConfigSource m_Config;
|
||||
|
||||
private Dictionary<string, GroupsServiceHGConnector> m_NetworkConnectors = new Dictionary<string, GroupsServiceHGConnector>();
|
||||
private RemoteConnectorCacheWrapper m_CacheWrapper; // for caching info of external group services
|
||||
|
||||
#region ISharedRegionModule
|
||||
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
IConfig groupsConfig = config.Configs["Groups"];
|
||||
if (groupsConfig == null)
|
||||
return;
|
||||
|
||||
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|
||||
|| (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Config = config;
|
||||
m_ServiceLocation = groupsConfig.GetString("LocalService", "local"); // local or remote
|
||||
m_LocalGroupsServiceLocation = groupsConfig.GetString("GroupsExternalURI", "http://127.0.0.1");
|
||||
m_Scenes = new List<Scene>();
|
||||
|
||||
m_Enabled = true;
|
||||
|
||||
m_log.DebugFormat("[Groups]: Initializing {0} with LocalService {1}", this.Name, m_ServiceLocation);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Groups HG Service Connector"; }
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Add(scene);
|
||||
|
||||
scene.EventManager.OnNewClient += OnNewClient;
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Remove(scene);
|
||||
}
|
||||
|
||||
public void RegionLoaded(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
if (m_UserManagement == null)
|
||||
{
|
||||
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
|
||||
m_OfflineIM = scene.RequestModuleInterface<IOfflineIMService>();
|
||||
m_Messaging = scene.RequestModuleInterface<IMessageTransferModule>();
|
||||
m_ForeignImporter = new ForeignImporter(m_UserManagement);
|
||||
|
||||
if (m_ServiceLocation.Equals("local"))
|
||||
{
|
||||
m_LocalGroupsConnector = new GroupsServiceLocalConnectorModule(m_Config, m_UserManagement);
|
||||
// Also, if local, create the endpoint for the HGGroupsService
|
||||
new HGGroupsServiceRobustConnector(m_Config, MainServer.Instance, string.Empty,
|
||||
scene.RequestModuleInterface<IOfflineIMService>(), scene.RequestModuleInterface<IUserAccountService>());
|
||||
|
||||
}
|
||||
else
|
||||
m_LocalGroupsConnector = new GroupsServiceRemoteConnectorModule(m_Config, m_UserManagement);
|
||||
|
||||
m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnNewClient(IClientAPI client)
|
||||
{
|
||||
client.OnCompleteMovementToRegion += OnCompleteMovementToRegion;
|
||||
}
|
||||
|
||||
void OnCompleteMovementToRegion(IClientAPI client, bool arg2)
|
||||
{
|
||||
if (client.SceneAgent is ScenePresence sp)
|
||||
{
|
||||
if (sp.PresenceType != PresenceType.Npc)
|
||||
{
|
||||
AgentCircuitData aCircuit = sp.Scene.AuthenticateHandler.GetAgentCircuitData(client.AgentId);
|
||||
if (aCircuit != null && (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0 &&
|
||||
m_OfflineIM != null && m_Messaging != null)
|
||||
{
|
||||
List<GridInstantMessage> ims = m_OfflineIM.GetMessages(aCircuit.AgentID);
|
||||
if (ims != null && ims.Count > 0)
|
||||
foreach (GridInstantMessage im in ims)
|
||||
m_Messaging.SendInstantMessage(im, delegate(bool success) { });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IGroupsServicesConnector
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (m_UserManagement.IsLocalGridUser(RequestingAgentID))
|
||||
return m_LocalGroupsConnector.CreateGroup(RequestingAgentID, name, charter, showInList, insigniaID,
|
||||
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
|
||||
else
|
||||
{
|
||||
reason = "Only local grid users are allowed to create a new group";
|
||||
return UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
string url = string.Empty;
|
||||
string name = string.Empty;
|
||||
if (IsLocal(groupID, out url, out name))
|
||||
return m_LocalGroupsConnector.UpdateGroup(AgentUUI(RequestingAgentID), groupID, charter, showInList, insigniaID, membershipFee,
|
||||
openEnrollment, allowPublish, maturePublish, out reason);
|
||||
else
|
||||
{
|
||||
reason = "Changes to remote group not allowed. Please go to the group's original world.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
string url = string.Empty;
|
||||
string name = string.Empty;
|
||||
if (IsLocal(GroupID, out url, out name))
|
||||
return m_LocalGroupsConnector.GetGroupRecord(AgentUUI(RequestingAgentID), GroupID, GroupName);
|
||||
else if (url != string.Empty)
|
||||
{
|
||||
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, GroupID);
|
||||
string accessToken = string.Empty;
|
||||
if (membership != null)
|
||||
accessToken = membership.AccessToken;
|
||||
else
|
||||
return null;
|
||||
|
||||
GroupsServiceHGConnector c = GetConnector(url);
|
||||
if (c != null)
|
||||
{
|
||||
ExtendedGroupRecord grec = m_CacheWrapper.GetGroupRecord(RequestingAgentID, GroupID, GroupName, delegate
|
||||
{
|
||||
return c.GetGroupRecord(AgentUUIForOutside(RequestingAgentID), GroupID, GroupName, accessToken);
|
||||
});
|
||||
|
||||
if (grec != null)
|
||||
ImportForeigner(grec.FounderUUI);
|
||||
return grec;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
|
||||
{
|
||||
return m_LocalGroupsConnector.FindGroups(RequestingAgentIDstr, search);
|
||||
}
|
||||
|
||||
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
{
|
||||
string agentID = AgentUUI(RequestingAgentID);
|
||||
return m_LocalGroupsConnector.GetGroupMembers(agentID, GroupID);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, GroupID);
|
||||
string accessToken = string.Empty;
|
||||
if (membership != null)
|
||||
accessToken = membership.AccessToken;
|
||||
else
|
||||
return null;
|
||||
|
||||
GroupsServiceHGConnector c = GetConnector(url);
|
||||
if (c != null)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupMembers(RequestingAgentID, GroupID, delegate
|
||||
{
|
||||
return c.GetGroupMembers(AgentUUIForOutside(RequestingAgentID), GroupID, accessToken);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
return new List<GroupMembersData>();
|
||||
}
|
||||
|
||||
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.AddGroupRole(AgentUUI(RequestingAgentID), groupID, roleID, name, description, title, powers, out reason);
|
||||
else
|
||||
{
|
||||
reason = "Operation not allowed outside this group's origin world.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.UpdateGroupRole(AgentUUI(RequestingAgentID), groupID, roleID, name, description, title, powers);
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
m_LocalGroupsConnector.RemoveGroupRole(AgentUUI(RequestingAgentID), groupID, roleID);
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID groupID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.GetGroupRoles(AgentUUI(RequestingAgentID), groupID);
|
||||
else if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, groupID);
|
||||
string accessToken = string.Empty;
|
||||
if (membership != null)
|
||||
accessToken = membership.AccessToken;
|
||||
else
|
||||
return null;
|
||||
|
||||
GroupsServiceHGConnector c = GetConnector(url);
|
||||
if (c != null)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupRoles(RequestingAgentID, groupID, delegate
|
||||
{
|
||||
return c.GetGroupRoles(AgentUUIForOutside(RequestingAgentID), groupID, accessToken);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return new List<GroupRolesData>();
|
||||
}
|
||||
|
||||
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID groupID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.GetGroupRoleMembers(AgentUUI(RequestingAgentID), groupID);
|
||||
else if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(RequestingAgentID, RequestingAgentID, groupID);
|
||||
string accessToken = string.Empty;
|
||||
if (membership != null)
|
||||
accessToken = membership.AccessToken;
|
||||
else
|
||||
return null;
|
||||
|
||||
GroupsServiceHGConnector c = GetConnector(url);
|
||||
if (c != null)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupRoleMembers(RequestingAgentID, groupID, delegate
|
||||
{
|
||||
return c.GetGroupRoleMembers(AgentUUIForOutside(RequestingAgentID), groupID, accessToken);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return new List<GroupRoleMembersData>();
|
||||
}
|
||||
|
||||
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
|
||||
{
|
||||
string url = string.Empty;
|
||||
string name = string.Empty;
|
||||
reason = string.Empty;
|
||||
|
||||
UUID uid = new UUID(AgentID);
|
||||
if (IsLocal(GroupID, out url, out name))
|
||||
{
|
||||
if (m_UserManagement.IsLocalGridUser(uid)) // local user
|
||||
{
|
||||
// normal case: local group, local user
|
||||
return m_LocalGroupsConnector.AddAgentToGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID, token, out reason);
|
||||
}
|
||||
else // local group, foreign user
|
||||
{
|
||||
// the user is accepting the invitation, or joining, where the group resides
|
||||
token = UUID.Random().ToString();
|
||||
bool success = m_LocalGroupsConnector.AddAgentToGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID, token, out reason);
|
||||
|
||||
if (success)
|
||||
{
|
||||
// Here we always return true. The user has been added to the local group,
|
||||
// independent of whether the remote operation succeeds or not
|
||||
url = m_UserManagement.GetUserServerURL(uid, "GroupsServerURI");
|
||||
if (url.Length == 0)
|
||||
{
|
||||
reason = "You don't have an accessible groups server in your home world. You membership to this group in only within this grid.";
|
||||
return true;
|
||||
}
|
||||
|
||||
GroupsServiceHGConnector c = GetConnector(url);
|
||||
if (c != null)
|
||||
c.CreateProxy(AgentUUI(RequestingAgentID), AgentID, token, GroupID, m_LocalGroupsServiceLocation, name, out reason);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (m_UserManagement.IsLocalGridUser(uid)) // local user
|
||||
{
|
||||
// foreign group, local user. She's been added already by the HG service.
|
||||
// Let's just check
|
||||
if (m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID) != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
reason = "Operation not allowed outside this group's origin world";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
string url = string.Empty, name = string.Empty;
|
||||
if (!IsLocal(GroupID, out url, out name) && url != string.Empty)
|
||||
{
|
||||
ExtendedGroupMembershipData membership = m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
|
||||
if (membership != null)
|
||||
{
|
||||
GroupsServiceHGConnector c = GetConnector(url);
|
||||
if (c != null)
|
||||
c.RemoveAgentFromGroup(AgentUUIForOutside(AgentID), GroupID, membership.AccessToken);
|
||||
}
|
||||
}
|
||||
|
||||
// remove from local service
|
||||
m_LocalGroupsConnector.RemoveAgentFromGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
|
||||
}
|
||||
|
||||
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.AddAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID, groupID, roleID, AgentUUI(agentID));
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
return m_LocalGroupsConnector.GetAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID);
|
||||
}
|
||||
|
||||
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
m_LocalGroupsConnector.RemoveAgentToGroupInvite(AgentUUI(RequestingAgentID), inviteID);
|
||||
}
|
||||
|
||||
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
m_LocalGroupsConnector.AddAgentToGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID);
|
||||
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
m_LocalGroupsConnector.RemoveAgentFromGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID);
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.GetAgentGroupRoles(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
|
||||
else
|
||||
return new List<GroupRolesData>();
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
m_LocalGroupsConnector.SetAgentActiveGroup(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
return m_LocalGroupsConnector.GetAgentActiveMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID));
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
m_LocalGroupsConnector.SetAgentActiveGroupRole(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, RoleID);
|
||||
}
|
||||
|
||||
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
|
||||
{
|
||||
m_LocalGroupsConnector.UpdateMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID, AcceptNotices, ListInProfile);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(GroupID, out url, out gname))
|
||||
return m_LocalGroupsConnector.GetAgentGroupMembership(AgentUUI(RequestingAgentID), AgentUUI(AgentID), GroupID);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
return m_LocalGroupsConnector.GetAgentGroupMemberships(AgentUUI(RequestingAgentID), AgentUUI(AgentID));
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
|
||||
{
|
||||
string url = string.Empty, gname = string.Empty;
|
||||
|
||||
if (IsLocal(groupID, out url, out gname))
|
||||
{
|
||||
if (m_LocalGroupsConnector.AddGroupNotice(AgentUUI(RequestingAgentID), groupID, noticeID, fromName, subject, message,
|
||||
hasAttachment, attType, attName, attItemID, AgentUUI(attOwnerID)))
|
||||
{
|
||||
// then send the notice to every grid for which there are members in this group
|
||||
List<GroupMembersData> members = m_LocalGroupsConnector.GetGroupMembers(AgentUUI(RequestingAgentID), groupID);
|
||||
List<string> urls = new List<string>();
|
||||
foreach (GroupMembersData m in members)
|
||||
{
|
||||
if (!m_UserManagement.IsLocalGridUser(m.AgentID))
|
||||
{
|
||||
string gURL = m_UserManagement.GetUserServerURL(m.AgentID, "GroupsServerURI");
|
||||
if (!urls.Contains(gURL))
|
||||
urls.Add(gURL);
|
||||
}
|
||||
}
|
||||
|
||||
// so we have the list of urls to send the notice to
|
||||
// this may take a long time...
|
||||
WorkManager.RunInThread(delegate
|
||||
{
|
||||
foreach (string u in urls)
|
||||
{
|
||||
GroupsServiceHGConnector c = GetConnector(u);
|
||||
if (c != null)
|
||||
{
|
||||
c.AddNotice(AgentUUIForOutside(RequestingAgentID), groupID, noticeID, fromName, subject, message,
|
||||
hasAttachment, attType, attName, attItemID, AgentUUIForOutside(attOwnerID));
|
||||
}
|
||||
}
|
||||
}, null, string.Format("AddGroupNotice (agent {0}, group {1})", RequestingAgentID, groupID));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
|
||||
{
|
||||
GroupNoticeInfo notice = m_LocalGroupsConnector.GetGroupNotice(AgentUUI(RequestingAgentID), noticeID);
|
||||
|
||||
if (notice != null && notice.noticeData.HasAttachment && notice.noticeData.AttachmentOwnerID != null)
|
||||
ImportForeigner(notice.noticeData.AttachmentOwnerID);
|
||||
|
||||
return notice;
|
||||
}
|
||||
|
||||
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_LocalGroupsConnector.GetGroupNotices(AgentUUI(RequestingAgentID), GroupID);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region hypergrid groups
|
||||
|
||||
private string AgentUUI(string AgentIDStr)
|
||||
{
|
||||
UUID AgentID = UUID.Zero;
|
||||
if (!UUID.TryParse(AgentIDStr, out AgentID) || AgentID.IsZero())
|
||||
return UUID.Zero.ToString();
|
||||
|
||||
if (m_UserManagement.IsLocalGridUser(AgentID))
|
||||
return AgentID.ToString();
|
||||
|
||||
AgentCircuitData agent = null;
|
||||
foreach (Scene scene in m_Scenes)
|
||||
{
|
||||
agent = scene.AuthenticateHandler.GetAgentCircuitData(AgentID);
|
||||
if (agent != null)
|
||||
break;
|
||||
}
|
||||
if (agent != null)
|
||||
return Util.ProduceUserUniversalIdentifier(agent);
|
||||
|
||||
// we don't know anything about this foreign user
|
||||
// try asking the user management module, which may know more
|
||||
return m_UserManagement.GetUserUUI(AgentID);
|
||||
|
||||
}
|
||||
|
||||
private string AgentUUIForOutside(string AgentIDStr)
|
||||
{
|
||||
UUID AgentID = UUID.Zero;
|
||||
if (!UUID.TryParse(AgentIDStr, out AgentID) || AgentID.IsZero())
|
||||
return UUID.ZeroString;
|
||||
|
||||
AgentCircuitData agent = null;
|
||||
foreach (Scene scene in m_Scenes)
|
||||
{
|
||||
agent = scene.AuthenticateHandler.GetAgentCircuitData(AgentID);
|
||||
if (agent != null)
|
||||
break;
|
||||
}
|
||||
if (agent == null) // oops
|
||||
return AgentID.ToString();
|
||||
|
||||
return Util.ProduceUserUniversalIdentifier(agent);
|
||||
}
|
||||
|
||||
private UUID ImportForeigner(string uID)
|
||||
{
|
||||
UUID userID = UUID.Zero;
|
||||
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
|
||||
if (Util.ParseUniversalUserIdentifier(uID, out userID, out url, out first, out last, out tmp))
|
||||
m_UserManagement.AddUser(userID, first, last, url);
|
||||
|
||||
return userID;
|
||||
}
|
||||
|
||||
private bool IsLocal(UUID groupID, out string serviceLocation, out string name)
|
||||
{
|
||||
serviceLocation = string.Empty;
|
||||
name = string.Empty;
|
||||
if (groupID.Equals(UUID.Zero))
|
||||
return true;
|
||||
|
||||
ExtendedGroupRecord group = m_LocalGroupsConnector.GetGroupRecord(UUID.Zero.ToString(), groupID, string.Empty);
|
||||
if (group == null)
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: IsLocal? group {0} not found -- no.", groupID);
|
||||
return false;
|
||||
}
|
||||
|
||||
serviceLocation = group.ServiceLocation;
|
||||
name = group.GroupName;
|
||||
bool isLocal = (group.ServiceLocation.Length == 0);
|
||||
//m_log.DebugFormat("[XXX]: IsLocal? {0}", isLocal);
|
||||
return isLocal;
|
||||
}
|
||||
|
||||
private GroupsServiceHGConnector GetConnector(string url)
|
||||
{
|
||||
lock (m_NetworkConnectors)
|
||||
{
|
||||
if (m_NetworkConnectors.ContainsKey(url))
|
||||
return m_NetworkConnectors[url];
|
||||
|
||||
GroupsServiceHGConnector c = new GroupsServiceHGConnector(url);
|
||||
m_NetworkConnectors[url] = c;
|
||||
}
|
||||
|
||||
return m_NetworkConnectors[url];
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Server.Handlers.Base;
|
||||
using log4net;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class HGGroupsServiceRobustConnector : ServiceConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private HGGroupsService m_GroupsService;
|
||||
private string m_ConfigName = "Groups";
|
||||
|
||||
// Called by Robust shell
|
||||
public HGGroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
this(config, server, configName, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
// Called by the sim-bound module
|
||||
public HGGroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName, IOfflineIMService im, IUserAccountService users) :
|
||||
base(config, server, configName)
|
||||
{
|
||||
if (configName != String.Empty)
|
||||
m_ConfigName = configName;
|
||||
|
||||
m_log.DebugFormat("[Groups.RobustHGConnector]: Starting with config name {0}", m_ConfigName);
|
||||
|
||||
string homeURI = Util.GetConfigVarFromSections<string>(config, "HomeURI",
|
||||
new string[] { "Startup", "Hypergrid", m_ConfigName}, string.Empty);
|
||||
if (homeURI.Length == 0)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide the HomeURI [Startup] or in section {0}", m_ConfigName));
|
||||
|
||||
IConfig cnf = config.Configs[m_ConfigName];
|
||||
if (cnf == null)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: {0} section does not exist", m_ConfigName));
|
||||
|
||||
if (im == null)
|
||||
{
|
||||
string imDll = cnf.GetString("OfflineIMService", string.Empty);
|
||||
if (imDll.Length == 0)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide OfflineIMService in section {0}", m_ConfigName));
|
||||
|
||||
Object[] args = new Object[] { config };
|
||||
im = ServerUtils.LoadPlugin<IOfflineIMService>(imDll, args);
|
||||
}
|
||||
|
||||
if (users == null)
|
||||
{
|
||||
string usersDll = cnf.GetString("UserAccountService", string.Empty);
|
||||
if (usersDll.Length == 0)
|
||||
throw new Exception(String.Format("[Groups.RobustHGConnector]: please provide UserAccountService in section {0}", m_ConfigName));
|
||||
|
||||
Object[] args = new Object[] { config };
|
||||
users = ServerUtils.LoadPlugin<IUserAccountService>(usersDll, args);
|
||||
}
|
||||
|
||||
m_GroupsService = new HGGroupsService(config, im, users, homeURI);
|
||||
|
||||
server.AddStreamHandler(new HGGroupsServicePostHandler(m_GroupsService));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class HGGroupsServicePostHandler : BaseStreamHandler
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private HGGroupsService m_GroupsService;
|
||||
|
||||
public HGGroupsServicePostHandler(HGGroupsService service) :
|
||||
base("POST", "/hg-groups")
|
||||
{
|
||||
m_GroupsService = service;
|
||||
}
|
||||
|
||||
protected override byte[] ProcessRequest(string path, Stream requestData,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
string body;
|
||||
using(StreamReader sr = new StreamReader(requestData))
|
||||
body = sr.ReadToEnd();
|
||||
|
||||
body = body.Trim();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: query String: {0}", body);
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> request =
|
||||
ServerUtils.ParseQueryString(body);
|
||||
|
||||
if (!request.ContainsKey("METHOD"))
|
||||
return FailureResult();
|
||||
|
||||
string method = request["METHOD"].ToString();
|
||||
request.Remove("METHOD");
|
||||
|
||||
m_log.DebugFormat("[Groups.RobustHGConnector]: {0}", method);
|
||||
switch (method)
|
||||
{
|
||||
case "POSTGROUP":
|
||||
return HandleAddGroupProxy(request);
|
||||
case "REMOVEAGENTFROMGROUP":
|
||||
return HandleRemoveAgentFromGroup(request);
|
||||
case "GETGROUP":
|
||||
return HandleGetGroup(request);
|
||||
case "ADDNOTICE":
|
||||
return HandleAddNotice(request);
|
||||
case "VERIFYNOTICE":
|
||||
return HandleVerifyNotice(request);
|
||||
case "GETGROUPMEMBERS":
|
||||
return HandleGetGroupMembers(request);
|
||||
case "GETGROUPROLES":
|
||||
return HandleGetGroupRoles(request);
|
||||
case "GETROLEMEMBERS":
|
||||
return HandleGetRoleMembers(request);
|
||||
|
||||
}
|
||||
m_log.DebugFormat("[Groups.RobustHGConnector]: unknown method request: {0}", method);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error(string.Format("[Groups.RobustHGConnector]: Exception {0} ", e.Message), e);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
}
|
||||
|
||||
byte[] HandleAddGroupProxy(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID")
|
||||
|| !request.ContainsKey("AgentID")
|
||||
|| !request.ContainsKey("AccessToken") || !request.ContainsKey("Location"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
string RequestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string agentID = request["AgentID"].ToString();
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string accessToken = request["AccessToken"].ToString();
|
||||
string location = request["Location"].ToString();
|
||||
string name = string.Empty;
|
||||
if (request.ContainsKey("Name"))
|
||||
name = request["Name"].ToString();
|
||||
|
||||
string reason = string.Empty;
|
||||
bool success = m_GroupsService.CreateGroupProxy(RequestingAgentID, agentID, accessToken, groupID, location, name, out reason);
|
||||
result["REASON"] = reason;
|
||||
result["RESULT"] = success.ToString();
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleRemoveAgentFromGroup(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("AccessToken") || !request.ContainsKey("AgentID") ||
|
||||
!request.ContainsKey("GroupID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string agentID = request["AgentID"].ToString();
|
||||
string token = request["AccessToken"].ToString();
|
||||
|
||||
if (!m_GroupsService.RemoveAgentFromGroup(agentID, agentID, groupID, token))
|
||||
NullResult(result, "Internal error");
|
||||
else
|
||||
result["RESULT"] = "true";
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
byte[] HandleGetGroup(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AccessToken"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
string RequestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string token = request["AccessToken"].ToString();
|
||||
|
||||
UUID groupID = UUID.Zero;
|
||||
string groupName = string.Empty;
|
||||
|
||||
if (request.ContainsKey("GroupID"))
|
||||
groupID = new UUID(request["GroupID"].ToString());
|
||||
if (request.ContainsKey("Name"))
|
||||
groupName = request["Name"].ToString();
|
||||
|
||||
ExtendedGroupRecord grec = m_GroupsService.GetGroupRecord(RequestingAgentID, groupID, groupName, token);
|
||||
if (grec == null)
|
||||
NullResult(result, "Group not found");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupRecord(grec);
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetGroupMembers(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string token = request["AccessToken"].ToString();
|
||||
|
||||
List<ExtendedGroupMembersData> members = m_GroupsService.GetGroupMembers(requestingAgentID, groupID, token);
|
||||
if (members == null || (members != null && members.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (ExtendedGroupMembersData m in members)
|
||||
{
|
||||
dict["m-" + i++] = GroupsDataUtils.GroupMembersData(m);
|
||||
}
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetGroupRoles(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string token = request["AccessToken"].ToString();
|
||||
|
||||
List<GroupRolesData> roles = m_GroupsService.GetGroupRoles(requestingAgentID, groupID, token);
|
||||
if (roles == null || (roles != null && roles.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (GroupRolesData r in roles)
|
||||
dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetRoleMembers(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AccessToken"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string token = request["AccessToken"].ToString();
|
||||
|
||||
List<ExtendedGroupRoleMembersData> rmembers = m_GroupsService.GetGroupRoleMembers(requestingAgentID, groupID, token);
|
||||
if (rmembers == null || (rmembers != null && rmembers.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (ExtendedGroupRoleMembersData rm in rmembers)
|
||||
dict["rm-" + i++] = GroupsDataUtils.GroupRoleMembersData(rm);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleAddNotice(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("NoticeID") ||
|
||||
!request.ContainsKey("FromName") || !request.ContainsKey("Subject") || !request.ContainsKey("Message") ||
|
||||
!request.ContainsKey("HasAttachment"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
|
||||
bool hasAtt = bool.Parse(request["HasAttachment"].ToString());
|
||||
byte attType = 0;
|
||||
string attName = string.Empty;
|
||||
string attOwner = string.Empty;
|
||||
UUID attItem = UUID.Zero;
|
||||
if (request.ContainsKey("AttachmentType"))
|
||||
attType = byte.Parse(request["AttachmentType"].ToString());
|
||||
if (request.ContainsKey("AttachmentName"))
|
||||
attName = request["AttachmentType"].ToString();
|
||||
if (request.ContainsKey("AttachmentItemID"))
|
||||
attItem = new UUID(request["AttachmentItemID"].ToString());
|
||||
if (request.ContainsKey("AttachmentOwnerID"))
|
||||
attOwner = request["AttachmentOwnerID"].ToString();
|
||||
|
||||
bool success = m_GroupsService.AddNotice(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
|
||||
new UUID(request["NoticeID"].ToString()), request["FromName"].ToString(), request["Subject"].ToString(),
|
||||
request["Message"].ToString(), hasAtt, attType, attName, attItem, attOwner);
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleVerifyNotice(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("NoticeID") || !request.ContainsKey("GroupID"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
UUID noticeID = new UUID(request["NoticeID"].ToString());
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
|
||||
bool success = m_GroupsService.VerifyNotice(noticeID, groupID);
|
||||
//m_log.DebugFormat("[XXX]: VerifyNotice returned {0}", success);
|
||||
result["RESULT"] = success.ToString();
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void NullResult(Dictionary<string, object> result, string reason)
|
||||
{
|
||||
result["RESULT"] = "NULL";
|
||||
result["REASON"] = reason;
|
||||
}
|
||||
|
||||
private byte[] FailureResult()
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
NullResult(result, "Unknown method");
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public interface IGroupsServicesConnector
|
||||
{
|
||||
UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
bool openEnrollment, bool allowPublish, bool maturePublish, UUID founderID, out string reason);
|
||||
bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason);
|
||||
ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName);
|
||||
List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search);
|
||||
List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID);
|
||||
|
||||
bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason);
|
||||
bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers);
|
||||
void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID);
|
||||
List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID);
|
||||
List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID);
|
||||
|
||||
bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason);
|
||||
void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID);
|
||||
|
||||
bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID);
|
||||
GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID);
|
||||
void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID);
|
||||
|
||||
void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID);
|
||||
void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID);
|
||||
List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID);
|
||||
|
||||
void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID);
|
||||
ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID);
|
||||
|
||||
void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID);
|
||||
void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile);
|
||||
|
||||
/// <summary>
|
||||
/// Get information about a specific group to which the user belongs.
|
||||
/// </summary>
|
||||
/// <param name="RequestingAgentID">The agent requesting the information.</param>
|
||||
/// <param name="AgentID">The agent requested.</param>
|
||||
/// <param name="GroupID">The group requested.</param>
|
||||
/// <returns>
|
||||
/// If the user is a member of the group then the data structure is returned. If not, then null is returned.
|
||||
/// </returns>
|
||||
ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID);
|
||||
|
||||
/// <summary>
|
||||
/// Get information about the groups to which a user belongs.
|
||||
/// </summary>
|
||||
/// <param name="RequestingAgentID">The agent requesting the information.</param>
|
||||
/// <param name="AgentID">The agent requested.</param>
|
||||
/// <returns>
|
||||
/// Information about the groups to which the user belongs. If the user belongs to no groups then an empty
|
||||
/// list is returned.
|
||||
/// </returns>
|
||||
List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID);
|
||||
|
||||
bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID);
|
||||
GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID);
|
||||
List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID);
|
||||
|
||||
}
|
||||
|
||||
public class GroupInviteInfo
|
||||
{
|
||||
public UUID GroupID = UUID.Zero;
|
||||
public UUID RoleID = UUID.Zero;
|
||||
public string AgentID = string.Empty;
|
||||
public UUID InviteID = UUID.Zero;
|
||||
}
|
||||
|
||||
public class GroupNoticeInfo
|
||||
{
|
||||
public ExtendedGroupNoticeData noticeData = new ExtendedGroupNoticeData();
|
||||
public UUID GroupID = UUID.Zero;
|
||||
public string Message = string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
using OpenMetaverse;
|
||||
using Mono.Addins;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceLocalConnectorModule")]
|
||||
public class GroupsServiceLocalConnectorModule : ISharedRegionModule, IGroupsServicesConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private bool m_Enabled = false;
|
||||
private GroupsService m_GroupsService;
|
||||
private IUserManagement m_UserManagement;
|
||||
private List<Scene> m_Scenes;
|
||||
private ForeignImporter m_ForeignImporter;
|
||||
|
||||
#region constructors
|
||||
public GroupsServiceLocalConnectorModule()
|
||||
{
|
||||
}
|
||||
|
||||
public GroupsServiceLocalConnectorModule(IConfigSource config, IUserManagement uman)
|
||||
{
|
||||
Init(config);
|
||||
m_UserManagement = uman;
|
||||
m_ForeignImporter = new ForeignImporter(uman);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void Init(IConfigSource config)
|
||||
{
|
||||
m_GroupsService = new GroupsService(config);
|
||||
m_Scenes = new List<Scene>();
|
||||
}
|
||||
|
||||
#region ISharedRegionModule
|
||||
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
IConfig groupsConfig = config.Configs["Groups"];
|
||||
if (groupsConfig == null)
|
||||
return;
|
||||
|
||||
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|
||||
|| (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Init(config);
|
||||
m_Enabled = true;
|
||||
|
||||
m_log.DebugFormat("[Groups]: Initializing {0}", this.Name);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Groups Local Service Connector"; }
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_log.DebugFormat("[Groups]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Add(scene);
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Remove(scene);
|
||||
}
|
||||
|
||||
public void RegionLoaded(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
if (m_UserManagement == null)
|
||||
{
|
||||
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
|
||||
m_ForeignImporter = new ForeignImporter(m_UserManagement);
|
||||
}
|
||||
}
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IGroupsServicesConnector
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
|
||||
{
|
||||
m_log.DebugFormat("[Groups]: Creating group {0}", name);
|
||||
reason = string.Empty;
|
||||
return m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID,
|
||||
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
|
||||
}
|
||||
|
||||
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
|
||||
return true;
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
if (!GroupID.IsZero())
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID);
|
||||
else if (GroupName != null)
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupName);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
|
||||
{
|
||||
return m_GroupsService.FindGroups(RequestingAgentIDstr, search);
|
||||
}
|
||||
|
||||
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
List<ExtendedGroupMembersData> _members = m_GroupsService.GetGroupMembers(RequestingAgentID, GroupID);
|
||||
if (_members != null && _members.Count > 0)
|
||||
{
|
||||
List<GroupMembersData> members = _members.ConvertAll<GroupMembersData>(new Converter<ExtendedGroupMembersData, GroupMembersData>(m_ForeignImporter.ConvertGroupMembersData));
|
||||
return members;
|
||||
}
|
||||
|
||||
return new List<GroupMembersData>();
|
||||
}
|
||||
|
||||
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
|
||||
{
|
||||
return m_GroupsService.AddGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, out reason);
|
||||
}
|
||||
|
||||
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
|
||||
{
|
||||
return m_GroupsService.UpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers);
|
||||
}
|
||||
|
||||
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
|
||||
{
|
||||
m_GroupsService.RemoveGroupRole(RequestingAgentID, groupID, roleID);
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_GroupsService.GetGroupRoles(RequestingAgentID, GroupID);
|
||||
}
|
||||
|
||||
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
List<ExtendedGroupRoleMembersData> _rm = m_GroupsService.GetGroupRoleMembers(RequestingAgentID, GroupID);
|
||||
if (_rm != null && _rm.Count > 0)
|
||||
{
|
||||
List<GroupRoleMembersData> rm = _rm.ConvertAll<GroupRoleMembersData>(new Converter<ExtendedGroupRoleMembersData, GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData));
|
||||
return rm;
|
||||
}
|
||||
|
||||
return new List<GroupRoleMembersData>();
|
||||
|
||||
}
|
||||
|
||||
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
|
||||
{
|
||||
return m_GroupsService.AddAgentToGroup(RequestingAgentID, AgentID, GroupID, RoleID, token, out reason);
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
m_GroupsService.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
|
||||
}
|
||||
|
||||
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
|
||||
{
|
||||
return m_GroupsService.AddAgentToGroupInvite(RequestingAgentID, inviteID, groupID, roleID, agentID);
|
||||
}
|
||||
|
||||
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID);
|
||||
}
|
||||
|
||||
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID);
|
||||
}
|
||||
|
||||
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
m_GroupsService.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
m_GroupsService.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
return m_GroupsService.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID);
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
m_GroupsService.SetAgentActiveGroup(RequestingAgentID, AgentID, GroupID);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
return m_GroupsService.GetAgentActiveMembership(RequestingAgentID, AgentID);
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
m_GroupsService.SetAgentActiveGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
}
|
||||
|
||||
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
|
||||
{
|
||||
m_GroupsService.UpdateMembership(RequestingAgentID, AgentID, GroupID, AcceptNotices, ListInProfile);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
return m_GroupsService.GetAgentGroupMembership(RequestingAgentID, AgentID, GroupID);
|
||||
}
|
||||
|
||||
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
return m_GroupsService.GetAgentGroupMemberships(RequestingAgentID, AgentID);
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
|
||||
{
|
||||
return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message,
|
||||
hasAttachment, attType, attName, attItemID, attOwnerID);
|
||||
}
|
||||
|
||||
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
|
||||
{
|
||||
GroupNoticeInfo notice = m_GroupsService.GetGroupNotice(RequestingAgentID, noticeID);
|
||||
|
||||
//if (notice != null && notice.noticeData.HasAttachment && notice.noticeData.AttachmentOwnerID != null)
|
||||
//{
|
||||
// UUID userID = UUID.Zero;
|
||||
// string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
|
||||
// Util.ParseUniversalUserIdentifier(notice.noticeData.AttachmentOwnerID, out userID, out url, out first, out last, out tmp);
|
||||
// if (url != string.Empty)
|
||||
// m_UserManagement.AddUser(userID, first, last, url);
|
||||
//}
|
||||
|
||||
return notice;
|
||||
}
|
||||
|
||||
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_GroupsService.GetGroupNotices(RequestingAgentID, GroupID);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenSim.Addons.Groups")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("http://opensimulator.org")]
|
||||
[assembly: AssemblyProduct("OpenSim.Addons.Groups")]
|
||||
[assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("313d4865-d179-4735-9b5a-fe74885878b2")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
[assembly: Addin("OpenSim.Groups", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
|
||||
@@ -1,696 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.ServiceAuth;
|
||||
using OpenSim.Server.Base;
|
||||
|
||||
using OpenMetaverse;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class GroupsServiceRemoteConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private string m_ServerURI;
|
||||
private IServiceAuth m_Auth;
|
||||
private object m_Lock = new object();
|
||||
|
||||
public GroupsServiceRemoteConnector(IConfigSource config)
|
||||
{
|
||||
IConfig groupsConfig = config.Configs["Groups"];
|
||||
string url = groupsConfig.GetString("GroupsServerURI", string.Empty);
|
||||
if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
throw new Exception(string.Format("[Groups.RemoteConnector]: Malformed groups server URL {0}. Fix it or disable the Groups feature.", url));
|
||||
|
||||
m_ServerURI = url;
|
||||
if (!m_ServerURI.EndsWith("/"))
|
||||
m_ServerURI += "/";
|
||||
|
||||
/// This is from BaseServiceConnector
|
||||
string authType = Util.GetConfigVarFromSections<string>(config, "AuthType", new string[] { "Network", "Groups" }, "None");
|
||||
|
||||
switch (authType)
|
||||
{
|
||||
case "BasicHttpAuthentication":
|
||||
m_Auth = new BasicHttpAuthentication(config, "Groups");
|
||||
break;
|
||||
}
|
||||
///
|
||||
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Groups server at {0}, authentication {1}",
|
||||
m_ServerURI, (m_Auth == null ? "None" : m_Auth.GetType().ToString()));
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord CreateGroup(string RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
ExtendedGroupRecord rec = new ExtendedGroupRecord();
|
||||
rec.AllowPublish = allowPublish;
|
||||
rec.Charter = charter;
|
||||
rec.FounderID = founderID;
|
||||
rec.GroupName = name;
|
||||
rec.GroupPicture = insigniaID;
|
||||
rec.MaturePublish = maturePublish;
|
||||
rec.MembershipFee = membershipFee;
|
||||
rec.OpenEnrollment = openEnrollment;
|
||||
rec.ShowInList = showInList;
|
||||
|
||||
Dictionary<string, object> sendData = GroupsDataUtils.GroupRecord(rec);
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "ADD";
|
||||
Dictionary<string, object> ret = MakeRequest("PUTGROUP", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
{
|
||||
reason = ret["REASON"].ToString();
|
||||
return null;
|
||||
}
|
||||
|
||||
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
|
||||
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish)
|
||||
{
|
||||
ExtendedGroupRecord rec = new ExtendedGroupRecord();
|
||||
rec.AllowPublish = allowPublish;
|
||||
rec.Charter = charter;
|
||||
rec.GroupPicture = insigniaID;
|
||||
rec.MaturePublish = maturePublish;
|
||||
rec.GroupID = groupID;
|
||||
rec.MembershipFee = membershipFee;
|
||||
rec.OpenEnrollment = openEnrollment;
|
||||
rec.ShowInList = showInList;
|
||||
|
||||
Dictionary<string, object> sendData = GroupsDataUtils.GroupRecord(rec);
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "UPDATE";
|
||||
Dictionary<string, object> ret = MakeRequest("PUTGROUP", sendData);
|
||||
|
||||
if (ret == null || (ret != null && (!ret.ContainsKey("RESULT") || ret["RESULT"].ToString() == "NULL")))
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
if (!GroupID.IsZero())
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
if (!string.IsNullOrEmpty(GroupName))
|
||||
sendData["Name"] = GroupsDataUtils.Sanitize(GroupName);
|
||||
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("GETGROUP", sendData);
|
||||
|
||||
if (ret == null || (ret != null && (!ret.ContainsKey("RESULT") || ret["RESULT"].ToString() == "NULL")))
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupRecord((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string query)
|
||||
{
|
||||
List<DirGroupsReplyData> hits = new List<DirGroupsReplyData>();
|
||||
if (string.IsNullOrEmpty(query))
|
||||
return hits;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["Query"] = query;
|
||||
sendData["RequestingAgentID"] = RequestingAgentIDstr;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("FINDGROUPS", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return hits;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return hits;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return hits;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
DirGroupsReplyData m = GroupsDataUtils.DirGroupsReplyData((Dictionary<string, object>)v);
|
||||
hits.Add(m);
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
public GroupMembershipData AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string,object>();
|
||||
sendData["AgentID"] = AgentID;
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RoleID"] = RoleID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["AccessToken"] = token;
|
||||
Dictionary<string, object> ret = MakeRequest("ADDAGENTTOGROUP", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
{
|
||||
reason = ret["REASON"].ToString();
|
||||
return null;
|
||||
}
|
||||
|
||||
return GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)ret["RESULT"]);
|
||||
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID;
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
MakeRequest("REMOVEAGENTFROMGROUP", sendData);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetMembership(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID;
|
||||
if (!GroupID.IsZero())
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETMEMBERSHIP", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public List<GroupMembershipData> GetMemberships(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
List<GroupMembershipData> memberships = new List<GroupMembershipData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID;
|
||||
sendData["ALL"] = "true";
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETMEMBERSHIP", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return memberships;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return memberships;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return memberships;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
GroupMembershipData m = GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)v);
|
||||
memberships.Add(m);
|
||||
}
|
||||
|
||||
return memberships;
|
||||
}
|
||||
|
||||
public List<ExtendedGroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
List<ExtendedGroupMembersData> members = new List<ExtendedGroupMembersData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("GETGROUPMEMBERS", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return members;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return members;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return members;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
ExtendedGroupMembersData m = GroupsDataUtils.GroupMembersData((Dictionary<string, object>)v);
|
||||
members.Add(m);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["RoleID"] = roleID.ToString();
|
||||
sendData["Name"] = GroupsDataUtils.Sanitize(name);
|
||||
sendData["Description"] = GroupsDataUtils.Sanitize(description);
|
||||
sendData["Title"] = GroupsDataUtils.Sanitize(title);
|
||||
sendData["Powers"] = powers.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "ADD";
|
||||
Dictionary<string, object> ret = MakeRequest("PUTROLE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
{
|
||||
reason = ret["REASON"].ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["RoleID"] = roleID.ToString();
|
||||
sendData["Name"] = GroupsDataUtils.Sanitize(name);
|
||||
sendData["Description"] = GroupsDataUtils.Sanitize(description);
|
||||
sendData["Title"] = GroupsDataUtils.Sanitize(title);
|
||||
sendData["Powers"] = powers.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "UPDATE";
|
||||
Dictionary<string, object> ret = MakeRequest("PUTROLE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["RoleID"] = roleID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
MakeRequest("REMOVEROLE", sendData);
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
List<GroupRolesData> roles = new List<GroupRolesData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETGROUPROLES", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return roles;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return roles;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return roles;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary<string, object>)v);
|
||||
roles.Add(m);
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public List<ExtendedGroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
List<ExtendedGroupRoleMembersData> rmembers = new List<ExtendedGroupRoleMembersData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETROLEMEMBERS", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return rmembers;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return rmembers;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return rmembers;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
ExtendedGroupRoleMembersData m = GroupsDataUtils.GroupRoleMembersData((Dictionary<string, object>)v);
|
||||
rmembers.Add(m);
|
||||
}
|
||||
|
||||
return rmembers;
|
||||
}
|
||||
|
||||
public bool AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RoleID"] = RoleID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "ADD";
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("AGENTROLE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RoleID"] = RoleID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "DELETE";
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("AGENTROLE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
List<GroupRolesData> roles = new List<GroupRolesData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETAGENTROLES", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return roles;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return roles;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return roles;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
GroupRolesData m = GroupsDataUtils.GroupRolesData((Dictionary<string, object>)v);
|
||||
roles.Add(m);
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public GroupMembershipData SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "GROUP";
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("SETACTIVE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupMembershipData((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RoleID"] = RoleID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "ROLE";
|
||||
|
||||
MakeRequest("SETACTIVE", sendData);
|
||||
}
|
||||
|
||||
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["AgentID"] = AgentID.ToString();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["AcceptNotices"] = AcceptNotices.ToString();
|
||||
sendData["ListInProfile"] = ListInProfile.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
MakeRequest("UPDATEMEMBERSHIP", sendData);
|
||||
}
|
||||
|
||||
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["InviteID"] = inviteID.ToString();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["RoleID"] = roleID.ToString();
|
||||
sendData["AgentID"] = agentID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "ADD";
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("INVITE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true") // it may return "NULL"
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["InviteID"] = inviteID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "GET";
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("INVITE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupInviteInfo((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["InviteID"] = inviteID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
sendData["OP"] = "DELETE";
|
||||
|
||||
MakeRequest("INVITE", sendData);
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = groupID.ToString();
|
||||
sendData["NoticeID"] = noticeID.ToString();
|
||||
sendData["FromName"] = GroupsDataUtils.Sanitize(fromName);
|
||||
sendData["Subject"] = GroupsDataUtils.Sanitize(subject);
|
||||
sendData["Message"] = GroupsDataUtils.Sanitize(message);
|
||||
sendData["HasAttachment"] = hasAttachment.ToString();
|
||||
if (hasAttachment)
|
||||
{
|
||||
sendData["AttachmentType"] = attType.ToString();
|
||||
sendData["AttachmentName"] = attName.ToString();
|
||||
sendData["AttachmentItemID"] = attItemID.ToString();
|
||||
sendData["AttachmentOwnerID"] = attOwnerID;
|
||||
}
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("ADDNOTICE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return false;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return false;
|
||||
|
||||
if (ret["RESULT"].ToString().ToLower() != "true")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["NoticeID"] = noticeID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("GETNOTICES", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return null;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return null;
|
||||
|
||||
return GroupsDataUtils.GroupNoticeInfo((Dictionary<string, object>)ret["RESULT"]);
|
||||
}
|
||||
|
||||
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
List<ExtendedGroupNoticeData> notices = new List<ExtendedGroupNoticeData>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["GroupID"] = GroupID.ToString();
|
||||
sendData["RequestingAgentID"] = RequestingAgentID;
|
||||
Dictionary<string, object> ret = MakeRequest("GETNOTICES", sendData);
|
||||
|
||||
if (ret == null)
|
||||
return notices;
|
||||
|
||||
if (!ret.ContainsKey("RESULT"))
|
||||
return notices;
|
||||
|
||||
if (ret["RESULT"].ToString() == "NULL")
|
||||
return notices;
|
||||
|
||||
foreach (object v in ((Dictionary<string, object>)ret["RESULT"]).Values)
|
||||
{
|
||||
ExtendedGroupNoticeData m = GroupsDataUtils.GroupNoticeData((Dictionary<string, object>)v);
|
||||
notices.Add(m);
|
||||
}
|
||||
|
||||
return notices;
|
||||
}
|
||||
|
||||
#region Make Request
|
||||
|
||||
private Dictionary<string, object> MakeRequest(string method, Dictionary<string, object> sendData)
|
||||
{
|
||||
sendData["METHOD"] = method;
|
||||
|
||||
string reply = string.Empty;
|
||||
lock (m_Lock)
|
||||
reply = SynchronousRestFormsRequester.MakeRequest("POST",
|
||||
m_ServerURI + "groups",
|
||||
ServerUtils.BuildQueryString(sendData),
|
||||
m_Auth);
|
||||
|
||||
if (reply.Length == 0)
|
||||
return null;
|
||||
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(
|
||||
reply);
|
||||
|
||||
return replyData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
using OpenMetaverse;
|
||||
using Mono.Addins;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsServiceRemoteConnectorModule")]
|
||||
public class GroupsServiceRemoteConnectorModule : ISharedRegionModule, IGroupsServicesConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private bool m_Enabled = false;
|
||||
private GroupsServiceRemoteConnector m_GroupsService;
|
||||
private IUserManagement m_UserManagement;
|
||||
private List<Scene> m_Scenes;
|
||||
|
||||
private RemoteConnectorCacheWrapper m_CacheWrapper;
|
||||
|
||||
#region constructors
|
||||
public GroupsServiceRemoteConnectorModule()
|
||||
{
|
||||
}
|
||||
|
||||
public GroupsServiceRemoteConnectorModule(IConfigSource config, IUserManagement uman)
|
||||
{
|
||||
Init(config);
|
||||
m_UserManagement = uman;
|
||||
m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void Init(IConfigSource config)
|
||||
{
|
||||
m_GroupsService = new GroupsServiceRemoteConnector(config);
|
||||
m_Scenes = new List<Scene>();
|
||||
|
||||
}
|
||||
|
||||
#region ISharedRegionModule
|
||||
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
IConfig groupsConfig = config.Configs["Groups"];
|
||||
if (groupsConfig == null)
|
||||
return;
|
||||
|
||||
if ((groupsConfig.GetBoolean("Enabled", false) == false)
|
||||
|| (groupsConfig.GetString("ServicesConnectorModule", string.Empty) != Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Init(config);
|
||||
|
||||
m_Enabled = true;
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Initializing {0}", this.Name);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Groups Remote Service Connector"; }
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Registering {0} with {1}", this.Name, scene.RegionInfo.RegionName);
|
||||
scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Add(scene);
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
|
||||
m_Scenes.Remove(scene);
|
||||
}
|
||||
|
||||
public void RegionLoaded(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
if (m_UserManagement == null)
|
||||
{
|
||||
m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
|
||||
m_CacheWrapper = new RemoteConnectorCacheWrapper(m_UserManagement);
|
||||
}
|
||||
}
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IGroupsServicesConnector
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment,
|
||||
bool allowPublish, bool maturePublish, UUID founderID, out string reason)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Creating group {0}", name);
|
||||
string r = string.Empty;
|
||||
|
||||
UUID groupID = m_CacheWrapper.CreateGroup(RequestingAgentID, delegate
|
||||
{
|
||||
return m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID,
|
||||
membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out r);
|
||||
});
|
||||
|
||||
reason = r;
|
||||
return groupID;
|
||||
}
|
||||
|
||||
public bool UpdateGroup(string RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee,
|
||||
bool openEnrollment, bool allowPublish, bool maturePublish, out string reason)
|
||||
{
|
||||
string r = string.Empty;
|
||||
|
||||
bool success = m_CacheWrapper.UpdateGroup(groupID, delegate
|
||||
{
|
||||
return m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
|
||||
});
|
||||
|
||||
reason = r;
|
||||
return success;
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName)
|
||||
{
|
||||
if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
return null;
|
||||
|
||||
return m_CacheWrapper.GetGroupRecord(RequestingAgentID,GroupID,GroupName, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
|
||||
});
|
||||
}
|
||||
|
||||
public List<DirGroupsReplyData> FindGroups(string RequestingAgentIDstr, string search)
|
||||
{
|
||||
// TODO!
|
||||
return m_GroupsService.FindGroups(RequestingAgentIDstr, search);
|
||||
}
|
||||
|
||||
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, string token, out string reason)
|
||||
{
|
||||
string agentFullID = AgentID;
|
||||
m_log.DebugFormat("[Groups.RemoteConnector]: Add agent {0} to group {1}", agentFullID, GroupID);
|
||||
string r = string.Empty;
|
||||
|
||||
bool success = m_CacheWrapper.AddAgentToGroup(RequestingAgentID, AgentID, GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.AddAgentToGroup(RequestingAgentID, agentFullID, GroupID, RoleID, token, out r);
|
||||
});
|
||||
|
||||
reason = r;
|
||||
return success;
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
m_CacheWrapper.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID, delegate
|
||||
{
|
||||
m_GroupsService.RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroup(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
m_CacheWrapper.SetAgentActiveGroup(AgentID, delegate
|
||||
{
|
||||
return m_GroupsService.SetAgentActiveGroup(RequestingAgentID, AgentID, GroupID);
|
||||
});
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentActiveMembership(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
return m_CacheWrapper.GetAgentActiveMembership(AgentID, delegate
|
||||
{
|
||||
return m_GroupsService.GetMembership(RequestingAgentID, AgentID, UUID.Zero);
|
||||
});
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentGroupMembership(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
return m_CacheWrapper.GetAgentGroupMembership(AgentID, GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.GetMembership(RequestingAgentID, AgentID, GroupID);
|
||||
});
|
||||
}
|
||||
|
||||
public List<GroupMembershipData> GetAgentGroupMemberships(string RequestingAgentID, string AgentID)
|
||||
{
|
||||
return m_CacheWrapper.GetAgentGroupMemberships(AgentID, delegate
|
||||
{
|
||||
return m_GroupsService.GetMemberships(RequestingAgentID, AgentID);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupMembers(RequestingAgentID, GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupMembers(RequestingAgentID, GroupID);
|
||||
});
|
||||
}
|
||||
|
||||
public bool AddGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, out string reason)
|
||||
{
|
||||
string r = string.Empty;
|
||||
bool success = m_CacheWrapper.AddGroupRole(groupID, roleID, description, name, powers, title, delegate
|
||||
{
|
||||
return m_GroupsService.AddGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers, out r);
|
||||
});
|
||||
|
||||
reason = r;
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool UpdateGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers)
|
||||
{
|
||||
return m_CacheWrapper.UpdateGroupRole(groupID, roleID, name, description, title, powers, delegate
|
||||
{
|
||||
return m_GroupsService.UpdateGroupRole(RequestingAgentID, groupID, roleID, name, description, title, powers);
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID)
|
||||
{
|
||||
m_CacheWrapper.RemoveGroupRole(RequestingAgentID, groupID, roleID, delegate
|
||||
{
|
||||
m_GroupsService.RemoveGroupRole(RequestingAgentID, groupID, roleID);
|
||||
});
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupRoles(RequestingAgentID, GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupRoles(RequestingAgentID, GroupID);
|
||||
});
|
||||
}
|
||||
|
||||
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupRoleMembers(RequestingAgentID, GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupRoleMembers(RequestingAgentID, GroupID);
|
||||
});
|
||||
}
|
||||
|
||||
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
m_CacheWrapper.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID, delegate
|
||||
{
|
||||
return m_GroupsService.AddAgentToGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
m_CacheWrapper.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID, delegate
|
||||
{
|
||||
return m_GroupsService.RemoveAgentFromGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
});
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID)
|
||||
{
|
||||
return m_CacheWrapper.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.GetAgentGroupRoles(RequestingAgentID, AgentID, GroupID); ;
|
||||
});
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID)
|
||||
{
|
||||
m_CacheWrapper.SetAgentActiveGroupRole(AgentID, GroupID, delegate
|
||||
{
|
||||
m_GroupsService.SetAgentActiveGroupRole(RequestingAgentID, AgentID, GroupID, RoleID);
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateMembership(string RequestingAgentID, string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
|
||||
{
|
||||
m_CacheWrapper.UpdateMembership(AgentID, GroupID, AcceptNotices, ListInProfile, delegate
|
||||
{
|
||||
m_GroupsService.UpdateMembership(RequestingAgentID, AgentID, GroupID, AcceptNotices, ListInProfile);
|
||||
});
|
||||
}
|
||||
|
||||
public bool AddAgentToGroupInvite(string RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, string agentID)
|
||||
{
|
||||
return m_GroupsService.AddAgentToGroupInvite(RequestingAgentID, inviteID, groupID, roleID, agentID);
|
||||
}
|
||||
|
||||
public GroupInviteInfo GetAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
return m_GroupsService.GetAgentToGroupInvite(RequestingAgentID, inviteID);
|
||||
}
|
||||
|
||||
public void RemoveAgentToGroupInvite(string RequestingAgentID, UUID inviteID)
|
||||
{
|
||||
m_GroupsService.RemoveAgentToGroupInvite(RequestingAgentID, inviteID);
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
|
||||
{
|
||||
GroupNoticeInfo notice = new GroupNoticeInfo();
|
||||
notice.GroupID = groupID;
|
||||
notice.Message = message;
|
||||
notice.noticeData = new ExtendedGroupNoticeData();
|
||||
notice.noticeData.AttachmentItemID = attItemID;
|
||||
notice.noticeData.AttachmentName = attName;
|
||||
notice.noticeData.AttachmentOwnerID = attOwnerID.ToString();
|
||||
notice.noticeData.AttachmentType = attType;
|
||||
notice.noticeData.FromName = fromName;
|
||||
notice.noticeData.HasAttachment = hasAttachment;
|
||||
notice.noticeData.NoticeID = noticeID;
|
||||
notice.noticeData.Subject = subject;
|
||||
notice.noticeData.Timestamp = (uint)Util.UnixTimeSinceEpoch();
|
||||
|
||||
return m_CacheWrapper.AddGroupNotice(groupID, noticeID, notice, delegate
|
||||
{
|
||||
return m_GroupsService.AddGroupNotice(RequestingAgentID, groupID, noticeID, fromName, subject, message,
|
||||
hasAttachment, attType, attName, attItemID, attOwnerID);
|
||||
});
|
||||
}
|
||||
|
||||
public GroupNoticeInfo GetGroupNotice(string RequestingAgentID, UUID noticeID)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupNotice(noticeID, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupNotice(RequestingAgentID, noticeID);
|
||||
});
|
||||
}
|
||||
|
||||
public List<ExtendedGroupNoticeData> GetGroupNotices(string RequestingAgentID, UUID GroupID)
|
||||
{
|
||||
return m_CacheWrapper.GetGroupNotices(GroupID, delegate
|
||||
{
|
||||
return m_GroupsService.GetGroupNotices(RequestingAgentID, GroupID);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,817 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Framework.ServiceAuth;
|
||||
using OpenSim.Server.Handlers.Base;
|
||||
using log4net;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class GroupsServiceRobustConnector : ServiceConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private GroupsService m_GroupsService;
|
||||
private string m_ConfigName = "Groups";
|
||||
|
||||
public GroupsServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
base(config, server, configName)
|
||||
{
|
||||
string key = string.Empty;
|
||||
if (configName != String.Empty)
|
||||
m_ConfigName = configName;
|
||||
|
||||
m_log.DebugFormat("[Groups.RobustConnector]: Starting with config name {0}", m_ConfigName);
|
||||
|
||||
IConfig groupsConfig = config.Configs[m_ConfigName];
|
||||
if (groupsConfig != null)
|
||||
{
|
||||
key = groupsConfig.GetString("SecretKey", string.Empty);
|
||||
m_log.DebugFormat("[Groups.RobustConnector]: Starting with secret key {0}", key);
|
||||
}
|
||||
// else
|
||||
// m_log.DebugFormat("[Groups.RobustConnector]: Unable to find {0} section in configuration", m_ConfigName);
|
||||
|
||||
m_GroupsService = new GroupsService(config);
|
||||
|
||||
IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName);
|
||||
|
||||
server.AddStreamHandler(new GroupsServicePostHandler(m_GroupsService, auth));
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupsServicePostHandler : BaseStreamHandler
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private GroupsService m_GroupsService;
|
||||
|
||||
public GroupsServicePostHandler(GroupsService service, IServiceAuth auth) :
|
||||
base("POST", "/groups", auth)
|
||||
{
|
||||
m_GroupsService = service;
|
||||
}
|
||||
|
||||
protected override byte[] ProcessRequest(string path, Stream requestData,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
string body;
|
||||
using(StreamReader sr = new StreamReader(requestData))
|
||||
body = sr.ReadToEnd();
|
||||
|
||||
body = body.Trim();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: query String: {0}", body);
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> request =
|
||||
ServerUtils.ParseQueryString(body);
|
||||
|
||||
if (!request.ContainsKey("METHOD"))
|
||||
return FailureResult();
|
||||
|
||||
string method = request["METHOD"].ToString();
|
||||
request.Remove("METHOD");
|
||||
|
||||
// m_log.DebugFormat("[Groups.Handler]: {0}", method);
|
||||
switch (method)
|
||||
{
|
||||
case "PUTGROUP":
|
||||
return HandleAddOrUpdateGroup(request);
|
||||
case "GETGROUP":
|
||||
return HandleGetGroup(request);
|
||||
case "ADDAGENTTOGROUP":
|
||||
return HandleAddAgentToGroup(request);
|
||||
case "REMOVEAGENTFROMGROUP":
|
||||
return HandleRemoveAgentFromGroup(request);
|
||||
case "GETMEMBERSHIP":
|
||||
return HandleGetMembership(request);
|
||||
case "GETGROUPMEMBERS":
|
||||
return HandleGetGroupMembers(request);
|
||||
case "PUTROLE":
|
||||
return HandlePutRole(request);
|
||||
case "REMOVEROLE":
|
||||
return HandleRemoveRole(request);
|
||||
case "GETGROUPROLES":
|
||||
return HandleGetGroupRoles(request);
|
||||
case "GETROLEMEMBERS":
|
||||
return HandleGetRoleMembers(request);
|
||||
case "AGENTROLE":
|
||||
return HandleAgentRole(request);
|
||||
case "GETAGENTROLES":
|
||||
return HandleGetAgentRoles(request);
|
||||
case "SETACTIVE":
|
||||
return HandleSetActive(request);
|
||||
case "UPDATEMEMBERSHIP":
|
||||
return HandleUpdateMembership(request);
|
||||
case "INVITE":
|
||||
return HandleInvite(request);
|
||||
case "ADDNOTICE":
|
||||
return HandleAddNotice(request);
|
||||
case "GETNOTICES":
|
||||
return HandleGetNotices(request);
|
||||
case "FINDGROUPS":
|
||||
return HandleFindGroups(request);
|
||||
}
|
||||
m_log.DebugFormat("[GROUPS HANDLER]: unknown method request: {0}", method);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error(string.Format("[GROUPS HANDLER]: Exception {0} ", e.Message), e);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
}
|
||||
|
||||
byte[] HandleAddOrUpdateGroup(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
ExtendedGroupRecord grec = GroupsDataUtils.GroupRecord(request);
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("OP"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
string RequestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string reason = string.Empty;
|
||||
string op = request["OP"].ToString();
|
||||
if (op == "ADD")
|
||||
{
|
||||
grec.GroupID = m_GroupsService.CreateGroup(RequestingAgentID, grec.GroupName, grec.Charter, grec.ShowInList, grec.GroupPicture, grec.MembershipFee,
|
||||
grec.OpenEnrollment, grec.AllowPublish, grec.MaturePublish, grec.FounderID, out reason);
|
||||
|
||||
}
|
||||
else if (op == "UPDATE")
|
||||
{
|
||||
m_GroupsService.UpdateGroup(RequestingAgentID, grec.GroupID, grec.Charter, grec.ShowInList, grec.GroupPicture, grec.MembershipFee,
|
||||
grec.OpenEnrollment, grec.AllowPublish, grec.MaturePublish);
|
||||
|
||||
}
|
||||
|
||||
if (!grec.GroupID.IsZero())
|
||||
{
|
||||
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, grec.GroupID);
|
||||
if (grec == null)
|
||||
NullResult(result, "Internal Error");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupRecord(grec);
|
||||
}
|
||||
else
|
||||
NullResult(result, reason);
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetGroup(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
string RequestingAgentID = request["RequestingAgentID"].ToString();
|
||||
ExtendedGroupRecord grec = null;
|
||||
if (request.ContainsKey("GroupID"))
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, groupID);
|
||||
}
|
||||
else if (request.ContainsKey("Name"))
|
||||
{
|
||||
string name = request["Name"].ToString();
|
||||
grec = m_GroupsService.GetGroupRecord(RequestingAgentID, name);
|
||||
}
|
||||
|
||||
if (grec == null)
|
||||
NullResult(result, "Group not found");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupRecord(grec);
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleAddAgentToGroup(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") ||
|
||||
!request.ContainsKey("GroupID") || !request.ContainsKey("RoleID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
UUID roleID = new UUID(request["RoleID"].ToString());
|
||||
string agentID = request["AgentID"].ToString();
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
string token = string.Empty;
|
||||
string reason = string.Empty;
|
||||
|
||||
if (request.ContainsKey("AccessToken"))
|
||||
token = request["AccessToken"].ToString();
|
||||
|
||||
if (!m_GroupsService.AddAgentToGroup(requestingAgentID, agentID, groupID, roleID, token, out reason))
|
||||
NullResult(result, reason);
|
||||
else
|
||||
{
|
||||
GroupMembershipData membership = m_GroupsService.GetAgentGroupMembership(requestingAgentID, agentID, groupID);
|
||||
if (membership == null)
|
||||
NullResult(result, "Internal error");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupMembershipData((ExtendedGroupMembershipData)membership);
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleRemoveAgentFromGroup(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || !request.ContainsKey("GroupID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string agentID = request["AgentID"].ToString();
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
|
||||
if (!m_GroupsService.RemoveAgentFromGroup(requestingAgentID, agentID, groupID))
|
||||
NullResult(result, string.Format("Insufficient permissions. {0}", agentID));
|
||||
else
|
||||
result["RESULT"] = "true";
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
byte[] HandleGetMembership(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
string agentID = request["AgentID"].ToString();
|
||||
UUID groupID = UUID.Zero;
|
||||
if (request.ContainsKey("GroupID"))
|
||||
groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
bool all = request.ContainsKey("ALL");
|
||||
|
||||
if (!all)
|
||||
{
|
||||
ExtendedGroupMembershipData membership = null;
|
||||
if (groupID.IsZero())
|
||||
{
|
||||
membership = m_GroupsService.GetAgentActiveMembership(requestingAgentID, agentID);
|
||||
}
|
||||
else
|
||||
{
|
||||
membership = m_GroupsService.GetAgentGroupMembership(requestingAgentID, agentID, groupID);
|
||||
}
|
||||
|
||||
if (membership == null)
|
||||
NullResult(result, "No such membership");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupMembershipData(membership);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<GroupMembershipData> memberships = m_GroupsService.GetAgentGroupMemberships(requestingAgentID, agentID);
|
||||
if (memberships == null || (memberships != null && memberships.Count == 0))
|
||||
{
|
||||
NullResult(result, "No memberships");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (GroupMembershipData m in memberships)
|
||||
dict["m-" + i++] = GroupsDataUtils.GroupMembershipData((ExtendedGroupMembershipData)m);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetGroupMembers(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
|
||||
List<ExtendedGroupMembersData> members = m_GroupsService.GetGroupMembers(requestingAgentID, groupID);
|
||||
if (members == null || (members != null && members.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (ExtendedGroupMembersData m in members)
|
||||
{
|
||||
dict["m-" + i++] = GroupsDataUtils.GroupMembersData(m);
|
||||
}
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandlePutRole(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID") ||
|
||||
!request.ContainsKey("Name") || !request.ContainsKey("Description") || !request.ContainsKey("Title") ||
|
||||
!request.ContainsKey("Powers") || !request.ContainsKey("OP"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
string op = request["OP"].ToString();
|
||||
string reason = string.Empty;
|
||||
|
||||
bool success = false;
|
||||
if (op == "ADD")
|
||||
success = m_GroupsService.AddGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
|
||||
new UUID(request["RoleID"].ToString()), request["Name"].ToString(), request["Description"].ToString(),
|
||||
request["Title"].ToString(), UInt64.Parse(request["Powers"].ToString()), out reason);
|
||||
|
||||
else if (op == "UPDATE")
|
||||
success = m_GroupsService.UpdateGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
|
||||
new UUID(request["RoleID"].ToString()), request["Name"].ToString(), request["Description"].ToString(),
|
||||
request["Title"].ToString(), UInt64.Parse(request["Powers"].ToString()));
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleRemoveRole(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
m_GroupsService.RemoveGroupRole(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
|
||||
new UUID(request["RoleID"].ToString()));
|
||||
result["RESULT"] = "true";
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
byte[] HandleGetGroupRoles(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
|
||||
List<GroupRolesData> roles = m_GroupsService.GetGroupRoles(requestingAgentID, groupID);
|
||||
if (roles == null || (roles != null && roles.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (GroupRolesData r in roles)
|
||||
dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetRoleMembers(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
|
||||
List<ExtendedGroupRoleMembersData> rmembers = m_GroupsService.GetGroupRoleMembers(requestingAgentID, groupID);
|
||||
if (rmembers == null || (rmembers != null && rmembers.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (ExtendedGroupRoleMembersData rm in rmembers)
|
||||
dict["rm-" + i++] = GroupsDataUtils.GroupRoleMembersData(rm);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleAgentRole(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("RoleID") ||
|
||||
!request.ContainsKey("AgentID") || !request.ContainsKey("OP"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
string op = request["OP"].ToString();
|
||||
|
||||
bool success = false;
|
||||
if (op == "ADD")
|
||||
success = m_GroupsService.AddAgentToGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
|
||||
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
|
||||
|
||||
else if (op == "DELETE")
|
||||
success = m_GroupsService.RemoveAgentFromGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
|
||||
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetAgentRoles(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("AgentID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID groupID = new UUID(request["GroupID"].ToString());
|
||||
string agentID = request["AgentID"].ToString();
|
||||
string requestingAgentID = request["RequestingAgentID"].ToString();
|
||||
|
||||
List<GroupRolesData> roles = m_GroupsService.GetAgentGroupRoles(requestingAgentID, agentID, groupID);
|
||||
if (roles == null || (roles != null && roles.Count == 0))
|
||||
{
|
||||
NullResult(result, "No members");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (GroupRolesData r in roles)
|
||||
dict["r-" + i++] = GroupsDataUtils.GroupRolesData(r);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleSetActive(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") ||
|
||||
!request.ContainsKey("AgentID") || !request.ContainsKey("OP"))
|
||||
{
|
||||
NullResult(result, "Bad network data");
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
else
|
||||
{
|
||||
string op = request["OP"].ToString();
|
||||
|
||||
if (op == "GROUP")
|
||||
{
|
||||
ExtendedGroupMembershipData group = m_GroupsService.SetAgentActiveGroup(request["RequestingAgentID"].ToString(),
|
||||
request["AgentID"].ToString(), new UUID(request["GroupID"].ToString()));
|
||||
|
||||
if (group == null)
|
||||
NullResult(result, "Internal error");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupMembershipData(group);
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
|
||||
}
|
||||
else if (op == "ROLE" && request.ContainsKey("RoleID"))
|
||||
{
|
||||
m_GroupsService.SetAgentActiveGroupRole(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(),
|
||||
new UUID(request["GroupID"].ToString()), new UUID(request["RoleID"].ToString()));
|
||||
result["RESULT"] = "true";
|
||||
}
|
||||
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
byte[] HandleUpdateMembership(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("AgentID") || !request.ContainsKey("GroupID") ||
|
||||
!request.ContainsKey("AcceptNotices") || !request.ContainsKey("ListInProfile"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
m_GroupsService.UpdateMembership(request["RequestingAgentID"].ToString(), request["AgentID"].ToString(), new UUID(request["GroupID"].ToString()),
|
||||
bool.Parse(request["AcceptNotices"].ToString()), bool.Parse(request["ListInProfile"].ToString()));
|
||||
|
||||
result["RESULT"] = "true";
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
byte[] HandleInvite(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("InviteID"))
|
||||
{
|
||||
NullResult(result, "Bad network data");
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
else
|
||||
{
|
||||
string op = request["OP"].ToString();
|
||||
|
||||
if (op == "ADD" && request.ContainsKey("GroupID") && request.ContainsKey("RoleID") && request.ContainsKey("AgentID"))
|
||||
{
|
||||
bool success = m_GroupsService.AddAgentToGroupInvite(request["RequestingAgentID"].ToString(),
|
||||
new UUID(request["InviteID"].ToString()), new UUID(request["GroupID"].ToString()),
|
||||
new UUID(request["RoleID"].ToString()), request["AgentID"].ToString());
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
|
||||
}
|
||||
else if (op == "DELETE")
|
||||
{
|
||||
m_GroupsService.RemoveAgentToGroupInvite(request["RequestingAgentID"].ToString(), new UUID(request["InviteID"].ToString()));
|
||||
result["RESULT"] = "true";
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
else if (op == "GET")
|
||||
{
|
||||
GroupInviteInfo invite = m_GroupsService.GetAgentToGroupInvite(request["RequestingAgentID"].ToString(),
|
||||
new UUID(request["InviteID"].ToString()));
|
||||
|
||||
if (invite != null)
|
||||
result["RESULT"] = GroupsDataUtils.GroupInviteInfo(invite);
|
||||
else
|
||||
result["RESULT"] = "NULL";
|
||||
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
NullResult(result, "Bad OP in request");
|
||||
return Util.UTF8NoBomEncoding.GetBytes(ServerUtils.BuildXmlResponse(result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
byte[] HandleAddNotice(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("GroupID") || !request.ContainsKey("NoticeID") ||
|
||||
!request.ContainsKey("FromName") || !request.ContainsKey("Subject") || !request.ContainsKey("Message") ||
|
||||
!request.ContainsKey("HasAttachment"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else
|
||||
{
|
||||
|
||||
bool hasAtt = bool.Parse(request["HasAttachment"].ToString());
|
||||
byte attType = 0;
|
||||
string attName = string.Empty;
|
||||
string attOwner = string.Empty;
|
||||
UUID attItem = UUID.Zero;
|
||||
if (request.ContainsKey("AttachmentType"))
|
||||
attType = byte.Parse(request["AttachmentType"].ToString());
|
||||
if (request.ContainsKey("AttachmentName"))
|
||||
attName = request["AttachmentName"].ToString();
|
||||
if (request.ContainsKey("AttachmentItemID"))
|
||||
attItem = new UUID(request["AttachmentItemID"].ToString());
|
||||
if (request.ContainsKey("AttachmentOwnerID"))
|
||||
attOwner = request["AttachmentOwnerID"].ToString();
|
||||
|
||||
bool success = m_GroupsService.AddGroupNotice(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()),
|
||||
new UUID(request["NoticeID"].ToString()), request["FromName"].ToString(), request["Subject"].ToString(),
|
||||
request["Message"].ToString(), hasAtt, attType, attName, attItem, attOwner);
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGetNotices(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
else if (request.ContainsKey("NoticeID")) // just one
|
||||
{
|
||||
GroupNoticeInfo notice = m_GroupsService.GetGroupNotice(request["RequestingAgentID"].ToString(), new UUID(request["NoticeID"].ToString()));
|
||||
|
||||
if (notice == null)
|
||||
NullResult(result, "NO such notice");
|
||||
else
|
||||
result["RESULT"] = GroupsDataUtils.GroupNoticeInfo(notice);
|
||||
|
||||
}
|
||||
else if (request.ContainsKey("GroupID")) // all notices for group
|
||||
{
|
||||
List<ExtendedGroupNoticeData> notices = m_GroupsService.GetGroupNotices(request["RequestingAgentID"].ToString(), new UUID(request["GroupID"].ToString()));
|
||||
|
||||
if (notices == null || (notices != null && notices.Count == 0))
|
||||
NullResult(result, "No notices");
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (ExtendedGroupNoticeData n in notices)
|
||||
dict["n-" + i++] = GroupsDataUtils.GroupNoticeData(n);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
NullResult(result, "Bad OP in request");
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleFindGroups(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("RequestingAgentID") || !request.ContainsKey("Query"))
|
||||
NullResult(result, "Bad network data");
|
||||
|
||||
List<DirGroupsReplyData> hits = m_GroupsService.FindGroups(request["RequestingAgentID"].ToString(), request["Query"].ToString());
|
||||
|
||||
if (hits == null || (hits != null && hits.Count == 0))
|
||||
NullResult(result, "No hits");
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (DirGroupsReplyData n in hits)
|
||||
dict["n-" + i++] = GroupsDataUtils.DirGroupsReplyData(n);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void NullResult(Dictionary<string, object> result, string reason)
|
||||
{
|
||||
result["RESULT"] = "NULL";
|
||||
result["REASON"] = reason;
|
||||
}
|
||||
|
||||
private byte[] FailureResult()
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
NullResult(result, "Unknown method");
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
private byte[] FailureResult(string reason)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
NullResult(result, reason);
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,814 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
using OpenSim.Framework;
|
||||
//using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public delegate ExtendedGroupRecord GroupRecordDelegate();
|
||||
public delegate GroupMembershipData GroupMembershipDelegate();
|
||||
public delegate List<GroupMembershipData> GroupMembershipListDelegate();
|
||||
public delegate List<ExtendedGroupMembersData> GroupMembersListDelegate();
|
||||
public delegate List<GroupRolesData> GroupRolesListDelegate();
|
||||
public delegate List<ExtendedGroupRoleMembersData> RoleMembersListDelegate();
|
||||
public delegate GroupNoticeInfo NoticeDelegate();
|
||||
public delegate List<ExtendedGroupNoticeData> NoticeListDelegate();
|
||||
public delegate void VoidDelegate();
|
||||
public delegate bool BooleanDelegate();
|
||||
|
||||
public class RemoteConnectorCacheWrapper
|
||||
{
|
||||
private const int GROUPS_CACHE_TIMEOUT = 1 * 60; // 1 minutes
|
||||
|
||||
private ForeignImporter m_ForeignImporter;
|
||||
private HashSet<string> m_ActiveRequests = new HashSet<string>();
|
||||
|
||||
// This all important cache caches objects of different types:
|
||||
// group-<GroupID> or group-<Name> => ExtendedGroupRecord
|
||||
// active-<AgentID> => GroupMembershipData
|
||||
// membership-<AgentID>-<GroupID> => GroupMembershipData
|
||||
// memberships-<AgentID> => List<GroupMembershipData>
|
||||
// members-<RequestingAgentID>-<GroupID> => List<ExtendedGroupMembersData>
|
||||
// role-<RoleID> => GroupRolesData
|
||||
// roles-<GroupID> => List<GroupRolesData> ; all roles in the group
|
||||
// roles-<GroupID>-<AgentID> => List<GroupRolesData> ; roles that the agent has
|
||||
// rolemembers-<RequestingAgentID>-<GroupID> => List<ExtendedGroupRoleMembersData>
|
||||
// notice-<noticeID> => GroupNoticeInfo
|
||||
// notices-<GroupID> => List<ExtendedGroupNoticeData>
|
||||
private ExpiringCacheOS<string, object> m_Cache = new ExpiringCacheOS<string, object>(30000);
|
||||
|
||||
public RemoteConnectorCacheWrapper(IUserManagement uman)
|
||||
{
|
||||
m_ForeignImporter = new ForeignImporter(uman);
|
||||
}
|
||||
|
||||
public UUID CreateGroup(UUID RequestingAgentID, GroupRecordDelegate d)
|
||||
{
|
||||
//m_log.DebugFormat("[Groups.RemoteConnector]: Creating group {0}", name);
|
||||
//reason = string.Empty;
|
||||
|
||||
//ExtendedGroupRecord group = m_GroupsService.CreateGroup(RequestingAgentID.ToString(), name, charter, showInList, insigniaID,
|
||||
// membershipFee, openEnrollment, allowPublish, maturePublish, founderID, out reason);
|
||||
ExtendedGroupRecord group = d();
|
||||
|
||||
if (group == null)
|
||||
return UUID.Zero;
|
||||
|
||||
if (!group.GroupID.IsZero())
|
||||
{
|
||||
m_Cache.AddOrUpdate("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT);
|
||||
m_Cache.Remove("memberships-" + RequestingAgentID.ToString());
|
||||
}
|
||||
return group.GroupID;
|
||||
}
|
||||
|
||||
public bool UpdateGroup(UUID groupID, GroupRecordDelegate d)
|
||||
{
|
||||
//reason = string.Empty;
|
||||
//ExtendedGroupRecord group = m_GroupsService.UpdateGroup(RequestingAgentID, groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
|
||||
ExtendedGroupRecord group = d();
|
||||
|
||||
if (!group.GroupID.IsZero())
|
||||
m_Cache.AddOrUpdate("group-" + group.GroupID.ToString(), group, GROUPS_CACHE_TIMEOUT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string GroupName, GroupRecordDelegate d)
|
||||
{
|
||||
//if (GroupID.IsZero() && string.IsNullOrEmpty(GroupName))
|
||||
// return null;
|
||||
|
||||
object group = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "group-";
|
||||
if (GroupID.IsZero())
|
||||
cacheKey += GroupName;
|
||||
else
|
||||
cacheKey += GroupID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetGroupRecord {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out group))
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: GetGroupRecord {0} cached!", cacheKey);
|
||||
return (ExtendedGroupRecord)group;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
//group = m_GroupsService.GetGroupRecord(RequestingAgentID, GroupID, GroupName);
|
||||
group = d();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, group, GROUPS_CACHE_TIMEOUT);
|
||||
return (ExtendedGroupRecord)group;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddAgentToGroup(string RequestingAgentID, string AgentID, UUID GroupID, GroupMembershipDelegate d)
|
||||
{
|
||||
GroupMembershipData membership = d();
|
||||
if (membership == null)
|
||||
return false;
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
// first, remove everything! add a user is a heavy-duty op
|
||||
m_Cache.Clear();
|
||||
|
||||
m_Cache.AddOrUpdate("active-" + AgentID.ToString(), membership, GROUPS_CACHE_TIMEOUT);
|
||||
m_Cache.AddOrUpdate("membership-" + AgentID.ToString() + "-" + GroupID.ToString(), membership, GROUPS_CACHE_TIMEOUT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, VoidDelegate d)
|
||||
{
|
||||
d();
|
||||
|
||||
string AgentIDToString = AgentID.ToString();
|
||||
string cacheKey = "active-" + AgentIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "memberships-" + AgentIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
string GroupIDToString = GroupID.ToString();
|
||||
cacheKey = "membership-" + AgentIDToString + "-" + GroupIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "roles-" + "-" + GroupIDToString + "-" + AgentIDToString;
|
||||
m_Cache.Remove(cacheKey);
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroup(string AgentID, GroupMembershipDelegate d)
|
||||
{
|
||||
GroupMembershipData activeGroup = d();
|
||||
string cacheKey = "active-" + AgentID.ToString();
|
||||
m_Cache.AddOrUpdate(cacheKey, activeGroup, GROUPS_CACHE_TIMEOUT);
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentActiveMembership(string AgentID, GroupMembershipDelegate d)
|
||||
{
|
||||
object membership = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "active-" + AgentID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetAgentActiveMembership {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out membership))
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: GetAgentActiveMembership {0} cached!", cacheKey);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
membership = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ExtendedGroupMembershipData GetAgentGroupMembership(string AgentID, UUID GroupID, GroupMembershipDelegate d)
|
||||
{
|
||||
object membership = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupMembership {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out membership))
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupMembership {0}", cacheKey);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
membership = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, membership, GROUPS_CACHE_TIMEOUT);
|
||||
return (ExtendedGroupMembershipData)membership;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public List<GroupMembershipData> GetAgentGroupMemberships(string AgentID, GroupMembershipListDelegate d)
|
||||
{
|
||||
object memberships = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "memberships-" + AgentID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupMemberships {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out memberships))
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupMemberships {0} cached!", cacheKey);
|
||||
return (List<GroupMembershipData>)memberships;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
memberships = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, memberships, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupMembershipData>)memberships;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public List<GroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID, GroupMembersListDelegate d)
|
||||
{
|
||||
object members = null;
|
||||
bool firstCall = false;
|
||||
// we need to key in also on the requester, because different ppl have different view privileges
|
||||
string cacheKey = "members-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetGroupMembers {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out members))
|
||||
{
|
||||
List<ExtendedGroupMembersData> xx = (List<ExtendedGroupMembersData>)members;
|
||||
return xx.ConvertAll<GroupMembersData>(new Converter<ExtendedGroupMembersData, GroupMembersData>(m_ForeignImporter.ConvertGroupMembersData));
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ExtendedGroupMembersData> _members = d();
|
||||
|
||||
if (_members != null && _members.Count > 0)
|
||||
members = _members.ConvertAll<GroupMembersData>(new Converter<ExtendedGroupMembersData, GroupMembersData>(m_ForeignImporter.ConvertGroupMembersData));
|
||||
else
|
||||
members = new List<GroupMembersData>();
|
||||
|
||||
m_Cache.AddOrUpdate(cacheKey, _members, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupMembersData>)members;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddGroupRole(UUID groupID, UUID roleID, string description, string name, ulong powers, string title, BooleanDelegate d)
|
||||
{
|
||||
if (d())
|
||||
{
|
||||
GroupRolesData role = new GroupRolesData();
|
||||
role.Description = description;
|
||||
role.Members = 0;
|
||||
role.Name = name;
|
||||
role.Powers = powers;
|
||||
role.RoleID = roleID;
|
||||
role.Title = title;
|
||||
|
||||
m_Cache.AddOrUpdate("role-" + roleID.ToString(), role, GROUPS_CACHE_TIMEOUT);
|
||||
|
||||
// also remove this list
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool UpdateGroupRole(UUID groupID, UUID roleID, string name, string description, string title, ulong powers, BooleanDelegate d)
|
||||
{
|
||||
if (d())
|
||||
{
|
||||
object role;
|
||||
lock (m_Cache)
|
||||
if (m_Cache.TryGetValue("role-" + roleID.ToString(), out role))
|
||||
{
|
||||
GroupRolesData r = (GroupRolesData)role;
|
||||
r.Description = description;
|
||||
r.Name = name;
|
||||
r.Powers = powers;
|
||||
r.Title = title;
|
||||
|
||||
m_Cache.AddOrUpdate("role-" + roleID.ToString(), r, GROUPS_CACHE_TIMEOUT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Cache.Remove("role-" + roleID.ToString());
|
||||
// also remove these lists, because they will have an outdated role
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveGroupRole(string RequestingAgentID, UUID groupID, UUID roleID, VoidDelegate d)
|
||||
{
|
||||
d();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
m_Cache.Remove("role-" + roleID.ToString());
|
||||
// also remove the list, because it will have an removed role
|
||||
m_Cache.Remove("roles-" + groupID.ToString());
|
||||
m_Cache.Remove("roles-" + groupID.ToString() + "-" + RequestingAgentID.ToString());
|
||||
m_Cache.Remove("rolemembers-" + RequestingAgentID.ToString() + "-" + groupID.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID, GroupRolesListDelegate d)
|
||||
{
|
||||
object roles = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "roles-" + GroupID.ToString();
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out roles))
|
||||
return (List<GroupRolesData>)roles;
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
roles = d();
|
||||
if (roles != null)
|
||||
{
|
||||
m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupRolesData>)roles;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public List<GroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, RoleMembersListDelegate d)
|
||||
{
|
||||
object rmembers = null;
|
||||
bool firstCall = false;
|
||||
// we need to key in also on the requester, because different ppl have different view privileges
|
||||
string cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetGroupRoleMembers {0}", cacheKey);
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out rmembers))
|
||||
{
|
||||
List<ExtendedGroupRoleMembersData> xx = (List<ExtendedGroupRoleMembersData>)rmembers;
|
||||
return xx.ConvertAll<GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData);
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ExtendedGroupRoleMembersData> _rmembers = d();
|
||||
|
||||
if (_rmembers != null && _rmembers.Count > 0)
|
||||
rmembers = _rmembers.ConvertAll<GroupRoleMembersData>(new Converter<ExtendedGroupRoleMembersData, GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData));
|
||||
else
|
||||
rmembers = new List<GroupRoleMembersData>();
|
||||
|
||||
// For some strange reason, when I cache the list of GroupRoleMembersData,
|
||||
// it gets emptied out. The TryGet gets an empty list...
|
||||
//m_Cache.AddOrUpdate(cacheKey, rmembers, GROUPS_CACHE_TIMEOUT);
|
||||
// Caching the list of ExtendedGroupRoleMembersData doesn't show that issue
|
||||
// I don't get it.
|
||||
m_Cache.AddOrUpdate(cacheKey, _rmembers, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupRoleMembersData>)rmembers;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAgentToGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, BooleanDelegate d)
|
||||
{
|
||||
if (d())
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
// update the cached role
|
||||
string cacheKey = "role-" + RoleID.ToString();
|
||||
object obj;
|
||||
if (m_Cache.TryGetValue(cacheKey, out obj))
|
||||
{
|
||||
GroupRolesData r = (GroupRolesData)obj;
|
||||
r.Members++;
|
||||
}
|
||||
|
||||
// add this agent to the list of role members
|
||||
cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
|
||||
if (m_Cache.TryGetValue(cacheKey, out obj))
|
||||
{
|
||||
try
|
||||
{
|
||||
// This may throw an exception, in which case the agentID is not a UUID but a full ID
|
||||
// In that case, let's just remove the whoe things from the cache
|
||||
UUID id = new UUID(AgentID);
|
||||
List<ExtendedGroupRoleMembersData> xx = (List<ExtendedGroupRoleMembersData>)obj;
|
||||
List<GroupRoleMembersData> rmlist = xx.ConvertAll<GroupRoleMembersData>(m_ForeignImporter.ConvertGroupRoleMembersData);
|
||||
GroupRoleMembersData rm = new GroupRoleMembersData();
|
||||
rm.MemberID = id;
|
||||
rm.RoleID = RoleID;
|
||||
rmlist.Add(rm);
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_Cache.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the cached info about this agent's roles
|
||||
// because we don't have enough local info about the new role
|
||||
cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAgentFromGroupRole(string RequestingAgentID, string AgentID, UUID GroupID, UUID RoleID, BooleanDelegate d)
|
||||
{
|
||||
if (d())
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
// update the cached role
|
||||
string cacheKey = "role-" + RoleID.ToString();
|
||||
object obj;
|
||||
if (m_Cache.TryGetValue(cacheKey, out obj))
|
||||
{
|
||||
GroupRolesData r = (GroupRolesData)obj;
|
||||
r.Members--;
|
||||
}
|
||||
|
||||
cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "rolemembers-" + RequestingAgentID.ToString() + "-" + GroupID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetAgentGroupRoles(string RequestingAgentID, string AgentID, UUID GroupID, GroupRolesListDelegate d)
|
||||
{
|
||||
object roles = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "roles-" + GroupID.ToString() + "-" + AgentID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out roles))
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0} cached!", cacheKey);
|
||||
return (List<GroupRolesData>)roles;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
roles = d();
|
||||
m_Cache.AddOrUpdate(cacheKey, roles, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<GroupRolesData>)roles;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAgentActiveGroupRole(string AgentID, UUID GroupID, VoidDelegate d)
|
||||
{
|
||||
d();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
// Invalidate cached info, because it has ActiveRoleID and Powers
|
||||
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "memberships-" + AgentID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMembership(string AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile, VoidDelegate d)
|
||||
{
|
||||
d();
|
||||
|
||||
lock (m_Cache)
|
||||
{
|
||||
string cacheKey = "membership-" + AgentID.ToString() + "-" + GroupID.ToString();
|
||||
if (m_Cache.Contains(cacheKey))
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "memberships-" + AgentID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
cacheKey = "active-" + AgentID.ToString();
|
||||
object m = null;
|
||||
if (m_Cache.TryGetValue(cacheKey, out m))
|
||||
{
|
||||
GroupMembershipData membership = (GroupMembershipData)m;
|
||||
membership.ListInProfile = ListInProfile;
|
||||
membership.AcceptNotices = AcceptNotices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddGroupNotice(UUID groupID, UUID noticeID, GroupNoticeInfo notice, BooleanDelegate d)
|
||||
{
|
||||
if (d())
|
||||
{
|
||||
m_Cache.AddOrUpdate("notice-" + noticeID.ToString(), notice, GROUPS_CACHE_TIMEOUT);
|
||||
string cacheKey = "notices-" + groupID.ToString();
|
||||
m_Cache.Remove(cacheKey);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public GroupNoticeInfo GetGroupNotice(UUID noticeID, NoticeDelegate d)
|
||||
{
|
||||
object notice = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "notice-" + noticeID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetAgentGroupRoles {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out notice))
|
||||
{
|
||||
return (GroupNoticeInfo)notice;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
GroupNoticeInfo _notice = d();
|
||||
|
||||
m_Cache.AddOrUpdate(cacheKey, _notice, GROUPS_CACHE_TIMEOUT);
|
||||
return _notice;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ExtendedGroupNoticeData> GetGroupNotices(UUID GroupID, NoticeListDelegate d)
|
||||
{
|
||||
object notices = null;
|
||||
bool firstCall = false;
|
||||
string cacheKey = "notices-" + GroupID.ToString();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: GetGroupNotices {0}", cacheKey);
|
||||
|
||||
while (true)
|
||||
{
|
||||
lock (m_Cache)
|
||||
{
|
||||
if (m_Cache.TryGetValue(cacheKey, out notices))
|
||||
{
|
||||
//m_log.DebugFormat("[XXX]: GetGroupNotices {0} cached!", cacheKey);
|
||||
return (List<ExtendedGroupNoticeData>)notices;
|
||||
}
|
||||
|
||||
// not cached
|
||||
if (!m_ActiveRequests.Contains(cacheKey))
|
||||
{
|
||||
m_ActiveRequests.Add(cacheKey);
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
notices = d();
|
||||
|
||||
m_Cache.AddOrUpdate(cacheKey, notices, GROUPS_CACHE_TIMEOUT);
|
||||
return (List<ExtendedGroupNoticeData>)notices;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_ActiveRequests.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Data;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Services.Base;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class GroupsServiceBase : ServiceBase
|
||||
{
|
||||
protected IGroupsData m_Database = null;
|
||||
protected IGridUserData m_GridUserService = null;
|
||||
|
||||
public GroupsServiceBase(IConfigSource config, string cName)
|
||||
: base(config)
|
||||
{
|
||||
string dllName = String.Empty;
|
||||
string connString = String.Empty;
|
||||
string realm = "os_groups";
|
||||
string usersRealm = "GridUser";
|
||||
string configName = (cName.Length == 0) ? "Groups" : cName;
|
||||
|
||||
//
|
||||
// Try reading the [DatabaseService] section, if it exists
|
||||
//
|
||||
IConfig dbConfig = config.Configs["DatabaseService"];
|
||||
if (dbConfig != null)
|
||||
{
|
||||
if (dllName.Length == 0)
|
||||
dllName = dbConfig.GetString("StorageProvider", String.Empty);
|
||||
if (connString.Length == 0)
|
||||
connString = dbConfig.GetString("ConnectionString", String.Empty);
|
||||
}
|
||||
|
||||
//
|
||||
// [Groups] section overrides [DatabaseService], if it exists
|
||||
//
|
||||
IConfig groupsConfig = config.Configs[configName];
|
||||
if (groupsConfig != null)
|
||||
{
|
||||
dllName = groupsConfig.GetString("StorageProvider", dllName);
|
||||
connString = groupsConfig.GetString("ConnectionString", connString);
|
||||
realm = groupsConfig.GetString("Realm", realm);
|
||||
}
|
||||
|
||||
//
|
||||
// We tried, but this doesn't exist. We can't proceed.
|
||||
//
|
||||
if (dllName.Equals(String.Empty))
|
||||
throw new Exception("No StorageProvider configured");
|
||||
|
||||
m_Database = LoadPlugin<IGroupsData>(dllName, new Object[] { connString, realm });
|
||||
if (m_Database == null)
|
||||
throw new Exception("Could not find a storage interface in the given module " + dllName);
|
||||
|
||||
//
|
||||
// [GridUserService] section overrides [DatabaseService], if it exists
|
||||
//
|
||||
IConfig usersConfig = config.Configs["GridUserService"];
|
||||
if (usersConfig != null)
|
||||
{
|
||||
dllName = usersConfig.GetString("StorageProvider", dllName);
|
||||
connString = usersConfig.GetString("ConnectionString", connString);
|
||||
usersRealm = usersConfig.GetString("Realm", usersRealm);
|
||||
}
|
||||
|
||||
m_GridUserService = LoadPlugin<IGridUserData>(dllName, new Object[] { connString, usersRealm });
|
||||
if (m_GridUserService == null)
|
||||
throw new Exception("Could not find a storage inferface for the given users module " + dllName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Timers;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Data;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
namespace OpenSim.Groups
|
||||
{
|
||||
public class HGGroupsService : GroupsService
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private IOfflineIMService m_OfflineIM;
|
||||
private IUserAccountService m_UserAccounts;
|
||||
private string m_HomeURI;
|
||||
|
||||
public HGGroupsService(IConfigSource config, IOfflineIMService im, IUserAccountService users, string homeURI)
|
||||
: base(config, string.Empty)
|
||||
{
|
||||
m_OfflineIM = im;
|
||||
m_UserAccounts = users;
|
||||
m_HomeURI = homeURI;
|
||||
if (!m_HomeURI.EndsWith("/"))
|
||||
m_HomeURI += "/";
|
||||
}
|
||||
|
||||
|
||||
#region HG specific operations
|
||||
|
||||
public bool CreateGroupProxy(string RequestingAgentID, string agentID, string accessToken, UUID groupID, string serviceLocation, string name, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
Uri uri = null;
|
||||
try
|
||||
{
|
||||
uri = new Uri(serviceLocation);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
reason = "Bad location for group proxy";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it already exists
|
||||
GroupData grec = m_Database.RetrieveGroup(groupID);
|
||||
if (grec == null ||
|
||||
(grec != null && grec.Data["Location"] != string.Empty && grec.Data["Location"].ToLower() != serviceLocation.ToLower()))
|
||||
{
|
||||
// Create the group
|
||||
grec = new GroupData();
|
||||
grec.GroupID = groupID;
|
||||
grec.Data = new Dictionary<string, string>();
|
||||
grec.Data["Name"] = name + " @ " + uri.Authority;
|
||||
grec.Data["Location"] = serviceLocation;
|
||||
grec.Data["Charter"] = string.Empty;
|
||||
grec.Data["InsigniaID"] = UUID.Zero.ToString();
|
||||
grec.Data["FounderID"] = UUID.Zero.ToString();
|
||||
grec.Data["MembershipFee"] = "0";
|
||||
grec.Data["OpenEnrollment"] = "0";
|
||||
grec.Data["ShowInList"] = "0";
|
||||
grec.Data["AllowPublish"] = "0";
|
||||
grec.Data["MaturePublish"] = "0";
|
||||
grec.Data["OwnerRoleID"] = UUID.Zero.ToString();
|
||||
|
||||
|
||||
if (!m_Database.StoreGroup(grec))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (grec.Data["Location"].Length == 0)
|
||||
{
|
||||
reason = "Cannot add proxy membership to non-proxy group";
|
||||
return false;
|
||||
}
|
||||
|
||||
UUID uid = UUID.Zero;
|
||||
string url = string.Empty, first = string.Empty, last = string.Empty, tmp = string.Empty;
|
||||
Util.ParseUniversalUserIdentifier(RequestingAgentID, out uid, out url, out first, out last, out tmp);
|
||||
string fromName = first + "." + last + "@" + url;
|
||||
|
||||
// Invite to group again
|
||||
InviteToGroup(fromName, groupID, new UUID(agentID), grec.Data["Name"]);
|
||||
|
||||
// Stick the proxy membership in the DB already
|
||||
// we'll delete it if the agent declines the invitation
|
||||
MembershipData membership = new MembershipData();
|
||||
membership.PrincipalID = agentID;
|
||||
membership.GroupID = groupID;
|
||||
membership.Data = new Dictionary<string, string>();
|
||||
membership.Data["SelectedRoleID"] = UUID.Zero.ToString();
|
||||
membership.Data["Contribution"] = "0";
|
||||
membership.Data["ListInProfile"] = "1";
|
||||
membership.Data["AcceptNotices"] = "1";
|
||||
membership.Data["AccessToken"] = accessToken;
|
||||
|
||||
m_Database.StoreMember(membership);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveAgentFromGroup(string RequestingAgentID, string AgentID, UUID GroupID, string token)
|
||||
{
|
||||
// check the token
|
||||
MembershipData membership = m_Database.RetrieveMember(GroupID, AgentID);
|
||||
if (membership != null)
|
||||
{
|
||||
if (token != string.Empty && token.Equals(membership.Data["AccessToken"]))
|
||||
{
|
||||
return RemoveAgentFromGroup(RequestingAgentID, AgentID, GroupID);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: access token {0} did not match stored one {1}", token, membership.Data["AccessToken"]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: membership not found for {0}", AgentID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ExtendedGroupRecord GetGroupRecord(string RequestingAgentID, UUID GroupID, string groupName, string token)
|
||||
{
|
||||
// check the token
|
||||
if (!VerifyToken(GroupID, RequestingAgentID, token))
|
||||
return null;
|
||||
|
||||
ExtendedGroupRecord grec;
|
||||
if (GroupID.IsZero())
|
||||
grec = GetGroupRecord(RequestingAgentID, groupName);
|
||||
else
|
||||
grec = GetGroupRecord(RequestingAgentID, GroupID);
|
||||
|
||||
if (grec != null)
|
||||
FillFounderUUI(grec);
|
||||
|
||||
return grec;
|
||||
}
|
||||
|
||||
public List<ExtendedGroupMembersData> GetGroupMembers(string RequestingAgentID, UUID GroupID, string token)
|
||||
{
|
||||
if (!VerifyToken(GroupID, RequestingAgentID, token))
|
||||
return new List<ExtendedGroupMembersData>();
|
||||
|
||||
List<ExtendedGroupMembersData> members = GetGroupMembers(RequestingAgentID, GroupID);
|
||||
|
||||
// convert UUIDs to UUIs
|
||||
members.ForEach(delegate (ExtendedGroupMembersData m)
|
||||
{
|
||||
if (m.AgentID.ToString().Length == 36) // UUID
|
||||
{
|
||||
UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, new UUID(m.AgentID));
|
||||
if (account != null)
|
||||
m.AgentID = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI);
|
||||
}
|
||||
});
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
public List<GroupRolesData> GetGroupRoles(string RequestingAgentID, UUID GroupID, string token)
|
||||
{
|
||||
if (!VerifyToken(GroupID, RequestingAgentID, token))
|
||||
return new List<GroupRolesData>();
|
||||
|
||||
return GetGroupRoles(RequestingAgentID, GroupID);
|
||||
}
|
||||
|
||||
public List<ExtendedGroupRoleMembersData> GetGroupRoleMembers(string RequestingAgentID, UUID GroupID, string token)
|
||||
{
|
||||
if (!VerifyToken(GroupID, RequestingAgentID, token))
|
||||
return new List<ExtendedGroupRoleMembersData>();
|
||||
|
||||
List<ExtendedGroupRoleMembersData> rolemembers = GetGroupRoleMembers(RequestingAgentID, GroupID);
|
||||
|
||||
// convert UUIDs to UUIs
|
||||
rolemembers.ForEach(delegate(ExtendedGroupRoleMembersData m)
|
||||
{
|
||||
if (m.MemberID.ToString().Length == 36) // UUID
|
||||
{
|
||||
UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, new UUID(m.MemberID));
|
||||
if (account != null)
|
||||
m.MemberID = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI);
|
||||
}
|
||||
});
|
||||
|
||||
return rolemembers;
|
||||
}
|
||||
|
||||
public bool AddNotice(string RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message,
|
||||
bool hasAttachment, byte attType, string attName, UUID attItemID, string attOwnerID)
|
||||
{
|
||||
// check that the group proxy exists
|
||||
ExtendedGroupRecord grec = GetGroupRecord(RequestingAgentID, groupID);
|
||||
if (grec == null)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to non-existent group proxy");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check that the group is remote
|
||||
if (grec.ServiceLocation.Length == 0)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: attempt at adding notice to local (non-proxy) group");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check that there isn't already a notice with the same ID
|
||||
if (GetGroupNotice(RequestingAgentID, noticeID) != null)
|
||||
{
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: a notice with the same ID already exists", grec.ServiceLocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
// This has good intentions (security) but it will potentially DDS the origin...
|
||||
// We'll need to send a proof along with the message. Maybe encrypt the message
|
||||
// using key pairs
|
||||
//
|
||||
//// check that the notice actually exists in the origin
|
||||
//GroupsServiceHGConnector c = new GroupsServiceHGConnector(grec.ServiceLocation);
|
||||
//if (!c.VerifyNotice(noticeID, groupID))
|
||||
//{
|
||||
// m_log.DebugFormat("[Groups.HGGroupsService]: notice does not exist at origin {0}", grec.ServiceLocation);
|
||||
// return false;
|
||||
//}
|
||||
|
||||
// ok, we're good!
|
||||
return _AddNotice(groupID, noticeID, fromName, subject, message, hasAttachment, attType, attName, attItemID, attOwnerID);
|
||||
}
|
||||
|
||||
public bool VerifyNotice(UUID noticeID, UUID groupID)
|
||||
{
|
||||
GroupNoticeInfo notice = GetGroupNotice(string.Empty, noticeID);
|
||||
|
||||
if (notice == null)
|
||||
return false;
|
||||
|
||||
if (notice.GroupID != groupID)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void InviteToGroup(string fromName, UUID groupID, UUID invitedAgentID, string groupName)
|
||||
{
|
||||
// Todo: Security check, probably also want to send some kind of notification
|
||||
UUID InviteID = UUID.Random();
|
||||
|
||||
if (AddAgentToGroupInvite(InviteID, groupID, invitedAgentID.ToString()))
|
||||
{
|
||||
Guid inviteUUID = InviteID.Guid;
|
||||
|
||||
GridInstantMessage msg = new GridInstantMessage();
|
||||
|
||||
msg.imSessionID = inviteUUID;
|
||||
|
||||
// msg.fromAgentID = agentID.Guid;
|
||||
msg.fromAgentID = groupID.Guid;
|
||||
msg.toAgentID = invitedAgentID.Guid;
|
||||
//msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
|
||||
msg.timestamp = 0;
|
||||
msg.fromAgentName = fromName;
|
||||
msg.message = string.Format("Please confirm your acceptance to join group {0}.", groupName);
|
||||
msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupInvitation;
|
||||
msg.fromGroup = true;
|
||||
msg.offline = (byte)0;
|
||||
msg.ParentEstateID = 0;
|
||||
msg.Position = Vector3.Zero;
|
||||
msg.RegionID = UUID.Zero.Guid;
|
||||
msg.binaryBucket = new byte[20];
|
||||
|
||||
string reason = string.Empty;
|
||||
m_OfflineIM.StoreMessage(msg, out reason);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddAgentToGroupInvite(UUID inviteID, UUID groupID, string agentID)
|
||||
{
|
||||
// Check whether the invitee is already a member of the group
|
||||
MembershipData m = m_Database.RetrieveMember(groupID, agentID);
|
||||
if (m != null)
|
||||
return false;
|
||||
|
||||
// Check whether there are pending invitations and delete them
|
||||
InvitationData invite = m_Database.RetrieveInvitation(groupID, agentID);
|
||||
if (invite != null)
|
||||
m_Database.DeleteInvite(invite.InviteID);
|
||||
|
||||
invite = new InvitationData();
|
||||
invite.InviteID = inviteID;
|
||||
invite.PrincipalID = agentID;
|
||||
invite.GroupID = groupID;
|
||||
invite.RoleID = UUID.Zero;
|
||||
invite.Data = new Dictionary<string, string>();
|
||||
|
||||
return m_Database.StoreInvitation(invite);
|
||||
}
|
||||
|
||||
private void FillFounderUUI(ExtendedGroupRecord grec)
|
||||
{
|
||||
UserAccount account = m_UserAccounts.GetUserAccount(UUID.Zero, grec.FounderID);
|
||||
if (account != null)
|
||||
grec.FounderUUI = Util.UniversalIdentifier(account.PrincipalID, account.FirstName, account.LastName, m_HomeURI);
|
||||
}
|
||||
|
||||
private bool VerifyToken(UUID groupID, string agentID, string token)
|
||||
{
|
||||
// check the token
|
||||
MembershipData membership = m_Database.RetrieveMember(groupID, agentID);
|
||||
if (membership != null)
|
||||
{
|
||||
if (token != string.Empty && token.Equals(membership.Data["AccessToken"]))
|
||||
return true;
|
||||
else
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: access token {0} did not match stored one {1}", token, membership.Data["AccessToken"]);
|
||||
}
|
||||
else
|
||||
m_log.DebugFormat("[Groups.HGGroupsService]: membership not found for {0}", agentID);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using log4net;
|
||||
using Mono.Addins;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Client;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
namespace OpenSim.OfflineIM
|
||||
{
|
||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "OfflineIMConnectorModule")]
|
||||
public class OfflineIMRegionModule : ISharedRegionModule, IOfflineIMService
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private bool m_Enabled = false;
|
||||
private List<Scene> m_SceneList = new List<Scene>();
|
||||
IMessageTransferModule m_TransferModule = null;
|
||||
private bool m_ForwardOfflineGroupMessages = true;
|
||||
|
||||
private IOfflineIMService m_OfflineIMService;
|
||||
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
IConfig cnf = config.Configs["Messaging"];
|
||||
if (cnf == null)
|
||||
return;
|
||||
if (cnf != null && cnf.GetString("OfflineMessageModule", string.Empty) != Name)
|
||||
return;
|
||||
|
||||
m_Enabled = true;
|
||||
|
||||
string serviceLocation = cnf.GetString("OfflineMessageURL", string.Empty);
|
||||
if (serviceLocation.Length == 0)
|
||||
m_OfflineIMService = new OfflineIMService(config);
|
||||
else
|
||||
m_OfflineIMService = new OfflineIMServiceRemoteConnector(config);
|
||||
|
||||
m_ForwardOfflineGroupMessages = cnf.GetBoolean("ForwardOfflineGroupMessages", m_ForwardOfflineGroupMessages);
|
||||
m_log.DebugFormat("[OfflineIM.V2]: Offline messages enabled by {0}", Name);
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
scene.RegisterModuleInterface<IOfflineIMService>(this);
|
||||
m_SceneList.Add(scene);
|
||||
scene.EventManager.OnNewClient += OnNewClient;
|
||||
}
|
||||
|
||||
public void RegionLoaded(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
if (m_TransferModule == null)
|
||||
{
|
||||
m_TransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
|
||||
if (m_TransferModule == null)
|
||||
{
|
||||
scene.EventManager.OnNewClient -= OnNewClient;
|
||||
|
||||
m_SceneList.Clear();
|
||||
|
||||
m_log.Error("[OfflineIM.V2]: No message transfer module is enabled. Disabling offline messages");
|
||||
}
|
||||
m_TransferModule.OnUndeliveredMessage += UndeliveredMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
m_SceneList.Remove(scene);
|
||||
scene.EventManager.OnNewClient -= OnNewClient;
|
||||
m_TransferModule.OnUndeliveredMessage -= UndeliveredMessage;
|
||||
|
||||
scene.ForEachClient(delegate(IClientAPI client)
|
||||
{
|
||||
client.OnRetrieveInstantMessages -= RetrieveInstantMessages;
|
||||
});
|
||||
}
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Offline Message Module V2"; }
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
m_SceneList.Clear();
|
||||
}
|
||||
|
||||
private Scene FindScene(UUID agentID)
|
||||
{
|
||||
foreach (Scene s in m_SceneList)
|
||||
{
|
||||
ScenePresence presence = s.GetScenePresence(agentID);
|
||||
if (presence != null && !presence.IsChildAgent)
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IClientAPI FindClient(UUID agentID)
|
||||
{
|
||||
foreach (Scene s in m_SceneList)
|
||||
{
|
||||
ScenePresence presence = s.GetScenePresence(agentID);
|
||||
if (presence != null && !presence.IsChildAgent)
|
||||
return presence.ControllingClient;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnNewClient(IClientAPI client)
|
||||
{
|
||||
client.OnRetrieveInstantMessages += RetrieveInstantMessages;
|
||||
}
|
||||
|
||||
private void RetrieveInstantMessages(IClientAPI client)
|
||||
{
|
||||
m_log.DebugFormat("[OfflineIM.V2]: Retrieving stored messages for {0}", client.AgentId);
|
||||
|
||||
List<GridInstantMessage> msglist = m_OfflineIMService.GetMessages(client.AgentId);
|
||||
|
||||
if (msglist == null)
|
||||
m_log.DebugFormat("[OfflineIM.V2]: WARNING null message list.");
|
||||
|
||||
foreach (GridInstantMessage im in msglist)
|
||||
{
|
||||
if (im.dialog == (byte)InstantMessageDialog.InventoryOffered)
|
||||
// send it directly or else the item will be given twice
|
||||
client.SendInstantMessage(im);
|
||||
else
|
||||
{
|
||||
// Send through scene event manager so all modules get a chance
|
||||
// to look at this message before it gets delivered.
|
||||
//
|
||||
// Needed for proper state management for stored group
|
||||
// invitations
|
||||
//
|
||||
Scene s = client.Scene as Scene;
|
||||
if (s != null)
|
||||
{
|
||||
im.offline = 1;
|
||||
s.EventManager.TriggerIncomingInstantMessage(im);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UndeliveredMessage(GridInstantMessage im)
|
||||
{
|
||||
if (im.dialog != (byte)InstantMessageDialog.MessageFromObject &&
|
||||
im.dialog != (byte)InstantMessageDialog.MessageFromAgent &&
|
||||
im.dialog != (byte)InstantMessageDialog.GroupNotice &&
|
||||
im.dialog != (byte)InstantMessageDialog.GroupInvitation &&
|
||||
im.dialog != (byte)InstantMessageDialog.InventoryOffered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_ForwardOfflineGroupMessages)
|
||||
{
|
||||
if (im.dialog == (byte)InstantMessageDialog.GroupNotice ||
|
||||
im.dialog == (byte)InstantMessageDialog.GroupInvitation)
|
||||
return;
|
||||
}
|
||||
|
||||
string reason = string.Empty;
|
||||
bool success = m_OfflineIMService.StoreMessage(im, out reason);
|
||||
|
||||
if (im.dialog == (byte)InstantMessageDialog.MessageFromAgent)
|
||||
{
|
||||
IClientAPI client = FindClient(new UUID(im.fromAgentID));
|
||||
if (client == null)
|
||||
return;
|
||||
|
||||
client.SendInstantMessage(new GridInstantMessage(
|
||||
null, new UUID(im.toAgentID),
|
||||
"System", new UUID(im.fromAgentID),
|
||||
(byte)InstantMessageDialog.MessageFromAgent,
|
||||
"User is not logged in. " +
|
||||
(success ? "Message saved." : "Message not saved: " + reason),
|
||||
false, new Vector3()));
|
||||
}
|
||||
}
|
||||
|
||||
#region IOfflineIM
|
||||
|
||||
public List<GridInstantMessage> GetMessages(UUID principalID)
|
||||
{
|
||||
return m_OfflineIMService.GetMessages(principalID);
|
||||
}
|
||||
|
||||
public bool StoreMessage(GridInstantMessage im, out string reason)
|
||||
{
|
||||
return m_OfflineIMService.StoreMessage(im, out reason);
|
||||
}
|
||||
|
||||
public void DeleteMessages(UUID userID)
|
||||
{
|
||||
m_OfflineIMService.DeleteMessages(userID);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenSim.Addons.OfflineIM")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("http://opensimulator.org")]
|
||||
[assembly: AssemblyProduct("OpenSim.Addons.OfflineIM")]
|
||||
[assembly: AssemblyCopyright("Copyright (c) OpenSimulator.org Developers")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("a16a9905-4393-4872-9fca-4c81bedbd9f2")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
[assembly: Addin("OpenSim.OfflineIM", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.ServiceAuth;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
using OpenMetaverse;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
namespace OpenSim.OfflineIM
|
||||
{
|
||||
public class OfflineIMServiceRemoteConnector : IOfflineIMService
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private string m_ServerURI = string.Empty;
|
||||
private IServiceAuth m_Auth;
|
||||
private object m_Lock = new object();
|
||||
|
||||
public OfflineIMServiceRemoteConnector(string url)
|
||||
{
|
||||
m_ServerURI = url;
|
||||
m_log.DebugFormat("[OfflineIM.V2.RemoteConnector]: Offline IM server at {0}", m_ServerURI);
|
||||
}
|
||||
|
||||
public OfflineIMServiceRemoteConnector(IConfigSource config)
|
||||
{
|
||||
IConfig cnf = config.Configs["Messaging"];
|
||||
if (cnf == null)
|
||||
{
|
||||
m_log.WarnFormat("[OfflineIM.V2.RemoteConnector]: Missing Messaging configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
m_ServerURI = cnf.GetString("OfflineMessageURL", string.Empty);
|
||||
|
||||
/// This is from BaseServiceConnector
|
||||
string authType = Util.GetConfigVarFromSections<string>(config, "AuthType", new string[] { "Network", "Messaging" }, "None");
|
||||
|
||||
switch (authType)
|
||||
{
|
||||
case "BasicHttpAuthentication":
|
||||
m_Auth = new BasicHttpAuthentication(config, "Messaging");
|
||||
break;
|
||||
}
|
||||
///
|
||||
m_log.DebugFormat("[OfflineIM.V2.RemoteConnector]: Offline IM server at {0} with auth {1}",
|
||||
m_ServerURI, (m_Auth == null ? "None" : m_Auth.GetType().ToString()));
|
||||
}
|
||||
|
||||
#region IOfflineIMService
|
||||
public List<GridInstantMessage> GetMessages(UUID principalID)
|
||||
{
|
||||
List<GridInstantMessage> ims = new List<GridInstantMessage>();
|
||||
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["PrincipalID"] = principalID;
|
||||
|
||||
Dictionary<string, object> ret = MakeRequest("GET", sendData);
|
||||
if (ret == null)
|
||||
return ims;
|
||||
|
||||
if (!ret.TryGetValue("RESULT", out object resultobj))
|
||||
return ims;
|
||||
|
||||
if(resultobj is string result)
|
||||
{
|
||||
if (result == "NULL" || result.Equals("false", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (ret.TryGetValue("REASON", out object rso))
|
||||
m_log.Debug($"[OfflineIM.V2.RemoteConnector]: GetMessages for {principalID} failed: {rso}");
|
||||
else
|
||||
m_log.Debug($"[OfflineIM.V2.RemoteConnector]: GetMessages for {principalID} failed: Unknown error");
|
||||
return ims;
|
||||
}
|
||||
}
|
||||
else if(resultobj is Dictionary<string, object> resultdic)
|
||||
{
|
||||
foreach (object v in resultdic.Values)
|
||||
{
|
||||
if (v is Dictionary<string, object> vdic)
|
||||
{
|
||||
GridInstantMessage m = OfflineIMDataUtils.GridInstantMessage(vdic);
|
||||
ims.Add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ims;
|
||||
}
|
||||
|
||||
public bool StoreMessage(GridInstantMessage im, out string reason)
|
||||
{
|
||||
Dictionary<string, object> sendData = OfflineIMDataUtils.GridInstantMessage(im);
|
||||
Dictionary<string, object> ret = MakeRequest("STORE", sendData);
|
||||
|
||||
if (ret == null)
|
||||
{
|
||||
reason = "Bad response from server";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ret.TryGetValue("RESULT", out object o))
|
||||
{
|
||||
string result = o.ToString();
|
||||
if (result == "NULL" || result.Equals("false", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if(ret.TryGetValue("REASON", out object ro))
|
||||
reason = ro.ToString();
|
||||
else
|
||||
reason = "Unknown error";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DeleteMessages(UUID userID)
|
||||
{
|
||||
Dictionary<string, object> sendData = new Dictionary<string, object>();
|
||||
sendData["UserID"] = userID;
|
||||
|
||||
MakeRequest("DELETE", sendData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Make Request
|
||||
|
||||
private Dictionary<string, object> MakeRequest(string method, Dictionary<string, object> sendData)
|
||||
{
|
||||
sendData["METHOD"] = method;
|
||||
|
||||
string reply = string.Empty;
|
||||
lock (m_Lock)
|
||||
reply = SynchronousRestFormsRequester.MakeRequest("POST",
|
||||
m_ServerURI + "/offlineim",
|
||||
ServerUtils.BuildQueryString(sendData),
|
||||
m_Auth);
|
||||
|
||||
Dictionary<string, object> replyData = ServerUtils.ParseXmlResponse(reply);
|
||||
|
||||
return replyData;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Server.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Framework.ServiceAuth;
|
||||
using OpenSim.Server.Handlers.Base;
|
||||
using log4net;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.OfflineIM
|
||||
{
|
||||
public class OfflineIMServiceRobustConnector : ServiceConnector
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private IOfflineIMService m_OfflineIMService;
|
||||
private string m_ConfigName = "Messaging";
|
||||
|
||||
public OfflineIMServiceRobustConnector(IConfigSource config, IHttpServer server, string configName) :
|
||||
base(config, server, configName)
|
||||
{
|
||||
if (configName != String.Empty)
|
||||
m_ConfigName = configName;
|
||||
|
||||
m_log.DebugFormat("[OfflineIM.V2.RobustConnector]: Starting with config name {0}", m_ConfigName);
|
||||
|
||||
m_OfflineIMService = new OfflineIMService(config);
|
||||
|
||||
IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName);
|
||||
|
||||
server.AddStreamHandler(new OfflineIMServicePostHandler(m_OfflineIMService, auth));
|
||||
}
|
||||
}
|
||||
|
||||
public class OfflineIMServicePostHandler : BaseStreamHandler
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private IOfflineIMService m_OfflineIMService;
|
||||
|
||||
public OfflineIMServicePostHandler(IOfflineIMService service, IServiceAuth auth) :
|
||||
base("POST", "/offlineim", auth)
|
||||
{
|
||||
m_OfflineIMService = service;
|
||||
}
|
||||
|
||||
protected override byte[] ProcessRequest(string path, Stream requestData,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
StreamReader sr = new StreamReader(requestData);
|
||||
string body = sr.ReadToEnd();
|
||||
sr.Close();
|
||||
body = body.Trim();
|
||||
|
||||
//m_log.DebugFormat("[XXX]: query String: {0}", body);
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> request =
|
||||
ServerUtils.ParseQueryString(body);
|
||||
|
||||
if (!request.ContainsKey("METHOD"))
|
||||
return FailureResult();
|
||||
|
||||
string method = request["METHOD"].ToString();
|
||||
request.Remove("METHOD");
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case "GET":
|
||||
return HandleGet(request);
|
||||
case "STORE":
|
||||
return HandleStore(request);
|
||||
case "DELETE":
|
||||
return HandleDelete(request);
|
||||
}
|
||||
m_log.DebugFormat("[OFFLINE IM HANDLER]: unknown method request: {0}", method);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error(string.Format("[OFFLINE IM HANDLER]: Exception {0} ", e.Message), e);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
}
|
||||
|
||||
byte[] HandleStore(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
GridInstantMessage im = OfflineIMDataUtils.GridInstantMessage(request);
|
||||
|
||||
string reason = string.Empty;
|
||||
|
||||
bool success = m_OfflineIMService.StoreMessage(im, out reason);
|
||||
|
||||
result["RESULT"] = success.ToString();
|
||||
if (!success)
|
||||
result["REASON"] = reason;
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleGet(Dictionary<string, object> request)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
if (!request.ContainsKey("PrincipalID"))
|
||||
NullResult(result, "Bad network data");
|
||||
else
|
||||
{
|
||||
UUID principalID = new UUID(request["PrincipalID"].ToString());
|
||||
List<GridInstantMessage> ims = m_OfflineIMService.GetMessages(principalID);
|
||||
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
int i = 0;
|
||||
foreach (GridInstantMessage m in ims)
|
||||
dict["im-" + i++] = OfflineIMDataUtils.GridInstantMessage(m);
|
||||
|
||||
result["RESULT"] = dict;
|
||||
}
|
||||
|
||||
string xmlString = ServerUtils.BuildXmlResponse(result);
|
||||
|
||||
//m_log.DebugFormat("[XXX]: resp string: {0}", xmlString);
|
||||
return Util.UTF8NoBomEncoding.GetBytes(xmlString);
|
||||
}
|
||||
|
||||
byte[] HandleDelete(Dictionary<string, object> request)
|
||||
{
|
||||
if (!request.ContainsKey("UserID"))
|
||||
{
|
||||
return FailureResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
UUID userID = new UUID(request["UserID"].ToString());
|
||||
m_OfflineIMService.DeleteMessages(userID);
|
||||
|
||||
return SuccessResult();
|
||||
}
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void NullResult(Dictionary<string, object> result, string reason)
|
||||
{
|
||||
result["RESULT"] = "NULL";
|
||||
result["REASON"] = reason;
|
||||
}
|
||||
|
||||
private byte[] FailureResult()
|
||||
{
|
||||
return BoolResult(false);
|
||||
}
|
||||
|
||||
private byte[] SuccessResult()
|
||||
{
|
||||
return BoolResult(true);
|
||||
}
|
||||
|
||||
private byte[] BoolResult(bool value)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
|
||||
XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration,
|
||||
"", "");
|
||||
|
||||
doc.AppendChild(xmlnode);
|
||||
|
||||
XmlElement rootElement = doc.CreateElement("", "ServerResponse",
|
||||
"");
|
||||
|
||||
doc.AppendChild(rootElement);
|
||||
|
||||
XmlElement result = doc.CreateElement("", "RESULT", "");
|
||||
result.AppendChild(doc.CreateTextNode(value.ToString()));
|
||||
|
||||
rootElement.AppendChild(result);
|
||||
|
||||
return Util.DocToBytes(doc);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Data;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Services.Base;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
namespace OpenSim.OfflineIM
|
||||
{
|
||||
public class OfflineIMService : ServiceBase, IOfflineIMService
|
||||
{
|
||||
//private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private IOfflineIMData m_Database = null;
|
||||
private int m_MaxOfflineIMs = 25;
|
||||
private XmlSerializer m_serializer;
|
||||
private static bool m_Initialized = false;
|
||||
|
||||
public OfflineIMService(IConfigSource config) : base(config)
|
||||
{
|
||||
string dllName = string.Empty;
|
||||
string connString = string.Empty;
|
||||
string realm = "im_offline";
|
||||
|
||||
//
|
||||
// Try reading the [DatabaseService] section, if it exists
|
||||
//
|
||||
IConfig dbConfig = config.Configs["DatabaseService"];
|
||||
if (dbConfig is not null)
|
||||
{
|
||||
if (dllName.Length == 0)
|
||||
dllName = dbConfig.GetString("StorageProvider", string.Empty);
|
||||
if (connString.Length == 0)
|
||||
connString = dbConfig.GetString("ConnectionString", string.Empty);
|
||||
}
|
||||
|
||||
//
|
||||
// [Messaging] section overrides [DatabaseService], if it exists
|
||||
//
|
||||
IConfig imConfig = config.Configs["Messaging"];
|
||||
if (imConfig is not null)
|
||||
{
|
||||
dllName = imConfig.GetString("StorageProvider", dllName);
|
||||
connString = imConfig.GetString("ConnectionString", connString);
|
||||
realm = imConfig.GetString("Realm", realm);
|
||||
m_MaxOfflineIMs = imConfig.GetInt("MaxOfflineIMs", m_MaxOfflineIMs);
|
||||
}
|
||||
|
||||
//
|
||||
// We tried, but this doesn't exist. We can't proceed.
|
||||
//
|
||||
if (string.IsNullOrEmpty(dllName))
|
||||
throw new Exception("No StorageProvider configured");
|
||||
|
||||
m_Database = LoadPlugin<IOfflineIMData>(dllName, new Object[] { connString, realm });
|
||||
if (m_Database is null)
|
||||
throw new Exception("Could not find a storage interface in the given module " + dllName);
|
||||
|
||||
m_serializer = new XmlSerializer(typeof(GridInstantMessage));
|
||||
if (!m_Initialized)
|
||||
{
|
||||
m_Database.DeleteOld();
|
||||
m_Initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public List<GridInstantMessage> GetMessages(UUID principalID)
|
||||
{
|
||||
List<GridInstantMessage> ims = new List<GridInstantMessage>();
|
||||
|
||||
OfflineIMData[] messages = m_Database.Get("PrincipalID", principalID.ToString());
|
||||
if (messages is null || messages.Length == 0)
|
||||
return ims;
|
||||
|
||||
foreach (OfflineIMData m in messages)
|
||||
{
|
||||
using (MemoryStream mstream = new MemoryStream(Encoding.UTF8.GetBytes(m.Data["Message"])))
|
||||
{
|
||||
GridInstantMessage im = (GridInstantMessage)m_serializer.Deserialize(mstream);
|
||||
ims.Add(im);
|
||||
}
|
||||
}
|
||||
|
||||
// Then, delete them
|
||||
m_Database.Delete("PrincipalID", principalID.ToString());
|
||||
|
||||
return ims;
|
||||
}
|
||||
|
||||
public bool StoreMessage(GridInstantMessage im, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
// Check limits
|
||||
UUID principalID = new UUID(im.toAgentID);
|
||||
long count = m_Database.GetCount("PrincipalID", principalID.ToString());
|
||||
if (count >= m_MaxOfflineIMs)
|
||||
{
|
||||
reason = "Number of offline IMs has maxed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
string imXml;
|
||||
using (MemoryStream mstream = new MemoryStream())
|
||||
{
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.Encoding = Util.UTF8NoBomEncoding;
|
||||
|
||||
using (XmlWriter writer = XmlWriter.Create(mstream, settings))
|
||||
{
|
||||
m_serializer.Serialize(writer, im);
|
||||
writer.Flush();
|
||||
imXml = Util.UTF8NoBomEncoding.GetString(mstream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
OfflineIMData data = new OfflineIMData();
|
||||
data.PrincipalID = principalID;
|
||||
data.FromID = new UUID(im.fromAgentID);
|
||||
data.Data = new Dictionary<string, string>();
|
||||
data.Data["Message"] = imXml;
|
||||
|
||||
return m_Database.Store(data);
|
||||
|
||||
}
|
||||
|
||||
public void DeleteMessages(UUID userID)
|
||||
{
|
||||
m_Database.Delete("PrincipalID", userID.ToString());
|
||||
m_Database.Delete("FromID", userID.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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,24 +95,16 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
|
||||
regionLoader = new RegionLoaderWebServer();
|
||||
}
|
||||
|
||||
// Load Estates Before Regions!
|
||||
if(estateLoader != null)
|
||||
{
|
||||
estateLoader.SetIniConfigSource(m_openSim.ConfigSource.Source);
|
||||
|
||||
estateLoader.LoadEstates();
|
||||
}
|
||||
|
||||
regionLoader.SetIniConfigSource(m_openSim.ConfigSource.Source);
|
||||
RegionInfo[] regionsToLoad = regionLoader.LoadRegions();
|
||||
|
||||
m_log.Info("[LOAD REGIONS PLUGIN]: Loading specific shared modules...");
|
||||
//m_log.Info("[LOAD REGIONS PLUGIN]: DynamicTextureModule...");
|
||||
//m_openSim.ModuleLoader.LoadDefaultSharedModule(new DynamicTextureModule());
|
||||
//m_log.Info("[LOAD REGIONS PLUGIN]: LoadImageURLModule...");
|
||||
//m_openSim.ModuleLoader.LoadDefaultSharedModule(new LoadImageURLModule());
|
||||
//m_log.Info("[LOAD REGIONS PLUGIN]: XMLRPCModule...");
|
||||
//m_openSim.ModuleLoader.LoadDefaultSharedModule(new XMLRPCModule());
|
||||
m_log.Info("[LOAD REGIONS PLUGIN]: DynamicTextureModule...");
|
||||
m_openSim.ModuleLoader.LoadDefaultSharedModule(new DynamicTextureModule());
|
||||
m_log.Info("[LOAD REGIONS PLUGIN]: LoadImageURLModule...");
|
||||
m_openSim.ModuleLoader.LoadDefaultSharedModule(new LoadImageURLModule());
|
||||
m_log.Info("[LOAD REGIONS PLUGIN]: XMLRPCModule...");
|
||||
m_openSim.ModuleLoader.LoadDefaultSharedModule(new XMLRPCModule());
|
||||
// m_log.Info("[LOADREGIONSPLUGIN]: AssetTransactionModule...");
|
||||
// m_openSim.ModuleLoader.LoadDefaultSharedModule(new AssetTransactionModule());
|
||||
m_log.Info("[LOAD REGIONS PLUGIN]: Done.");
|
||||
@@ -127,34 +115,30 @@ namespace OpenSim.ApplicationPlugins.LoadRegions
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
List<IScene> createdScenes = new List<IScene>();
|
||||
|
||||
for (int i = 0; i < regionsToLoad.Length; i++)
|
||||
{
|
||||
IScene scene;
|
||||
m_log.Debug("[LOAD REGIONS PLUGIN]: Creating Region: " + regionsToLoad[i].RegionName + " (ThreadID: " +
|
||||
Thread.CurrentThread.ManagedThreadId.ToString() +
|
||||
")");
|
||||
|
||||
|
||||
bool changed = m_openSim.PopulateRegionEstateInfo(regionsToLoad[i]);
|
||||
|
||||
m_openSim.CreateRegion(regionsToLoad[i], true, out scene);
|
||||
createdScenes.Add(scene);
|
||||
|
||||
if (changed)
|
||||
m_openSim.EstateDataService.StoreEstateSettings(regionsToLoad[i].EstateSettings);
|
||||
}
|
||||
|
||||
foreach (IScene scene in createdScenes)
|
||||
{
|
||||
scene.Start();
|
||||
|
||||
m_newRegionCreatedHandler = OnNewRegionCreated;
|
||||
if (m_newRegionCreatedHandler != null)
|
||||
regionsToLoad[i].EstateSettings.Save();
|
||||
|
||||
if (scene != null)
|
||||
{
|
||||
m_newRegionCreatedHandler(scene);
|
||||
m_newRegionCreatedHandler = OnNewRegionCreated;
|
||||
if (m_newRegionCreatedHandler != null)
|
||||
{
|
||||
m_newRegionCreatedHandler(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_openSim.ModuleLoader.PostInitialise();
|
||||
m_openSim.ModuleLoader.ClearCache();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -175,7 +159,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,7 @@ using Mono.Addins;
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("0.6.5.*")]
|
||||
|
||||
[assembly : AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
[assembly: Addin("OpenSim.ApplicationPlugins.LoadRegions", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly : AssemblyVersion("0.6.5.*")]
|
||||
[assembly : AssemblyFileVersion("0.6.5.0")]
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.LoadRegions
|
||||
{
|
||||
public class RegionLoaderWebServer : IRegionLoader
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private IConfigSource m_configSource;
|
||||
|
||||
public void SetIniConfigSource(IConfigSource configSource)
|
||||
{
|
||||
m_configSource = configSource;
|
||||
}
|
||||
|
||||
public RegionInfo[] LoadRegions()
|
||||
{
|
||||
int tries = 3;
|
||||
int wait = 2000;
|
||||
|
||||
if (m_configSource == null)
|
||||
{
|
||||
m_log.Error("[WEBLOADER]: Unable to load configuration source!");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
IConfig startupConfig = (IConfig)m_configSource.Configs["Startup"];
|
||||
string url = startupConfig.GetString("regionload_webserver_url", String.Empty).Trim();
|
||||
bool allowRegionless = startupConfig.GetBoolean("allow_regionless", false);
|
||||
|
||||
if (url.Length == 0)
|
||||
{
|
||||
m_log.Error("[WEBLOADER]: Unable to load webserver URL - URL was empty.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (tries > 0)
|
||||
{
|
||||
RegionInfo[] regionInfos = Array.Empty<RegionInfo>();
|
||||
int regionCount = 0;
|
||||
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
|
||||
webRequest.Timeout = 30000; //30 Second Timeout
|
||||
m_log.DebugFormat("[WEBLOADER]: Sending download request to {0}", url);
|
||||
|
||||
try
|
||||
{
|
||||
string xmlSource = String.Empty;
|
||||
m_log.Debug("[WEBLOADER]: Downloading region information...");
|
||||
using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
|
||||
using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
|
||||
{
|
||||
string tempStr;
|
||||
while ((tempStr = reader.ReadLine()) != null)
|
||||
{
|
||||
xmlSource += tempStr;
|
||||
}
|
||||
}
|
||||
|
||||
m_log.Debug("[WEBLOADER]: Done downloading region information from server. Total Bytes: " +
|
||||
xmlSource.Length);
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(xmlSource);
|
||||
if (xmlDoc.FirstChild.Name == "Nini")
|
||||
{
|
||||
regionCount = xmlDoc.FirstChild.ChildNodes.Count;
|
||||
|
||||
if (regionCount > 0)
|
||||
{
|
||||
regionInfos = new RegionInfo[regionCount];
|
||||
int i;
|
||||
for (i = 0; i < xmlDoc.FirstChild.ChildNodes.Count; i++)
|
||||
{
|
||||
m_log.Debug(xmlDoc.FirstChild.ChildNodes[i].OuterXml);
|
||||
regionInfos[i] =
|
||||
new RegionInfo("REGION CONFIG #" + (i + 1), xmlDoc.FirstChild.ChildNodes[i], false, m_configSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
if (!allowRegionless)
|
||||
throw;
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
|
||||
if (regionCount > 0 || allowRegionless)
|
||||
return regionInfos;
|
||||
|
||||
m_log.Debug("[WEBLOADER]: Request yielded no regions.");
|
||||
tries--;
|
||||
if (tries > 0)
|
||||
{
|
||||
m_log.Debug("[WEBLOADER]: Retrying");
|
||||
System.Threading.Thread.Sleep(wait);
|
||||
}
|
||||
}
|
||||
|
||||
m_log.Error("[WEBLOADER]: No region configs were available.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenSim.ApplicationPlugins.RegionModulesController")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("http://opensimulator.org")]
|
||||
[assembly: AssemblyProduct("OpenSim")]
|
||||
[assembly: AssemblyCopyright("OpenSimulator developers")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("c023816d-194e-40c1-9195-a0f281d4ac5d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
[assembly: Addin("OpenSim.ApplicationPlugins.RegionModulesController", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
|
||||
@@ -32,24 +32,18 @@ using log4net;
|
||||
using Mono.Addins;
|
||||
using Nini.Config;
|
||||
using OpenSim;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
{
|
||||
[Extension(Path = "/OpenSim/Startup", Id = "LoadRegions", NodeName = "Plugin")]
|
||||
public class RegionModulesControllerPlugin : IRegionModulesController,
|
||||
IApplicationPlugin
|
||||
{
|
||||
// Logger
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether we load modules from Mono.Addins.
|
||||
/// </summary>
|
||||
/// <remarks>For debug purposes. Defaults to true.</remarks>
|
||||
public bool LoadModulesFromAddins { get; set; }
|
||||
private static readonly ILog m_log =
|
||||
LogManager.GetLogger(
|
||||
MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
// Config access
|
||||
private OpenSimBase m_openSim;
|
||||
@@ -58,28 +52,23 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
private string m_name;
|
||||
|
||||
// Internal lists to collect information about modules present
|
||||
private List<TypeExtensionNode> m_nonSharedModules = new List<TypeExtensionNode>();
|
||||
private List<TypeExtensionNode> m_sharedModules = new List<TypeExtensionNode>();
|
||||
private List<TypeExtensionNode> m_nonSharedModules =
|
||||
new List<TypeExtensionNode>();
|
||||
private List<TypeExtensionNode> m_sharedModules =
|
||||
new List<TypeExtensionNode>();
|
||||
|
||||
// List of shared module instances, for adding to Scenes
|
||||
private List<ISharedRegionModule> m_sharedInstances = new List<ISharedRegionModule>();
|
||||
|
||||
public RegionModulesControllerPlugin()
|
||||
{
|
||||
LoadModulesFromAddins = true;
|
||||
}
|
||||
private List<ISharedRegionModule> m_sharedInstances =
|
||||
new List<ISharedRegionModule>();
|
||||
|
||||
#region IApplicationPlugin implementation
|
||||
|
||||
|
||||
public void Initialise (OpenSimBase openSim)
|
||||
{
|
||||
m_openSim = openSim;
|
||||
m_openSim.ApplicationRegistry.RegisterInterface<IRegionModulesController>(this);
|
||||
m_log.DebugFormat("[REGIONMODULES]: Initializing...");
|
||||
|
||||
if (!LoadModulesFromAddins)
|
||||
return;
|
||||
|
||||
// Who we are
|
||||
string id = AddinManager.CurrentAddin.Id;
|
||||
|
||||
@@ -91,24 +80,35 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
m_name = id.Substring(pos + 1);
|
||||
|
||||
// The [Modules] section in the ini file
|
||||
IConfig modulesConfig = m_openSim.ConfigSource.Source.Configs["Modules"];
|
||||
IConfig modulesConfig =
|
||||
m_openSim.ConfigSource.Source.Configs["Modules"];
|
||||
if (modulesConfig == null)
|
||||
modulesConfig = m_openSim.ConfigSource.Source.AddConfig("Modules");
|
||||
|
||||
Dictionary<RuntimeAddin, IList<int>> loadedModules = new Dictionary<RuntimeAddin, IList<int>>();
|
||||
|
||||
// Scan modules and load all that aren't disabled
|
||||
foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes("/OpenSim/RegionModules"))
|
||||
AddNode(node, modulesConfig, loadedModules);
|
||||
|
||||
foreach (KeyValuePair<RuntimeAddin, IList<int>> loadedModuleData in loadedModules)
|
||||
foreach (TypeExtensionNode node in
|
||||
AddinManager.GetExtensionNodes("/OpenSim/RegionModules"))
|
||||
{
|
||||
m_log.InfoFormat(
|
||||
"[REGIONMODULES]: From plugin {0}, (version {1}), loaded {2} modules, {3} shared, {4} non-shared {5} unknown",
|
||||
loadedModuleData.Key.Id,
|
||||
loadedModuleData.Key.Version,
|
||||
loadedModuleData.Value[0] + loadedModuleData.Value[1] + loadedModuleData.Value[2],
|
||||
loadedModuleData.Value[0], loadedModuleData.Value[1], loadedModuleData.Value[2]);
|
||||
if (node.Type.GetInterface(typeof(ISharedRegionModule).ToString()) != null)
|
||||
{
|
||||
if (CheckModuleEnabled(node, modulesConfig))
|
||||
{
|
||||
m_log.DebugFormat("[REGIONMODULES]: Found shared region module {0}, class {1}", node.Id, node.Type);
|
||||
m_sharedModules.Add(node);
|
||||
}
|
||||
}
|
||||
else if (node.Type.GetInterface(typeof(INonSharedRegionModule).ToString()) != null)
|
||||
{
|
||||
if (CheckModuleEnabled(node, modulesConfig))
|
||||
{
|
||||
m_log.DebugFormat("[REGIONMODULES]: Found non-shared region module {0}, class {1}", node.Id, node.Type);
|
||||
m_nonSharedModules.Add(node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.DebugFormat("[REGIONMODULES]: Found unknown type of module {0}, class {1}", node.Id, node.Type);
|
||||
}
|
||||
}
|
||||
|
||||
// Load and init the module. We try a constructor with a port
|
||||
@@ -120,19 +120,18 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
//
|
||||
foreach (TypeExtensionNode node in m_sharedModules)
|
||||
{
|
||||
object[] ctorArgs = new object[] { (uint)0 };
|
||||
Object[] ctorArgs = new Object[] { (uint)0 };
|
||||
|
||||
// Read the config again
|
||||
string moduleString = modulesConfig.GetString("Setup_" + node.Id, string.Empty);
|
||||
// Test to see if we want this module
|
||||
if (moduleString == "disabled")
|
||||
continue;
|
||||
string moduleString =
|
||||
modulesConfig.GetString("Setup_" + node.Id, String.Empty);
|
||||
|
||||
// Get the port number, if there is one
|
||||
if (moduleString != string.Empty)
|
||||
if (moduleString != String.Empty)
|
||||
{
|
||||
// Get the port number from the string
|
||||
string[] moduleParts = moduleString.Split(new char[] { '/' }, 2);
|
||||
string[] moduleParts = moduleString.Split(new char[] { '/' },
|
||||
2);
|
||||
if (moduleParts.Length > 1)
|
||||
ctorArgs[0] = Convert.ToUInt32(moduleParts[0]);
|
||||
}
|
||||
@@ -143,11 +142,13 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
|
||||
try
|
||||
{
|
||||
module = (ISharedRegionModule)Activator.CreateInstance(node.Type, ctorArgs);
|
||||
module = (ISharedRegionModule)Activator.CreateInstance(
|
||||
node.Type, ctorArgs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
module = (ISharedRegionModule)Activator.CreateInstance(node.Type);
|
||||
module = (ISharedRegionModule)Activator.CreateInstance(
|
||||
node.Type);
|
||||
}
|
||||
|
||||
// OK, we're up and running
|
||||
@@ -171,40 +172,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
|
||||
#region IPlugin implementation
|
||||
|
||||
private void AddNode(TypeExtensionNode node, IConfig modulesConfig, Dictionary<RuntimeAddin, IList<int>> loadedModules)
|
||||
{
|
||||
IList<int> loadedModuleData;
|
||||
|
||||
if (!loadedModules.ContainsKey(node.Addin))
|
||||
loadedModules.Add(node.Addin, new List<int> { 0, 0, 0 });
|
||||
|
||||
loadedModuleData = loadedModules[node.Addin];
|
||||
|
||||
if (node.Type.GetInterface(typeof(ISharedRegionModule).ToString()) != null)
|
||||
{
|
||||
if (CheckModuleEnabled(node, modulesConfig))
|
||||
{
|
||||
m_log.DebugFormat("[REGIONMODULES]: Found shared region module {0}, class {1}", node.Id, node.Type);
|
||||
m_sharedModules.Add(node);
|
||||
loadedModuleData[0]++;
|
||||
}
|
||||
}
|
||||
else if (node.Type.GetInterface(typeof(INonSharedRegionModule).ToString()) != null)
|
||||
{
|
||||
if (CheckModuleEnabled(node, modulesConfig))
|
||||
{
|
||||
m_log.DebugFormat("[REGIONMODULES]: Found non-shared region module {0}, class {1}", node.Id, node.Type);
|
||||
m_nonSharedModules.Add(node);
|
||||
loadedModuleData[1]++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.WarnFormat("[REGIONMODULES]: Found unknown type of module {0}, class {1}", node.Id, node.Type);
|
||||
loadedModuleData[2]++;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't do that here
|
||||
//
|
||||
public void Initialise ()
|
||||
@@ -226,7 +193,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
m_sharedInstances[0].Close();
|
||||
m_sharedInstances.RemoveAt(0);
|
||||
}
|
||||
|
||||
m_sharedModules.Clear();
|
||||
m_nonSharedModules.Clear();
|
||||
}
|
||||
@@ -249,8 +215,8 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
}
|
||||
}
|
||||
|
||||
#region Region Module interfacesController implementation
|
||||
|
||||
#region IRegionModulesController implementation
|
||||
|
||||
/// <summary>
|
||||
/// Check that the given module is no disabled in the [Modules] section of the config files.
|
||||
/// </summary>
|
||||
@@ -260,10 +226,11 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
protected bool CheckModuleEnabled(TypeExtensionNode node, IConfig modulesConfig)
|
||||
{
|
||||
// Get the config string
|
||||
string moduleString = modulesConfig.GetString("Setup_" + node.Id, string.Empty);
|
||||
string moduleString =
|
||||
modulesConfig.GetString("Setup_" + node.Id, String.Empty);
|
||||
|
||||
// We have a selector
|
||||
if (!string.IsNullOrEmpty(moduleString))
|
||||
if (moduleString != String.Empty)
|
||||
{
|
||||
// Allow disabling modules even if they don't have
|
||||
// support for it
|
||||
@@ -281,10 +248,10 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
if (className != String.Empty &&
|
||||
node.Type.ToString() != className)
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The root of all evil.
|
||||
// This is where we handle adding the modules to scenes when they
|
||||
@@ -293,8 +260,10 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
//
|
||||
public void AddRegionToModules (Scene scene)
|
||||
{
|
||||
Dictionary<Type, ISharedRegionModule> deferredSharedModules = new Dictionary<Type, ISharedRegionModule>();
|
||||
Dictionary<Type, INonSharedRegionModule> deferredNonSharedModules = new Dictionary<Type, INonSharedRegionModule>();
|
||||
Dictionary<Type, ISharedRegionModule> deferredSharedModules =
|
||||
new Dictionary<Type, ISharedRegionModule>();
|
||||
Dictionary<Type, INonSharedRegionModule> deferredNonSharedModules =
|
||||
new Dictionary<Type, INonSharedRegionModule>();
|
||||
|
||||
// We need this to see if a module has already been loaded and
|
||||
// has defined a replaceable interface. It's a generic call,
|
||||
@@ -303,7 +272,8 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
MethodInfo mi = s.GetMethod("RequestModuleInterface");
|
||||
|
||||
// This will hold the shared modules we actually load
|
||||
List<ISharedRegionModule> sharedlist = new List<ISharedRegionModule>();
|
||||
List<ISharedRegionModule> sharedlist =
|
||||
new List<ISharedRegionModule>();
|
||||
|
||||
// Iterate over the shared modules that have been loaded
|
||||
// Add them to the new Scene
|
||||
@@ -340,26 +310,25 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
sharedlist.Add(module);
|
||||
}
|
||||
|
||||
IConfig modulesConfig = m_openSim.ConfigSource.Source.Configs["Modules"];
|
||||
IConfig modulesConfig =
|
||||
m_openSim.ConfigSource.Source.Configs["Modules"];
|
||||
|
||||
// Scan for, and load, nonshared modules
|
||||
List<INonSharedRegionModule> list = new List<INonSharedRegionModule>();
|
||||
foreach (TypeExtensionNode node in m_nonSharedModules)
|
||||
{
|
||||
object[] ctorArgs = new object[] {0};
|
||||
Object[] ctorArgs = new Object[] {0};
|
||||
|
||||
// Read the config
|
||||
string moduleString = modulesConfig.GetString("Setup_" + node.Id, string.Empty);
|
||||
|
||||
// We may not want to load this at all
|
||||
if (moduleString == "disabled")
|
||||
continue;
|
||||
string moduleString =
|
||||
modulesConfig.GetString("Setup_" + node.Id, String.Empty);
|
||||
|
||||
// Get the port number, if there is one
|
||||
if (!string.IsNullOrEmpty(moduleString))
|
||||
if (moduleString != String.Empty)
|
||||
{
|
||||
// Get the port number from the string
|
||||
string[] moduleParts = moduleString.Split(new char[] {'/'}, 2);
|
||||
string[] moduleParts = moduleString.Split(new char[] {'/'},
|
||||
2);
|
||||
if (moduleParts.Length > 1)
|
||||
ctorArgs[0] = Convert.ToUInt32(moduleParts[0]);
|
||||
}
|
||||
@@ -438,7 +407,8 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
}
|
||||
|
||||
// Same thing for nonshared modules, load them unless overridden
|
||||
List<INonSharedRegionModule> deferredlist = new List<INonSharedRegionModule>();
|
||||
List<INonSharedRegionModule> deferredlist =
|
||||
new List<INonSharedRegionModule>();
|
||||
|
||||
foreach (INonSharedRegionModule module in deferredNonSharedModules.Values)
|
||||
{
|
||||
@@ -490,8 +460,6 @@ namespace OpenSim.ApplicationPlugins.RegionModulesController
|
||||
{
|
||||
module.RegionLoaded(scene);
|
||||
}
|
||||
|
||||
scene.AllModulesLoaded();
|
||||
}
|
||||
|
||||
public void RemoveRegionFromModules (Scene scene)
|
||||
|
||||
@@ -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,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Addins;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenSim.ApplicationPlugins.RemoteController")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("http://opensimulator.org")]
|
||||
[assembly: AssemblyProduct("OpenSim")]
|
||||
[assembly: AssemblyCopyright("Copyright OpenSimulator developers © 2012")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("efec6e69-fc4a-4e21-86e6-4a261c12d4db")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
[assembly: Addin("OpenSim.ApplicationPlugins.RemoteController", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDependency("OpenSim", OpenSim.VersionInfo.VersionNumber)]
|
||||
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>
|
||||
43
OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs
Normal file
43
OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface represents the boundary between the general purpose
|
||||
/// REST plugin handling, and the functionally specific handlers. The
|
||||
/// handler knows only to initialize and terminate all such handlers
|
||||
/// that it finds. Implementing this interface identifies the class as
|
||||
/// a REST handler implementation.
|
||||
/// </summary>
|
||||
|
||||
internal interface IRest
|
||||
{
|
||||
void Initialize();
|
||||
void Close();
|
||||
}
|
||||
}
|
||||
@@ -25,30 +25,35 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
using OMV = OpenMetaverse;
|
||||
|
||||
namespace osWebRtcVoice
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
|
||||
/// <remarks>
|
||||
/// The handler delegates are not noteworthy. The allocator allows
|
||||
/// a given handler to optionally subclass the base RequestData
|
||||
/// structure to carry any locally required per-request state
|
||||
/// needed.
|
||||
/// </remarks>
|
||||
|
||||
public delegate void RestMethodHandler(RequestData rdata);
|
||||
public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path);
|
||||
|
||||
/// <summary>
|
||||
/// This is the interface for the viewer session. It is used to store the
|
||||
/// state of the viewer session and to disconnect the session when needed.
|
||||
/// This interface exports the generic plugin-handling services
|
||||
/// available to each loaded REST services module (IRest implementation)
|
||||
/// </summary>
|
||||
public interface IVoiceViewerSession
|
||||
|
||||
internal interface IRestHandler
|
||||
{
|
||||
// This ID is passed to and from the viewer to identify the session
|
||||
public string ViewerSessionID { get; set; }
|
||||
public IWebRtcVoiceService VoiceService { get; set; }
|
||||
// THis ID is passed between us and the voice service to idetify the session
|
||||
public string VoiceServiceSessionId { get; set; }
|
||||
// The UUID of the region that is being connected to
|
||||
public OMV.UUID RegionId { get; set; }
|
||||
|
||||
// The simulator has a GUID to identify the user
|
||||
public OMV.UUID AgentId { get; set; }
|
||||
string MsgId { get; }
|
||||
string RequestId { get; }
|
||||
|
||||
void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ma);
|
||||
void AddStreamHandler(string httpMethod, string path, RestMethod method);
|
||||
|
||||
// Disconnect the connection to the voice service for this session
|
||||
public Task Shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
1465
OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
Normal file
1465
OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
<Addin id="OpenSim.ApplicationPlugins.Rest.Inventory" version="0.1">
|
||||
<Runtime>
|
||||
<Import assembly="OpenSim.ApplicationPlugins.Rest.Inventory.dll"/>
|
||||
</Runtime>
|
||||
<Dependencies>
|
||||
<Addin id="OpenSim" version="0.5" />
|
||||
</Dependencies>
|
||||
<Extension path = "/OpenSim/Startup">
|
||||
<Plugin id="RestInventory" type="OpenSim.ApplicationPlugins.Rest.Inventory.RestHandler" />
|
||||
</Extension>
|
||||
</Addin>
|
||||
551
OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
Normal file
551
OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
Normal file
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Communications;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using IAvatarService = OpenSim.Services.Interfaces.IAvatarService;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
public class Rest
|
||||
{
|
||||
internal static readonly ILog Log =
|
||||
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
internal static bool DEBUG = Log.IsDebugEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Supported authentication schemes
|
||||
/// </summary>
|
||||
|
||||
public const string AS_BASIC = "Basic"; // simple user/password verification
|
||||
public const string AS_DIGEST = "Digest"; // password safe authentication
|
||||
|
||||
/// Supported Digest algorithms
|
||||
|
||||
public const string Digest_MD5 = "MD5"; // assumed default if omitted
|
||||
public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST?
|
||||
|
||||
public const string Qop_Auth = "auth"; // authentication only
|
||||
public const string Qop_Int = "auth-int"; // TODO
|
||||
|
||||
/// <summary>
|
||||
/// These values have a single value for the whole
|
||||
/// domain and lifetime of the plugin handler. We
|
||||
/// make them static for ease of reference within
|
||||
/// the assembly. These are initialized by the
|
||||
/// RestHandler class during start-up.
|
||||
/// </summary>
|
||||
|
||||
internal static IRestHandler Plugin = null;
|
||||
internal static OpenSimBase main = null;
|
||||
internal static string Prefix = null;
|
||||
internal static IConfig Config = null;
|
||||
internal static string GodKey = null;
|
||||
internal static bool Authenticate = true;
|
||||
internal static bool Secure = true;
|
||||
internal static bool ExtendedEscape = true;
|
||||
internal static bool DumpAsset = false;
|
||||
internal static bool Fill = true;
|
||||
internal static bool FlushEnabled = true;
|
||||
internal static string Realm = "OpenSim REST";
|
||||
internal static string Scheme = AS_BASIC;
|
||||
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
|
||||
|
||||
/// <summary>
|
||||
/// These are all dependent upon the Comms manager
|
||||
/// being initialized. So they have to be properties
|
||||
/// because the comms manager is now a module and is
|
||||
/// not guaranteed to be there when the rest handler
|
||||
/// initializes.
|
||||
/// </summary>
|
||||
|
||||
internal static IInventoryService InventoryServices
|
||||
{
|
||||
get { return main.SceneManager.CurrentOrFirstScene.InventoryService; }
|
||||
}
|
||||
|
||||
internal static IUserAccountService UserServices
|
||||
{
|
||||
get { return main.SceneManager.CurrentOrFirstScene.UserAccountService; }
|
||||
}
|
||||
|
||||
internal static IAuthenticationService AuthServices
|
||||
{
|
||||
get { return main.SceneManager.CurrentOrFirstScene.AuthenticationService; }
|
||||
}
|
||||
|
||||
internal static IAvatarService AvatarServices
|
||||
{
|
||||
get { return main.SceneManager.CurrentOrFirstScene.AvatarService; }
|
||||
}
|
||||
|
||||
internal static IAssetService AssetServices
|
||||
{
|
||||
get { return main.SceneManager.CurrentOrFirstScene.AssetService; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP requires that status information be generated for PUT
|
||||
/// and POST opertaions. This is in support of that. The
|
||||
/// operation verb gets substituted into the first string,
|
||||
/// and the completion code is inserted into the tail. The
|
||||
/// strings are put here to encourage consistency.
|
||||
/// </summary>
|
||||
|
||||
internal static string statusHead = "<html><body><title>{0} status</title><break>";
|
||||
internal static string statusTail = "</body></html>";
|
||||
|
||||
internal static Dictionary<int,string> HttpStatusDesc;
|
||||
|
||||
static Rest()
|
||||
{
|
||||
HttpStatusDesc = new Dictionary<int,string>();
|
||||
if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length)
|
||||
{
|
||||
Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match");
|
||||
throw new Exception("HTTP Status array discrepancy");
|
||||
}
|
||||
|
||||
// Repackage the data into something more tractable. The sparse
|
||||
// nature of HTTP return codes makes an array a bad choice.
|
||||
|
||||
for (int i=0; i<HttpStatusCodeArray.Length; i++)
|
||||
{
|
||||
HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
internal static int CreationDate
|
||||
{
|
||||
get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; }
|
||||
}
|
||||
|
||||
internal static string MsgId
|
||||
{
|
||||
get { return Plugin.MsgId; }
|
||||
}
|
||||
|
||||
internal static string RequestId
|
||||
{
|
||||
get { return Plugin.RequestId; }
|
||||
}
|
||||
|
||||
internal static Encoding Encoding = Util.UTF8;
|
||||
|
||||
/// <summary>
|
||||
/// Version control for REST implementation. This
|
||||
/// refers to the overall infrastructure represented
|
||||
/// by the following classes
|
||||
/// RequestData
|
||||
/// RequestInventoryPlugin
|
||||
/// Rest
|
||||
/// It does no describe implementation classes such as
|
||||
/// RestInventoryServices, which may morph much more
|
||||
/// often. Such classes ARE dependent upon this however
|
||||
/// and should check it in their Initialize method.
|
||||
/// </summary>
|
||||
|
||||
public static readonly float Version = 1.0F;
|
||||
public const string Name = "REST 1.0";
|
||||
|
||||
/// <summary>
|
||||
/// Currently defined HTTP methods.
|
||||
/// Only GET and HEAD are required to be
|
||||
/// supported by all servers. See Respond
|
||||
/// to see how these are handled.
|
||||
/// </summary>
|
||||
|
||||
// REST AGENT 1.0 interpretations
|
||||
public const string GET = "get"; // information retrieval - server state unchanged
|
||||
public const string HEAD = "head"; // same as get except only the headers are returned.
|
||||
public const string POST = "post"; // Replace the URI designated resource with the entity.
|
||||
public const string PUT = "put"; // Add the entity to the context represented by the URI
|
||||
public const string DELETE = "delete"; // Remove the URI designated resource from the server.
|
||||
|
||||
public const string OPTIONS = "options"; //
|
||||
public const string TRACE = "trace"; //
|
||||
public const string CONNECT = "connect"; //
|
||||
|
||||
// Define this in one place...
|
||||
|
||||
public const string UrlPathSeparator = "/";
|
||||
public const string UrlMethodSeparator = ":";
|
||||
|
||||
// Redirection qualifications
|
||||
|
||||
public const bool PERMANENT = false;
|
||||
public const bool TEMPORARY = true;
|
||||
|
||||
// Constant arrays used by String.Split
|
||||
|
||||
public static readonly char C_SPACE = ' ';
|
||||
public static readonly char C_SLASH = '/';
|
||||
public static readonly char C_PATHSEP = '/';
|
||||
public static readonly char C_COLON = ':';
|
||||
public static readonly char C_PLUS = '+';
|
||||
public static readonly char C_PERIOD = '.';
|
||||
public static readonly char C_COMMA = ',';
|
||||
public static readonly char C_DQUOTE = '"';
|
||||
|
||||
public static readonly string CS_SPACE = " ";
|
||||
public static readonly string CS_SLASH = "/";
|
||||
public static readonly string CS_PATHSEP = "/";
|
||||
public static readonly string CS_COLON = ":";
|
||||
public static readonly string CS_PLUS = "+";
|
||||
public static readonly string CS_PERIOD = ".";
|
||||
public static readonly string CS_COMMA = ",";
|
||||
public static readonly string CS_DQUOTE = "\"";
|
||||
|
||||
public static readonly char[] CA_SPACE = { C_SPACE };
|
||||
public static readonly char[] CA_SLASH = { C_SLASH };
|
||||
public static readonly char[] CA_PATHSEP = { C_PATHSEP };
|
||||
public static readonly char[] CA_COLON = { C_COLON };
|
||||
public static readonly char[] CA_PERIOD = { C_PERIOD };
|
||||
public static readonly char[] CA_PLUS = { C_PLUS };
|
||||
public static readonly char[] CA_COMMA = { C_COMMA };
|
||||
public static readonly char[] CA_DQUOTE = { C_DQUOTE };
|
||||
|
||||
// HTTP Code Values (in value order)
|
||||
|
||||
public const int HttpStatusCodeContinue = 100;
|
||||
public const int HttpStatusCodeSwitchingProtocols = 101;
|
||||
|
||||
public const int HttpStatusCodeOK = 200;
|
||||
public const int HttpStatusCodeCreated = 201;
|
||||
public const int HttpStatusCodeAccepted = 202;
|
||||
public const int HttpStatusCodeNonAuthoritative = 203;
|
||||
public const int HttpStatusCodeNoContent = 204;
|
||||
public const int HttpStatusCodeResetContent = 205;
|
||||
public const int HttpStatusCodePartialContent = 206;
|
||||
|
||||
public const int HttpStatusCodeMultipleChoices = 300;
|
||||
public const int HttpStatusCodePermanentRedirect = 301;
|
||||
public const int HttpStatusCodeFound = 302;
|
||||
public const int HttpStatusCodeSeeOther = 303;
|
||||
public const int HttpStatusCodeNotModified = 304;
|
||||
public const int HttpStatusCodeUseProxy = 305;
|
||||
public const int HttpStatusCodeReserved306 = 306;
|
||||
public const int HttpStatusCodeTemporaryRedirect = 307;
|
||||
|
||||
public const int HttpStatusCodeBadRequest = 400;
|
||||
public const int HttpStatusCodeNotAuthorized = 401;
|
||||
public const int HttpStatusCodePaymentRequired = 402;
|
||||
public const int HttpStatusCodeForbidden = 403;
|
||||
public const int HttpStatusCodeNotFound = 404;
|
||||
public const int HttpStatusCodeMethodNotAllowed = 405;
|
||||
public const int HttpStatusCodeNotAcceptable = 406;
|
||||
public const int HttpStatusCodeProxyAuthenticate = 407;
|
||||
public const int HttpStatusCodeTimeOut = 408;
|
||||
public const int HttpStatusCodeConflict = 409;
|
||||
public const int HttpStatusCodeGone = 410;
|
||||
public const int HttpStatusCodeLengthRequired = 411;
|
||||
public const int HttpStatusCodePreconditionFailed = 412;
|
||||
public const int HttpStatusCodeEntityTooLarge = 413;
|
||||
public const int HttpStatusCodeUriTooLarge = 414;
|
||||
public const int HttpStatusCodeUnsupportedMedia = 415;
|
||||
public const int HttpStatusCodeRangeNotSatsified = 416;
|
||||
public const int HttpStatusCodeExpectationFailed = 417;
|
||||
|
||||
public const int HttpStatusCodeServerError = 500;
|
||||
public const int HttpStatusCodeNotImplemented = 501;
|
||||
public const int HttpStatusCodeBadGateway = 502;
|
||||
public const int HttpStatusCodeServiceUnavailable = 503;
|
||||
public const int HttpStatusCodeGatewayTimeout = 504;
|
||||
public const int HttpStatusCodeHttpVersionError = 505;
|
||||
|
||||
public static readonly int[] HttpStatusCodeArray = {
|
||||
HttpStatusCodeContinue,
|
||||
HttpStatusCodeSwitchingProtocols,
|
||||
HttpStatusCodeOK,
|
||||
HttpStatusCodeCreated,
|
||||
HttpStatusCodeAccepted,
|
||||
HttpStatusCodeNonAuthoritative,
|
||||
HttpStatusCodeNoContent,
|
||||
HttpStatusCodeResetContent,
|
||||
HttpStatusCodePartialContent,
|
||||
HttpStatusCodeMultipleChoices,
|
||||
HttpStatusCodePermanentRedirect,
|
||||
HttpStatusCodeFound,
|
||||
HttpStatusCodeSeeOther,
|
||||
HttpStatusCodeNotModified,
|
||||
HttpStatusCodeUseProxy,
|
||||
HttpStatusCodeReserved306,
|
||||
HttpStatusCodeTemporaryRedirect,
|
||||
HttpStatusCodeBadRequest,
|
||||
HttpStatusCodeNotAuthorized,
|
||||
HttpStatusCodePaymentRequired,
|
||||
HttpStatusCodeForbidden,
|
||||
HttpStatusCodeNotFound,
|
||||
HttpStatusCodeMethodNotAllowed,
|
||||
HttpStatusCodeNotAcceptable,
|
||||
HttpStatusCodeProxyAuthenticate,
|
||||
HttpStatusCodeTimeOut,
|
||||
HttpStatusCodeConflict,
|
||||
HttpStatusCodeGone,
|
||||
HttpStatusCodeLengthRequired,
|
||||
HttpStatusCodePreconditionFailed,
|
||||
HttpStatusCodeEntityTooLarge,
|
||||
HttpStatusCodeUriTooLarge,
|
||||
HttpStatusCodeUnsupportedMedia,
|
||||
HttpStatusCodeRangeNotSatsified,
|
||||
HttpStatusCodeExpectationFailed,
|
||||
HttpStatusCodeServerError,
|
||||
HttpStatusCodeNotImplemented,
|
||||
HttpStatusCodeBadGateway,
|
||||
HttpStatusCodeServiceUnavailable,
|
||||
HttpStatusCodeGatewayTimeout,
|
||||
HttpStatusCodeHttpVersionError
|
||||
};
|
||||
|
||||
// HTTP Status Descriptions (in status code order)
|
||||
// This array must be kept strictly consistent with respect
|
||||
// to the status code array above.
|
||||
|
||||
public static readonly string[] HttpStatusDescArray = {
|
||||
"Continue Request",
|
||||
"Switching Protocols",
|
||||
"OK",
|
||||
"CREATED",
|
||||
"ACCEPTED",
|
||||
"NON-AUTHORITATIVE INFORMATION",
|
||||
"NO CONTENT",
|
||||
"RESET CONTENT",
|
||||
"PARTIAL CONTENT",
|
||||
"MULTIPLE CHOICES",
|
||||
"PERMANENT REDIRECT",
|
||||
"FOUND",
|
||||
"SEE OTHER",
|
||||
"NOT MODIFIED",
|
||||
"USE PROXY",
|
||||
"RESERVED CODE 306",
|
||||
"TEMPORARY REDIRECT",
|
||||
"BAD REQUEST",
|
||||
"NOT AUTHORIZED",
|
||||
"PAYMENT REQUIRED",
|
||||
"FORBIDDEN",
|
||||
"NOT FOUND",
|
||||
"METHOD NOT ALLOWED",
|
||||
"NOT ACCEPTABLE",
|
||||
"PROXY AUTHENTICATION REQUIRED",
|
||||
"TIMEOUT",
|
||||
"CONFLICT",
|
||||
"GONE",
|
||||
"LENGTH REQUIRED",
|
||||
"PRECONDITION FAILED",
|
||||
"ENTITY TOO LARGE",
|
||||
"URI TOO LARGE",
|
||||
"UNSUPPORTED MEDIA",
|
||||
"RANGE NOT SATISFIED",
|
||||
"EXPECTATION FAILED",
|
||||
"SERVER ERROR",
|
||||
"NOT IMPLEMENTED",
|
||||
"BAD GATEWAY",
|
||||
"SERVICE UNAVAILABLE",
|
||||
"GATEWAY TIMEOUT",
|
||||
"HTTP VERSION NOT SUPPORTED"
|
||||
};
|
||||
|
||||
// HTTP Headers
|
||||
|
||||
public const string HttpHeaderAccept = "Accept";
|
||||
public const string HttpHeaderAcceptCharset = "Accept-Charset";
|
||||
public const string HttpHeaderAcceptEncoding = "Accept-Encoding";
|
||||
public const string HttpHeaderAcceptLanguage = "Accept-Language";
|
||||
public const string HttpHeaderAcceptRanges = "Accept-Ranges";
|
||||
public const string HttpHeaderAge = "Age";
|
||||
public const string HttpHeaderAllow = "Allow";
|
||||
public const string HttpHeaderAuthorization = "Authorization";
|
||||
public const string HttpHeaderCacheControl = "Cache-Control";
|
||||
public const string HttpHeaderConnection = "Connection";
|
||||
public const string HttpHeaderContentEncoding = "Content-Encoding";
|
||||
public const string HttpHeaderContentLanguage = "Content-Language";
|
||||
public const string HttpHeaderContentLength = "Content-Length";
|
||||
public const string HttpHeaderContentLocation = "Content-Location";
|
||||
public const string HttpHeaderContentMD5 = "Content-MD5";
|
||||
public const string HttpHeaderContentRange = "Content-Range";
|
||||
public const string HttpHeaderContentType = "Content-Type";
|
||||
public const string HttpHeaderDate = "Date";
|
||||
public const string HttpHeaderETag = "ETag";
|
||||
public const string HttpHeaderExpect = "Expect";
|
||||
public const string HttpHeaderExpires = "Expires";
|
||||
public const string HttpHeaderFrom = "From";
|
||||
public const string HttpHeaderHost = "Host";
|
||||
public const string HttpHeaderIfMatch = "If-Match";
|
||||
public const string HttpHeaderIfModifiedSince = "If-Modified-Since";
|
||||
public const string HttpHeaderIfNoneMatch = "If-None-Match";
|
||||
public const string HttpHeaderIfRange = "If-Range";
|
||||
public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since";
|
||||
public const string HttpHeaderLastModified = "Last-Modified";
|
||||
public const string HttpHeaderLocation = "Location";
|
||||
public const string HttpHeaderMaxForwards = "Max-Forwards";
|
||||
public const string HttpHeaderPragma = "Pragma";
|
||||
public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate";
|
||||
public const string HttpHeaderProxyAuthorization = "Proxy-Authorization";
|
||||
public const string HttpHeaderRange = "Range";
|
||||
public const string HttpHeaderReferer = "Referer";
|
||||
public const string HttpHeaderRetryAfter = "Retry-After";
|
||||
public const string HttpHeaderServer = "Server";
|
||||
public const string HttpHeaderTE = "TE";
|
||||
public const string HttpHeaderTrailer = "Trailer";
|
||||
public const string HttpHeaderTransferEncoding = "Transfer-Encoding";
|
||||
public const string HttpHeaderUpgrade = "Upgrade";
|
||||
public const string HttpHeaderUserAgent = "User-Agent";
|
||||
public const string HttpHeaderVary = "Vary";
|
||||
public const string HttpHeaderVia = "Via";
|
||||
public const string HttpHeaderWarning = "Warning";
|
||||
public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
|
||||
|
||||
/// Utility routines
|
||||
|
||||
public static string StringToBase64(string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] encData_byte = new byte[str.Length];
|
||||
encData_byte = Util.UTF8.GetBytes(str);
|
||||
return Convert.ToBase64String(encData_byte);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Base64ToString(string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Util.Base64ToString(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private const string hvals = "0123456789abcdef";
|
||||
|
||||
public static int Hex2Int(string hex)
|
||||
{
|
||||
int val = 0;
|
||||
int sum = 0;
|
||||
string tmp = null;
|
||||
|
||||
if (hex != null)
|
||||
{
|
||||
tmp = hex.ToLower();
|
||||
for (int i = 0; i < tmp.Length; i++)
|
||||
{
|
||||
val = hvals.IndexOf(tmp[i]);
|
||||
if (val == -1)
|
||||
break;
|
||||
sum *= 16;
|
||||
sum += val;
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Nonce management
|
||||
|
||||
public static string NonceGenerator()
|
||||
{
|
||||
return StringToBase64(CreationDate + Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
// Dump the specified data stream
|
||||
|
||||
public static void Dump(byte[] data)
|
||||
{
|
||||
char[] buffer = new char[DumpLineSize];
|
||||
int cc = 0;
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (i % DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8"));
|
||||
|
||||
if (i % 4 == 0) Console.Write(" ");
|
||||
|
||||
Console.Write("{0}",data[i].ToString("x2"));
|
||||
|
||||
if (data[i] < 127 && data[i] > 31)
|
||||
buffer[i % DumpLineSize] = (char) data[i];
|
||||
else
|
||||
buffer[i % DumpLineSize] = '.';
|
||||
|
||||
cc++;
|
||||
|
||||
if (i != 0 && (i + 1) % DumpLineSize == 0)
|
||||
{
|
||||
Console.Write(" |"+(new String(buffer))+"|");
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish off any incomplete line
|
||||
|
||||
if (cc != 0)
|
||||
{
|
||||
for (int i = cc ; i < DumpLineSize; i++)
|
||||
{
|
||||
if (i % 4 == 0) Console.Write(" ");
|
||||
Console.Write(" ");
|
||||
buffer[i % DumpLineSize] = ' ';
|
||||
}
|
||||
Console.WriteLine(" |"+(new String(buffer))+"|");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Local exception type
|
||||
|
||||
public class RestException : Exception
|
||||
{
|
||||
internal int statusCode;
|
||||
internal string statusDesc;
|
||||
internal string httpmethod;
|
||||
internal string httppath;
|
||||
|
||||
public RestException(string msg) : base(msg)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
|
||||
public class RestAppearanceServices : IRest
|
||||
{
|
||||
// private static readonly int PARM_USERID = 0;
|
||||
|
||||
// private static readonly int PARM_PATH = 1;
|
||||
|
||||
// private bool enabled = false;
|
||||
private string qPrefix = "appearance";
|
||||
|
||||
/// <summary>
|
||||
/// The constructor makes sure that the service prefix is absolute
|
||||
/// and the registers the service handler and the allocator.
|
||||
/// </summary>
|
||||
|
||||
public RestAppearanceServices()
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId);
|
||||
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
|
||||
|
||||
// If a relative path was specified for the handler's domain,
|
||||
// add the standard prefix to make it absolute, e.g. /admin
|
||||
|
||||
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
|
||||
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
|
||||
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
|
||||
Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
|
||||
}
|
||||
|
||||
// Register interface using the absolute URI.
|
||||
|
||||
Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate);
|
||||
|
||||
// Activate if everything went OK
|
||||
|
||||
// enabled = true;
|
||||
|
||||
Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post-construction, pre-enabled initialization opportunity
|
||||
/// Not currently exploited.
|
||||
/// </summary>
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the plug-in to halt service processing. Local processing is
|
||||
/// disabled.
|
||||
/// </summary>
|
||||
|
||||
public void Close()
|
||||
{
|
||||
// enabled = false;
|
||||
Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This property is declared locally because it is used a lot and
|
||||
/// brevity is nice.
|
||||
/// </summary>
|
||||
|
||||
internal string MsgId
|
||||
{
|
||||
get { return Rest.MsgId; }
|
||||
}
|
||||
|
||||
#region Interface
|
||||
|
||||
/// <summary>
|
||||
/// The plugin (RestHandler) calls this method to allocate the request
|
||||
/// state carrier for a new request. It is destroyed when the request
|
||||
/// completes. All request-instance specific state is kept here. This
|
||||
/// is registered when this service provider is registered.
|
||||
/// </summary>
|
||||
/// <param name=request>Inbound HTTP request information</param>
|
||||
/// <param name=response>Outbound HTTP request information</param>
|
||||
/// <param name=qPrefix>REST service domain prefix</param>
|
||||
/// <returns>A RequestData instance suitable for this service</returns>
|
||||
|
||||
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
{
|
||||
return (RequestData) new AppearanceRequestData(request, response, prefix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is registered with the handler when this service provider
|
||||
/// is initialized. It is called whenever the plug-in identifies this service
|
||||
/// provider as the best match for a given request.
|
||||
/// It handles all aspects of inventory REST processing, i.e. /admin/inventory
|
||||
/// </summary>
|
||||
/// <param name=hdata>A consolidated HTTP request work area</param>
|
||||
|
||||
private void DoAppearance(RequestData hdata)
|
||||
{
|
||||
// !!! REFACTORIMG PROBLEM. This needs rewriting for 0.7
|
||||
|
||||
//AppearanceRequestData rdata = (AppearanceRequestData) hdata;
|
||||
|
||||
//Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId);
|
||||
|
||||
//// If we're disabled, do nothing.
|
||||
|
||||
//if (!enabled)
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
|
||||
//// Now that we know this is a serious attempt to
|
||||
//// access inventory data, we should find out who
|
||||
//// is asking, and make sure they are authorized
|
||||
//// to do so. We need to validate the caller's
|
||||
//// identity before revealing anything about the
|
||||
//// status quo. Authenticate throws an exception
|
||||
//// via Fail if no identity information is present.
|
||||
////
|
||||
//// With the present HTTP server we can't use the
|
||||
//// builtin authentication mechanisms because they
|
||||
//// would be enforced for all in-bound requests.
|
||||
//// Instead we look at the headers ourselves and
|
||||
//// handle authentication directly.
|
||||
|
||||
//try
|
||||
//{
|
||||
// if (!rdata.IsAuthenticated)
|
||||
// {
|
||||
// rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
|
||||
// }
|
||||
//}
|
||||
//catch (RestException e)
|
||||
//{
|
||||
// if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
|
||||
// {
|
||||
// Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
|
||||
// Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
|
||||
// Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
|
||||
// }
|
||||
// throw (e);
|
||||
//}
|
||||
|
||||
//Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
|
||||
|
||||
//// We can only get here if we are authorized
|
||||
////
|
||||
//// The requestor may have specified an UUID or
|
||||
//// a conjoined FirstName LastName string. We'll
|
||||
//// try both. If we fail with the first, UUID,
|
||||
//// attempt, we try the other. As an example, the
|
||||
//// URI for a valid inventory request might be:
|
||||
////
|
||||
//// http://<host>:<port>/admin/inventory/Arthur Dent
|
||||
////
|
||||
//// Indicating that this is an inventory request for
|
||||
//// an avatar named Arthur Dent. This is ALL that is
|
||||
//// required to designate a GET for an entire
|
||||
//// inventory.
|
||||
////
|
||||
|
||||
//// Do we have at least a user agent name?
|
||||
|
||||
//if (rdata.Parameters.Length < 1)
|
||||
//{
|
||||
// Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId);
|
||||
// rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
|
||||
//}
|
||||
|
||||
//// The first parameter MUST be the agent identification, either an UUID
|
||||
//// or a space-separated First-name Last-Name specification. We check for
|
||||
//// an UUID first, if anyone names their character using a valid UUID
|
||||
//// that identifies another existing avatar will cause this a problem...
|
||||
|
||||
//try
|
||||
//{
|
||||
// rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]);
|
||||
// Rest.Log.DebugFormat("{0} UUID supplied", MsgId);
|
||||
// rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
// string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
|
||||
// if (names.Length == 2)
|
||||
// {
|
||||
// Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
|
||||
// rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
|
||||
// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
|
||||
// }
|
||||
//}
|
||||
|
||||
//// If the user profile is null then either the server is broken, or the
|
||||
//// user is not known. We always assume the latter case.
|
||||
|
||||
//if (rdata.userProfile != null)
|
||||
//{
|
||||
// Rest.Log.DebugFormat("{0} User profile obtained for agent {1} {2}",
|
||||
// MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path);
|
||||
// rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
|
||||
//}
|
||||
|
||||
//// If we get to here, then we have effectively validated the user's
|
||||
|
||||
//switch (rdata.method)
|
||||
//{
|
||||
// case Rest.HEAD : // Do the processing, set the status code, suppress entity
|
||||
// DoGet(rdata);
|
||||
// rdata.buffer = null;
|
||||
// break;
|
||||
|
||||
// case Rest.GET : // Do the processing, set the status code, return entity
|
||||
// DoGet(rdata);
|
||||
// break;
|
||||
|
||||
// case Rest.PUT : // Update named element
|
||||
// DoUpdate(rdata);
|
||||
// break;
|
||||
|
||||
// case Rest.POST : // Add new information to identified context.
|
||||
// DoExtend(rdata);
|
||||
// break;
|
||||
|
||||
// case Rest.DELETE : // Delete information
|
||||
// DoDelete(rdata);
|
||||
// break;
|
||||
|
||||
// default :
|
||||
// Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
|
||||
// MsgId, rdata.method, rdata.path);
|
||||
// rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
|
||||
// String.Format("{0} not supported", rdata.method));
|
||||
// break;
|
||||
//}
|
||||
}
|
||||
|
||||
#endregion Interface
|
||||
|
||||
#region method-specific processing
|
||||
|
||||
/// <summary>
|
||||
/// This method implements GET processing for user's appearance.
|
||||
/// </summary>
|
||||
/// <param name=rdata>HTTP service request work area</param>
|
||||
|
||||
// private void DoGet(AppearanceRequestData rdata)
|
||||
// {
|
||||
// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID);
|
||||
//
|
||||
// if (adata == null)
|
||||
// {
|
||||
// rdata.Fail(Rest.HttpStatusCodeNoContent,
|
||||
// String.Format("appearance data not found for user {0} {1}",
|
||||
// rdata.userProfile.FirstName, rdata.userProfile.SurName));
|
||||
// }
|
||||
// rdata.userAppearance = adata.ToAvatarAppearance(rdata.userProfile.ID);
|
||||
//
|
||||
// rdata.initXmlWriter();
|
||||
//
|
||||
// FormatUserAppearance(rdata);
|
||||
//
|
||||
// // Indicate a successful request
|
||||
//
|
||||
// rdata.Complete();
|
||||
//
|
||||
// // Send the response to the user. The body will be implicitly
|
||||
// // constructed from the result of the XML writer.
|
||||
//
|
||||
// rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method));
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// POST adds NEW information to the user profile database.
|
||||
/// This effectively resets the appearance before applying those
|
||||
/// characteristics supplied in the request.
|
||||
/// </summary>
|
||||
|
||||
// private void DoExtend(AppearanceRequestData rdata)
|
||||
// {
|
||||
//
|
||||
// bool created = false;
|
||||
// bool modified = false;
|
||||
// string newnode = String.Empty;
|
||||
//
|
||||
// Rest.Log.DebugFormat("{0} POST ENTRY", MsgId);
|
||||
//
|
||||
// //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);
|
||||
//
|
||||
// rdata.userAppearance = new AvatarAppearance();
|
||||
//
|
||||
// // Although the following behavior is admitted by HTTP I am becoming
|
||||
// // increasingly doubtful that it is appropriate for REST. If I attempt to
|
||||
// // add a new record, and it already exists, then it seems to me that the
|
||||
// // attempt should fail, rather than update the existing record.
|
||||
// AvatarData adata = null;
|
||||
// if (GetUserAppearance(rdata))
|
||||
// {
|
||||
// modified = rdata.userAppearance != null;
|
||||
// created = !modified;
|
||||
// adata = new AvatarData(rdata.userAppearance);
|
||||
// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
|
||||
// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// created = true;
|
||||
// adata = new AvatarData(rdata.userAppearance);
|
||||
// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
|
||||
// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
|
||||
// }
|
||||
//
|
||||
// if (created)
|
||||
// {
|
||||
// newnode = String.Format("{0} {1}", rdata.userProfile.FirstName,
|
||||
// rdata.userProfile.SurName);
|
||||
// // Must include a location header with a URI that identifies the new resource.
|
||||
//
|
||||
// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}",
|
||||
// rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode));
|
||||
// rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
//
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (modified)
|
||||
// {
|
||||
// rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
|
||||
//
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// This updates the user's appearance. not all aspects need to be provided,
|
||||
/// only those supplied will be changed.
|
||||
/// </summary>
|
||||
|
||||
// private void DoUpdate(AppearanceRequestData rdata)
|
||||
// {
|
||||
//
|
||||
// // REFACTORING PROBLEM This was commented out. It doesn't work for 0.7
|
||||
//
|
||||
// //bool created = false;
|
||||
// //bool modified = false;
|
||||
//
|
||||
//
|
||||
// //rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);
|
||||
//
|
||||
// //// If the user exists then this is considered a modification regardless
|
||||
// //// of what may, or may not be, specified in the payload.
|
||||
//
|
||||
// //if (rdata.userAppearance != null)
|
||||
// //{
|
||||
// // modified = true;
|
||||
// // Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);
|
||||
// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
|
||||
// //}
|
||||
//
|
||||
// //if (created)
|
||||
// //{
|
||||
// // rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
// //}
|
||||
// //else
|
||||
// //{
|
||||
// // if (modified)
|
||||
// // {
|
||||
// // rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
// // }
|
||||
// // else
|
||||
// // {
|
||||
// // rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
// // }
|
||||
// //}
|
||||
//
|
||||
// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
|
||||
//
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Delete the specified user's appearance. This actually performs a reset
|
||||
/// to the default avatar appearance, if the info is already there.
|
||||
/// Existing ownership is preserved. All prior updates are lost and can not
|
||||
/// be recovered.
|
||||
/// </summary>
|
||||
// private void DoDelete(AppearanceRequestData rdata)
|
||||
// {
|
||||
// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID);
|
||||
//
|
||||
// if (adata != null)
|
||||
// {
|
||||
// AvatarAppearance old = adata.ToAvatarAppearance(rdata.userProfile.ID);
|
||||
// rdata.userAppearance = new AvatarAppearance();
|
||||
// rdata.userAppearance.Owner = old.Owner;
|
||||
// adata = new AvatarData(rdata.userAppearance);
|
||||
//
|
||||
// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
|
||||
//
|
||||
// rdata.Complete();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
//
|
||||
// rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
// }
|
||||
//
|
||||
// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
|
||||
// }
|
||||
|
||||
#endregion method-specific processing
|
||||
|
||||
private bool GetUserAppearance(AppearanceRequestData rdata)
|
||||
{
|
||||
|
||||
XmlReader xml;
|
||||
bool indata = false;
|
||||
|
||||
rdata.initXmlReader();
|
||||
xml = rdata.reader;
|
||||
|
||||
while (xml.Read())
|
||||
{
|
||||
switch (xml.NodeType)
|
||||
{
|
||||
case XmlNodeType.Element :
|
||||
switch (xml.Name)
|
||||
{
|
||||
case "Appearance" :
|
||||
if (xml.MoveToAttribute("Height"))
|
||||
{
|
||||
rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value);
|
||||
indata = true;
|
||||
}
|
||||
// if (xml.MoveToAttribute("Owner"))
|
||||
// {
|
||||
// rdata.userAppearance.Owner = (UUID)xml.Value;
|
||||
// indata = true;
|
||||
// }
|
||||
if (xml.MoveToAttribute("Serial"))
|
||||
{
|
||||
rdata.userAppearance.Serial = Convert.ToInt32(xml.Value);
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
/*
|
||||
case "Body" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.BodyItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.BodyAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Skin" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.SkinItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.SkinAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Hair" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.HairItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.HairAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Eyes" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.EyesItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.EyesAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Shirt" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.ShirtItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.ShirtAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Pants" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.PantsItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.PantsAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Shoes" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.ShoesItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.ShoesAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Socks" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.SocksItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.SocksAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Jacket" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.JacketItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.JacketAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Gloves" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.GlovesItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.GlovesAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "UnderShirt" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.UnderShirtItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "UnderPants" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.UnderPantsItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Skirt" :
|
||||
if (xml.MoveToAttribute("Item"))
|
||||
{
|
||||
rdata.userAppearance.SkirtItem = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
rdata.userAppearance.SkirtAsset = (UUID)xml.Value;
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
*/
|
||||
case "Attachment" :
|
||||
{
|
||||
|
||||
int ap;
|
||||
UUID asset;
|
||||
UUID item;
|
||||
|
||||
if (xml.MoveToAttribute("AtPoint"))
|
||||
{
|
||||
ap = Convert.ToInt32(xml.Value);
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
asset = new UUID(xml.Value);
|
||||
if (xml.MoveToAttribute("Asset"))
|
||||
{
|
||||
item = new UUID(xml.Value);
|
||||
rdata.userAppearance.SetAttachment(ap, item, asset);
|
||||
indata = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Texture" :
|
||||
if (xml.MoveToAttribute("Default"))
|
||||
{
|
||||
rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value));
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
case "Face" :
|
||||
{
|
||||
uint index;
|
||||
if (xml.MoveToAttribute("Index"))
|
||||
{
|
||||
index = Convert.ToUInt32(xml.Value);
|
||||
if (xml.MoveToAttribute("Id"))
|
||||
{
|
||||
rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value);
|
||||
indata = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "VisualParameters" :
|
||||
{
|
||||
xml.ReadContentAsBase64(rdata.userAppearance.VisualParams,
|
||||
0, rdata.userAppearance.VisualParams.Length);
|
||||
indata = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return indata;
|
||||
|
||||
}
|
||||
|
||||
private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset)
|
||||
{
|
||||
if (item != UUID.Zero || asset != UUID.Zero)
|
||||
{
|
||||
rdata.writer.WriteStartElement(part);
|
||||
if (item != UUID.Zero)
|
||||
{
|
||||
rdata.writer.WriteAttributeString("Item",item.ToString());
|
||||
}
|
||||
|
||||
if (asset != UUID.Zero)
|
||||
{
|
||||
rdata.writer.WriteAttributeString("Asset",asset.ToString());
|
||||
}
|
||||
rdata.writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormatUserAppearance(AppearanceRequestData rdata)
|
||||
{
|
||||
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId);
|
||||
|
||||
if (rdata.userAppearance != null)
|
||||
{
|
||||
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId);
|
||||
rdata.writer.WriteStartElement("Appearance");
|
||||
|
||||
rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString());
|
||||
// if (rdata.userAppearance.Owner != UUID.Zero)
|
||||
// rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString());
|
||||
rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString());
|
||||
|
||||
/*
|
||||
FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset);
|
||||
FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset);
|
||||
FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset);
|
||||
FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset);
|
||||
|
||||
FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset);
|
||||
FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset);
|
||||
FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset);
|
||||
FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset);
|
||||
FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset);
|
||||
|
||||
FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset);
|
||||
FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset);
|
||||
|
||||
FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset);
|
||||
FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset);
|
||||
*/
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId);
|
||||
|
||||
rdata.writer.WriteStartElement("Attachments");
|
||||
List<AvatarAttachment> attachments = rdata.userAppearance.GetAttachments();
|
||||
foreach (AvatarAttachment attach in attachments)
|
||||
{
|
||||
rdata.writer.WriteStartElement("Attachment");
|
||||
rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString());
|
||||
rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString());
|
||||
rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString());
|
||||
rdata.writer.WriteEndElement();
|
||||
}
|
||||
rdata.writer.WriteEndElement();
|
||||
|
||||
Primitive.TextureEntry texture = rdata.userAppearance.Texture;
|
||||
|
||||
if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId);
|
||||
|
||||
rdata.writer.WriteStartElement("Texture");
|
||||
|
||||
if (texture.DefaultTexture != null)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId);
|
||||
rdata.writer.WriteAttributeString("Default",
|
||||
texture.DefaultTexture.TextureID.ToString());
|
||||
}
|
||||
|
||||
if (texture.FaceTextures != null)
|
||||
{
|
||||
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId);
|
||||
|
||||
for (int i=0; i<texture.FaceTextures.Length;i++)
|
||||
{
|
||||
if (texture.FaceTextures[i] != null)
|
||||
{
|
||||
rdata.writer.WriteStartElement("Face");
|
||||
rdata.writer.WriteAttributeString("Index", i.ToString());
|
||||
rdata.writer.WriteAttributeString("Id",
|
||||
texture.FaceTextures[i].TextureID.ToString());
|
||||
rdata.writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rdata.writer.WriteEndElement();
|
||||
}
|
||||
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting visual parameters", MsgId);
|
||||
|
||||
rdata.writer.WriteStartElement("VisualParameters");
|
||||
rdata.writer.WriteBase64(rdata.userAppearance.VisualParams,0,
|
||||
rdata.userAppearance.VisualParams.Length);
|
||||
rdata.writer.WriteEndElement();
|
||||
rdata.writer.WriteFullEndElement();
|
||||
}
|
||||
|
||||
Rest.Log.DebugFormat("{0} FormatUserAppearance: completed", MsgId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#region appearance RequestData extension
|
||||
|
||||
internal class AppearanceRequestData : RequestData
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// These are the inventory specific request/response state
|
||||
/// extensions.
|
||||
/// </summary>
|
||||
|
||||
internal UUID uuid = UUID.Zero;
|
||||
internal UserProfileData userProfile = null;
|
||||
internal AvatarAppearance userAppearance = null;
|
||||
|
||||
internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
: base(request, response, prefix)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion Appearance RequestData extension
|
||||
|
||||
}
|
||||
}
|
||||
383
OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
Normal file
383
OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Xml;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
public class RestAssetServices : IRest
|
||||
{
|
||||
private bool enabled = false;
|
||||
private string qPrefix = "assets";
|
||||
|
||||
// A simple constructor is used to handle any once-only
|
||||
// initialization of working classes.
|
||||
|
||||
public RestAssetServices()
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
|
||||
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
|
||||
|
||||
// If the handler specifies a relative path for its domain
|
||||
// then we must add the standard absolute prefix, e.g. /admin
|
||||
|
||||
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
|
||||
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
|
||||
Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
|
||||
}
|
||||
|
||||
// Register interface using the fully-qualified prefix
|
||||
|
||||
Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate);
|
||||
|
||||
// Activate if all went OK
|
||||
|
||||
enabled = true;
|
||||
|
||||
Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId);
|
||||
}
|
||||
|
||||
// Post-construction, pre-enabled initialization opportunity
|
||||
// Not currently exploited.
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
// Called by the plug-in to halt REST processing. Local processing is
|
||||
// disabled, and control blocks until all current processing has
|
||||
// completed. No new processing will be started
|
||||
|
||||
public void Close()
|
||||
{
|
||||
enabled = false;
|
||||
Rest.Log.InfoFormat("{0} Asset services ({1}) closing down", MsgId, qPrefix);
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
internal string MsgId
|
||||
{
|
||||
get { return Rest.MsgId; }
|
||||
}
|
||||
|
||||
#region Interface
|
||||
|
||||
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
{
|
||||
return (RequestData) new AssetRequestData(request, response, prefix);
|
||||
}
|
||||
|
||||
// Asset Handler
|
||||
|
||||
private void DoAsset(RequestData rparm)
|
||||
{
|
||||
if (!enabled) return;
|
||||
|
||||
AssetRequestData rdata = (AssetRequestData) rparm;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST Asset handler ({1}) ENTRY", MsgId, qPrefix);
|
||||
|
||||
// Now that we know this is a serious attempt to
|
||||
// access inventory data, we should find out who
|
||||
// is asking, and make sure they are authorized
|
||||
// to do so. We need to validate the caller's
|
||||
// identity before revealing anything about the
|
||||
// status quo. Authenticate throws an exception
|
||||
// via Fail if no identity information is present.
|
||||
//
|
||||
// With the present HTTP server we can't use the
|
||||
// builtin authentication mechanisms because they
|
||||
// would be enforced for all in-bound requests.
|
||||
// Instead we look at the headers ourselves and
|
||||
// handle authentication directly.
|
||||
|
||||
try
|
||||
{
|
||||
if (!rdata.IsAuthenticated)
|
||||
{
|
||||
rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
|
||||
}
|
||||
}
|
||||
catch (RestException e)
|
||||
{
|
||||
if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
|
||||
rdata.request.Headers.Get("Authorization"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
|
||||
rdata.request.Headers.Get("Authorization"));
|
||||
}
|
||||
throw (e);
|
||||
}
|
||||
|
||||
// Remove the prefix and what's left are the parameters. If we don't have
|
||||
// the parameters we need, fail the request. Parameters do NOT include
|
||||
// any supplied query values.
|
||||
|
||||
if (rdata.Parameters.Length > 0)
|
||||
{
|
||||
switch (rdata.method)
|
||||
{
|
||||
case "get" :
|
||||
DoGet(rdata);
|
||||
break;
|
||||
case "put" :
|
||||
DoPut(rdata);
|
||||
break;
|
||||
case "post" :
|
||||
DoPost(rdata);
|
||||
break;
|
||||
case "delete" :
|
||||
default :
|
||||
Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}",
|
||||
MsgId, rdata.method);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
|
||||
}
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId);
|
||||
}
|
||||
|
||||
#endregion Interface
|
||||
|
||||
/// <summary>
|
||||
/// The only parameter we recognize is a UUID.If an asset with this identification is
|
||||
/// found, it's content, base-64 encoded, is returned to the client.
|
||||
/// </summary>
|
||||
|
||||
private void DoGet(AssetRequestData rdata)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length == 1)
|
||||
{
|
||||
UUID uuid = new UUID(rdata.Parameters[0]);
|
||||
AssetBase asset = Rest.AssetServices.Get(uuid.ToString());
|
||||
|
||||
if (asset != null)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]);
|
||||
|
||||
rdata.initXmlWriter();
|
||||
|
||||
rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty);
|
||||
|
||||
rdata.writer.WriteAttributeString("id", asset.ID);
|
||||
rdata.writer.WriteAttributeString("name", asset.Name);
|
||||
rdata.writer.WriteAttributeString("desc", asset.Description);
|
||||
rdata.writer.WriteAttributeString("type", asset.Type.ToString());
|
||||
rdata.writer.WriteAttributeString("local", asset.Local.ToString());
|
||||
rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString());
|
||||
|
||||
rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length);
|
||||
|
||||
rdata.writer.WriteFullEndElement();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Complete();
|
||||
rdata.Respond(String.Format("Asset <{0}> : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UPDATE existing item, if it exists. URI identifies the item in question.
|
||||
/// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
|
||||
/// is decoded and stored in the database, identified by the supplied UUID.
|
||||
/// </summary>
|
||||
private void DoPut(AssetRequestData rdata)
|
||||
{
|
||||
bool modified = false;
|
||||
bool created = false;
|
||||
|
||||
AssetBase asset = null;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length == 1)
|
||||
{
|
||||
|
||||
rdata.initXmlReader();
|
||||
XmlReader xml = rdata.reader;
|
||||
|
||||
if (!xml.ReadToFollowing("Asset"))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
|
||||
}
|
||||
|
||||
UUID uuid = new UUID(rdata.Parameters[0]);
|
||||
asset = Rest.AssetServices.Get(uuid.ToString());
|
||||
|
||||
modified = (asset != null);
|
||||
created = !modified;
|
||||
|
||||
asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString());
|
||||
asset.Description = xml.GetAttribute("desc");
|
||||
asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0;
|
||||
asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0;
|
||||
asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", ""));
|
||||
|
||||
if (asset.ID != rdata.Parameters[0])
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} URI and payload disagree on UUID U:{1} vs P:{2}",
|
||||
MsgId, rdata.Parameters[0], asset.ID);
|
||||
}
|
||||
|
||||
Rest.AssetServices.Store(asset);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
|
||||
}
|
||||
|
||||
if (created)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
|
||||
rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modified)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
|
||||
rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
}
|
||||
else
|
||||
{
|
||||
rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CREATE new item, replace if it exists. URI identifies the context for the item in question.
|
||||
/// No parameters are required for POST, just thepayload.
|
||||
/// </summary>
|
||||
|
||||
private void DoPost(AssetRequestData rdata)
|
||||
{
|
||||
|
||||
bool modified = false;
|
||||
bool created = false;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length != 0)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Parameters ignored <{1}>", MsgId, rdata.path);
|
||||
Rest.Log.InfoFormat("{0} POST of an asset has no parameters", MsgId, rdata.path);
|
||||
}
|
||||
|
||||
rdata.initXmlReader();
|
||||
XmlReader xml = rdata.reader;
|
||||
|
||||
if (!xml.ReadToFollowing("Asset"))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
|
||||
}
|
||||
|
||||
UUID uuid = new UUID(xml.GetAttribute("id"));
|
||||
AssetBase asset = Rest.AssetServices.Get(uuid.ToString());
|
||||
|
||||
modified = (asset != null);
|
||||
created = !modified;
|
||||
|
||||
asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString());
|
||||
asset.Description = xml.GetAttribute("desc");
|
||||
asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0;
|
||||
asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0;
|
||||
asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", ""));
|
||||
|
||||
Rest.AssetServices.Store(asset);
|
||||
|
||||
if (created)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
|
||||
rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modified)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
|
||||
rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
}
|
||||
else
|
||||
{
|
||||
rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asset processing has no special data area requirements.
|
||||
/// </summary>
|
||||
|
||||
internal class AssetRequestData : RequestData
|
||||
{
|
||||
internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
: base(request, response, prefix)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
448
OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs
Normal file
448
OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs
Normal file
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
public class RestFileServices : IRest
|
||||
{
|
||||
private bool enabled = false;
|
||||
private string qPrefix = "files";
|
||||
|
||||
// A simple constructor is used to handle any once-only
|
||||
// initialization of working classes.
|
||||
|
||||
public RestFileServices()
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} File services initializing", MsgId);
|
||||
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
|
||||
|
||||
// If the handler specifies a relative path for its domain
|
||||
// then we must add the standard absolute prefix, e.g. /admin
|
||||
|
||||
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
|
||||
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
|
||||
Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
|
||||
}
|
||||
|
||||
// Register interface using the fully-qualified prefix
|
||||
|
||||
Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate);
|
||||
|
||||
// Activate if all went OK
|
||||
|
||||
enabled = true;
|
||||
|
||||
Rest.Log.InfoFormat("{0} File services initialization complete", MsgId);
|
||||
}
|
||||
|
||||
// Post-construction, pre-enabled initialization opportunity
|
||||
// Not currently exploited.
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
// Called by the plug-in to halt REST processing. Local processing is
|
||||
// disabled, and control blocks until all current processing has
|
||||
// completed. No new processing will be started
|
||||
|
||||
public void Close()
|
||||
{
|
||||
enabled = false;
|
||||
Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix);
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
internal string MsgId
|
||||
{
|
||||
get { return Rest.MsgId; }
|
||||
}
|
||||
|
||||
#region Interface
|
||||
|
||||
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
{
|
||||
return (RequestData) new FileRequestData(request, response, prefix);
|
||||
}
|
||||
|
||||
// Asset Handler
|
||||
|
||||
private void DoFile(RequestData rparm)
|
||||
{
|
||||
if (!enabled) return;
|
||||
|
||||
FileRequestData rdata = (FileRequestData) rparm;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix);
|
||||
|
||||
// Now that we know this is a serious attempt to
|
||||
// access file data, we should find out who
|
||||
// is asking, and make sure they are authorized
|
||||
// to do so. We need to validate the caller's
|
||||
// identity before revealing anything about the
|
||||
// status quo. Authenticate throws an exception
|
||||
// via Fail if no identity information is present.
|
||||
//
|
||||
// With the present HTTP server we can't use the
|
||||
// builtin authentication mechanisms because they
|
||||
// would be enforced for all in-bound requests.
|
||||
// Instead we look at the headers ourselves and
|
||||
// handle authentication directly.
|
||||
|
||||
try
|
||||
{
|
||||
if (!rdata.IsAuthenticated)
|
||||
{
|
||||
rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
|
||||
}
|
||||
}
|
||||
catch (RestException e)
|
||||
{
|
||||
if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
|
||||
rdata.request.Headers.Get("Authorization"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
|
||||
rdata.request.Headers.Get("Authorization"));
|
||||
}
|
||||
throw (e);
|
||||
}
|
||||
|
||||
// Remove the prefix and what's left are the parameters. If we don't have
|
||||
// the parameters we need, fail the request. Parameters do NOT include
|
||||
// any supplied query values.
|
||||
|
||||
if (rdata.Parameters.Length > 0)
|
||||
{
|
||||
switch (rdata.method)
|
||||
{
|
||||
case "get" :
|
||||
DoGet(rdata);
|
||||
break;
|
||||
case "put" :
|
||||
DoPut(rdata);
|
||||
break;
|
||||
case "post" :
|
||||
DoPost(rdata);
|
||||
break;
|
||||
case "delete" :
|
||||
DoDelete(rdata);
|
||||
break;
|
||||
default :
|
||||
Rest.Log.WarnFormat("{0} File: Method not supported: {1}",
|
||||
MsgId, rdata.method);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
|
||||
}
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId);
|
||||
|
||||
}
|
||||
|
||||
#endregion Interface
|
||||
|
||||
/// <summary>
|
||||
/// The only parameter we recognize is a UUID.If an asset with this identification is
|
||||
/// found, it's content, base-64 encoded, is returned to the client.
|
||||
/// </summary>
|
||||
|
||||
private void DoGet(FileRequestData rdata)
|
||||
{
|
||||
|
||||
string path = String.Empty;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length > 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path);
|
||||
Byte[] data = File.ReadAllBytes(path);
|
||||
rdata.initXmlWriter();
|
||||
rdata.writer.WriteStartElement(String.Empty,"File",String.Empty);
|
||||
rdata.writer.WriteAttributeString("name", path);
|
||||
rdata.writer.WriteBase64(data,0,data.Length);
|
||||
rdata.writer.WriteFullEndElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
|
||||
path, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Complete();
|
||||
rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UPDATE existing item, if it exists. URI identifies the item in question.
|
||||
/// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
|
||||
/// is decoded and stored in the database, identified by the supplied UUID.
|
||||
/// </summary>
|
||||
private void DoPut(FileRequestData rdata)
|
||||
{
|
||||
bool modified = false;
|
||||
bool created = false;
|
||||
string path = String.Empty;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length > 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
|
||||
bool maymod = File.Exists(path);
|
||||
|
||||
rdata.initXmlReader();
|
||||
XmlReader xml = rdata.reader;
|
||||
|
||||
if (!xml.ReadToFollowing("File"))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
|
||||
}
|
||||
|
||||
Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
|
||||
|
||||
File.WriteAllBytes(path,data);
|
||||
modified = maymod;
|
||||
created = ! maymod;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
|
||||
e.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
|
||||
}
|
||||
|
||||
if (created)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
|
||||
rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modified)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
|
||||
rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
}
|
||||
else
|
||||
{
|
||||
rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CREATE new item, replace if it exists. URI identifies the context for the item in question.
|
||||
/// No parameters are required for POST, just thepayload.
|
||||
/// </summary>
|
||||
|
||||
private void DoPost(FileRequestData rdata)
|
||||
{
|
||||
|
||||
bool modified = false;
|
||||
bool created = false;
|
||||
string path = String.Empty;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length > 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
|
||||
bool maymod = File.Exists(path);
|
||||
|
||||
rdata.initXmlReader();
|
||||
XmlReader xml = rdata.reader;
|
||||
|
||||
if (!xml.ReadToFollowing("File"))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
|
||||
}
|
||||
|
||||
Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
|
||||
|
||||
File.WriteAllBytes(path,data);
|
||||
modified = maymod;
|
||||
created = ! maymod;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
|
||||
e.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
|
||||
}
|
||||
|
||||
if (created)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
|
||||
rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modified)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
|
||||
rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
}
|
||||
else
|
||||
{
|
||||
rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CREATE new item, replace if it exists. URI identifies the context for the item in question.
|
||||
/// No parameters are required for POST, just thepayload.
|
||||
/// </summary>
|
||||
|
||||
private void DoDelete(FileRequestData rdata)
|
||||
{
|
||||
|
||||
bool modified = false;
|
||||
bool created = false;
|
||||
string path = String.Empty;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
|
||||
|
||||
if (rdata.Parameters.Length > 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
|
||||
e.Message);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
|
||||
path, e.Message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
|
||||
rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
|
||||
}
|
||||
|
||||
if (created)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
|
||||
rdata.Complete(Rest.HttpStatusCodeCreated);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modified)
|
||||
{
|
||||
rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
|
||||
rdata.Complete(Rest.HttpStatusCodeOK);
|
||||
}
|
||||
else
|
||||
{
|
||||
rdata.Complete(Rest.HttpStatusCodeNoContent);
|
||||
}
|
||||
}
|
||||
|
||||
rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// File processing has no special data area requirements.
|
||||
/// </summary>
|
||||
|
||||
internal class FileRequestData : RequestData
|
||||
{
|
||||
internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
: base(request, response, prefix)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
662
OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
Normal file
662
OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
/// <remarks>
|
||||
/// The class signature reveals the roles that RestHandler plays.
|
||||
///
|
||||
/// [1] It is a sub-class of RestPlugin. It inherits and extends
|
||||
/// the functionality of this class, constraining it to the
|
||||
/// specific needs of this REST implementation. This relates
|
||||
/// to the plug-in mechanism supported by OpenSim, the specifics
|
||||
/// of which are mostly hidden by RestPlugin.
|
||||
/// [2] IRestHandler describes the interface that this class
|
||||
/// exports to service implementations. This is the services
|
||||
/// management interface.
|
||||
/// [3] IHttpAgentHandler describes the interface that is exported
|
||||
/// to the BaseHttpServer in support of this particular HTTP
|
||||
/// processing model. This is the request interface of the
|
||||
/// handler.
|
||||
/// </remarks>
|
||||
|
||||
public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler
|
||||
{
|
||||
// Handler tables: both stream and REST are supported. The path handlers and their
|
||||
// respective allocators are stored in separate tables.
|
||||
|
||||
internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
|
||||
internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
|
||||
internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
|
||||
|
||||
#region local static state
|
||||
|
||||
private static bool handlersLoaded = false;
|
||||
private static List<Type> classes = new List<Type>();
|
||||
private static List<IRest> handlers = new List<IRest>();
|
||||
private static Type[] parms = new Type[0];
|
||||
private static Object[] args = new Object[0];
|
||||
|
||||
/// <summary>
|
||||
/// This static initializer scans the ASSEMBLY for classes that
|
||||
/// export the IRest interface and builds a list of them. These
|
||||
/// are later activated by the handler. To add a new handler it
|
||||
/// is only necessary to create a new services class that implements
|
||||
/// the IRest interface, and recompile the handler. This gives
|
||||
/// all of the build-time flexibility of a modular approach
|
||||
/// while not introducing yet-another module loader. Note that
|
||||
/// multiple assembles can still be built, each with its own set
|
||||
/// of handlers. Examples of services classes are RestInventoryServices
|
||||
/// and RestSkeleton.
|
||||
/// </summary>
|
||||
|
||||
static RestHandler()
|
||||
{
|
||||
Module[] mods = Assembly.GetExecutingAssembly().GetModules();
|
||||
|
||||
foreach (Module m in mods)
|
||||
{
|
||||
Type[] types = m.GetTypes();
|
||||
foreach (Type t in types)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (t.GetInterface("IRest") != null)
|
||||
{
|
||||
classes.Add(t);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t);
|
||||
Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion local static state
|
||||
|
||||
#region local instance state
|
||||
|
||||
/// <summary>
|
||||
/// This routine loads all of the handlers discovered during
|
||||
/// instance initialization.
|
||||
/// A table of all loaded and successfully constructed handlers
|
||||
/// is built, and this table is then used by the constructor to
|
||||
/// initialize each of the handlers in turn.
|
||||
/// NOTE: The loading process does not automatically imply that
|
||||
/// the handler has registered any kind of an interface, that
|
||||
/// may be (optionally) done by the handler either during
|
||||
/// construction, or during initialization.
|
||||
///
|
||||
/// I was not able to make this code work within a constructor
|
||||
/// so it is isolated within this method.
|
||||
/// </summary>
|
||||
|
||||
private void LoadHandlers()
|
||||
{
|
||||
lock (handlers)
|
||||
{
|
||||
if (!handlersLoaded)
|
||||
{
|
||||
ConstructorInfo ci;
|
||||
Object ht;
|
||||
|
||||
foreach (Type t in classes)
|
||||
{
|
||||
try
|
||||
{
|
||||
ci = t.GetConstructor(parms);
|
||||
ht = ci.Invoke(args);
|
||||
handlers.Add((IRest)ht);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message);
|
||||
}
|
||||
}
|
||||
handlersLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion local instance state
|
||||
|
||||
#region overriding properties
|
||||
|
||||
// These properties override definitions
|
||||
// in the base class.
|
||||
|
||||
// Name is used to differentiate the message header.
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "HANDLER"; }
|
||||
}
|
||||
|
||||
// Used to partition the .ini configuration space.
|
||||
|
||||
public override string ConfigName
|
||||
{
|
||||
get { return "RestHandler"; }
|
||||
}
|
||||
|
||||
// We have to rename these because we want
|
||||
// to be able to share the values with other
|
||||
// classes in our assembly and the base
|
||||
// names are protected.
|
||||
|
||||
public string MsgId
|
||||
{
|
||||
get { return base.MsgID; }
|
||||
}
|
||||
|
||||
public string RequestId
|
||||
{
|
||||
get { return base.RequestID; }
|
||||
}
|
||||
|
||||
#endregion overriding properties
|
||||
|
||||
#region overriding methods
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by OpenSimMain immediately after loading the
|
||||
/// plugin and after basic server setup, but before running any server commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that entries MUST be added to the active configuration files before
|
||||
/// the plugin can be enabled.
|
||||
/// </remarks>
|
||||
|
||||
public override void Initialise(OpenSimBase openSim)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This plugin will only be enabled if the broader
|
||||
// REST plugin mechanism is enabled.
|
||||
|
||||
//Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId);
|
||||
|
||||
base.Initialise(openSim);
|
||||
|
||||
// IsEnabled is implemented by the base class and
|
||||
// reflects an overall RestPlugin status
|
||||
|
||||
if (!IsEnabled)
|
||||
{
|
||||
//Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
|
||||
return;
|
||||
}
|
||||
|
||||
Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name);
|
||||
Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName);
|
||||
|
||||
// These are stored in static variables to make
|
||||
// them easy to reach from anywhere in the assembly.
|
||||
|
||||
Rest.main = openSim;
|
||||
if (Rest.main == null)
|
||||
throw new Exception("OpenSim base pointer is null");
|
||||
|
||||
Rest.Plugin = this;
|
||||
Rest.Config = Config;
|
||||
Rest.Prefix = Prefix;
|
||||
Rest.GodKey = GodKey;
|
||||
Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate);
|
||||
Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme);
|
||||
Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure);
|
||||
Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape);
|
||||
Rest.Realm = Rest.Config.GetString("realm", Rest.Realm);
|
||||
Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset);
|
||||
Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill);
|
||||
Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize);
|
||||
Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled);
|
||||
|
||||
// Note: Odd spacing is required in the following strings
|
||||
|
||||
Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
|
||||
(Rest.Authenticate ? "" : "not "));
|
||||
|
||||
Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
|
||||
(Rest.Secure ? "" : "not "));
|
||||
|
||||
Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
|
||||
(Rest.ExtendedEscape ? "" : "not "));
|
||||
|
||||
Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
|
||||
(Rest.DumpAsset ? "" : "not "));
|
||||
|
||||
// The supplied prefix MUST be absolute
|
||||
|
||||
if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix);
|
||||
Rest.Log.InfoFormat("{0} Prefix changed to </{1}>", MsgId, Rest.Prefix);
|
||||
Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix);
|
||||
}
|
||||
|
||||
// If data dumping is requested, report on the chosen line
|
||||
// length.
|
||||
|
||||
if (Rest.DumpAsset)
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize);
|
||||
}
|
||||
|
||||
// Load all of the handlers present in the
|
||||
// assembly
|
||||
|
||||
// In principle, as we're an application plug-in,
|
||||
// most of what needs to be done could be done using
|
||||
// static resources, however the Open Sim plug-in
|
||||
// model makes this an instance, so that's what we
|
||||
// need to be.
|
||||
// There is only one Communications manager per
|
||||
// server, and by inference, only one each of the
|
||||
// user, asset, and inventory servers. So we can cache
|
||||
// those using a static initializer.
|
||||
// We move all of this processing off to another
|
||||
// services class to minimize overlap between function
|
||||
// and infrastructure.
|
||||
|
||||
LoadHandlers();
|
||||
|
||||
// The intention of a post construction initializer
|
||||
// is to allow for setup that is dependent upon other
|
||||
// activities outside of the agency.
|
||||
|
||||
foreach (IRest handler in handlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.Initialize();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that everything is setup we can proceed to
|
||||
// add THIS agent to the HTTP server's handler list
|
||||
|
||||
// FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will
|
||||
// have to be handled through the AddHttpHandler interface.
|
||||
// if (!AddAgentHandler(Rest.Name,this))
|
||||
// {
|
||||
// Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
|
||||
// foreach (IRest handler in handlers)
|
||||
// {
|
||||
// handler.Close();
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the interests of efficiency, and because we cannot determine whether
|
||||
/// or not this instance will actually be harvested, we clobber the only
|
||||
/// anchoring reference to the working state for this plug-in. What the
|
||||
/// call to close does is irrelevant to this class beyond knowing that it
|
||||
/// can nullify the reference when it returns.
|
||||
/// To make sure everything is copacetic we make sure the primary interface
|
||||
/// is disabled by deleting the handler from the HTTP server tables.
|
||||
/// </summary>
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
|
||||
|
||||
// FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will
|
||||
// have to be handled through the AddHttpHandler interface.
|
||||
// try
|
||||
// {
|
||||
// RemoveAgentHandler(Rest.Name, this);
|
||||
// }
|
||||
// catch (KeyNotFoundException){}
|
||||
|
||||
foreach (IRest handler in handlers)
|
||||
{
|
||||
handler.Close();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion overriding methods
|
||||
|
||||
#region interface methods
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by the HTTP server to match an incoming
|
||||
/// request. It scans all of the strings registered by the
|
||||
/// underlying handlers and looks for the best match. It returns
|
||||
/// true if a match is found.
|
||||
/// The matching process could be made arbitrarily complex.
|
||||
/// Note: The match is case-insensitive.
|
||||
/// </summary>
|
||||
|
||||
public bool Match(OSHttpRequest request, OSHttpResponse response)
|
||||
{
|
||||
|
||||
string path = request.RawUrl.ToLower();
|
||||
|
||||
// Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string key in pathHandlers.Keys)
|
||||
{
|
||||
// Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
|
||||
|
||||
// Note that Match will not necessarily find the handler that will
|
||||
// actually be used - it does no test for the "closest" fit. It
|
||||
// simply reflects that at least one possible handler exists.
|
||||
|
||||
if (path.StartsWith(key))
|
||||
{
|
||||
// Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
|
||||
|
||||
// This apparently odd evaluation is needed to prevent a match
|
||||
// on anything other than a URI token boundary. Otherwise we
|
||||
// may match on URL's that were not intended for this handler.
|
||||
|
||||
return (path.Length == key.Length ||
|
||||
path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
|
||||
|
||||
foreach (string key in streamHandlers.Keys)
|
||||
{
|
||||
// Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
|
||||
|
||||
// Note that Match will not necessarily find the handler that will
|
||||
// actually be used - it does no test for the "closest" fit. It
|
||||
// simply reflects that at least one possible handler exists.
|
||||
|
||||
if (path.StartsWith(key))
|
||||
{
|
||||
// Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
|
||||
|
||||
// This apparently odd evaluation is needed to prevent a match
|
||||
// on anything other than a URI token boundary. Otherwise we
|
||||
// may match on URL's that were not intended for this handler.
|
||||
|
||||
return (path.Length == key.Length ||
|
||||
path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called by the HTTP server once the handler has indicated
|
||||
/// that it is able to handle the request.
|
||||
/// Preconditions:
|
||||
/// [1] request != null and is a valid request object
|
||||
/// [2] response != null and is a valid response object
|
||||
/// Behavior is undefined if preconditions are not satisfied.
|
||||
/// </summary>
|
||||
|
||||
public bool Handle(OSHttpRequest request, OSHttpResponse response)
|
||||
{
|
||||
bool handled;
|
||||
base.MsgID = base.RequestID;
|
||||
|
||||
// Debug only
|
||||
|
||||
if (Rest.DEBUG)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} ENTRY", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
|
||||
Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
|
||||
|
||||
for (int i = 0; i < request.Headers.Count; i++)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
|
||||
MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
|
||||
}
|
||||
Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
|
||||
}
|
||||
|
||||
// If a path handler worked we're done, otherwise try any
|
||||
// available stream handlers too.
|
||||
|
||||
try
|
||||
{
|
||||
handled = (FindPathHandler(request, response) ||
|
||||
FindStreamHandler(request, response));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// A raw exception indicates that something we weren't expecting has
|
||||
// happened. This should always reflect a shortcoming in the plugin,
|
||||
// or a failure to satisfy the preconditions. It should not reflect
|
||||
// an error in the request itself. Under such circumstances the state
|
||||
// of the request cannot be determined and we are obliged to mark it
|
||||
// as 'handled'.
|
||||
|
||||
Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
Rest.Log.DebugFormat("{0} EXIT", MsgId);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
#endregion interface methods
|
||||
|
||||
/// <summary>
|
||||
/// If there is a stream handler registered that can handle the
|
||||
/// request, then fine. If the request is not matched, do
|
||||
/// nothing.
|
||||
/// Note: The selection is case-insensitive
|
||||
/// </summary>
|
||||
|
||||
private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
|
||||
{
|
||||
RequestData rdata = new RequestData(request, response, String.Empty);
|
||||
|
||||
string bestMatch = String.Empty;
|
||||
string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower();
|
||||
|
||||
Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
|
||||
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string pattern in streamHandlers.Keys)
|
||||
{
|
||||
if (path.StartsWith(pattern))
|
||||
{
|
||||
if (pattern.Length > bestMatch.Length)
|
||||
{
|
||||
bestMatch = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle using the best match available
|
||||
|
||||
if (bestMatch.Length > 0)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
|
||||
RestStreamHandler handler = streamHandlers[bestMatch];
|
||||
rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
|
||||
rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
|
||||
rdata.Respond("FindStreamHandler Completion");
|
||||
}
|
||||
|
||||
return rdata.handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a stream handler for the designated HTTP method and path prefix.
|
||||
/// If the handler is not enabled, the request is ignored. If the path
|
||||
/// does not start with the REST prefix, it is added. If method-qualified
|
||||
/// path has not already been registered, the method is added to the active
|
||||
/// handler table.
|
||||
/// </summary>
|
||||
public void AddStreamHandler(string httpMethod, string path, RestMethod method)
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!path.StartsWith(Rest.Prefix))
|
||||
{
|
||||
path = String.Format("{0}{1}", Rest.Prefix, path);
|
||||
}
|
||||
|
||||
path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
|
||||
|
||||
// Conditionally add to the list
|
||||
|
||||
if (!streamHandlers.ContainsKey(path))
|
||||
{
|
||||
streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
|
||||
Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the supplied request/response, if the handler is enabled, the inbound
|
||||
/// information is used to match an entry in the active path handler tables, using
|
||||
/// the method-qualified path information. If a match is found, then the handler is
|
||||
/// invoked. The result is the boolean result of the handler, or false if no
|
||||
/// handler was located. The boolean indicates whether or not the request has been
|
||||
/// handled, not whether or not the request was successful - that information is in
|
||||
/// the response.
|
||||
/// Note: The selection process is case-insensitive
|
||||
/// </summary>
|
||||
|
||||
internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
|
||||
{
|
||||
RequestData rdata = null;
|
||||
string bestMatch = null;
|
||||
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Conditionally add to the list
|
||||
|
||||
Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
|
||||
|
||||
foreach (string pattern in pathHandlers.Keys)
|
||||
{
|
||||
if (request.RawUrl.ToLower().StartsWith(pattern))
|
||||
{
|
||||
if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
|
||||
{
|
||||
bestMatch = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(bestMatch))
|
||||
{
|
||||
rdata = pathAllocators[bestMatch](request, response, bestMatch);
|
||||
|
||||
Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
|
||||
|
||||
try
|
||||
{
|
||||
pathHandlers[bestMatch](rdata);
|
||||
}
|
||||
|
||||
// A plugin generated error indicates a request-related error
|
||||
// that has been handled by the plugin.
|
||||
|
||||
catch (RestException r)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return (rdata == null) ? false : rdata.handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method handler and a request allocator are stored using the designated
|
||||
/// path as a key. If an entry already exists, it is replaced by the new one.
|
||||
/// </summary>
|
||||
|
||||
public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathHandlers.ContainsKey(path))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
|
||||
pathHandlers.Remove(path);
|
||||
}
|
||||
|
||||
if (pathAllocators.ContainsKey(path))
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
|
||||
pathAllocators.Remove(path);
|
||||
}
|
||||
|
||||
Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
|
||||
|
||||
pathHandlers.Add(path, mh);
|
||||
pathAllocators.Add(path, ra);
|
||||
}
|
||||
}
|
||||
}
|
||||
2343
OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
Normal file
2343
OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
Normal file
File diff suppressed because it is too large
Load Diff
246
OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
Normal file
246
OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
public class RestTestServices : IRest
|
||||
{
|
||||
private bool enabled = false;
|
||||
private string qPrefix = "test";
|
||||
|
||||
// A simple constructor is used to handle any once-only
|
||||
// initialization of working classes.
|
||||
|
||||
public RestTestServices()
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Test services initializing", MsgId);
|
||||
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
|
||||
|
||||
// If a relative path was specified, make it absolute by adding
|
||||
// the standard prefix, e.g. /admin
|
||||
|
||||
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
|
||||
qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
|
||||
Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
|
||||
}
|
||||
|
||||
// Load test cases
|
||||
|
||||
loadTests();
|
||||
foreach (ITest test in tests)
|
||||
{
|
||||
test.Initialize();
|
||||
}
|
||||
|
||||
// Register interface
|
||||
|
||||
Rest.Plugin.AddPathHandler(DoTests,qPrefix,Allocate);
|
||||
|
||||
// Activate
|
||||
|
||||
enabled = true;
|
||||
|
||||
Rest.Log.InfoFormat("{0} Test services initialization complete", MsgId);
|
||||
}
|
||||
|
||||
// Post-construction, pre-enabled initialization opportunity
|
||||
// Not currently exploited.
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
// Called by the plug-in to halt REST processing. Local processing is
|
||||
// disabled, and control blocks until all current processing has
|
||||
// completed. No new processing will be started
|
||||
|
||||
public void Close()
|
||||
{
|
||||
enabled = false;
|
||||
foreach (ITest test in tests)
|
||||
{
|
||||
test.Close();
|
||||
}
|
||||
Rest.Log.InfoFormat("{0} Test services closing down", MsgId);
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
internal string MsgId
|
||||
{
|
||||
get { return Rest.MsgId; }
|
||||
}
|
||||
|
||||
#region Interface
|
||||
|
||||
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
|
||||
{
|
||||
return new RequestData(request, response, prefix);
|
||||
}
|
||||
|
||||
// Inventory Handler
|
||||
|
||||
private void DoTests(RequestData rdata)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
// Now that we know this is a serious attempt to
|
||||
// access inventory data, we should find out who
|
||||
// is asking, and make sure they are authorized
|
||||
// to do so. We need to validate the caller's
|
||||
// identity before revealing anything about the
|
||||
// status quo. Authenticate throws an exception
|
||||
// via Fail if no identity information is present.
|
||||
//
|
||||
// With the present HTTP server we can't use the
|
||||
// builtin authentication mechanisms because they
|
||||
// would be enforced for all in-bound requests.
|
||||
// Instead we look at the headers ourselves and
|
||||
// handle authentication directly.
|
||||
|
||||
try
|
||||
{
|
||||
if (!rdata.IsAuthenticated)
|
||||
{
|
||||
rdata.Fail(Rest.HttpStatusCodeNotAuthorized,
|
||||
String.Format("user \"{0}\" could not be authenticated", rdata.userName));
|
||||
}
|
||||
}
|
||||
catch (RestException e)
|
||||
{
|
||||
if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
|
||||
Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
|
||||
}
|
||||
throw (e);
|
||||
}
|
||||
|
||||
// Check that a test was specified
|
||||
|
||||
if (rdata.Parameters.Length < 1)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters");
|
||||
}
|
||||
|
||||
// Select the test
|
||||
|
||||
foreach (ITest test in tests)
|
||||
{
|
||||
if (!rdata.handled)
|
||||
test.Execute(rdata);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Interface
|
||||
|
||||
private static bool testsLoaded = false;
|
||||
private static List<Type> classes = new List<Type>();
|
||||
private static List<ITest> tests = new List<ITest>();
|
||||
private static Type[] parms = new Type[0];
|
||||
private static Object[] args = new Object[0];
|
||||
|
||||
static RestTestServices()
|
||||
{
|
||||
Module[] mods = Assembly.GetExecutingAssembly().GetModules();
|
||||
foreach (Module m in mods)
|
||||
{
|
||||
Type[] types = m.GetTypes();
|
||||
foreach (Type t in types)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (t.GetInterface("ITest") != null)
|
||||
{
|
||||
classes.Add(t);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This routine loads all of the handlers discovered during
|
||||
/// instance initialization. Each handler is responsible for
|
||||
/// registering itself with this handler.
|
||||
/// I was not able to make this code work in a constructor.
|
||||
/// </summary>
|
||||
|
||||
private void loadTests()
|
||||
{
|
||||
lock (tests)
|
||||
{
|
||||
if (!testsLoaded)
|
||||
{
|
||||
|
||||
ConstructorInfo ci;
|
||||
Object ht;
|
||||
|
||||
foreach (Type t in classes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (t.GetInterface("ITest") != null)
|
||||
{
|
||||
ci = t.GetConstructor(parms);
|
||||
ht = ci.Invoke(args);
|
||||
tests.Add((ITest)ht);
|
||||
Rest.Log.InfoFormat("{0} Test {1} added", MsgId, t);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message);
|
||||
}
|
||||
}
|
||||
testsLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
46
OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs
Normal file
46
OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents the boundary between the general purpose
|
||||
/// REST plugin handling, and the functionally specific handlers. The
|
||||
/// handler knows only to initialzie and terminate all such handlers
|
||||
/// that it finds.
|
||||
/// </summary>
|
||||
|
||||
internal interface ITest
|
||||
{
|
||||
void Initialize();
|
||||
void Execute(RequestData rdata);
|
||||
void Close();
|
||||
}
|
||||
|
||||
}
|
||||
204
OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs
Normal file
204
OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Inventory.Tests
|
||||
{
|
||||
public class Remote : ITest
|
||||
{
|
||||
private static readonly int PARM_TESTID = 0;
|
||||
private static readonly int PARM_COMMAND = 1;
|
||||
|
||||
private static readonly int PARM_MOVE_AVATAR = 2;
|
||||
private static readonly int PARM_MOVE_X = 3;
|
||||
private static readonly int PARM_MOVE_Y = 4;
|
||||
private static readonly int PARM_MOVE_Z = 5;
|
||||
|
||||
private bool enabled = false;
|
||||
|
||||
// No constructor code is required.
|
||||
|
||||
public Remote()
|
||||
{
|
||||
Rest.Log.InfoFormat("{0} Remote services constructor", MsgId);
|
||||
}
|
||||
|
||||
// Post-construction, pre-enabled initialization opportunity
|
||||
// Not currently exploited.
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
enabled = true;
|
||||
Rest.Log.InfoFormat("{0} Remote services initialized", MsgId);
|
||||
}
|
||||
|
||||
// Called by the plug-in to halt REST processing. Local processing is
|
||||
// disabled, and control blocks until all current processing has
|
||||
// completed. No new processing will be started
|
||||
|
||||
public void Close()
|
||||
{
|
||||
enabled = false;
|
||||
Rest.Log.InfoFormat("{0} Remote services closing down", MsgId);
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
internal string MsgId
|
||||
{
|
||||
get { return Rest.MsgId; }
|
||||
}
|
||||
|
||||
// Remote Handler
|
||||
// Key information of interest here is the Parameters array, each
|
||||
// entry represents an element of the URI, with element zero being
|
||||
// the
|
||||
|
||||
public void Execute(RequestData rdata)
|
||||
{
|
||||
if (!enabled) return;
|
||||
|
||||
// If we can't relate to what's there, leave it for others.
|
||||
|
||||
if (rdata.Parameters.Length == 0 || rdata.Parameters[PARM_TESTID] != "remote")
|
||||
return;
|
||||
|
||||
Rest.Log.DebugFormat("{0} REST Remote handler ENTRY", MsgId);
|
||||
|
||||
// Remove the prefix and what's left are the parameters. If we don't have
|
||||
// the parameters we need, fail the request. Parameters do NOT include
|
||||
// any supplied query values.
|
||||
|
||||
if (rdata.Parameters.Length > 1)
|
||||
{
|
||||
switch (rdata.Parameters[PARM_COMMAND].ToLower())
|
||||
{
|
||||
case "move" :
|
||||
DoMove(rdata);
|
||||
break;
|
||||
default :
|
||||
DoHelp(rdata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DoHelp(rdata);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoHelp(RequestData rdata)
|
||||
{
|
||||
rdata.body = Help;
|
||||
rdata.Complete();
|
||||
rdata.Respond("Help");
|
||||
}
|
||||
|
||||
private void DoMove(RequestData rdata)
|
||||
{
|
||||
if (rdata.Parameters.Length < 6)
|
||||
{
|
||||
Rest.Log.WarnFormat("{0} Move: No movement information provided", MsgId);
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest, "no movement information provided");
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] names = rdata.Parameters[PARM_MOVE_AVATAR].Split(Rest.CA_SPACE);
|
||||
ScenePresence presence = null;
|
||||
Scene scene = null;
|
||||
|
||||
if (names.Length != 2)
|
||||
{
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,
|
||||
String.Format("invalid avatar name: <{0}>",rdata.Parameters[PARM_MOVE_AVATAR]));
|
||||
}
|
||||
|
||||
Rest.Log.WarnFormat("{0} '{1}' command received for {2} {3}",
|
||||
MsgId, rdata.Parameters[0], names[0], names[1]);
|
||||
|
||||
// The first parameter should be an avatar name, look for the
|
||||
// avatar in the known regions first.
|
||||
|
||||
Rest.main.SceneManager.ForEachScene(delegate(Scene s)
|
||||
{
|
||||
s.ForEachRootScenePresence(delegate(ScenePresence sp)
|
||||
{
|
||||
if (sp.Firstname == names[0] && sp.Lastname == names[1])
|
||||
{
|
||||
scene = s;
|
||||
presence = sp;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (presence != null)
|
||||
{
|
||||
Rest.Log.DebugFormat("{0} Move : Avatar {1} located in region {2}",
|
||||
MsgId, rdata.Parameters[PARM_MOVE_AVATAR], scene.RegionInfo.RegionName);
|
||||
|
||||
try
|
||||
{
|
||||
float x = Convert.ToSingle(rdata.Parameters[PARM_MOVE_X]);
|
||||
float y = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Y]);
|
||||
float z = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Z]);
|
||||
Vector3 vector = new Vector3(x, y, z);
|
||||
presence.MoveToTarget(vector, false, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,
|
||||
String.Format("invalid parameters: {0}", e.Message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rdata.Fail(Rest.HttpStatusCodeBadRequest,
|
||||
String.Format("avatar {0} not present", rdata.Parameters[PARM_MOVE_AVATAR]));
|
||||
}
|
||||
|
||||
rdata.Complete();
|
||||
rdata.Respond("OK");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string Help =
|
||||
"<html>"
|
||||
+ "<head><title>Remote Command Usage</title></head>"
|
||||
+ "<body>"
|
||||
+ "<p>Supported commands are:</p>"
|
||||
+ "<dl>"
|
||||
+ "<dt>move/avatar-name/x/y/z</dt>"
|
||||
+ "<dd>moves the specified avatar to another location</dd>"
|
||||
+ "</dl>"
|
||||
+ "</body>"
|
||||
+ "</html>"
|
||||
;
|
||||
}
|
||||
}
|
||||
228
OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs
Normal file
228
OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Regions
|
||||
{
|
||||
public partial class RestRegionPlugin : RestPlugin
|
||||
{
|
||||
#region GET methods
|
||||
public string GetHandler(string request, string path, string param,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
// foreach (string h in httpRequest.Headers.AllKeys)
|
||||
// foreach (string v in httpRequest.Headers.GetValues(h))
|
||||
// m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v);
|
||||
|
||||
MsgID = RequestID;
|
||||
m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param);
|
||||
|
||||
try
|
||||
{
|
||||
// param empty: regions list
|
||||
if (String.IsNullOrEmpty(param)) return GetHandlerRegions(httpResponse);
|
||||
|
||||
// param not empty: specific region
|
||||
return GetHandlerRegion(httpResponse, param);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetHandlerRegions(IOSHttpResponse httpResponse)
|
||||
{
|
||||
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
|
||||
|
||||
rxw.WriteStartElement(String.Empty, "regions", String.Empty);
|
||||
foreach (Scene s in App.SceneManager.Scenes)
|
||||
{
|
||||
rxw.WriteStartElement(String.Empty, "uuid", String.Empty);
|
||||
rxw.WriteString(s.RegionInfo.RegionID.ToString());
|
||||
rxw.WriteEndElement();
|
||||
}
|
||||
rxw.WriteEndElement();
|
||||
|
||||
return rxw.ToString();
|
||||
}
|
||||
|
||||
protected string ShortRegionInfo(string key, string value)
|
||||
{
|
||||
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
|
||||
|
||||
if (String.IsNullOrEmpty(value) ||
|
||||
String.IsNullOrEmpty(key)) return null;
|
||||
|
||||
rxw.WriteStartElement(String.Empty, "region", String.Empty);
|
||||
rxw.WriteStartElement(String.Empty, key, String.Empty);
|
||||
rxw.WriteString(value);
|
||||
rxw.WriteEndDocument();
|
||||
|
||||
return rxw.ToString();
|
||||
}
|
||||
|
||||
public string GetHandlerRegion(IOSHttpResponse httpResponse, string param)
|
||||
{
|
||||
// be resilient and don't get confused by a terminating '/'
|
||||
param = param.TrimEnd(new char[]{'/'});
|
||||
string[] comps = param.Split('/');
|
||||
UUID regionID = (UUID)comps[0];
|
||||
|
||||
m_log.DebugFormat("{0} GET region UUID {1}", MsgID, regionID.ToString());
|
||||
|
||||
if (UUID.Zero == regionID) throw new Exception("missing region ID");
|
||||
|
||||
Scene scene = null;
|
||||
App.SceneManager.TryGetScene(regionID, out scene);
|
||||
if (null == scene) return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound,
|
||||
"GET", "cannot find region {0}", regionID.ToString());
|
||||
|
||||
RegionDetails details = new RegionDetails(scene.RegionInfo);
|
||||
|
||||
// m_log.DebugFormat("{0} GET comps {1}", MsgID, comps.Length);
|
||||
// for (int i = 0; i < comps.Length; i++) m_log.DebugFormat("{0} GET comps[{1}] >{2}<", MsgID, i, comps[i]);
|
||||
|
||||
if (1 == comps.Length)
|
||||
{
|
||||
// complete region details requested
|
||||
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
|
||||
XmlSerializer xs = new XmlSerializer(typeof(RegionDetails));
|
||||
xs.Serialize(rxw, details, _xmlNs);
|
||||
return rxw.ToString();
|
||||
}
|
||||
|
||||
if (2 == comps.Length)
|
||||
{
|
||||
string resp = ShortRegionInfo(comps[1], details[comps[1]]);
|
||||
if (null != resp) return resp;
|
||||
|
||||
// m_log.DebugFormat("{0} GET comps advanced: >{1}<", MsgID, comps[1]);
|
||||
|
||||
// check for {terrain,stats,prims}
|
||||
switch (comps[1].ToLower())
|
||||
{
|
||||
case "terrain":
|
||||
return RegionTerrain(httpResponse, scene);
|
||||
|
||||
case "stats":
|
||||
return RegionStats(httpResponse, scene);
|
||||
|
||||
case "prims":
|
||||
return RegionPrims(httpResponse, scene, Vector3.Zero, Vector3.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
if (3 == comps.Length)
|
||||
{
|
||||
switch (comps[1].ToLower())
|
||||
{
|
||||
case "prims":
|
||||
string[] subregion = comps[2].Split(',');
|
||||
if (subregion.Length == 6)
|
||||
{
|
||||
Vector3 min, max;
|
||||
try
|
||||
{
|
||||
min = new Vector3((float)Double.Parse(subregion[0], Culture.NumberFormatInfo), (float)Double.Parse(subregion[1], Culture.NumberFormatInfo), (float)Double.Parse(subregion[2], Culture.NumberFormatInfo));
|
||||
max = new Vector3((float)Double.Parse(subregion[3], Culture.NumberFormatInfo), (float)Double.Parse(subregion[4], Culture.NumberFormatInfo), (float)Double.Parse(subregion[5], Culture.NumberFormatInfo));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest,
|
||||
"GET", "invalid subregion parameter");
|
||||
}
|
||||
return RegionPrims(httpResponse, scene, min, max);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest,
|
||||
"GET", "invalid subregion parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest,
|
||||
"GET", "too many parameters {0}", param);
|
||||
}
|
||||
#endregion GET methods
|
||||
|
||||
protected string RegionTerrain(IOSHttpResponse httpResponse, Scene scene)
|
||||
{
|
||||
httpResponse.SendChunked = true;
|
||||
httpResponse.ContentType = "text/xml";
|
||||
|
||||
return scene.Heightmap.SaveToXmlString();
|
||||
//return Failure(httpResponse, OSHttpStatusCode.ServerErrorNotImplemented,
|
||||
// "GET", "terrain not implemented");
|
||||
}
|
||||
|
||||
protected string RegionStats(IOSHttpResponse httpResponse, Scene scene)
|
||||
{
|
||||
int users = scene.GetRootAgentCount();
|
||||
int objects = scene.Entities.Count - users;
|
||||
|
||||
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
|
||||
|
||||
rxw.WriteStartElement(String.Empty, "region", String.Empty);
|
||||
rxw.WriteStartElement(String.Empty, "stats", String.Empty);
|
||||
|
||||
rxw.WriteStartElement(String.Empty, "users", String.Empty);
|
||||
rxw.WriteString(users.ToString());
|
||||
rxw.WriteEndElement();
|
||||
|
||||
rxw.WriteStartElement(String.Empty, "objects", String.Empty);
|
||||
rxw.WriteString(objects.ToString());
|
||||
rxw.WriteEndElement();
|
||||
|
||||
rxw.WriteEndDocument();
|
||||
|
||||
return rxw.ToString();
|
||||
}
|
||||
|
||||
protected string RegionPrims(IOSHttpResponse httpResponse, Scene scene, Vector3 min, Vector3 max)
|
||||
{
|
||||
httpResponse.SendChunked = true;
|
||||
httpResponse.ContentType = "text/xml";
|
||||
|
||||
IRegionSerialiserModule serialiser = scene.RequestModuleInterface<IRegionSerialiserModule>();
|
||||
if (serialiser != null)
|
||||
serialiser.SavePrimsToXml2(scene, new StreamWriter(httpResponse.OutputStream), min, max);
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
136
OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs
Normal file
136
OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Regions
|
||||
{
|
||||
public partial class RestRegionPlugin : RestPlugin
|
||||
{
|
||||
#region GET methods
|
||||
public string GetRegionInfoHandler(string request, string path, string param,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
// foreach (string h in httpRequest.Headers.AllKeys)
|
||||
// foreach (string v in httpRequest.Headers.GetValues(h))
|
||||
// m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v);
|
||||
|
||||
MsgID = RequestID;
|
||||
m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param);
|
||||
|
||||
try
|
||||
{
|
||||
// param empty: regions list
|
||||
// if (String.IsNullOrEmpty(param))
|
||||
return GetRegionInfoHandlerRegions(httpResponse);
|
||||
|
||||
// // param not empty: specific region
|
||||
// return GetRegionInfoHandlerRegion(httpResponse, param);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetRegionInfoHandlerRegions(IOSHttpResponse httpResponse)
|
||||
{
|
||||
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
|
||||
|
||||
// regions info
|
||||
rxw.WriteStartElement(String.Empty, "regions", String.Empty);
|
||||
{
|
||||
// regions info: number of regions
|
||||
rxw.WriteStartAttribute(String.Empty, "number", String.Empty);
|
||||
rxw.WriteValue(App.SceneManager.Scenes.Count);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
// regions info: max number of regions
|
||||
rxw.WriteStartAttribute(String.Empty, "max", String.Empty);
|
||||
if (App.ConfigSource.Source.Configs["RemoteAdmin"] != null)
|
||||
{
|
||||
rxw.WriteValue(App.ConfigSource.Source.Configs["RemoteAdmin"].GetInt("region_limit", -1));
|
||||
}
|
||||
else
|
||||
{
|
||||
rxw.WriteValue(-1);
|
||||
}
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
// regions info: region
|
||||
foreach (Scene s in App.SceneManager.Scenes)
|
||||
{
|
||||
rxw.WriteStartElement(String.Empty, "region", String.Empty);
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "uuid", String.Empty);
|
||||
rxw.WriteString(s.RegionInfo.RegionID.ToString());
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "name", String.Empty);
|
||||
rxw.WriteString(s.RegionInfo.RegionName);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "x", String.Empty);
|
||||
rxw.WriteValue(s.RegionInfo.RegionLocX);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "y", String.Empty);
|
||||
rxw.WriteValue(s.RegionInfo.RegionLocY);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "external_hostname", String.Empty);
|
||||
rxw.WriteString(s.RegionInfo.ExternalHostName);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "ip", String.Empty);
|
||||
rxw.WriteString(s.RegionInfo.InternalEndPoint.ToString());
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
int users = s.GetRootAgentCount();
|
||||
rxw.WriteStartAttribute(String.Empty, "avatars", String.Empty);
|
||||
rxw.WriteValue(users);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteStartAttribute(String.Empty, "objects", String.Empty);
|
||||
rxw.WriteValue(s.Entities.Count - users);
|
||||
rxw.WriteEndAttribute();
|
||||
|
||||
rxw.WriteEndElement();
|
||||
}
|
||||
}
|
||||
return rxw.ToString();
|
||||
}
|
||||
#endregion GET methods
|
||||
}
|
||||
}
|
||||
122
OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs
Normal file
122
OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Regions
|
||||
{
|
||||
public partial class RestRegionPlugin : RestPlugin
|
||||
{
|
||||
#region POST methods
|
||||
|
||||
public string PostHandler(string request, string path, string param,
|
||||
IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
// foreach (string h in httpRequest.Headers.AllKeys)
|
||||
// foreach (string v in httpRequest.Headers.GetValues(h))
|
||||
// m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v);
|
||||
|
||||
MsgID = RequestID;
|
||||
m_log.DebugFormat("{0} POST path {1} param {2}", MsgID, path, param);
|
||||
|
||||
try
|
||||
{
|
||||
// param empty: new region post
|
||||
if (!IsGod(httpRequest))
|
||||
// XXX: this needs to be turned into a FailureUnauthorized(...)
|
||||
return Failure(httpResponse, OSHttpStatusCode.ClientErrorUnauthorized,
|
||||
"GET", "you are not god");
|
||||
|
||||
if (String.IsNullOrEmpty(param)) return CreateRegion(httpRequest, httpResponse);
|
||||
|
||||
// Parse region ID and other parameters
|
||||
param = param.TrimEnd(new char[] {'/'});
|
||||
string[] comps = param.Split('/');
|
||||
UUID regionID = (UUID) comps[0];
|
||||
|
||||
m_log.DebugFormat("{0} POST region UUID {1}", MsgID, regionID.ToString());
|
||||
if (UUID.Zero == regionID) throw new Exception("missing region ID");
|
||||
|
||||
Scene scene = null;
|
||||
App.SceneManager.TryGetScene(regionID, out scene);
|
||||
if (null == scene)
|
||||
return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound,
|
||||
"POST", "cannot find region {0}", regionID.ToString());
|
||||
|
||||
if (2 == comps.Length)
|
||||
{
|
||||
// check for {prims}
|
||||
switch (comps[1].ToLower())
|
||||
{
|
||||
case "prims":
|
||||
return LoadPrims(request, httpRequest, httpResponse, scene);
|
||||
}
|
||||
}
|
||||
|
||||
return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound,
|
||||
"POST", "url {0} not supported", param);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "POST", e);
|
||||
}
|
||||
}
|
||||
|
||||
public string CreateRegion(IOSHttpRequest request, IOSHttpResponse response)
|
||||
{
|
||||
RestXmlWriter rxw = new RestXmlWriter(new StringWriter());
|
||||
|
||||
rxw.WriteStartElement(String.Empty, "regions", String.Empty);
|
||||
foreach (Scene s in App.SceneManager.Scenes)
|
||||
{
|
||||
rxw.WriteStartElement(String.Empty, "uuid", String.Empty);
|
||||
rxw.WriteString(s.RegionInfo.RegionID.ToString());
|
||||
rxw.WriteEndElement();
|
||||
}
|
||||
rxw.WriteEndElement();
|
||||
|
||||
return rxw.ToString();
|
||||
}
|
||||
|
||||
public string LoadPrims(string requestBody, IOSHttpRequest request, IOSHttpResponse response, Scene scene)
|
||||
{
|
||||
IRegionSerialiserModule serialiser = scene.RequestModuleInterface<IRegionSerialiserModule>();
|
||||
if (serialiser != null)
|
||||
serialiser.LoadPrimsFromXml2(scene, new StringReader(requestBody), true);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#endregion POST methods
|
||||
}
|
||||
}
|
||||
98
OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs
Normal file
98
OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Xml.Serialization;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Regions
|
||||
{
|
||||
[XmlRoot(ElementName="region", IsNullable = false)]
|
||||
public class RegionDetails
|
||||
{
|
||||
public string region_name;
|
||||
public string region_id;
|
||||
public uint region_x;
|
||||
public uint region_y;
|
||||
public string region_owner;
|
||||
public string region_owner_id;
|
||||
public uint region_http_port;
|
||||
public uint region_port;
|
||||
public string region_server_uri;
|
||||
public string region_external_hostname;
|
||||
|
||||
public RegionDetails()
|
||||
{
|
||||
}
|
||||
|
||||
public RegionDetails(RegionInfo regInfo)
|
||||
{
|
||||
region_name = regInfo.RegionName;
|
||||
region_id = regInfo.RegionID.ToString();
|
||||
region_x = regInfo.RegionLocX;
|
||||
region_y = regInfo.RegionLocY;
|
||||
region_owner_id = regInfo.EstateSettings.EstateOwner.ToString();
|
||||
region_http_port = regInfo.HttpPort;
|
||||
region_server_uri = regInfo.ServerURI;
|
||||
region_external_hostname = regInfo.ExternalHostName;
|
||||
|
||||
Uri uri = new Uri(region_server_uri);
|
||||
region_port = (uint)uri.Port;
|
||||
}
|
||||
|
||||
public string this[string idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (idx.ToLower())
|
||||
{
|
||||
case "name":
|
||||
return region_name;
|
||||
case "id":
|
||||
return region_id;
|
||||
case "location":
|
||||
return String.Format("<x>{0}</x><y>{1}</y>", region_x, region_y);
|
||||
case "owner":
|
||||
return region_owner;
|
||||
case "owner_id":
|
||||
return region_owner_id;
|
||||
case "http_port":
|
||||
return region_http_port.ToString();
|
||||
case "server_uri":
|
||||
return region_server_uri;
|
||||
case "external_hostname":
|
||||
case "hostname":
|
||||
return region_external_hostname;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Addin id="OpenSim.ApplicationPlugins.Rest.Regions" version="0.1">
|
||||
<Runtime>
|
||||
<Import assembly="OpenSim.ApplicationPlugins.Rest.Regions.dll"/>
|
||||
</Runtime>
|
||||
<Dependencies>
|
||||
<Addin id="OpenSim" version="0.5" />
|
||||
</Dependencies>
|
||||
<Extension path = "/OpenSim/Startup">
|
||||
<Plugin id="RestRegions" type="OpenSim.ApplicationPlugins.Rest.Regions.RestRegionPlugin" />
|
||||
</Extension>
|
||||
</Addin>
|
||||
94
OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs
Normal file
94
OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest.Regions
|
||||
{
|
||||
public partial class RestRegionPlugin : RestPlugin
|
||||
{
|
||||
private static XmlSerializerNamespaces _xmlNs;
|
||||
|
||||
static RestRegionPlugin()
|
||||
{
|
||||
_xmlNs = new XmlSerializerNamespaces();
|
||||
_xmlNs.Add(String.Empty, String.Empty);
|
||||
}
|
||||
|
||||
#region overriding properties
|
||||
public override string Name
|
||||
{
|
||||
get { return "REGION"; }
|
||||
}
|
||||
|
||||
public override string ConfigName
|
||||
{
|
||||
get { return "RestRegionPlugin"; }
|
||||
}
|
||||
#endregion overriding properties
|
||||
|
||||
#region overriding methods
|
||||
/// <summary>
|
||||
/// This method is called by OpenSimMain immediately after loading the
|
||||
/// plugin and after basic server setup, but before running any server commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that entries MUST be added to the active configuration files before
|
||||
/// the plugin can be enabled.
|
||||
/// </remarks>
|
||||
public override void Initialise(OpenSimBase openSim)
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Initialise(openSim);
|
||||
if (!IsEnabled)
|
||||
{
|
||||
//m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID);
|
||||
return;
|
||||
}
|
||||
|
||||
m_log.InfoFormat("{0} REST region plugin enabled", MsgID);
|
||||
|
||||
// add REST method handlers
|
||||
AddRestStreamHandler("GET", "/regions/", GetHandler);
|
||||
AddRestStreamHandler("POST", "/regions/", PostHandler);
|
||||
AddRestStreamHandler("GET", "/regioninfo/", GetRegionInfoHandler);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message);
|
||||
m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
}
|
||||
#endregion overriding methods
|
||||
}
|
||||
}
|
||||
417
OpenSim/ApplicationPlugins/Rest/RestPlugin.cs
Normal file
417
OpenSim/ApplicationPlugins/Rest/RestPlugin.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest
|
||||
{
|
||||
public abstract class RestPlugin : IApplicationPlugin
|
||||
{
|
||||
#region properties
|
||||
|
||||
protected static readonly ILog m_log =
|
||||
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private IConfig _config; // Configuration source: Rest Plugins
|
||||
private IConfig _pluginConfig; // Configuration source: Plugin specific
|
||||
private OpenSimBase _app; // The 'server'
|
||||
private BaseHttpServer _httpd; // The server's RPC interface
|
||||
private string _prefix; // URL prefix below
|
||||
// which all REST URLs
|
||||
// are living
|
||||
// private StringWriter _sw = null;
|
||||
// private RestXmlWriter _xw = null;
|
||||
|
||||
private string _godkey;
|
||||
private int _reqk;
|
||||
|
||||
[ThreadStatic]
|
||||
private static string _threadRequestID = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Return an ever increasing request ID for logging
|
||||
/// </summary>
|
||||
protected string RequestID
|
||||
{
|
||||
get { return _reqk++.ToString(); }
|
||||
set { _reqk = Convert.ToInt32(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread-constant message IDs for logging.
|
||||
/// </summary>
|
||||
protected string MsgID
|
||||
{
|
||||
get { return String.Format("[REST-{0}] #{1}", Name, _threadRequestID); }
|
||||
set { _threadRequestID = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if Rest Plugins are enabled.
|
||||
/// </summary>
|
||||
public bool PluginsAreEnabled
|
||||
{
|
||||
get { return null != _config; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if specific Rest Plugin is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return (null != _pluginConfig) && _pluginConfig.GetBoolean("enabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OpenSimMain application
|
||||
/// </summary>
|
||||
public OpenSimBase App
|
||||
{
|
||||
get { return _app; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RPC server
|
||||
/// </summary>
|
||||
public BaseHttpServer HttpServer
|
||||
{
|
||||
get { return _httpd; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL prefix to use for all REST handlers
|
||||
/// </summary>
|
||||
public string Prefix
|
||||
{
|
||||
get { return _prefix; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access to GOD password string
|
||||
/// </summary>
|
||||
protected string GodKey
|
||||
{
|
||||
get { return _godkey; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of the plugin
|
||||
/// </summary>
|
||||
public IConfig Config
|
||||
{
|
||||
get { return _pluginConfig; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the plugin
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Return the config section name
|
||||
/// </summary>
|
||||
public abstract string ConfigName { get; }
|
||||
|
||||
// public XmlTextWriter XmlWriter
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (null == _xw)
|
||||
// {
|
||||
// _sw = new StringWriter();
|
||||
// _xw = new RestXmlWriter(_sw);
|
||||
// _xw.Formatting = Formatting.Indented;
|
||||
// }
|
||||
// return _xw;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public string XmlWriterResult
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// _xw.Flush();
|
||||
// _xw.Close();
|
||||
// _xw = null;
|
||||
|
||||
// return _sw.ToString();
|
||||
// }
|
||||
// }
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region methods
|
||||
|
||||
// TODO: required by IPlugin, but likely not at all right
|
||||
private string m_version = "0.0";
|
||||
|
||||
public string Version
|
||||
{
|
||||
get { return m_version; }
|
||||
}
|
||||
|
||||
public void Initialise()
|
||||
{
|
||||
m_log.Info("[RESTPLUGIN]: " + Name + " cannot be default-initialized!");
|
||||
throw new PluginNotInitialisedException(Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by OpenSimMain immediately after loading the
|
||||
/// plugin and after basic server setup, but before running any server commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that entries MUST be added to the active configuration files before
|
||||
/// the plugin can be enabled.
|
||||
/// </remarks>
|
||||
public virtual void Initialise(OpenSimBase openSim)
|
||||
{
|
||||
RequestID = "0";
|
||||
MsgID = RequestID;
|
||||
|
||||
try
|
||||
{
|
||||
if ((_config = openSim.ConfigSource.Source.Configs["RestPlugins"]) == null)
|
||||
{
|
||||
m_log.WarnFormat("{0} Rest Plugins not configured", MsgID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_config.GetBoolean("enabled", false))
|
||||
{
|
||||
//m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID);
|
||||
return;
|
||||
}
|
||||
|
||||
_app = openSim;
|
||||
_httpd = openSim.HttpServer;
|
||||
|
||||
// Retrieve GOD key value, if any.
|
||||
_godkey = _config.GetString("god_key", String.Empty);
|
||||
|
||||
// Retrive prefix if any.
|
||||
_prefix = _config.GetString("prefix", "/admin");
|
||||
|
||||
// Get plugin specific config
|
||||
_pluginConfig = openSim.ConfigSource.Source.Configs[ConfigName];
|
||||
|
||||
m_log.InfoFormat("{0} Rest Plugins Enabled", MsgID);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// we can safely ignore this, as it just means that
|
||||
// the key lookup in Configs failed, which signals to
|
||||
// us that noone is interested in our services...they
|
||||
// don't know what they are missing out on...
|
||||
// NOTE: Under the present OpenSimulator implementation it is
|
||||
// not possible for the openSimulator pointer to be null. However
|
||||
// were the implementation to be changed, this could
|
||||
// result in a silent initialization failure. Harmless
|
||||
// except for lack of function and lack of any
|
||||
// diagnostic indication as to why. The same is true if
|
||||
// the HTTP server reference is bad.
|
||||
// We should at least issue a message...
|
||||
m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message);
|
||||
m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void PostInitialise()
|
||||
{
|
||||
}
|
||||
|
||||
private List<RestStreamHandler> _handlers = new List<RestStreamHandler>();
|
||||
private Dictionary<string, IHttpAgentHandler> _agents = new Dictionary<string, IHttpAgentHandler>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a REST stream handler to the underlying HTTP server.
|
||||
/// </summary>
|
||||
/// <param name="httpMethod">GET/PUT/POST/DELETE or
|
||||
/// similar</param>
|
||||
/// <param name="path">URL prefix</param>
|
||||
/// <param name="method">RestMethod handler doing the actual work</param>
|
||||
public virtual void AddRestStreamHandler(string httpMethod, string path, RestMethod method)
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
if (!path.StartsWith(_prefix))
|
||||
{
|
||||
path = String.Format("{0}{1}", _prefix, path);
|
||||
}
|
||||
|
||||
RestStreamHandler h = new RestStreamHandler(httpMethod, path, method);
|
||||
_httpd.AddStreamHandler(h);
|
||||
_handlers.Add(h);
|
||||
|
||||
m_log.DebugFormat("{0} Added REST handler {1} {2}", MsgID, httpMethod, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a powerful Agent handler to the underlying HTTP
|
||||
/// server.
|
||||
/// </summary>
|
||||
/// <param name="agentName">name of agent handler</param>
|
||||
/// <param name="handler">agent handler method</param>
|
||||
/// <returns>false when the plugin is disabled or the agent
|
||||
/// handler could not be added. Any generated exceptions are
|
||||
/// allowed to drop through to the caller, i.e. ArgumentException.
|
||||
/// </returns>
|
||||
public bool AddAgentHandler(string agentName, IHttpAgentHandler handler)
|
||||
{
|
||||
if (!IsEnabled) return false;
|
||||
_agents.Add(agentName, handler);
|
||||
// return _httpd.AddAgentHandler(agentName, handler);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a powerful Agent handler from the underlying HTTP
|
||||
/// server.
|
||||
/// </summary>
|
||||
/// <param name="agentName">name of agent handler</param>
|
||||
/// <param name="handler">agent handler method</param>
|
||||
/// <returns>false when the plugin is disabled or the agent
|
||||
/// handler could not be removed. Any generated exceptions are
|
||||
/// allowed to drop through to the caller, i.e. KeyNotFound.
|
||||
/// </returns>
|
||||
public bool RemoveAgentHandler(string agentName, IHttpAgentHandler handler)
|
||||
{
|
||||
if (!IsEnabled) return false;
|
||||
if (_agents[agentName] == handler)
|
||||
{
|
||||
_agents.Remove(agentName);
|
||||
// return _httpd.RemoveAgentHandler(agentName, handler);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the HTTP request came from god; that is, is
|
||||
/// the god_key as configured in the config section supplied
|
||||
/// via X-OpenSim-Godkey?
|
||||
/// </summary>
|
||||
/// <param name="request">HTTP request header</param>
|
||||
/// <returns>true when the HTTP request came from god.</returns>
|
||||
protected bool IsGod(IOSHttpRequest request)
|
||||
{
|
||||
string[] keys = request.Headers.GetValues("X-OpenSim-Godkey");
|
||||
if (null == keys) return false;
|
||||
|
||||
// we take the last key supplied
|
||||
return keys[keys.Length - 1] == _godkey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks wether the X-OpenSim-Password value provided in the
|
||||
/// HTTP header is indeed the password on file for the avatar
|
||||
/// specified by the UUID
|
||||
/// </summary>
|
||||
protected bool IsVerifiedUser(IOSHttpRequest request, UUID uuid)
|
||||
{
|
||||
// XXX under construction
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up and remove all handlers that were added earlier.
|
||||
/// </summary>
|
||||
public virtual void Close()
|
||||
{
|
||||
foreach (RestStreamHandler h in _handlers)
|
||||
{
|
||||
_httpd.RemoveStreamHandler(h.HttpMethod, h.Path);
|
||||
}
|
||||
_handlers = null;
|
||||
// foreach (KeyValuePair<string, IHttpAgentHandler> h in _agents)
|
||||
// {
|
||||
// _httpd.RemoveAgentHandler(h.Key, h.Value);
|
||||
// }
|
||||
_agents = null;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a failure message.
|
||||
/// </summary>
|
||||
/// <param name="method">origin of the failure message</param>
|
||||
/// <param name="message">failure message</param>
|
||||
/// <remarks>This should probably set a return code as
|
||||
/// well. (?)</remarks>
|
||||
protected string Failure(IOSHttpResponse response, OSHttpStatusCode status,
|
||||
string method, string format, params string[] msg)
|
||||
{
|
||||
string m = String.Format(format, msg);
|
||||
|
||||
response.StatusCode = (int) status;
|
||||
response.StatusDescription = m;
|
||||
|
||||
m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, m);
|
||||
return String.Format("<error>{0}</error>", m);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a failure message.
|
||||
/// </summary>
|
||||
/// <param name="method">origin of the failure message</param>
|
||||
/// <param name="e">exception causing the failure message</param>
|
||||
/// <remarks>This should probably set a return code as
|
||||
/// well. (?)</remarks>
|
||||
public string Failure(IOSHttpResponse response, OSHttpStatusCode status,
|
||||
string method, Exception e)
|
||||
{
|
||||
string m = String.Format("exception occurred: {0}", e.Message);
|
||||
|
||||
response.StatusCode = (int) status;
|
||||
response.StatusDescription = m;
|
||||
|
||||
m_log.DebugFormat("{0} {1} failed: {2}", MsgID, method, e.ToString());
|
||||
m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, e.Message);
|
||||
|
||||
return String.Format("<error>{0}</error>", e.Message);
|
||||
}
|
||||
|
||||
#endregion methods
|
||||
}
|
||||
}
|
||||
72
OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs
Normal file
72
OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.Rest
|
||||
{
|
||||
public class RestXmlWriter: XmlTextWriter
|
||||
{
|
||||
private StringWriter m_sw = null;
|
||||
|
||||
public RestXmlWriter(StringWriter sw) : base(sw)
|
||||
{
|
||||
m_sw = sw;
|
||||
Formatting = Formatting.Indented;
|
||||
}
|
||||
|
||||
public RestXmlWriter(TextWriter textWriter) : base(textWriter)
|
||||
{
|
||||
}
|
||||
|
||||
public RestXmlWriter(Stream stream)
|
||||
: this(stream, Encoding.UTF8)
|
||||
{
|
||||
}
|
||||
|
||||
public RestXmlWriter(Stream stream, Encoding enc) : base(stream, enc)
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteStartDocument()
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteStartDocument(bool standalone)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
Flush();
|
||||
Close();
|
||||
return m_sw.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
276
OpenSim/ApplicationPlugins/Rest/rest.xsd
Normal file
276
OpenSim/ApplicationPlugins/Rest/rest.xsd
Normal file
@@ -0,0 +1,276 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
Open Simulator Export/Import XML schema
|
||||
August 2008
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
<!-- WARNING!!!
|
||||
This is currently a draft, it does not reflect
|
||||
what is exported, nor what will be understood
|
||||
on import. It is included as a working document
|
||||
and this comment will be removed at such time as
|
||||
the schema corresponds to reality.
|
||||
-->
|
||||
|
||||
<!--
|
||||
REST-related information
|
||||
Inventory data is always framed by an
|
||||
inventory element. Consists of zero or
|
||||
more elements representing either folders
|
||||
or items within those folders. The inventory
|
||||
element represents the "real" root folder.
|
||||
-->
|
||||
|
||||
<xsd:element name="inventory" type="inventory_ct" />
|
||||
|
||||
<!--
|
||||
The inventory complex type is just an arbitrary
|
||||
sequence of folders and items. In reality it is
|
||||
typically just folders. Both item and folder
|
||||
have corresponding complex types. It is distinct
|
||||
from folders insofar as it has no other defining
|
||||
attributes.
|
||||
-->
|
||||
|
||||
<xsd:complexType name="inventory_ct">
|
||||
<xsd:element name="folder" type="folder_ct" maxOccurs="unbounded"/>
|
||||
<xsd:element name="item" type="item_ct" maxOccurs="unbounded" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="folder_ct">
|
||||
<xsd:attribute name="UUID" type="uuid_st" />
|
||||
<xsd:attribute name="name" type="name_st" />
|
||||
<xsd:attribute name="type" type="folder_type_st" />
|
||||
<xsd:attribute name="description" type="xsd:string" /> <!-- added -->
|
||||
<xsd:attribute name="version" type="unsignedShort" />
|
||||
<xsd:attribute name="owner" type="uuid_st" />
|
||||
|
||||
<xsd:attribute name="creator" type="uuid_st" /> <!-- added -->
|
||||
<xsd:attribute name="creationdate" type="date_st" /> <!-- added -->
|
||||
|
||||
<xsd:attribute name="parent" type="uuid_st" />
|
||||
|
||||
<xsd:element name="permissions" type="permissions_ct" maxOccurs="unbounded" /> <!-- added -->
|
||||
<xsd:element name="folder" type="folder_ct" maxOccurs="unbounded" />
|
||||
<xsd:element name="item" type="item_ct" maxOccurs="unbounded" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="item_ct">
|
||||
<xsd:attribute name="UUID" type="uuid_st" />
|
||||
<xsd:attribute name="name" type="name_st" />
|
||||
<xsd:attribute name="type" type="inventory_type_st" />
|
||||
<xsd:attribute name="description" type="xsd:string" />
|
||||
<xsd:attribute name="version" type="unsignedShort" /> <!-- added -->
|
||||
<xsd:attribute name="owner" type="uuid_st" />
|
||||
|
||||
<xsd:attribute name="creator" type="uuid_st" />
|
||||
<xsd:attribute name="creationdate" type="date_st" />
|
||||
|
||||
<xsd:attribute name="folder" type="uuid_st" />
|
||||
<xsd:attribute name="groupid" type="uuid_st" />
|
||||
<xsd:attribute name="groupowned" type="xsd:boolean" />
|
||||
<xsd:attribute name="saletype" type="sale_st" />
|
||||
<xsd:attribute name="saleprice" type="xsd:decimal" />
|
||||
|
||||
<xsd:element name="permissions" type="permissions_ct" maxOccurs="unbounded" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="asset_ct">
|
||||
<xsd:attribute name="UUID" type="uuid_st" />
|
||||
<xsd:attribute name="name" type="name_st" />
|
||||
<xsd:attribute name="type" type="asset_type_st" />
|
||||
<xsd:attribute name="description" type="xsd:string" />
|
||||
<xsd:attribute name="version" type="unsignedShort" /> <!-- added -->
|
||||
<xsd:attribute name="owner" type="uuid_st" />
|
||||
|
||||
<xsd:attribute name="creator" type="uuid_st" />
|
||||
<xsd:attribute name="creationdate" type="date_st" />
|
||||
|
||||
<xsd:attribute name="temporary" type="xsd:boolean" />
|
||||
<xsd:attribute name="local" type="xsd:boolean" />
|
||||
<xsd:attribute name="inline" type="xsd:boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<!-- Constrained Simple Data types -->
|
||||
|
||||
<!--
|
||||
We need to specify name as a simple type because on
|
||||
some platforms it is constrained by a certain length
|
||||
limitation. For completeness we indicate that whitespace
|
||||
should be preserved exactly as specified.
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="name_st">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<whiteSpace value="preserve" />
|
||||
<minLength value="0" />
|
||||
<maxLength value="64" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<!--
|
||||
Type information in the folder is meant to indicate
|
||||
the preferred asset type for this folder. As such, that
|
||||
currently corresponds to the type values allowed for
|
||||
assets, however that is not mandated, so for
|
||||
now at least I'll represent this as a distinct
|
||||
enumeration.
|
||||
This seems inappropriate; it seems like the folder's
|
||||
content should reflect the InventoryType classifications
|
||||
rather than the asset types.
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="folder_type_st">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Texture" />
|
||||
<xsd:enumeration value="Sound" />
|
||||
<xsd:enumeration value="CallingCard" />
|
||||
<xsd:enumeration value="Landmark" />
|
||||
<xsd:enumeration value="Script" />
|
||||
<xsd:enumeration value="Clothing" />
|
||||
<xsd:enumeration value="Object" />
|
||||
<xsd:enumeration value="Notecard" />
|
||||
<xsd:enumeration value="LSLText" />
|
||||
<xsd:enumeration value="LSLByteCode" />
|
||||
<xsd:enumeration value="TextureTGA" />
|
||||
<xsd:enumeration value="BodyPart" />
|
||||
<xsd:enumeration value="SoundWAV" />
|
||||
<xsd:enumeration value="ImageTGA" />
|
||||
<xsd:enumeration value="ImageJPEG" />
|
||||
<xsd:enumeration value="Animation" />
|
||||
<xsd:enumeration value="Gesture" />
|
||||
<xsd:enumeration value="Simstate" />
|
||||
<xsd:enumeration value="Unknown" />
|
||||
<xsd:enumeration value="LostAndFoundFolder" />
|
||||
<xsd:enumeration value="SnapshotFolder" />
|
||||
<xsd:enumeration value="TrashFolder" />
|
||||
<xsd:enumeration value="Folder" />
|
||||
<xsd:enumeration value="RootFolder" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<!--
|
||||
Inventory item type designates an asset class, rather
|
||||
than a specific asset type. For example, "SnapShot"
|
||||
might include a number of asset types such as JPEG,
|
||||
TGA, etc.. This is not a consistent interpretation,
|
||||
classifications such as LostAndFound are meta-types
|
||||
relative to asset classes.
|
||||
|
||||
These types should be abstract and not be tied to a
|
||||
specific platform. A world's import facility should be
|
||||
responsible for mapping these to meaningful internal
|
||||
representations.
|
||||
|
||||
These types were based on information in:
|
||||
libsecondlife/InventoryManager.cs
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="inventory_type_st">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Texture" />
|
||||
<xsd:enumeration value="Sound" />
|
||||
<xsd:enumeration value="CallingCard" />
|
||||
<xsd:enumeration value="Landmark" />
|
||||
<xsd:enumeration value="Script" />
|
||||
<xsd:enumeration value="Clothing" />
|
||||
<xsd:enumeration value="Object" />
|
||||
<xsd:enumeration value="Notecard" />
|
||||
<xsd:enumeration value="LSL" />
|
||||
<xsd:enumeration value="LSLBytecode" />
|
||||
<xsd:enumeration value="TextureTGA" />
|
||||
<xsd:enumeration value="BodyPart" />
|
||||
<xsd:enumeration value="Snapshot" />
|
||||
<xsd:enumeration value="Attachment" />
|
||||
<xsd:enumeration value="Wearable" />
|
||||
<xsd:enumeration value="Animation" />
|
||||
<xsd:enumeration value="Gesture" />
|
||||
<xsd:enumeration value="Folder" />
|
||||
<xsd:enumeration value="Unknown" />
|
||||
<xsd:enumeration value="LostAndFound" />
|
||||
<xsd:enumeration value="Trash" />
|
||||
<xsd:enumeration value="Root" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<!--
|
||||
The asset types seem to be even more disarrayed than
|
||||
the inventory types. It seems to be little more than
|
||||
a reiteration of the inventory type information,
|
||||
which adds little or nothing to the overall data
|
||||
model.
|
||||
|
||||
Of course, given that these are drawn from the
|
||||
libsecondlife definitions, we aren't at liberty to
|
||||
simply redefine them in place. But the XML definitions
|
||||
here could be made more useful.
|
||||
|
||||
These types were based on information in:
|
||||
libsecondlife/AssetManager.cs
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="asset_type_st">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Texture" />
|
||||
<xsd:enumeration value="Sound" />
|
||||
<xsd:enumeration value="CallingCard" />
|
||||
<xsd:enumeration value="Landmark" />
|
||||
<xsd:enumeration value="Script" />
|
||||
<xsd:enumeration value="Clothing" />
|
||||
<xsd:enumeration value="Object" />
|
||||
<xsd:enumeration value="Notecard" />
|
||||
<xsd:enumeration value="LSLText" />
|
||||
<xsd:enumeration value="LSLByteCode" />
|
||||
<xsd:enumeration value="TextureTGA" />
|
||||
<xsd:enumeration value="BodyPart" />
|
||||
<xsd:enumeration value="SoundWAV" />
|
||||
<xsd:enumeration value="ImageTGA" />
|
||||
<xsd:enumeration value="ImageJPEG" />
|
||||
<xsd:enumeration value="Animation" />
|
||||
<xsd:enumeration value="Gesture" />
|
||||
<xsd:enumeration value="Simstate" />
|
||||
<xsd:enumeration value="Unknown" />
|
||||
<xsd:enumeration value="LostAndFoundFolder" />
|
||||
<xsd:enumeration value="SnapshotFolder" />
|
||||
<xsd:enumeration value="TrashFolder" />
|
||||
<xsd:enumeration value="Folder" />
|
||||
<xsd:enumeration value="RootFolder" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<!-- This is describing the apparent form of a UUID. If
|
||||
we ever want a more metaphysical definition we'll
|
||||
need to add to it.
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="uuid_st">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<!-- This constrains the date representation. Currently
|
||||
it is simply an integer representing the elapsed
|
||||
?? since ??.
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="date_st">
|
||||
<xsd:restriction base="xsd:positiveInteger">
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<!-- This constrains the representation of sale price.
|
||||
Currently it is a simple decimal with no unit
|
||||
specified.
|
||||
Issues: interoperability.
|
||||
-->
|
||||
|
||||
<xsd:simpleType name="sale_st">
|
||||
<xsd:restriction base="xsd:decimal">
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
</xsd:schema>
|
||||
@@ -28,14 +28,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Services.Interfaces;
|
||||
|
||||
// using OpenSim.Region.Framework.Interfaces;
|
||||
|
||||
@@ -48,28 +48,26 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// </summary>
|
||||
public delegate IClientAPI GetClientDelegate(UUID agentID);
|
||||
|
||||
public class Caps : IDisposable
|
||||
public class Caps
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
// private static readonly ILog m_log =
|
||||
// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private readonly string m_httpListenerHostName;
|
||||
private readonly uint m_httpListenPort;
|
||||
private string m_httpListenerHostName;
|
||||
private uint m_httpListenPort;
|
||||
|
||||
/// <summary>
|
||||
/// This is the uuid portion of every CAPS path. It is used to make capability urls private to the requester.
|
||||
/// </summary>
|
||||
private readonly string m_capsObjectPath;
|
||||
private string m_capsObjectPath;
|
||||
public string CapsObjectPath { get { return m_capsObjectPath; } }
|
||||
|
||||
private readonly CapsHandlers m_capsHandlers;
|
||||
private readonly ConcurrentDictionary<string, PollServiceEventArgs> m_pollServiceHandlers = new ();
|
||||
private readonly Dictionary<string, string> m_externalCapsHandlers = new ();
|
||||
private CapsHandlers m_capsHandlers;
|
||||
private Dictionary<string, string> m_externalCapsHandlers;
|
||||
|
||||
private readonly IHttpServer m_httpListener;
|
||||
private readonly UUID m_agentID;
|
||||
private readonly string m_regionName;
|
||||
private ManualResetEvent m_capsActive = new(false);
|
||||
private readonly string m_baseCapsURL;
|
||||
private IHttpServer m_httpListener;
|
||||
private UUID m_agentID;
|
||||
private string m_regionName;
|
||||
|
||||
public UUID AgentID
|
||||
{
|
||||
@@ -116,22 +114,6 @@ namespace OpenSim.Framework.Capabilities
|
||||
get { return m_externalCapsHandlers; }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CapsFlags: uint
|
||||
{
|
||||
None = 0,
|
||||
SentSeeds = 1,
|
||||
|
||||
ObjectAnim = 0x100,
|
||||
WLEnv = 0x200,
|
||||
AdvEnv = 0x400,
|
||||
PBR = 0x800,
|
||||
ViewerBenefits = 0x1000,
|
||||
TPBR = 0x2000
|
||||
}
|
||||
|
||||
public CapsFlags Flags { get; set;}
|
||||
|
||||
public Caps(IHttpServer httpServer, string httpListen, uint httpPort, string capsPath,
|
||||
UUID agent, string regionName)
|
||||
{
|
||||
@@ -141,7 +123,7 @@ namespace OpenSim.Framework.Capabilities
|
||||
|
||||
m_httpListenPort = httpPort;
|
||||
|
||||
if (httpServer is not null && httpServer.UseSSL)
|
||||
if (httpServer != null && httpServer.UseSSL)
|
||||
{
|
||||
m_httpListenPort = httpServer.SSLPort;
|
||||
httpListen = httpServer.SSLCommonName;
|
||||
@@ -149,41 +131,9 @@ namespace OpenSim.Framework.Capabilities
|
||||
}
|
||||
|
||||
m_agentID = agent;
|
||||
m_capsHandlers = new CapsHandlers(httpServer, httpListen, httpPort);
|
||||
m_capsHandlers = new CapsHandlers(httpServer, httpListen, httpPort, (httpServer == null) ? false : httpServer.UseSSL);
|
||||
m_externalCapsHandlers = new Dictionary<string, string>();
|
||||
m_regionName = regionName;
|
||||
Flags = CapsFlags.None;
|
||||
m_capsActive.Reset();
|
||||
if (MainServer.Instance.UseSSL)
|
||||
m_baseCapsURL = $"https://{MainServer.Instance.SSLCommonName}:{MainServer.Instance.SSLPort}";
|
||||
else
|
||||
{
|
||||
if (MainServer.Instance is null)
|
||||
m_baseCapsURL = $"http://{m_httpListenerHostName}:0";
|
||||
else
|
||||
m_baseCapsURL = $"http://{m_httpListenerHostName}:{MainServer.Instance.Port}";
|
||||
}
|
||||
}
|
||||
|
||||
~Caps()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Dispose(bool disposing)
|
||||
{
|
||||
Flags = CapsFlags.None;
|
||||
if (m_capsActive != null)
|
||||
{
|
||||
DeregisterHandlers();
|
||||
m_capsActive.Dispose();
|
||||
m_capsActive = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -193,45 +143,8 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// <param name="handler"></param>
|
||||
public void RegisterHandler(string capName, IRequestHandler handler)
|
||||
{
|
||||
//m_log.DebugFormat("[CAPS]: Registering handler for \"{0}\": path {1}", capName, handler.Path);
|
||||
m_capsHandlers[capName] = handler;
|
||||
}
|
||||
|
||||
public void RegisterSimpleHandler(string capName, ISimpleStreamHandler handler, bool addToListener = true)
|
||||
{
|
||||
//m_log.DebugFormat("[CAPS]: Registering handler for \"{0}\": path {1}", capName, handler.Path);
|
||||
m_capsHandlers.AddSimpleHandler(capName, handler, addToListener);
|
||||
}
|
||||
|
||||
public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler)
|
||||
{
|
||||
//m_log.DebugFormat(
|
||||
// "[CAPS]: Registering handler with name {0}, url {1} for {2}",
|
||||
// capName, pollServiceHandler.Url, m_agentID, m_regionName);
|
||||
|
||||
if(!m_pollServiceHandlers.TryAdd(capName, pollServiceHandler))
|
||||
{
|
||||
m_log.ErrorFormat(
|
||||
"[CAPS]: Handler with name {0} already registered (ulr {1}, agent {2}, region {3}",
|
||||
capName, pollServiceHandler.Url, m_agentID, m_regionName);
|
||||
return;
|
||||
}
|
||||
|
||||
m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler);
|
||||
|
||||
//uint port = (MainServer.Instance == null) ? 0 : MainServer.Instance.Port;
|
||||
//string protocol = "http";
|
||||
//string hostName = m_httpListenerHostName;
|
||||
|
||||
//if (MainServer.Instance.UseSSL)
|
||||
//{
|
||||
// hostName = MainServer.Instance.SSLCommonName;
|
||||
// port = MainServer.Instance.SSLPort;
|
||||
// protocol = "https";
|
||||
//}
|
||||
|
||||
//RegisterHandler(
|
||||
// capName, String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, pollServiceHandler.Url));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,101 +163,13 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// </summary>
|
||||
public void DeregisterHandlers()
|
||||
{
|
||||
foreach (string capsName in m_capsHandlers.Caps)
|
||||
if (m_capsHandlers != null)
|
||||
{
|
||||
m_capsHandlers.Remove(capsName);
|
||||
}
|
||||
|
||||
foreach (PollServiceEventArgs handler in m_pollServiceHandlers.Values)
|
||||
{
|
||||
m_httpListener.RemovePollServiceHTTPHandler(handler.Url);
|
||||
}
|
||||
m_pollServiceHandlers.Clear();
|
||||
}
|
||||
public bool TryGetPollHandler(string name, out PollServiceEventArgs pollHandler)
|
||||
{
|
||||
return m_pollServiceHandlers.TryGetValue(name, out pollHandler);
|
||||
}
|
||||
|
||||
public Dictionary<string, PollServiceEventArgs> GetPollHandlers()
|
||||
{
|
||||
return new Dictionary<string, PollServiceEventArgs>(m_pollServiceHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return an LLSD-serializable Hashtable describing the
|
||||
/// capabilities and their handler details.
|
||||
/// </summary>
|
||||
/// <param name="excludeSeed">If true, then exclude the seed cap.</param>
|
||||
public Hashtable GetCapsDetails(bool excludeSeed, List<string> requestedCaps)
|
||||
{
|
||||
Hashtable caps = CapsHandlers.GetCapsDetails(excludeSeed, requestedCaps);
|
||||
|
||||
lock (m_pollServiceHandlers)
|
||||
{
|
||||
foreach (KeyValuePair<string, PollServiceEventArgs> kvp in m_pollServiceHandlers)
|
||||
foreach (string capsName in m_capsHandlers.Caps)
|
||||
{
|
||||
if (!requestedCaps.Contains(kvp.Key))
|
||||
continue;
|
||||
|
||||
string hostName = m_httpListenerHostName;
|
||||
uint port = (MainServer.Instance is null) ? 0 : MainServer.Instance.Port;
|
||||
string protocol = "http";
|
||||
|
||||
if (MainServer.Instance.UseSSL)
|
||||
{
|
||||
hostName = MainServer.Instance.SSLCommonName;
|
||||
port = MainServer.Instance.SSLPort;
|
||||
protocol = "https";
|
||||
}
|
||||
caps[kvp.Key] = string.Format("{0}://{1}:{2}{3}", protocol, hostName, port, kvp.Value.Url);
|
||||
m_capsHandlers.Remove(capsName);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the external too
|
||||
foreach (KeyValuePair<string, string> kvp in ExternalCapsHandlers)
|
||||
{
|
||||
if (!requestedCaps.Contains(kvp.Key))
|
||||
continue;
|
||||
|
||||
caps[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
public void GetCapsDetailsLLSDxml(HashSet<string> requestedCaps, osUTF8 sb)
|
||||
{
|
||||
|
||||
CapsHandlers.GetCapsDetailsLLSDxml(requestedCaps, sb);
|
||||
|
||||
lock (m_pollServiceHandlers)
|
||||
{
|
||||
foreach (KeyValuePair<string, PollServiceEventArgs> kvp in m_pollServiceHandlers)
|
||||
{
|
||||
if (requestedCaps.Contains(kvp.Key))
|
||||
LLSDxmlEncode2.AddElem(kvp.Key, m_baseCapsURL + kvp.Value.Url, sb);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the external too
|
||||
foreach (KeyValuePair<string, string> kvp in ExternalCapsHandlers)
|
||||
{
|
||||
if (requestedCaps.Contains(kvp.Key))
|
||||
LLSDxmlEncode2.AddElem(kvp.Key, kvp.Value, sb);
|
||||
}
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
m_capsActive.Set();
|
||||
}
|
||||
|
||||
public bool WaitForActivation()
|
||||
{
|
||||
// Wait for 30s. If that elapses, return false and run without caps
|
||||
return m_capsActive.WaitOne(120000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,8 @@
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using OpenSim.Framework.Servers;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
@@ -42,13 +39,11 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// </summary>
|
||||
public class CapsHandlers
|
||||
{
|
||||
private readonly Dictionary<string, IRequestHandler> m_capsHandlers = new Dictionary<string, IRequestHandler>();
|
||||
private readonly ConcurrentDictionary<string, ISimpleStreamHandler> m_capsSimpleHandlers = new ConcurrentDictionary<string, ISimpleStreamHandler>();
|
||||
private Dictionary <string, IRequestHandler> m_capsHandlers = new Dictionary<string, IRequestHandler>();
|
||||
private IHttpServer m_httpListener;
|
||||
private string m_httpListenerHostName;
|
||||
private uint m_httpListenerPort;
|
||||
|
||||
public readonly string BaseURL;
|
||||
private bool m_useSSL = false;
|
||||
|
||||
/// <summary></summary>
|
||||
/// CapsHandlers is a cap handler container but also takes
|
||||
@@ -58,15 +53,31 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// <param name="httpListener">base HTTP server</param>
|
||||
/// <param name="httpListenerHostname">host name of the HTTP server</param>
|
||||
/// <param name="httpListenerPort">HTTP port</param>
|
||||
public CapsHandlers(IHttpServer httpListener, string httpListenerHostname, uint httpListenerPort)
|
||||
public CapsHandlers(BaseHttpServer httpListener, string httpListenerHostname, uint httpListenerPort)
|
||||
: this(httpListener,httpListenerHostname,httpListenerPort, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
/// CapsHandlers is a cap handler container but also takes
|
||||
/// care of adding and removing cap handlers to and from the
|
||||
/// supplied BaseHttpServer.
|
||||
/// </summary>
|
||||
/// <param name="httpListener">base HTTP server</param>
|
||||
/// <param name="httpListenerHostname">host name of the HTTP
|
||||
/// server</param>
|
||||
/// <param name="httpListenerPort">HTTP port</param>
|
||||
public CapsHandlers(IHttpServer httpListener, string httpListenerHostname, uint httpListenerPort, bool https)
|
||||
{
|
||||
m_httpListener = httpListener;
|
||||
m_httpListenerHostName = httpListenerHostname;
|
||||
m_httpListenerPort = httpListenerPort;
|
||||
if (httpListener != null && httpListener.UseSSL)
|
||||
BaseURL = $"https://{m_httpListenerHostName}:{m_httpListenerPort}";
|
||||
else
|
||||
BaseURL = $"http://{m_httpListenerHostName}:{m_httpListenerPort}";
|
||||
m_useSSL = https;
|
||||
if (httpListener != null && m_useSSL)
|
||||
{
|
||||
m_httpListenerHostName = httpListener.SSLCommonName;
|
||||
m_httpListenerPort = httpListener.SSLPort;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,35 +89,16 @@ namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
lock (m_capsHandlers)
|
||||
{
|
||||
if(m_capsHandlers.ContainsKey(capsName))
|
||||
{
|
||||
m_httpListener.RemoveStreamHandler("POST", m_capsHandlers[capsName].Path);
|
||||
m_httpListener.RemoveStreamHandler("PUT", m_capsHandlers[capsName].Path);
|
||||
m_httpListener.RemoveStreamHandler("GET", m_capsHandlers[capsName].Path);
|
||||
m_httpListener.RemoveStreamHandler("DELETE", m_capsHandlers[capsName].Path);
|
||||
m_capsHandlers.Remove(capsName);
|
||||
}
|
||||
m_httpListener.RemoveStreamHandler("POST", m_capsHandlers[capsName].Path);
|
||||
m_httpListener.RemoveStreamHandler("GET", m_capsHandlers[capsName].Path);
|
||||
m_capsHandlers.Remove(capsName);
|
||||
}
|
||||
if(m_capsSimpleHandlers.TryRemove(capsName, out ISimpleStreamHandler hdr))
|
||||
{
|
||||
m_httpListener.RemoveSimpleStreamHandler(hdr.Path);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSimpleHandler(string capName, ISimpleStreamHandler handler, bool addToListener = true)
|
||||
{
|
||||
if(ContainsCap(capName))
|
||||
Remove(capName);
|
||||
if(m_capsSimpleHandlers.TryAdd(capName, handler) && addToListener)
|
||||
m_httpListener.AddSimpleStreamHandler(handler);
|
||||
}
|
||||
|
||||
public bool ContainsCap(string cap)
|
||||
{
|
||||
lock (m_capsHandlers)
|
||||
if (m_capsHandlers.ContainsKey(cap))
|
||||
return true;
|
||||
return m_capsSimpleHandlers.ContainsKey(cap);
|
||||
return m_capsHandlers.ContainsKey(cap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,14 +125,11 @@ namespace OpenSim.Framework.Capabilities
|
||||
if (m_capsHandlers.ContainsKey(idx))
|
||||
{
|
||||
m_httpListener.RemoveStreamHandler("POST", m_capsHandlers[idx].Path);
|
||||
m_httpListener.RemoveStreamHandler("PUT", m_capsHandlers[idx].Path);
|
||||
m_httpListener.RemoveStreamHandler("GET", m_capsHandlers[idx].Path);
|
||||
m_httpListener.RemoveStreamHandler("DELETE", m_capsHandlers[idx].Path);
|
||||
m_capsHandlers.Remove(idx);
|
||||
}
|
||||
|
||||
|
||||
if (null == value) return;
|
||||
|
||||
|
||||
m_capsHandlers[idx] = value;
|
||||
m_httpListener.AddStreamHandler(value);
|
||||
}
|
||||
@@ -157,9 +146,8 @@ namespace OpenSim.Framework.Capabilities
|
||||
{
|
||||
lock (m_capsHandlers)
|
||||
{
|
||||
string[] __keys = new string[m_capsHandlers.Keys.Count + m_capsSimpleHandlers.Keys.Count];
|
||||
string[] __keys = new string[m_capsHandlers.Keys.Count];
|
||||
m_capsHandlers.Keys.CopyTo(__keys, 0);
|
||||
m_capsSimpleHandlers.Keys.CopyTo(__keys, m_capsHandlers.Keys.Count);
|
||||
return __keys;
|
||||
}
|
||||
}
|
||||
@@ -170,85 +158,28 @@ namespace OpenSim.Framework.Capabilities
|
||||
/// capabilities and their handler details.
|
||||
/// </summary>
|
||||
/// <param name="excludeSeed">If true, then exclude the seed cap.</param>
|
||||
public Hashtable GetCapsDetails(bool excludeSeed, List<string> requestedCaps)
|
||||
public Hashtable GetCapsDetails(bool excludeSeed)
|
||||
{
|
||||
Hashtable caps = new Hashtable();
|
||||
string protocol = "http://";
|
||||
|
||||
if (m_useSSL)
|
||||
protocol = "https://";
|
||||
|
||||
if (requestedCaps == null)
|
||||
{
|
||||
lock (m_capsHandlers)
|
||||
{
|
||||
foreach (KeyValuePair<string, ISimpleStreamHandler> kvp in m_capsSimpleHandlers)
|
||||
caps[kvp.Key] = BaseURL + kvp.Value.Path;
|
||||
foreach (KeyValuePair<string, IRequestHandler> kvp in m_capsHandlers)
|
||||
caps[kvp.Key] = BaseURL + kvp.Value.Path;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
string baseUrl = protocol + m_httpListenerHostName + ":" + m_httpListenerPort.ToString();
|
||||
|
||||
lock (m_capsHandlers)
|
||||
{
|
||||
for (int i = 0; i < requestedCaps.Count; ++i)
|
||||
foreach (string capsName in m_capsHandlers.Keys)
|
||||
{
|
||||
string capsName = requestedCaps[i];
|
||||
if (excludeSeed && "SEED" == capsName)
|
||||
continue;
|
||||
|
||||
if (m_capsSimpleHandlers.TryGetValue(capsName, out ISimpleStreamHandler shdr))
|
||||
{
|
||||
caps[capsName] = BaseURL + shdr.Path;
|
||||
continue;
|
||||
}
|
||||
if (m_capsHandlers.TryGetValue(capsName, out IRequestHandler chdr))
|
||||
{
|
||||
caps[capsName] = BaseURL + chdr.Path;
|
||||
}
|
||||
caps[capsName] = baseUrl + m_capsHandlers[capsName].Path;
|
||||
}
|
||||
}
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetCapsLocalPaths()
|
||||
{
|
||||
Dictionary<string, string> caps = new();
|
||||
lock (m_capsHandlers)
|
||||
{
|
||||
foreach (KeyValuePair<string, ISimpleStreamHandler> kvp in m_capsSimpleHandlers)
|
||||
caps[kvp.Key] = kvp.Value.Path;
|
||||
foreach (KeyValuePair<string, IRequestHandler> kvp in m_capsHandlers)
|
||||
caps[kvp.Key] = kvp.Value.Path;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
public void GetCapsDetailsLLSDxml(HashSet<string> requestedCaps, osUTF8 sb)
|
||||
{
|
||||
lock (m_capsHandlers)
|
||||
{
|
||||
if (requestedCaps is null)
|
||||
return;
|
||||
|
||||
foreach (string capsName in requestedCaps)
|
||||
{
|
||||
if (m_capsSimpleHandlers.TryGetValue(capsName, out ISimpleStreamHandler shdr))
|
||||
LLSDxmlEncode2.AddElem(capsName, BaseURL + shdr.Path, sb);
|
||||
else if (m_capsHandlers.TryGetValue(capsName, out IRequestHandler chdr))
|
||||
LLSDxmlEncode2.AddElem(capsName, BaseURL + chdr.Path, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the dictionary of all the HTTP cap handlers
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The dictionary copy. The key is the capability name, the value is the HTTP handler.
|
||||
/// </returns>
|
||||
public Dictionary<string, IRequestHandler> GetCapsHandlers()
|
||||
{
|
||||
lock (m_capsHandlers)
|
||||
return new Dictionary<string, IRequestHandler>(m_capsHandlers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
public override byte[] Handle(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
|
||||
{
|
||||
Hashtable ret = new Hashtable();
|
||||
ret["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound;
|
||||
ret["content_type"] = "text/plain";
|
||||
ret["int_bytes"] = 0;
|
||||
string textureStr = (string)request["texture_id"];
|
||||
string format = (string)request["format"];
|
||||
// Try to parse the texture ID from the request URL
|
||||
NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query);
|
||||
string textureStr = query.GetOne("texture_id");
|
||||
string format = query.GetOne("format");
|
||||
|
||||
//m_log.DebugFormat("[GETTEXTURE]: called {0}", textureStr);
|
||||
|
||||
if (m_assetService == null)
|
||||
{
|
||||
m_log.Error("[GETTEXTURE]: Cannot fetch texture " + textureStr + " without an asset service");
|
||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||
}
|
||||
|
||||
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,90 @@ namespace OpenSim.Capabilities.Handlers
|
||||
if (format != DefaultFormat)
|
||||
fullID = fullID + "-" + format;
|
||||
|
||||
// try the cache
|
||||
texture = m_assetService.GetCached(fullID);
|
||||
|
||||
if (texture == null)
|
||||
if (!String.IsNullOrEmpty(REDIRECT_URL))
|
||||
{
|
||||
//m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
|
||||
|
||||
// Fetch locally or remotely. Misses return a 404
|
||||
texture = m_assetService.Get(textureID.ToString());
|
||||
// Only try to fetch locally cached textures. Misses are redirected
|
||||
texture = m_assetService.GetCached(fullID);
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
if (texture.Type != (sbyte)AssetType.Texture)
|
||||
return true;
|
||||
|
||||
if (format == DefaultFormat)
|
||||
{
|
||||
WriteTextureData(request, response, texture, format);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
|
||||
newTexture.Data = ConvertTextureData(texture, format);
|
||||
if (newTexture.Data.Length == 0)
|
||||
return false; // !!! Caller try another codec, please!
|
||||
|
||||
newTexture.Flags = AssetFlags.Collectable;
|
||||
newTexture.Temporary = true;
|
||||
newTexture.Local = true;
|
||||
m_assetService.Store(newTexture);
|
||||
WriteTextureData(request, response, newTexture, format);
|
||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||
return true;
|
||||
}
|
||||
WriteTextureData(httpRequest, httpResponse, texture, format);
|
||||
}
|
||||
}
|
||||
else // it was on the cache
|
||||
{
|
||||
//m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
|
||||
WriteTextureData(request, response, texture, format);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string textureUrl = REDIRECT_URL + textureID.ToString();
|
||||
m_log.Debug("[GETTEXTURE]: Redirecting texture request to " + textureUrl);
|
||||
httpResponse.RedirectLocation = textureUrl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // no redirect
|
||||
{
|
||||
// try the cache
|
||||
texture = m_assetService.GetCached(fullID);
|
||||
|
||||
//response = new Hashtable();
|
||||
if (texture == null)
|
||||
{
|
||||
// m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
|
||||
|
||||
// Fetch locally or remotely. Misses return a 404
|
||||
texture = m_assetService.Get(textureID.ToString());
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
if (texture.Type != (sbyte)AssetType.Texture)
|
||||
{
|
||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||
return true;
|
||||
}
|
||||
if (format == DefaultFormat)
|
||||
{
|
||||
WriteTextureData(httpRequest, httpResponse, texture, format);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
|
||||
newTexture.Data = ConvertTextureData(texture, format);
|
||||
if (newTexture.Data.Length == 0)
|
||||
return false; // !!! Caller try another codec, please!
|
||||
|
||||
newTexture.Flags = AssetFlags.Collectable;
|
||||
newTexture.Temporary = true;
|
||||
m_assetService.Store(newTexture);
|
||||
WriteTextureData(httpRequest, httpResponse, newTexture, format);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // it was on the cache
|
||||
{
|
||||
// m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
|
||||
WriteTextureData(httpRequest, httpResponse, texture, format);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//WriteTextureData(request,response,null,format);
|
||||
// not found
|
||||
//m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
|
||||
return false;
|
||||
// m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
|
||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void WriteTextureData(Hashtable request, Hashtable response, AssetBase texture, string format)
|
||||
private void WriteTextureData(IOSHttpRequest request, IOSHttpResponse response, AssetBase texture, string format)
|
||||
{
|
||||
Hashtable headers = new Hashtable();
|
||||
response["headers"] = headers;
|
||||
|
||||
string range = String.Empty;
|
||||
|
||||
if (((Hashtable)request["headers"])["range"] != null)
|
||||
range = (string)((Hashtable)request["headers"])["range"];
|
||||
|
||||
else if (((Hashtable)request["headers"])["Range"] != null)
|
||||
range = (string)((Hashtable)request["headers"])["Range"];
|
||||
string range = request.Headers.GetOne("Range");
|
||||
|
||||
if (!String.IsNullOrEmpty(range)) // JP2's only
|
||||
{
|
||||
// Range request
|
||||
int start, end;
|
||||
if (Util.TryParseHttpRange(range, out start, out end))
|
||||
if (TryParseRange(range, out start, out end))
|
||||
{
|
||||
// Before clamping start make sure we can satisfy it in order to avoid
|
||||
// sending back the last byte instead of an error status
|
||||
@@ -232,8 +239,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 +257,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 +304,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 +400,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 +423,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenSim.Capabilities.Handlers")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("http://opensimulator.org")]
|
||||
[assembly: AssemblyProduct("OpenSim")]
|
||||
[assembly: AssemblyCopyright("OpenSimulator developers")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("32350823-e1df-45e3-b7fa-0a58b4372433")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user