Compare commits
622 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
187f4da29d | ||
|
|
9aadeb1117 | ||
|
|
d4ed7bb4ce | ||
|
|
d4937d6c8a | ||
|
|
0c6c9c7d36 | ||
|
|
2b51109abf | ||
|
|
c18e4f73a8 | ||
|
|
5afb098ce3 | ||
|
|
80e7ee8c60 | ||
|
|
de00d96c9b | ||
|
|
41076ed4b8 | ||
|
|
af105ab93f | ||
|
|
02bc889fa8 | ||
|
|
7b339a99f8 | ||
|
|
9d029a2918 | ||
|
|
be4bc11f70 | ||
|
|
1fa91dd7df | ||
|
|
4d565e7424 | ||
|
|
e53df6383d | ||
|
|
2d4e39520d | ||
|
|
2d225f62c5 | ||
|
|
1b31b63c91 | ||
|
|
0019726a3f | ||
|
|
5b34b87e4c | ||
|
|
5abacc8967 | ||
|
|
b94e2e4dc1 | ||
|
|
4a940367b4 | ||
|
|
510319133f | ||
|
|
7ee976e5ce | ||
|
|
d5282fbc57 | ||
|
|
672ab2b4d0 | ||
|
|
0fd31a093d | ||
|
|
fd0837847c | ||
|
|
5711a3be99 | ||
|
|
a34c808858 | ||
|
|
8fdd953d3c | ||
|
|
ecec2583d8 | ||
|
|
fbd73b705c | ||
|
|
adc3c7cc2c | ||
|
|
d9aff8a1ef | ||
|
|
0ba44f692f | ||
|
|
5af602770b | ||
|
|
e8541ef53d | ||
|
|
e641a8779f | ||
|
|
6c8dd35190 | ||
|
|
7ac9d6a5a0 | ||
|
|
1a99beb038 | ||
|
|
d383756d23 | ||
|
|
5c363ea2a7 | ||
|
|
3802201a35 | ||
|
|
8e48e9c14d | ||
|
|
ba935366db | ||
|
|
114dfebf81 | ||
|
|
21240bb535 | ||
|
|
c9a82de7a0 | ||
|
|
3983bcb862 | ||
|
|
c14b1b9fdb | ||
|
|
44740af3f3 | ||
|
|
cf8f9bd089 | ||
|
|
6da4ede986 | ||
|
|
7bcd776298 | ||
|
|
99541f486e | ||
|
|
db53755801 | ||
|
|
3668132706 | ||
|
|
481cea32f2 | ||
|
|
4631b9c76a | ||
|
|
d8b47797d4 | ||
|
|
a7231176ba | ||
|
|
e387bc42ee | ||
|
|
7c062d5949 | ||
|
|
c72d04600f | ||
|
|
66aca6e308 | ||
|
|
68258367e7 | ||
|
|
db65440674 | ||
|
|
57c140d9a5 | ||
|
|
9860e99a53 | ||
|
|
2922c59de9 | ||
|
|
78d1eb15b9 | ||
|
|
103e21f3ed | ||
|
|
1d92dee88c | ||
|
|
ce1f31864f | ||
|
|
cb1bde70db | ||
|
|
667695b393 | ||
|
|
4999a8e926 | ||
|
|
35aa56566e | ||
|
|
3d61fb9c08 | ||
|
|
92a845e192 | ||
|
|
73e5ff9868 | ||
|
|
03ab7627bc | ||
|
|
f521cc201a | ||
|
|
d54d841571 | ||
|
|
0162fc1d4b | ||
|
|
dafe5275b5 | ||
|
|
1480c97de1 | ||
|
|
6e399e5971 | ||
|
|
02f5f46eed | ||
|
|
c8d6444cc6 | ||
|
|
7bdd4c1215 | ||
|
|
8e99ed7c40 | ||
|
|
e4d4ba0d0e | ||
|
|
85a3a112b3 | ||
|
|
423b9034d4 | ||
|
|
b90a1b94c0 | ||
|
|
2ca89cae1d | ||
|
|
da9e0498fe | ||
|
|
0ffe739692 | ||
|
|
552b1d52b7 | ||
|
|
d74be8f606 | ||
|
|
0998a8c4ff | ||
|
|
6c89054ab7 | ||
|
|
e31b17565a | ||
|
|
160adff017 | ||
|
|
0546231a91 | ||
|
|
5adecfe5cd | ||
|
|
6b3b9cd901 | ||
|
|
b2e1e0d123 | ||
|
|
6ce42168ff | ||
|
|
21611d3da5 | ||
|
|
f17db633bc | ||
|
|
b6e08a6a9b | ||
|
|
f1f833f0a2 | ||
|
|
2f810c71ec | ||
|
|
4890ca2765 | ||
|
|
7a05050c93 | ||
|
|
db590aa229 | ||
|
|
112349e78b | ||
|
|
196ab7e5af | ||
|
|
cf27856c26 | ||
|
|
fdf58bc890 | ||
|
|
79e0f120b2 | ||
|
|
4dd7a0e8d8 | ||
|
|
e9155e833b | ||
|
|
20760c35ae | ||
|
|
68bb990f22 | ||
|
|
23abc64a5d | ||
|
|
9bd91eba81 | ||
|
|
aca3d0f320 | ||
|
|
5a05a45dcf | ||
|
|
5f37f77734 | ||
|
|
6f0aef668a | ||
|
|
988446fb6f | ||
|
|
d40b3c9e03 | ||
|
|
32e1839f5c | ||
|
|
c3611ed4fe | ||
|
|
b69a0ea279 | ||
|
|
902e14ca5a | ||
|
|
a688ea7018 | ||
|
|
c7f2aab58b | ||
|
|
450b3e7f1b | ||
|
|
d405b71f20 | ||
|
|
7503d1a23f | ||
|
|
c1942f70c5 | ||
|
|
bd663a823b | ||
|
|
ce3aff2237 | ||
|
|
eb324ae342 | ||
|
|
d6f74f26b7 | ||
|
|
62b0580361 | ||
|
|
efc0f3dea6 | ||
|
|
42decad1ee | ||
|
|
832edd880e | ||
|
|
e83c21fea9 | ||
|
|
5aa342e570 | ||
|
|
585d128035 | ||
|
|
838414b63b | ||
|
|
d02f1e529a | ||
|
|
152fbee376 | ||
|
|
758f7464fc | ||
|
|
843810ddae | ||
|
|
afb29e1909 | ||
|
|
4a5c44aacd | ||
|
|
77262731c9 | ||
|
|
ae37c27034 | ||
|
|
f78eb6c8af | ||
|
|
e163391c90 | ||
|
|
8a146e7805 | ||
|
|
81b3081f00 | ||
|
|
0e6cdcb356 | ||
|
|
d35ff5dcd9 | ||
|
|
10d9461d99 | ||
|
|
f06f3c889c | ||
|
|
817a87330c | ||
|
|
466e92047a | ||
|
|
c13e1aac27 | ||
|
|
fbf9f0ef29 | ||
|
|
9fcebdefe0 | ||
|
|
6e8b823508 | ||
|
|
e597578c05 | ||
|
|
01e11f5161 | ||
|
|
3e027250d5 | ||
|
|
a04af02c91 | ||
|
|
8376a9a444 | ||
|
|
433ce41c61 | ||
|
|
a8e6c6c81d | ||
|
|
7fecf837db | ||
|
|
424d209516 | ||
|
|
43300d36cc | ||
|
|
5466118445 | ||
|
|
8a882f2415 | ||
|
|
324a57a53e | ||
|
|
c7705c6314 | ||
|
|
9487a34450 | ||
|
|
7b6810c286 | ||
|
|
e70f6b9d44 | ||
|
|
c580ffcfeb | ||
|
|
d6c81ac0c7 | ||
|
|
7319328a78 | ||
|
|
d50a31f7f1 | ||
|
|
123289e5d2 | ||
|
|
770e5b110f | ||
|
|
8e2a582efc | ||
|
|
f0298ba08c | ||
|
|
359020d4e6 | ||
|
|
37a3dd615f | ||
|
|
243bab823e | ||
|
|
fceedaa80c | ||
|
|
5fe5f98cdd | ||
|
|
5d99e33009 | ||
|
|
2578fd1b25 | ||
|
|
42e2cd5760 | ||
|
|
b3479bcb06 | ||
|
|
a44ca25589 | ||
|
|
4919bb8067 | ||
|
|
5616128a88 | ||
|
|
0e6dc2d968 | ||
|
|
2f6a026b5a | ||
|
|
e708b309af | ||
|
|
9fe56711ec | ||
|
|
f304a881b2 | ||
|
|
a7d635967e | ||
|
|
67f3b24ae1 | ||
|
|
3aadd18418 | ||
|
|
db227ab5e9 | ||
|
|
67974184c9 | ||
|
|
621dafa222 | ||
|
|
2064a3caed | ||
|
|
c345a13dea | ||
|
|
9f61683603 | ||
|
|
0ccd0dd45b | ||
|
|
ffbeda5c38 | ||
|
|
bcf2a5f929 | ||
|
|
8dfa6c3b11 | ||
|
|
423d8c88e3 | ||
|
|
bcfeeadebd | ||
|
|
f0ee56bf70 | ||
|
|
4590eb535e | ||
|
|
b9784df0e8 | ||
|
|
feabd5e537 | ||
|
|
57b5db3467 | ||
|
|
ac9f16365c | ||
|
|
4ad8dca196 | ||
|
|
c2bfe69f33 | ||
|
|
2dda244390 | ||
|
|
58d10aa79a | ||
|
|
8c1ce17bdb | ||
|
|
dbf79e9716 | ||
|
|
7fbf7f0675 | ||
|
|
b08f456a13 | ||
|
|
7cff935649 | ||
|
|
5f124e3420 | ||
|
|
945ee5b500 | ||
|
|
6685756f30 | ||
|
|
990cf3e56e | ||
|
|
29bdac8a66 | ||
|
|
1a4b444c24 | ||
|
|
a221e2d3db | ||
|
|
f32e361174 | ||
|
|
b9b86468fc | ||
|
|
ced4b7c528 | ||
|
|
717dad94c3 | ||
|
|
6594702c8a | ||
|
|
bea24c55d4 | ||
|
|
e6f0affac6 | ||
|
|
bd2f10f350 | ||
|
|
997caae560 | ||
|
|
e87b380280 | ||
|
|
a6828818ad | ||
|
|
ce87e5fd87 | ||
|
|
335753e92f | ||
|
|
82da0f77b7 | ||
|
|
e40f507dae | ||
|
|
98b6b58598 | ||
|
|
bc74ad52b2 | ||
|
|
dd45ff7a0c | ||
|
|
01cc050a2d | ||
|
|
5eb13c4cb3 | ||
|
|
894fff9cfb | ||
|
|
b83c999cf4 | ||
|
|
63d36c5546 | ||
|
|
dde182e1cc | ||
|
|
df59617648 | ||
|
|
efd33e8563 | ||
|
|
748afbd7f5 | ||
|
|
d66548acde | ||
|
|
8d4c21c3e2 | ||
|
|
0507750245 | ||
|
|
e6f926607b | ||
|
|
6761b1ab0c | ||
|
|
e443815405 | ||
|
|
2ac52e9274 | ||
|
|
c5b3697eaa | ||
|
|
b8cac979d1 | ||
|
|
dee6701318 | ||
|
|
534c3c1c5c | ||
|
|
7dab1682a0 | ||
|
|
db06b2f1ad | ||
|
|
7ded9a2c0f | ||
|
|
4883c1d80f | ||
|
|
076fe94388 | ||
|
|
45bd03ad2b | ||
|
|
74b66b8bdb | ||
|
|
f725245040 | ||
|
|
83c14a5ce3 | ||
|
|
73fb461607 | ||
|
|
2ded4a4956 | ||
|
|
c76667b47c | ||
|
|
df729b4e76 | ||
|
|
00e932933f | ||
|
|
dc957ba4b3 | ||
|
|
8b14a70473 | ||
|
|
aae0d872de | ||
|
|
813ccf39b7 | ||
|
|
3b39c985bf | ||
|
|
0a7d3ebf2d | ||
|
|
086261de20 | ||
|
|
d03b8ac712 | ||
|
|
0eddd6e5ee | ||
|
|
df287a1603 | ||
|
|
75c46ffb1e | ||
|
|
8dd08dfaad | ||
|
|
c38bb9d025 | ||
|
|
e8654343c1 | ||
|
|
32a6cc851f | ||
|
|
a08105338e | ||
|
|
8d1fa1efd1 | ||
|
|
eed17c716f | ||
|
|
c667858d22 | ||
|
|
66280e982f | ||
|
|
6802f0acb8 | ||
|
|
7a2c8f688f | ||
|
|
bf15a31a57 | ||
|
|
8137562074 | ||
|
|
7b59f4d76d | ||
|
|
68c12024fe | ||
|
|
258efc97af | ||
|
|
10ac92f6a0 | ||
|
|
1940dea666 | ||
|
|
9b5844ed26 | ||
|
|
125e84431e | ||
|
|
b5bc45f7a7 | ||
|
|
646ae05206 | ||
|
|
37832b9c87 | ||
|
|
605391fc3a | ||
|
|
a9428458d7 | ||
|
|
d1a9c122f0 | ||
|
|
f5b5cb46c1 | ||
|
|
a519de3dfc | ||
|
|
b258108d17 | ||
|
|
c1e7f951a1 | ||
|
|
048b2670c5 | ||
|
|
0fe8b426d8 | ||
|
|
2fa28d6ce3 | ||
|
|
72332de1ad | ||
|
|
1d14fee2c7 | ||
|
|
5f62c49450 | ||
|
|
e6dbd8b477 | ||
|
|
b392cd9d2d | ||
|
|
f7e899c6b7 | ||
|
|
933b1e97f9 | ||
|
|
8db177e76f | ||
|
|
2a5f01bfd2 | ||
|
|
3fbac63c3b | ||
|
|
c3d562ce61 | ||
|
|
fe1a79a878 | ||
|
|
7a6e51552e | ||
|
|
9c2bf18565 | ||
|
|
20cebb7770 | ||
|
|
5cd4fdb8ff | ||
|
|
07b7d947c8 | ||
|
|
1e0789b06b | ||
|
|
793f94027d | ||
|
|
10c04e3d71 | ||
|
|
eeffce3e44 | ||
|
|
d9f0cdee5c | ||
|
|
e2ecd04e9a | ||
|
|
bdbf95d1ca | ||
|
|
b1cd284b5d | ||
|
|
ace2f0bdcb | ||
|
|
0ddd30c1d8 | ||
|
|
4d91349a92 | ||
|
|
d3a842852f | ||
|
|
fddc9eac26 | ||
|
|
b1f64a2dd0 | ||
|
|
4907534173 | ||
|
|
a5fc6752f6 | ||
|
|
55fbe537c7 | ||
|
|
fc736b2f62 | ||
|
|
e30926e8b3 | ||
|
|
def073f6c8 | ||
|
|
02be06a439 | ||
|
|
1b05e71f54 | ||
|
|
a5a78ef165 | ||
|
|
c971a55f48 | ||
|
|
4ef49f70cb | ||
|
|
236e80f5c3 | ||
|
|
e55ce0cdf4 | ||
|
|
f2e3787424 | ||
|
|
fb24eab5bc | ||
|
|
8199a62091 | ||
|
|
06c232f20f | ||
|
|
b1acf760d1 | ||
|
|
21e5c84ba6 | ||
|
|
b002256496 | ||
|
|
5c618d2986 | ||
|
|
69af836523 | ||
|
|
3f047f53a0 | ||
|
|
6fbdb47f5b | ||
|
|
d58864c86c | ||
|
|
25ff65ae05 | ||
|
|
27e0684cbc | ||
|
|
6d31514719 | ||
|
|
04a78df6f7 | ||
|
|
7de334ccbd | ||
|
|
ef0b17113d | ||
|
|
b1e8b76d59 | ||
|
|
55c2c08e3b | ||
|
|
d98b77a229 | ||
|
|
4e3b0129fe | ||
|
|
21c42357c1 | ||
|
|
ea7f84f0ff | ||
|
|
a91e68c609 | ||
|
|
abd2a5c76c | ||
|
|
03f67418b2 | ||
|
|
de69662138 | ||
|
|
40c90fdca6 | ||
|
|
1af18b201e | ||
|
|
85dc3c6e2a | ||
|
|
1c22b2a531 | ||
|
|
2aab323e56 | ||
|
|
4d6e3b1133 | ||
|
|
2d0006014c | ||
|
|
9e5c81b4cd | ||
|
|
d9c1624327 | ||
|
|
910050b382 | ||
|
|
08b0d1fdfb | ||
|
|
b4a4190f53 | ||
|
|
5aa4ea9d71 | ||
|
|
72a5f17ed1 | ||
|
|
eb0a516c32 | ||
|
|
7a470457c8 | ||
|
|
991c8d08c6 | ||
|
|
d7474630b0 | ||
|
|
49c5f5e70c | ||
|
|
07dc6b3ec0 | ||
|
|
4d20dfc65a | ||
|
|
4c4925d758 | ||
|
|
e452d141ac | ||
|
|
743091fe94 | ||
|
|
eed9169708 | ||
|
|
0ed19f6ed9 | ||
|
|
a80f14a705 | ||
|
|
0e886b85f1 | ||
|
|
032c981cbd | ||
|
|
2f56acf6ea | ||
|
|
d9b1fce36e | ||
|
|
d9c7abfcd1 | ||
|
|
264cc7ce34 | ||
|
|
d23667eaf9 | ||
|
|
6f90f98ab3 | ||
|
|
6b3f7be469 | ||
|
|
9256cfe326 | ||
|
|
6538ab9694 | ||
|
|
b4a1d3b496 | ||
|
|
58a5d4dab6 | ||
|
|
337e2d6cb1 | ||
|
|
7a58d49693 | ||
|
|
51fea3fd6b | ||
|
|
a635f3f284 | ||
|
|
3cc7afc511 | ||
|
|
e03d15cff6 | ||
|
|
c55507857b | ||
|
|
b28fd6a0a6 | ||
|
|
9ce83e46e4 | ||
|
|
9f954d0c41 | ||
|
|
bca88d85a9 | ||
|
|
0a8bc87903 | ||
|
|
1ea930749a | ||
|
|
797b5d005a | ||
|
|
6a2c1801de | ||
|
|
b868072ac0 | ||
|
|
81a6391633 | ||
|
|
1b39a376fc | ||
|
|
3727520d7a | ||
|
|
ff41bede4a | ||
|
|
530fe86a8a | ||
|
|
e5a16c97fb | ||
|
|
87657699ac | ||
|
|
3a17aeceef | ||
|
|
aa20cf751c | ||
|
|
05b84d661e | ||
|
|
edb40ba944 | ||
|
|
752d871e44 | ||
|
|
5670b03208 | ||
|
|
2b510f2ced | ||
|
|
8ce5e0122a | ||
|
|
d4b7a24ffb | ||
|
|
9851848d12 | ||
|
|
29f80da12d | ||
|
|
e0a8c9ce12 | ||
|
|
43273fa24f | ||
|
|
bf4f218e0e | ||
|
|
11ddb82aa1 | ||
|
|
6299fd8dd1 | ||
|
|
1c95722b70 | ||
|
|
1e84dfe8c9 | ||
|
|
ea5926b946 | ||
|
|
51df696e64 | ||
|
|
cca0f4e607 | ||
|
|
343dd5350d | ||
|
|
1d62ad9080 | ||
|
|
49db05a1a8 | ||
|
|
adf3752eb6 | ||
|
|
b69889220f | ||
|
|
9178068d57 | ||
|
|
465814f0e5 | ||
|
|
8a930cfae8 | ||
|
|
84d056a07a | ||
|
|
0b743c6495 | ||
|
|
e832126e3d | ||
|
|
df3e69d425 | ||
|
|
06db60e46b | ||
|
|
5f0dfd7b57 | ||
|
|
5308cc6c92 | ||
|
|
8b74d85b4e | ||
|
|
66e2a98068 | ||
|
|
511860c408 | ||
|
|
61ce621b9d | ||
|
|
34a17490d5 | ||
|
|
d4de788fb3 | ||
|
|
3ca00ad4bb | ||
|
|
c10638ae02 | ||
|
|
674b7aa079 | ||
|
|
08612d2dad | ||
|
|
f885e5ba16 | ||
|
|
4262ac57e7 | ||
|
|
1f4f739578 | ||
|
|
95a9325b06 | ||
|
|
26c84cbb46 | ||
|
|
d06d1c4dd6 | ||
|
|
1a2ce55c44 | ||
|
|
c78ed0b731 | ||
|
|
8cb3c7a736 | ||
|
|
a8768e1b8d | ||
|
|
0f67c653cd | ||
|
|
34e1713554 | ||
|
|
f464a7e992 | ||
|
|
fd515e128f | ||
|
|
f87d699757 | ||
|
|
3903caa517 | ||
|
|
26665acd7a | ||
|
|
cb3ae3cc48 | ||
|
|
d3da948652 | ||
|
|
f38d763dd0 | ||
|
|
9b73df3a10 | ||
|
|
eecd1324c3 | ||
|
|
fc8566281f | ||
|
|
83503ed529 | ||
|
|
047c9971c2 | ||
|
|
9925365091 | ||
|
|
52ae6745f3 | ||
|
|
d89a8c6708 | ||
|
|
eadd7bdff7 | ||
|
|
8d133e52c9 | ||
|
|
05347db242 | ||
|
|
f5fb8b81d8 | ||
|
|
7ddfd275fc | ||
|
|
fbcea660df | ||
|
|
b72df30151 | ||
|
|
b5c3d157cb | ||
|
|
5531b329fe | ||
|
|
46a14dc8da | ||
|
|
8bb1cf747d | ||
|
|
6447fae297 | ||
|
|
f218605904 | ||
|
|
823602d69a | ||
|
|
f7cb87f6e4 | ||
|
|
b8a6b6d3c1 | ||
|
|
121ae48f17 | ||
|
|
94a9a53f9f | ||
|
|
3ade7afc34 | ||
|
|
3b96981528 | ||
|
|
80254a2a8a | ||
|
|
2fca7b9989 | ||
|
|
dd7db65ded | ||
|
|
5716cc6a12 | ||
|
|
983947d8f7 | ||
|
|
18b74627e5 | ||
|
|
1d34111f58 | ||
|
|
a280a6e34e | ||
|
|
f83c7839dc | ||
|
|
781ff605e0 | ||
|
|
abf61a464b | ||
|
|
3464cabdb7 | ||
|
|
ce04c54904 | ||
|
|
2a3dc1d341 | ||
|
|
aa6fced881 | ||
|
|
2a8a073cd6 | ||
|
|
68885c933b | ||
|
|
3b6f2c81dd | ||
|
|
3b5e9933ef | ||
|
|
52cd010067 | ||
|
|
41af1a1793 | ||
|
|
93ad6e73cf | ||
|
|
7a9c591f99 | ||
|
|
409f0d4096 | ||
|
|
7ae53fdb0a | ||
|
|
2a73429fee | ||
|
|
7945fe64a9 | ||
|
|
c5f90d87b5 | ||
|
|
6d018e8646 | ||
|
|
d58c0bbc2d | ||
|
|
775f18c4bd | ||
|
|
54f6e52a06 |
@@ -706,6 +706,21 @@ namespace OpenSim.Groups
|
||||
|
||||
m_groupData.SetAgentActiveGroup(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID);
|
||||
|
||||
ScenePresence sp = ((Scene)(remoteClient.Scene)).GetScenePresence(remoteClient.AgentId);
|
||||
List<SceneObjectGroup> attachments = sp.GetAttachments();
|
||||
|
||||
foreach(SceneObjectGroup so in attachments)
|
||||
{
|
||||
//m_log.DebugFormat("[GROUPS MODULE]: Setting new group and checking scripts to run in attachment {0} for {1}", so.Name, so.OwnerID);
|
||||
so.SetGroup(groupID, remoteClient);
|
||||
if (so.ContainsScripts() && so.RunningScriptCount() == 0)
|
||||
{
|
||||
so.RootPart.ParentGroup.CreateScriptInstances(
|
||||
0, false, sp.Scene.DefaultScriptEngine, sp.GetStateSource());
|
||||
so.ResumeScripts();
|
||||
}
|
||||
}
|
||||
|
||||
// Changing active group changes title, active powers, all kinds of things
|
||||
// anyone who is in any region that can see this client, should probably be
|
||||
// updated with new group info. At a minimum, they should get ScenePresence
|
||||
@@ -981,11 +996,16 @@ namespace OpenSim.Groups
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[Groups]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
m_groupData.SetAgentActiveGroupRole(GetRequestingAgentIDStr(remoteClient), GetRequestingAgentIDStr(remoteClient), groupID, titleRoleID);
|
||||
UUID agentID = remoteClient.AgentId;
|
||||
m_groupData.SetAgentActiveGroupRole(agentID.ToString(), agentID.ToString(), groupID, titleRoleID);
|
||||
|
||||
// TODO: Not sure what all is needed here, but if the active group role change is for the group
|
||||
// the client currently has set active, then we need to do a scene presence update too
|
||||
// if (m_groupData.GetAgentActiveMembership(GetRequestingAgentID(remoteClient)).GroupID == GroupID)
|
||||
// If the active group role change is for the group
|
||||
// the client currently has set active, then we need to
|
||||
// set the active group again for the tag to update
|
||||
if (m_groupData.GetAgentActiveMembership(agentID.ToString(), agentID.ToString()).GroupID == groupID)
|
||||
{
|
||||
m_groupData.SetAgentActiveGroup(agentID.ToString(), agentID.ToString(), groupID);
|
||||
}
|
||||
|
||||
SendDataUpdate(remoteClient, true);
|
||||
}
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,3 +84,14 @@ CREATE TABLE IF NOT EXISTS `usersettings` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
commit;
|
||||
|
||||
|
||||
:VERSION 6 # Unicode profiles
|
||||
|
||||
ALTER TABLE classifieds CONVERT TO CHARACTER SET utf8;
|
||||
ALTER TABLE usernotes CONVERT TO CHARACTER SET utf8;
|
||||
ALTER TABLE userpicks CONVERT TO CHARACTER SET utf8;
|
||||
ALTER TABLE userprofile CONVERT TO CHARACTER SET utf8;
|
||||
ALTER TABLE userdata CONVERT TO CHARACTER SET utf8;
|
||||
|
||||
commit;
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace OpenSim.Data.PGSQL
|
||||
|
||||
public GridUserData[] GetAll(string userID)
|
||||
{
|
||||
return base.Get(String.Format("\"UserID\" LIKE '{0}%'", userID));
|
||||
return base.Get(String.Format("\"UserID\" ILIKE '{0}%'", userID));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace OpenSim.Data.PGSQL
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern = " \"ShowInList\" = 1 AND lower(\"Name\") LIKE lower('%" + pattern + "%')";
|
||||
pattern = " \"ShowInList\" = 1 AND \"Name\" ILIKE '%" + pattern + "%'";
|
||||
|
||||
return m_Groups.Get(pattern, new NpgsqlParameter("pattern", pattern));
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace OpenSim.Data.PGSQL
|
||||
|
||||
public List<RegionData> Get(string regionName, UUID scopeID)
|
||||
{
|
||||
string sql = "select * from "+m_Realm+" where lower(\"regionName\") like lower(:regionName) ";
|
||||
string sql = "select * from "+m_Realm+" where \"regionName\" ILIKE :regionName ";
|
||||
if (!scopeID.IsZero())
|
||||
sql += " and \"ScopeID\" = :scopeID";
|
||||
sql += " order by lower(\"regionName\")";
|
||||
|
||||
@@ -298,14 +298,14 @@ namespace OpenSim.Data.PGSQL
|
||||
{
|
||||
if (words.Length == 1)
|
||||
{
|
||||
sql = String.Format(@"select * from {0} where (""ScopeID""=:ScopeID or ""ScopeID""=:UUIDZero) and (LOWER(""FirstName"" COLLATE ""en_US.utf8"") like LOWER(:search) or LOWER(""LastName"" COLLATE ""en_US.utf8"") like LOWER(:search))", m_Realm);
|
||||
sql = String.Format(@"select * from {0} where (""ScopeID""=:ScopeID or ""ScopeID""=:UUIDZero) and (""FirstName"" COLLATE ""en_US.utf8"" ILIKE :search or ""LastName"" COLLATE ""en_US.utf8"" ILIKE :search)", m_Realm);
|
||||
cmd.Parameters.Add(m_database.CreateParameter("ScopeID", scopeID));
|
||||
cmd.Parameters.Add (m_database.CreateParameter("UUIDZero", UUID.Zero));
|
||||
cmd.Parameters.Add(m_database.CreateParameter("search", "%" + words[0] + "%"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sql = String.Format(@"select * from {0} where (""ScopeID""=:ScopeID or ""ScopeID""=:UUIDZero) and (LOWER(""FirstName"" COLLATE ""en_US.utf8"") like LOWER(:searchFirst) or LOWER(""LastName"" COLLATE ""en_US.utf8"") like LOWER(:searchLast))", m_Realm);
|
||||
sql = String.Format(@"select * from {0} where (""ScopeID""=:ScopeID or ""ScopeID""=:UUIDZero) and (""FirstName"" COLLATE ""en_US.utf8"" ILIKE :searchFirst or ""LastName"" COLLATE ""en_US.utf8"" ILIKE :searchLast)", m_Realm);
|
||||
cmd.Parameters.Add(m_database.CreateParameter("searchFirst", "%" + words[0] + "%"));
|
||||
cmd.Parameters.Add(m_database.CreateParameter("searchLast", "%" + words[1] + "%"));
|
||||
cmd.Parameters.Add (m_database.CreateParameter("UUIDZero", UUID.Zero));
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace OpenSim.Framework
|
||||
Flags = OSHTTPURIFlags.None;
|
||||
try
|
||||
{
|
||||
Uri m_checkuri = new(uri);
|
||||
Uri m_checkuri = new Uri(uri);
|
||||
|
||||
if(m_checkuri.Scheme != Uri.UriSchemeHttp && m_checkuri.Scheme != Uri.UriSchemeHttps)
|
||||
return;
|
||||
@@ -368,11 +368,10 @@ namespace OpenSim.Framework
|
||||
private string m_SearchURL = string.Empty;
|
||||
private string m_DestinationGuideURL = string.Empty;
|
||||
private string m_economyURL = string.Empty;
|
||||
private string[] m_StunServers = null;
|
||||
|
||||
public GridInfo (IConfigSource config, string defaultURI = "")
|
||||
{
|
||||
string[] sections = ["Const", "Startup", "Hypergrid"];
|
||||
string[] sections = new string[] {"Const", "Startup", "Hypergrid"};
|
||||
|
||||
string gatekeeper = Util.GetConfigVarFromSections<string>(config, "GatekeeperURI", sections, string.Empty);
|
||||
if (string.IsNullOrEmpty(gatekeeper))
|
||||
@@ -417,7 +416,8 @@ namespace OpenSim.Framework
|
||||
OSHHTPHost tmp = new OSHHTPHost(alias[i].Trim(), false);
|
||||
if (tmp.IsValidHost)
|
||||
{
|
||||
m_gateKeeperAlias ??= new HashSet<OSHHTPHost>();
|
||||
if (m_gateKeeperAlias == null)
|
||||
m_gateKeeperAlias = new HashSet<OSHHTPHost>();
|
||||
m_gateKeeperAlias.Add(tmp);
|
||||
}
|
||||
}
|
||||
@@ -431,7 +431,6 @@ namespace OpenSim.Framework
|
||||
m_gridUrlAlias[i++] = a.URI;
|
||||
}
|
||||
|
||||
|
||||
string home = Util.GetConfigVarFromSections<string>(config, "HomeURI", sections, string.Empty);
|
||||
|
||||
if (string.IsNullOrEmpty(home))
|
||||
@@ -459,13 +458,14 @@ namespace OpenSim.Framework
|
||||
OSHHTPHost tmp = new OSHHTPHost(alias[i].Trim(), false);
|
||||
if (tmp.IsValidHost)
|
||||
{
|
||||
m_homeURLAlias ??= new HashSet<OSHHTPHost>();
|
||||
if (m_homeURLAlias == null)
|
||||
m_homeURLAlias = new HashSet<OSHHTPHost>();
|
||||
m_homeURLAlias.Add(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string[] namessections = ["Const", "GridInfo", "SimulatorFeatures"];
|
||||
string[] namessections = new string[] { "Const", "GridInfo", "SimulatorFeatures" };
|
||||
m_GridName = Util.GetConfigVarFromSections<string>(config, "GridName", namessections, string.Empty);
|
||||
if (string.IsNullOrEmpty(m_GridName))
|
||||
m_GridName = Util.GetConfigVarFromSections<string>(config, "gridname", namessections, string.Empty);
|
||||
@@ -504,8 +504,7 @@ namespace OpenSim.Framework
|
||||
m_DestinationGuideURL = tmpuri.URI;
|
||||
}
|
||||
|
||||
|
||||
m_economyURL = Util.GetConfigVarFromSections<string>(config, "economy", ["Economy", "GridInfo"]);
|
||||
m_economyURL = Util.GetConfigVarFromSections<string>(config, "economy", new string[] { "Economy", "GridInfo" });
|
||||
if (!string.IsNullOrEmpty(m_economyURL))
|
||||
{
|
||||
tmpuri = new OSHTTPURI(m_economyURL.Trim(), true);
|
||||
@@ -516,20 +515,6 @@ namespace OpenSim.Framework
|
||||
}
|
||||
m_economyURL = tmpuri.URI;
|
||||
}
|
||||
|
||||
string stunservers = Util.GetConfigVarFromSections<string>(config, "StunServers", ["Const", "Startup", "GridInfo", "SimulatorFeatures"], string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(stunservers))
|
||||
{
|
||||
string[] stuns = stunservers.Split(',');
|
||||
List<string> stunsarr = new List<string>(stuns.Length);
|
||||
for (int i = 0; i < stuns.Length; ++i)
|
||||
{
|
||||
OSHHTPHost tmp = new OSHHTPHost(stuns[i].Trim(), false);
|
||||
if (tmp.IsValidHost)
|
||||
stunsarr.Add("stun:" + tmp.HostAndPort);
|
||||
}
|
||||
m_StunServers = stunsarr.Count > 0 ? stunsarr.ToArray() : null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasHGConfig
|
||||
@@ -700,7 +685,6 @@ namespace OpenSim.Framework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string GridName
|
||||
{
|
||||
get { return m_GridName; }
|
||||
@@ -772,27 +756,5 @@ namespace OpenSim.Framework
|
||||
m_log.Error((tmp.IsValidHost ? "Could not resolve EconomyURL" : "EconomyURL is a invalid host ") + value ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
public string[] StunServers
|
||||
{
|
||||
get { return m_StunServers; }
|
||||
set
|
||||
{
|
||||
if(value.Length > 0)
|
||||
{
|
||||
List<string> values = new List<string>(value.Length);
|
||||
for (int i = 0; i < value.Length; ++i)
|
||||
{
|
||||
OSHHTPHost tmp = new OSHHTPHost(value[i].Trim(), false);
|
||||
if (tmp.IsValidHost)
|
||||
values.Add("stun:" + tmp.HostAndPort);
|
||||
}
|
||||
m_StunServers = values.Count > 0 ? values.ToArray() : null;
|
||||
}
|
||||
else
|
||||
m_StunServers = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace OpenSim.Framework
|
||||
private uint _flags = (uint)ParcelFlags.AllowFly | (uint)ParcelFlags.AllowLandmark |
|
||||
(uint)ParcelFlags.AllowAPrimitiveEntry |
|
||||
(uint)ParcelFlags.AllowDeedToGroup |
|
||||
(uint)ParcelFlags.CreateObjects | (uint)ParcelFlags.AllowOtherScripts |
|
||||
(uint)ParcelFlags.AllowOtherScripts |
|
||||
(uint)ParcelFlags.AllowVoiceChat;
|
||||
|
||||
private byte _landingType = (byte)OpenMetaverse.LandingType.Direct;
|
||||
@@ -84,7 +84,7 @@ namespace OpenSim.Framework
|
||||
private UUID _snapshotID = UUID.Zero;
|
||||
private Vector3 _userLocation = new();
|
||||
private Vector3 _userLookAt = new();
|
||||
private int _otherCleanTime = 0;
|
||||
private int _otherCleanTime = 5;
|
||||
private string _mediaType = "none/none";
|
||||
private string _mediaDescription = "";
|
||||
private int _mediaHeight = 0;
|
||||
|
||||
@@ -240,10 +240,12 @@ namespace OpenSim.Framework
|
||||
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found [{0}] in blacklist for {1}", url, Name);
|
||||
|
||||
// Check blacklist exceptions
|
||||
allowed
|
||||
= OutboundUrlFilter.IsInNetwork(
|
||||
addr, url.Port, m_blacklistExceptionNetworks, m_blacklistExceptionEndPoints, Name);
|
||||
|
||||
if (!url.AbsolutePath.StartsWith("/lslhttp/"))
|
||||
{
|
||||
allowed
|
||||
= OutboundUrlFilter.IsInNetwork(
|
||||
addr, url.Port, m_blacklistExceptionNetworks, m_blacklistExceptionEndPoints, Name);
|
||||
}
|
||||
// if (allowed)
|
||||
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found [{0}] in whitelist for {1}", url, Name);
|
||||
}
|
||||
|
||||
@@ -110,10 +110,10 @@ namespace OpenSim.Framework
|
||||
/// <value>
|
||||
/// These appear to be terrain textures that are shipped with the client.
|
||||
/// </value>
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_1 = new("b8d3965a-ad78-bf43-699b-bff8eca6c975");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_2 = new("abb783e6-3e93-26c0-248a-247666855da3");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_3 = new("179cdabd-398a-9b6b-1391-4dc333ba321f");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_4 = new("beb169c7-11ea-fff2-efe5-0f24dc881df2");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_1 = new("0bc58228-74a0-7e83-89bc-5c23464bcec5");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_2 = new("63338ede-0037-c4fd-855b-015d77112fc8");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_3 = new("303cd381-8560-7579-23f1-f0a880799740");
|
||||
public static readonly UUID DEFAULT_TERRAIN_TEXTURE_4 = new("53a2f406-4895-1d13-d541-d2e3b86bc19c");
|
||||
|
||||
public static readonly UUID DEFAULT_TERRAIN_PBR_1 = new("b8d3965a-ad78-bf43-699b-bff8eca6c975");
|
||||
public static readonly UUID DEFAULT_TERRAIN_PBR_2 = new("abb783e6-3e93-26c0-248a-247666855da3");
|
||||
|
||||
@@ -286,8 +286,7 @@ namespace OpenSim.Framework
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
|
||||
using CancellationTokenSource cts = new(30000);
|
||||
using Stream respStream = responseMessage.Content.ReadAsStream(cts.Token);
|
||||
Stream respStream = responseMessage.Content.ReadAsStream();
|
||||
int length = respStream.Read(_readbuf, 0, BufferSize);
|
||||
while (length > 0)
|
||||
{
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace OpenSim.Framework.Serialization.External
|
||||
writer.WriteStartElement("GroupOwned");
|
||||
writer.WriteString(inventoryItem.GroupOwned.ToString());
|
||||
writer.WriteEndElement();
|
||||
if (options.ContainsKey("creators") && !string.IsNullOrEmpty(inventoryItem.CreatorData))
|
||||
if (!options.ContainsKey("nocreators") && !string.IsNullOrEmpty(inventoryItem.CreatorData))
|
||||
writer.WriteElementString("CreatorData", inventoryItem.CreatorData);
|
||||
else if (options.ContainsKey("home"))
|
||||
{
|
||||
|
||||
@@ -2299,7 +2299,7 @@ namespace OpenSim.Framework.Servers.HttpServer
|
||||
}
|
||||
if (httpRequest.QueryFlags.Contains("about"))
|
||||
{
|
||||
httpResponse.Redirect("http://opensimulator.org/wiki/0.9.3.1_Release");
|
||||
httpResponse.Redirect("https://github.com/lickx/opensim-lickx");
|
||||
return;
|
||||
}
|
||||
if (!httpRequest.QueryAsDictionary.TryGetValue("method", out string method) || string.IsNullOrWhiteSpace(method))
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace OpenSim
|
||||
public const string VersionNumber = "0.9.3.1";
|
||||
public const string AssemblyVersionNumber = "0.9.3.1";
|
||||
|
||||
public const Flavour VERSION_FLAVOUR = Flavour.Dev;
|
||||
public const Flavour VERSION_FLAVOUR = Flavour.Lickx;
|
||||
|
||||
public enum Flavour
|
||||
{
|
||||
@@ -43,7 +43,8 @@ namespace OpenSim
|
||||
RC3,
|
||||
Release,
|
||||
Post_Fixes,
|
||||
Extended
|
||||
Extended,
|
||||
Lickx
|
||||
}
|
||||
|
||||
public static string Version
|
||||
@@ -57,7 +58,7 @@ namespace OpenSim
|
||||
return versionString.PadRight(VERSIONINFO_VERSION_LENGTH);
|
||||
}
|
||||
|
||||
public const int VERSIONINFO_VERSION_LENGTH = 27;
|
||||
public const int VERSIONINFO_VERSION_LENGTH = 29;
|
||||
|
||||
/// <value>
|
||||
/// This is the external interface version. It is separate from the OpenSimulator project version.
|
||||
|
||||
@@ -37,7 +37,6 @@ using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
@@ -425,8 +424,7 @@ namespace OpenSim.Framework
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
|
||||
using CancellationTokenSource cts = new(30000);
|
||||
Stream resStream = responseMessage.Content.ReadAsStream(cts.Token);
|
||||
Stream resStream = responseMessage.Content.ReadAsStream();
|
||||
if (resStream is not null)
|
||||
{
|
||||
using StreamReader reader = new(resStream);
|
||||
@@ -591,8 +589,7 @@ namespace OpenSim.Framework
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
|
||||
using CancellationTokenSource cts = new(30000);
|
||||
using StreamReader reader = new(responseMessage.Content.ReadAsStream(cts.Token));
|
||||
using StreamReader reader = new(responseMessage.Content.ReadAsStream());
|
||||
string responseStr = reader.ReadToEnd();
|
||||
rcvlen = responseStr.Length;
|
||||
if (DebugLevel >= 5)
|
||||
@@ -1163,7 +1160,7 @@ namespace OpenSim.Framework
|
||||
auth?.AddAuthorization(request.Headers);
|
||||
|
||||
request.Headers.ExpectContinue = false;
|
||||
request.Headers.TransferEncodingChunked = false;
|
||||
request.Headers.TransferEncodingChunked = false; if (timeoutsecs > 0)
|
||||
|
||||
if (keepalive)
|
||||
{
|
||||
@@ -1194,8 +1191,7 @@ namespace OpenSim.Framework
|
||||
|
||||
if ((responseMessage.Content.Headers.ContentLength is long contentLength) && contentLength != 0)
|
||||
{
|
||||
using CancellationTokenSource cts = new(30000);
|
||||
using StreamReader reader = new(responseMessage.Content.ReadAsStream(cts.Token));
|
||||
using StreamReader reader = new(responseMessage.Content.ReadAsStream());
|
||||
respstring = reader.ReadToEnd();
|
||||
rcvlen = respstring.Length;
|
||||
}
|
||||
@@ -1280,8 +1276,7 @@ namespace OpenSim.Framework
|
||||
|
||||
if ((responseMessage.Content.Headers.ContentLength is long contentLength) && contentLength != 0)
|
||||
{
|
||||
using CancellationTokenSource cts = new(30000);
|
||||
using StreamReader reader = new(responseMessage.Content.ReadAsStream(cts.Token));
|
||||
using StreamReader reader = new(responseMessage.Content.ReadAsStream());
|
||||
respstring = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
@@ -1424,7 +1419,7 @@ namespace OpenSim.Framework
|
||||
request.Content.Headers.TryAddWithoutValidation("Content-Length", sendlen.ToString());
|
||||
}
|
||||
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseContentRead);
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
|
||||
int rcvlen = 0;
|
||||
@@ -1518,7 +1513,7 @@ namespace OpenSim.Framework
|
||||
//else
|
||||
// request.Headers.TryAddWithoutValidation("Connection", "close");
|
||||
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseContentRead);
|
||||
responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
|
||||
int rcvlen = 0;
|
||||
|
||||
@@ -38,6 +38,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Timers;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using log4net;
|
||||
using NDesk.Options;
|
||||
using Nini.Config;
|
||||
@@ -79,6 +80,7 @@ namespace OpenSim
|
||||
private string m_timedScript = "disabled";
|
||||
private int m_timeInterval = 1200;
|
||||
private System.Timers.Timer m_scriptTimer;
|
||||
private PosixSignalRegistration m_signalReg;
|
||||
|
||||
public OpenSim(IConfigSource configSource) : base(configSource)
|
||||
{
|
||||
@@ -132,6 +134,12 @@ namespace OpenSim
|
||||
m_log.Info("[OPENSIM MAIN]: Using async_call_method " + Util.FireAndForgetMethod);
|
||||
|
||||
m_log.InfoFormat("[OPENSIM MAIN] Running GC in {0} mode", GCSettings.IsServerGC ? "server":"workstation");
|
||||
|
||||
m_signalReg = PosixSignalRegistration.Create(PosixSignal.SIGTERM, context =>
|
||||
{
|
||||
m_log.Info("Received SIGTERM, shutting down");
|
||||
MainConsole.Instance.RunCommand("shutdown");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -302,8 +310,8 @@ namespace OpenSim
|
||||
+ "--force-assets will load the OAR and try to upload the assets it contains.\n"
|
||||
+ " it will REPLACE those assets on local region cache\n"
|
||||
+ " it will also try to upload them to service, but that only work if they are not present"
|
||||
+ " Avoid using this except for some recovery attempts"
|
||||
+ "--default-user will use this user for any objects with an owner whose UUID is not found in the grid.\n"
|
||||
+ " Avoid using this except for some recovery atempts"
|
||||
+ "--default-user will use this user for any objects and parcels with an owner whose UUID is not found in the grid.\n"
|
||||
+ "--no-objects suppresses the addition of any objects (good for loading only the terrain).\n"
|
||||
+ "--rotation specified rotation to be applied to the oar. Specified in degrees.\n"
|
||||
+ "--bounding-origin will only place objects that after displacement and rotation fall within the bounding cube who's position starts at <x,y,z>. Defaults to <0,0,0>.\n"
|
||||
@@ -321,7 +329,7 @@ namespace OpenSim
|
||||
|
||||
m_console.Commands.AddCommand("Archiving", false, "save oar",
|
||||
//"save oar [-v|--version=<N>] [-p|--profile=<url>] [<OAR path>]",
|
||||
"save oar [-h|--home=<url>] [--noassets] [--publish] [--perm=<permissions>] [--all] [<OAR path>]",
|
||||
"save oar [-h|--home=<url>] [--noassets] [--publish] [--perm=<permissions>] [--all] [--tenant=<uuid>] [<OAR path>]",
|
||||
"Save a region's data to an OAR archive.",
|
||||
// "-v|--version=<N> generates scene objects as per older versions of the serialization (e.g. -v=0)" + Environment.NewLine
|
||||
"-h|--home=<url> adds the url of the profile service to the saved user information.\n"
|
||||
@@ -331,6 +339,7 @@ namespace OpenSim
|
||||
+ " this is useful if you're making oars generally available that might be reloaded to the same grid from which you published\n"
|
||||
+ "--perm=<permissions> stops objects with insufficient permissions from being saved to the OAR.\n"
|
||||
+ " <permissions> can contain one or more of these characters: \"C\" = Copy, \"T\" = Transfer\n"
|
||||
+ "--tenant=<uuid> only save objects owned by tenant with uuid.\n"
|
||||
+ "--all saves all the regions in the simulator, instead of just the current region.\n"
|
||||
+ "The OAR path must be a filesystem path."
|
||||
+ " If this is not given then the oar is saved to region.oar in the current directory.",
|
||||
@@ -1520,5 +1529,7 @@ namespace OpenSim
|
||||
result = result.TrimEnd(' ');
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
|
||||
ad.AttachmentObjectStates = null;
|
||||
|
||||
if (attachments.Count > 0)
|
||||
m_scene.IncomingAttechments(sp, attachments);
|
||||
m_scene.IncomingAttachments(sp, attachments);
|
||||
else
|
||||
sp.GotAttachmentsData = true;
|
||||
}
|
||||
|
||||
@@ -263,10 +263,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends
|
||||
{
|
||||
//m_log.DebugFormat("[HGFRIENDS MODULE]: Entering StatusNotify for {0}", userID);
|
||||
|
||||
// First, let's divide the friends on a per-domain basis
|
||||
List<FriendInfo> locallst = new(friendList.Count);
|
||||
List<UUID> hglst = new();
|
||||
|
||||
Dictionary<string, List<FriendInfo>> friendsPerDomain = new Dictionary<string, List<FriendInfo>>();
|
||||
foreach (FriendInfo friend in friendList)
|
||||
{
|
||||
if (UUID.TryParse(friend.Friend, out UUID friendID))
|
||||
@@ -284,12 +283,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends
|
||||
if (LocalStatusNotification(userID, friendID, online))
|
||||
continue;
|
||||
|
||||
if (!friendsPerDomain.TryGetValue(url, out List<FriendInfo> lst))
|
||||
{
|
||||
lst = new List<FriendInfo>();
|
||||
friendsPerDomain[url] = lst;
|
||||
}
|
||||
lst.Add(friend);
|
||||
hglst.Add(friendID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,8 +293,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends
|
||||
if (locallst.Count > 0)
|
||||
base.StatusNotify(locallst, userID, online);
|
||||
|
||||
if(friendsPerDomain.Count > 0)
|
||||
m_StatusNotifier.Notify(userID, friendsPerDomain, online);
|
||||
if(hglst.Count > 0)
|
||||
m_StatusNotifier.Notify(userID, hglst, online);
|
||||
|
||||
//m_log.DebugFormat("[HGFRIENDS MODULE]: Exiting StatusNotify for {0}", userID);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends
|
||||
m_FriendsModule = friendsModule;
|
||||
}
|
||||
|
||||
// Currently unused
|
||||
/*
|
||||
public void Notify(UUID userID, Dictionary<string, List<FriendInfo>> friendsPerDomain, bool online)
|
||||
{
|
||||
if(m_FriendsModule is null)
|
||||
@@ -33,41 +35,84 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends
|
||||
|
||||
foreach (KeyValuePair<string, List<FriendInfo>> kvp in friendsPerDomain)
|
||||
{
|
||||
// For the others, call the user agent service
|
||||
List<string> ids = new(kvp.Value.Count);
|
||||
foreach (FriendInfo f in kvp.Value)
|
||||
ids.Add(f.Friend);
|
||||
|
||||
if (ids.Count == 0)
|
||||
if (kvp.Value.Count == 0)
|
||||
continue; // no one to notify. caller don't do this
|
||||
|
||||
//m_log.DebugFormat("[HG STATUS NOTIFIER]: Notifying {0} friends in {1}", ids.Count, kvp.Key);
|
||||
// ASSUMPTION: we assume that all users for one home domain
|
||||
// have exactly the same set of service URLs.
|
||||
// If this is ever not true, we need to change this.
|
||||
if (Util.ParseUniversalUserIdentifier(ids[0], out UUID friendID))
|
||||
FriendInfo id0Info = kvp.Value[0];
|
||||
string id0 = id0Info.Friend; // the hgname of the first friend
|
||||
if (Util.ParseUniversalUserIdentifier(id0, out UUID firstID))
|
||||
{
|
||||
string friendsServerURI = m_FriendsModule.UserManagementModule.GetUserServerURL(friendID, "FriendsServerURI");
|
||||
if (!string.IsNullOrEmpty(friendsServerURI))
|
||||
{
|
||||
HGFriendsServicesConnector fConn = new(friendsServerURI);
|
||||
string friendsServerURI = m_FriendsModule.UserManagementModule.GetUserServerURL(firstID, "FriendsServerURI");
|
||||
if (string.IsNullOrEmpty(friendsServerURI))
|
||||
continue;
|
||||
|
||||
List<UUID> friendsOnline = fConn.StatusNotification(ids, userID, online);
|
||||
if (friendsOnline.Count > 0)
|
||||
HGFriendsServicesConnector fConn = new(friendsServerURI);
|
||||
|
||||
List<string> ids = new(kvp.Value.Count);
|
||||
foreach (FriendInfo f in kvp.Value)
|
||||
{
|
||||
if (Util.ParseUniversalUserIdentifier(f.Friend, out UUID friendID))
|
||||
ids.Add(friendID.ToString());
|
||||
}
|
||||
if (ids.Count == 0)
|
||||
continue;
|
||||
|
||||
// Note: first argument in StatusNotification needs to be
|
||||
// a List<string> of UUIDs, not a List<string> of hgnames!
|
||||
List<UUID> friendsOnline = fConn.StatusNotification(ids, userID, online);
|
||||
if (friendsOnline.Count == 0)
|
||||
continue;
|
||||
|
||||
IClientAPI client = m_FriendsModule.LocateClientObject(userID);
|
||||
if(client is not null)
|
||||
{
|
||||
m_log.DebugFormat("[HG STATUS NOTIFIER]: Notifying {0} friends in {1}", friendsOnline.Count, kvp.Key);
|
||||
m_FriendsModule.CacheFriendsOnline(userID, friendsOnline, online);
|
||||
if(online)
|
||||
client?.SendAgentOnline(friendsOnline.ToArray());
|
||||
else
|
||||
client?.SendAgentOffline(friendsOnline.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public void Notify(UUID userID, List<UUID>friendIds, bool online)
|
||||
{
|
||||
if(m_FriendsModule is null)
|
||||
return;
|
||||
|
||||
if (friendIds.Count == 0)
|
||||
return; // no one to notify. caller don't do this
|
||||
|
||||
m_log.DebugFormat("[HG STATUS NOTIFIER]: Notifying {0} foreign friends", friendIds.Count);
|
||||
foreach (UUID friendID in friendIds)
|
||||
{
|
||||
string friendsServerURI = m_FriendsModule.UserManagementModule.GetUserServerURL(friendID, "FriendsServerURI");
|
||||
if (!string.IsNullOrEmpty(friendsServerURI))
|
||||
{
|
||||
HGFriendsServicesConnector fConn = new(friendsServerURI);
|
||||
|
||||
List<UUID> friendsOnline = fConn.StatusNotification(new List<string> { friendID.ToString() }, userID, online);
|
||||
if (friendsOnline.Count > 0)
|
||||
{
|
||||
IClientAPI client = m_FriendsModule.LocateClientObject(userID);
|
||||
if(client is not null)
|
||||
{
|
||||
IClientAPI client = m_FriendsModule.LocateClientObject(userID);
|
||||
if(client is not null)
|
||||
{
|
||||
m_FriendsModule.CacheFriendsOnline(userID, friendsOnline, online);
|
||||
if(online)
|
||||
client?.SendAgentOnline(friendsOnline.ToArray());
|
||||
else
|
||||
client?.SendAgentOffline(friendsOnline.ToArray());
|
||||
}
|
||||
m_FriendsModule.CacheFriendsOnline(userID, friendsOnline, online);
|
||||
if(online)
|
||||
client?.SendAgentOnline(friendsOnline.ToArray());
|
||||
else
|
||||
client?.SendAgentOffline(friendsOnline.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver
|
||||
|
||||
scene.AddCommand(
|
||||
"Archiving", this, "save iar",
|
||||
"save iar [-h|--home=<url>] [--noassets | --skipbadassets] <first> <last> <inventory path> <password> [<IAR path>] [-c|--creators] [-e|--exclude=<name/uuid>] [-f|--excludefolder=<foldername/uuid>] [-v|--verbose]",
|
||||
"save iar [-h|--home=<url>] [--noassets | --skipbadassets] <first> <last> <inventory path> <password> [<IAR path>] [--nocreators] [-e|--exclude=<name/uuid>] [-f|--excludefolder=<foldername/uuid>] [-v|--verbose]",
|
||||
"Save user inventory archive (IAR).",
|
||||
"<first> is the user's first name.\n"
|
||||
+ "<last> is the user's last name.\n"
|
||||
@@ -136,7 +136,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver
|
||||
+ "<IAR path> is the filesystem path at which to save the IAR."
|
||||
+ string.Format(" If this is not given then the filename {0} in the current directory is used.\n", DEFAULT_INV_BACKUP_FILENAME)
|
||||
+ "-h|--home=<url> adds the url of the profile service to the saved user information.\n"
|
||||
+ "-c|--creators preserves information about foreign creators.\n"
|
||||
+ " --nocreators stops saving information about foreign creators.\n"
|
||||
+ "-e|--exclude=<name/uuid> don't save the inventory item in archive" + Environment.NewLine
|
||||
+ "-f|--excludefolder=<folder/uuid> don't save contents of the folder in archive" + Environment.NewLine
|
||||
+ "-v|--verbose extra debug messages.\n"
|
||||
@@ -445,7 +445,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver
|
||||
//ops.Add("v|version=", delegate(string v) { options["version"] = v; });
|
||||
ops.Add("h|home=", delegate(string v) { options["home"] = v; });
|
||||
ops.Add("v|verbose", delegate(string v) { options["verbose"] = v; });
|
||||
ops.Add("c|creators", delegate(string v) { options["creators"] = v; });
|
||||
ops.Add("c|creators", delegate(string v) { });
|
||||
ops.Add("nocreators", delegate(string v) { options["nocreators"] = v; });
|
||||
ops.Add("noassets", delegate(string v) { options["noassets"] = v != null; });
|
||||
ops.Add("skipbadassets", delegate(string v) { options["skipbadassets"] = v != null; });
|
||||
ops.Add("e|exclude=", delegate(string v)
|
||||
@@ -469,7 +470,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver
|
||||
if (mainParams.Count < 6)
|
||||
{
|
||||
m_log.Error(
|
||||
"[INVENTORY ARCHIVER]: save iar [-h|--home=<url>] [--noassets | --skipbadassets] <first> <last> <inventory path> <password> [<IAR path>] [-c|--creators] [-e|--exclude=<name/uuid>] [-f|--excludefolder=<foldername/uuid>] [-v|--verbose]");
|
||||
"[INVENTORY ARCHIVER]: save iar [-h|--home=<url>] [--noassets | --skipbadassets] <first> <last> <inventory path> <password> [<IAR path>] [--nocreators] [-e|--exclude=<name/uuid>] [-f|--excludefolder=<foldername/uuid>] [-v|--verbose]");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ using OpenSim.Services.Connectors.Hypergrid;
|
||||
using OpenSim.Framework.Servers.HttpServer;
|
||||
using OpenSim.Services.UserProfilesService;
|
||||
using GridRegion = OpenSim.Services.Interfaces.GridRegion;
|
||||
using OpenSim.Region.CoreModules.Avatar.Friends;
|
||||
|
||||
namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
{
|
||||
@@ -60,12 +61,10 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
// The pair of Dictionaries are used to handle the switching of classified ads
|
||||
// by maintaining a cache of classified id to creator id mappings and an interest
|
||||
// count. The entries are removed when the interest count reaches 0.
|
||||
readonly Dictionary<UUID, UUID> m_classifiedCache = [];
|
||||
readonly Dictionary<UUID, int> m_classifiedInterest = [];
|
||||
readonly Dictionary<UUID, UUID> m_classifiedCache = new();
|
||||
readonly Dictionary<UUID, int> m_classifiedInterest = new();
|
||||
readonly ExpiringCacheOS<UUID, UserProfileCacheEntry> m_profilesCache = new(60000);
|
||||
|
||||
IGroupsModule m_groupsModule = null;
|
||||
IUserManagement m_userManagementModule = null;
|
||||
|
||||
private readonly JsonRpcRequestManager rpc = new();
|
||||
private bool m_allowUserProfileWebURLs = true;
|
||||
@@ -78,150 +77,128 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
public int reqtype;
|
||||
}
|
||||
|
||||
private readonly ConcurrentStack<AsyncPropsRequest> m_asyncLocalRequests = new();
|
||||
private readonly object m_asyncLocalRequestsLock = new();
|
||||
private bool m_asyncLocalRequestsRunning = false;
|
||||
private readonly ConcurrentStack<AsyncPropsRequest> m_asyncHGRequests = new();
|
||||
private readonly object m_asyncHGRequestsLock = new();
|
||||
private bool m_asyncHGRequestsRunning = false;
|
||||
private readonly ConcurrentStack<AsyncPropsRequest> m_asyncRequests = new();
|
||||
private readonly object m_asyncRequestsLock = new();
|
||||
private bool m_asyncRequestsRunning = false;
|
||||
|
||||
private void ProcessLocalRequests()
|
||||
private void ProcessRequests()
|
||||
{
|
||||
lock(m_asyncLocalRequestsLock)
|
||||
lock(m_asyncRequestsLock)
|
||||
{
|
||||
while (m_asyncLocalRequests.TryPop(out AsyncPropsRequest req))
|
||||
while (m_asyncRequests.TryPop(out AsyncPropsRequest req))
|
||||
{
|
||||
IClientAPI client = req.client;
|
||||
if(!client.IsActive)
|
||||
continue;
|
||||
ProcessRequest(req);
|
||||
}
|
||||
m_asyncLocalRequestsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessHGRequests()
|
||||
{
|
||||
lock(m_asyncHGRequestsLock)
|
||||
{
|
||||
while (m_asyncHGRequests.TryPop(out AsyncPropsRequest req))
|
||||
{
|
||||
IClientAPI client = req.client;
|
||||
if(!client.IsActive)
|
||||
continue;
|
||||
ProcessRequest(req);
|
||||
}
|
||||
m_asyncHGRequestsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessRequest(AsyncPropsRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
IClientAPI client = req.client;
|
||||
if(req.reqtype == 0)
|
||||
{
|
||||
ScenePresence p = req.presence;
|
||||
|
||||
bool foreign = GetUserProfileServerURI(req.agent, out string serverURI);
|
||||
bool ok = serverURI.Length > 0;
|
||||
|
||||
byte[] membershipType = new byte[1];
|
||||
string born = string.Empty;
|
||||
uint flags = 0x00;
|
||||
|
||||
if (ok && GetUserAccountData(req.agent, out UserAccount acc))
|
||||
try
|
||||
{
|
||||
flags = (uint)(acc.UserFlags & 0xff);
|
||||
IClientAPI client = req.client;
|
||||
if(!client.IsActive)
|
||||
continue;
|
||||
|
||||
if (acc.UserTitle.Length == 0)
|
||||
membershipType[0] = (byte)((acc.UserFlags & 0x0f00) >> 8);
|
||||
else
|
||||
membershipType = Utils.StringToBytes(acc.UserTitle);
|
||||
|
||||
int val_born = acc.Created;
|
||||
if (val_born != 0)
|
||||
born = Util.ToDateTime(val_born).ToString("M/d/yyyy", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
ok = false;
|
||||
|
||||
UserProfileProperties props = new() { UserId = req.agent };
|
||||
|
||||
if (ok)
|
||||
ok = GetProfileData(ref props, foreign, serverURI, out string result);
|
||||
|
||||
if (!ok)
|
||||
props.AboutText = "Profile not available at this time. User may still be unknown to this grid";
|
||||
|
||||
if (!m_allowUserProfileWebURLs)
|
||||
props.WebUrl = "";
|
||||
|
||||
GroupMembershipData[] agentGroups = null;
|
||||
if(ok && m_groupsModule is not null)
|
||||
agentGroups = m_groupsModule.GetMembershipData(req.agent);
|
||||
|
||||
HashSet<IClientAPI> clients;
|
||||
lock (m_profilesCache)
|
||||
{
|
||||
if (!m_profilesCache.TryGetValue(props.UserId, out UserProfileCacheEntry uce) || uce is null)
|
||||
uce = new UserProfileCacheEntry();
|
||||
uce.props = props;
|
||||
uce.born = born;
|
||||
uce.membershipType = membershipType;
|
||||
uce.flags = flags;
|
||||
clients = uce.ClientsWaitingProps;
|
||||
uce.ClientsWaitingProps = null;
|
||||
uce.avatarGroups = agentGroups;
|
||||
m_profilesCache.AddOrUpdate(props.UserId, uce, PROFILECACHEEXPIRE);
|
||||
}
|
||||
|
||||
if (IsFriendOnline(req.client, req.agent))
|
||||
flags |= (uint)ProfileFlags.Online;
|
||||
else
|
||||
flags &= (uint)~ProfileFlags.Online;
|
||||
|
||||
if (clients is null)
|
||||
{
|
||||
client.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
|
||||
client.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
if (agentGroups is not null)
|
||||
client.SendAvatarGroupsReply(req.agent, agentGroups);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!clients.Contains(client) && client.IsActive)
|
||||
if(req.reqtype == 0)
|
||||
{
|
||||
client.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
ScenePresence p = req.presence;
|
||||
|
||||
client.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
if (agentGroups is not null)
|
||||
client.SendAvatarGroupsReply(req.agent, agentGroups);
|
||||
}
|
||||
foreach (IClientAPI cli in clients)
|
||||
{
|
||||
if (!cli.IsActive)
|
||||
continue;
|
||||
cli.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
bool foreign = GetUserProfileServerURI(req.agent, out string serverURI);
|
||||
bool ok = serverURI.Length > 0;
|
||||
|
||||
cli.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
if (agentGroups is not null)
|
||||
cli.SendAvatarGroupsReply(req.agent, agentGroups);
|
||||
byte[] membershipType = new byte[1];
|
||||
string born = string.Empty;
|
||||
uint flags = 0x00;
|
||||
|
||||
if (ok && GetUserAccountData(req.agent, out UserAccount acc))
|
||||
{
|
||||
flags = (uint)(acc.UserFlags & 0xff);
|
||||
|
||||
if (acc.UserTitle.Length == 0)
|
||||
membershipType[0] = (byte)((acc.UserFlags & 0x0f00) >> 8);
|
||||
else
|
||||
membershipType = Utils.StringToBytes(acc.UserTitle);
|
||||
|
||||
int val_born = acc.Created;
|
||||
if (val_born != 0)
|
||||
born = Util.ToDateTime(val_born).ToString("M/d/yyyy", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
ok = false;
|
||||
|
||||
UserProfileProperties props = new() { UserId = req.agent };
|
||||
|
||||
if (ok)
|
||||
ok = GetProfileData(ref props, foreign, serverURI, out string result);
|
||||
|
||||
if (!ok)
|
||||
props.AboutText = "Profile not available at this time. User may still be unknown to this grid";
|
||||
|
||||
if (!m_allowUserProfileWebURLs)
|
||||
props.WebUrl = "";
|
||||
|
||||
GroupMembershipData[] agentGroups = null;
|
||||
if(ok && m_groupsModule is not null)
|
||||
agentGroups = m_groupsModule.GetMembershipData(req.agent);
|
||||
|
||||
HashSet<IClientAPI> clients;
|
||||
lock (m_profilesCache)
|
||||
{
|
||||
if (!m_profilesCache.TryGetValue(props.UserId, out UserProfileCacheEntry uce) || uce is null)
|
||||
uce = new UserProfileCacheEntry();
|
||||
uce.props = props;
|
||||
uce.born = born;
|
||||
uce.membershipType = membershipType;
|
||||
uce.flags = flags;
|
||||
clients = uce.ClientsWaitingProps;
|
||||
uce.ClientsWaitingProps = null;
|
||||
uce.avatarGroups = agentGroups;
|
||||
m_profilesCache.AddOrUpdate(props.UserId, uce, PROFILECACHEEXPIRE);
|
||||
}
|
||||
|
||||
if (IsFriendOnline(req.client, req.agent))
|
||||
flags |= (uint)ProfileFlags.Online;
|
||||
else
|
||||
flags &= (uint)~ProfileFlags.Online;
|
||||
|
||||
if (clients is null)
|
||||
{
|
||||
client.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
|
||||
client.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
if (agentGroups is not null)
|
||||
client.SendAvatarGroupsReply(req.agent, agentGroups);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!clients.Contains(client) && client.IsActive)
|
||||
{
|
||||
client.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
|
||||
client.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
if (agentGroups is not null)
|
||||
client.SendAvatarGroupsReply(req.agent, agentGroups);
|
||||
}
|
||||
foreach (IClientAPI cli in clients)
|
||||
{
|
||||
if (!cli.IsActive)
|
||||
continue;
|
||||
cli.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
|
||||
cli.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
if (agentGroups is not null)
|
||||
cli.SendAvatarGroupsReply(req.agent, agentGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.ErrorFormat("[UserProfileModule]: Process fail {0} : {1}", e.Message, e.StackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error($"[UserProfileModule]: Process fail {e.Message} : {e.StackTrace}");
|
||||
m_asyncRequestsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +231,16 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
set;
|
||||
}
|
||||
|
||||
IProfileModule ProfileModule
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
IUserManagement UserManagementModule
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this
|
||||
/// <see cref="OpenSim.Region.Coremodules.UserProfiles.UserProfileModule"/> is enabled.
|
||||
@@ -264,7 +251,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
public bool Enabled
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
set;
|
||||
}
|
||||
|
||||
private GridInfo m_thisGridInfo;
|
||||
@@ -281,6 +268,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
public void Initialise(IConfigSource source)
|
||||
{
|
||||
Config = source;
|
||||
ReplaceableInterface = typeof(IProfileModule);
|
||||
|
||||
IConfig profileConfig = Config.Configs["UserProfiles"];
|
||||
|
||||
@@ -312,19 +300,10 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
m_allowUserProfileWebURLs = profileConfig.GetBoolean("AllowUserProfileWebURLs", m_allowUserProfileWebURLs);
|
||||
|
||||
m_log.Debug("[UserProfileModule]: Full Profiles Enabled");
|
||||
|
||||
MainConsole.Instance.Commands.AddCommand("Debug", false, "profiles status",
|
||||
"profiles status",
|
||||
"Show user profile Queues count",
|
||||
HandleShowStatus);
|
||||
ReplaceableInterface = null;
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
private void HandleShowStatus(string module, string[] cmdparms)
|
||||
{
|
||||
MainConsole.Instance.Output($"Profile requests in '{Scene.Name}' Local: {m_asyncLocalRequests.Count} HG: {m_asyncHGRequests.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the region.
|
||||
/// </summary>
|
||||
@@ -339,6 +318,10 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
Scene = scene;
|
||||
m_thisGridInfo ??= scene.SceneGridInfo;
|
||||
Scene.RegisterModuleInterface<IProfileModule>(this);
|
||||
Scene.EventManager.OnNewClient += OnNewClient;
|
||||
Scene.EventManager.OnClientClosed += OnClientClosed;
|
||||
|
||||
UserManagementModule = Scene.RequestModuleInterface<IUserManagement>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -370,19 +353,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
{
|
||||
if(!Enabled)
|
||||
return;
|
||||
|
||||
m_groupsModule = Scene.RequestModuleInterface<IGroupsModule>();
|
||||
|
||||
m_userManagementModule = Scene.RequestModuleInterface<IUserManagement>();
|
||||
if(m_userManagementModule is null)
|
||||
{
|
||||
m_log.Error("[UserProfileModule]: UserManagementModule not loaded. Profiles Disabled");
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Scene.EventManager.OnNewClient += OnNewClient;
|
||||
Scene.EventManager.OnClientClosed += OnClientClosed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -398,7 +369,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
/// </value>
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -458,9 +429,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
|
||||
void OnClientClosed(UUID AgentId, Scene scene)
|
||||
{
|
||||
if(!scene.TryGetScenePresence(AgentId, out ScenePresence sp))
|
||||
return;
|
||||
|
||||
ScenePresence sp = scene.GetScenePresence(AgentId);
|
||||
IClientAPI client = sp.ControllingClient;
|
||||
if (client is null)
|
||||
return;
|
||||
@@ -1250,9 +1219,9 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
|
||||
UserProfilePick pick = null;
|
||||
Dictionary<UUID, string> curpicks = GetPicks(creatorID);
|
||||
if(curpicks is not null && !curpicks.ContainsKey(pickID))
|
||||
if(!curpicks.ContainsKey(pickID))
|
||||
{
|
||||
if(curpicks.Count >= Constants.MaxProfilePicks)
|
||||
if(curpicks is not null && curpicks.Count >= Constants.MaxProfilePicks)
|
||||
{
|
||||
remoteClient.SendAvatarPicksReply(remoteClient.AgentId, curpicks);
|
||||
return;
|
||||
@@ -1675,46 +1644,124 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
reqtype = 0
|
||||
};
|
||||
|
||||
if(m_userManagementModule.IsLocalGridUser(avatarID))
|
||||
{
|
||||
m_asyncLocalRequests.Push(req);
|
||||
m_asyncRequests.Push(req);
|
||||
|
||||
if (Monitor.TryEnter(m_asyncLocalRequestsLock))
|
||||
if (Monitor.TryEnter(m_asyncRequestsLock))
|
||||
{
|
||||
if (!m_asyncRequestsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!m_asyncLocalRequestsRunning)
|
||||
{
|
||||
m_asyncLocalRequestsRunning = true;
|
||||
Util.FireAndForget(x => ProcessLocalRequests());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(m_asyncLocalRequestsLock);
|
||||
}
|
||||
m_asyncRequestsRunning = true;
|
||||
Util.FireAndForget(x => ProcessRequests());
|
||||
}
|
||||
Monitor.Exit(m_asyncRequestsLock);
|
||||
}
|
||||
|
||||
/*
|
||||
string serverURI = string.Empty;
|
||||
bool foreign = GetUserProfileServerURI(avatarID, out serverURI);
|
||||
|
||||
UserAccount account = null;
|
||||
Dictionary<string,object> userInfo;
|
||||
|
||||
if (!foreign)
|
||||
{
|
||||
account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, avatarID);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_asyncHGRequests.Push(req);
|
||||
userInfo = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
if (Monitor.TryEnter(m_asyncHGRequestsLock))
|
||||
Byte[] membershipType = new Byte[1];
|
||||
string born = string.Empty;
|
||||
uint flags = 0x00;
|
||||
|
||||
if (null != account)
|
||||
{
|
||||
if (account.UserTitle.Length == 0)
|
||||
membershipType[0] = (Byte)((account.UserFlags & 0xf00) >> 8);
|
||||
else
|
||||
membershipType = Utils.StringToBytes(account.UserTitle);
|
||||
|
||||
born = Util.ToDateTime(account.Created).ToString(
|
||||
"M/d/yyyy", CultureInfo.InvariantCulture);
|
||||
flags = (uint)(account.UserFlags & 0xff);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetUserAccountData(avatarID, out userInfo) == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!m_asyncHGRequestsRunning)
|
||||
{
|
||||
m_asyncHGRequestsRunning = true;
|
||||
Util.FireAndForget(x => ProcessHGRequests());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(m_asyncHGRequestsLock);
|
||||
}
|
||||
if ((string)userInfo["user_title"].Length == 0)
|
||||
membershipType[0] = (Byte)(((Byte)userInfo["user_flags"] & 0xf00) >> 8);
|
||||
else
|
||||
membershipType = Utils.StringToBytes((string)userInfo["user_title"]);
|
||||
|
||||
int val_born = (int)userInfo["user_created"];
|
||||
if(val_born != 0)
|
||||
born = Util.ToDateTime(val_born).ToString(
|
||||
"M/d/yyyy", CultureInfo.InvariantCulture);
|
||||
|
||||
// picky, picky
|
||||
int val_flags = (int)userInfo["user_flags"];
|
||||
flags = (uint)(val_flags & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
props = new UserProfileProperties();
|
||||
props.UserId = avatarID;
|
||||
|
||||
string result = string.Empty;
|
||||
if(!GetProfileData(ref props, foreign, serverURI, out result))
|
||||
{
|
||||
props.AboutText ="Profile not available at this time. User may still be unknown to this grid";
|
||||
}
|
||||
|
||||
if(!m_allowUserProfileWebURLs)
|
||||
props.WebUrl ="";
|
||||
|
||||
HashSet<IClientAPI> clients;
|
||||
lock(m_profilesCache)
|
||||
{
|
||||
if(!m_profilesCache.TryGetValue(props.UserId, out uce) || uce == null)
|
||||
uce = new UserProfileCacheEntry();
|
||||
uce.props = props;
|
||||
uce.born = born;
|
||||
uce.membershipType = membershipType;
|
||||
uce.flags = flags;
|
||||
clients = uce.ClientsWaitingProps;
|
||||
uce.ClientsWaitingProps = null;
|
||||
m_profilesCache.AddOrUpdate(props.UserId, uce, PROFILECACHEEXPIRE);
|
||||
}
|
||||
|
||||
// if on same region force online
|
||||
if(p != null && !p.IsDeleted)
|
||||
flags |= 0x10;
|
||||
|
||||
if(clients == null)
|
||||
{
|
||||
remoteClient.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType , props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
|
||||
remoteClient.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!clients.Contains(remoteClient))
|
||||
clients.Add(remoteClient);
|
||||
foreach(IClientAPI cli in clients)
|
||||
{
|
||||
if(!cli.IsActive)
|
||||
continue;
|
||||
cli.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType , props.FirstLifeText, flags,
|
||||
props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
|
||||
|
||||
cli.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
|
||||
(uint)props.SkillsMask, props.SkillsText, props.Language);
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1830,7 +1877,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
bool GetUserAccountData(UUID userID, out UserAccount account)
|
||||
{
|
||||
account = null;
|
||||
if (m_userManagementModule.IsLocalGridUser(userID))
|
||||
if (UserManagementModule.IsLocalGridUser(userID))
|
||||
{
|
||||
// Is local
|
||||
IUserAccountService uas = Scene.UserAccountService;
|
||||
@@ -1840,7 +1887,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
else
|
||||
{
|
||||
// Is Foreign
|
||||
string home_url = m_userManagementModule.GetUserServerURL(userID, "HomeURI", out bool recentFailedWeb);
|
||||
string home_url = UserManagementModule.GetUserServerURL(userID, "HomeURI", out bool recentFailedWeb);
|
||||
if (recentFailedWeb || string.IsNullOrEmpty(home_url))
|
||||
return false;
|
||||
|
||||
@@ -1854,7 +1901,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Debug("[PROFILES]: GetUserInfo call failed ", e);
|
||||
m_userManagementModule.UserWebFailed(userID);
|
||||
UserManagementModule.UserWebFailed(userID);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1862,6 +1909,9 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
return false;
|
||||
|
||||
account = new UserAccount();
|
||||
if (info.ContainsKey("user_firstname") && info.ContainsKey("user_lastname"))
|
||||
account.FirstName = info["user_firstname"] + "." + info["user_lastname"];
|
||||
|
||||
if (info.ContainsKey("user_flags"))
|
||||
account.UserFlags = (int)info["user_flags"];
|
||||
|
||||
@@ -1887,9 +1937,9 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
/// </param>
|
||||
bool GetUserProfileServerURI(UUID userID, out string serverURI)
|
||||
{
|
||||
if (!m_userManagementModule.IsLocalGridUser(userID))
|
||||
if (!UserManagementModule.IsLocalGridUser(userID))
|
||||
{
|
||||
serverURI = m_userManagementModule.GetUserServerURL(userID, "ProfileServerURI", out bool failed);
|
||||
serverURI = UserManagementModule.GetUserServerURL(userID, "ProfileServerURI", out bool failed);
|
||||
if(failed)
|
||||
serverURI = string.Empty;
|
||||
// Is Foreign
|
||||
@@ -1908,7 +1958,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
if(imageID.IsZero())
|
||||
return;
|
||||
|
||||
string assetServerURI = m_userManagementModule.GetUserServerURL(agent, "AssetServerURI");
|
||||
string assetServerURI = UserManagementModule.GetUserServerURL(agent, "AssetServerURI");
|
||||
if(string.IsNullOrWhiteSpace(assetServerURI))
|
||||
return;
|
||||
|
||||
@@ -1946,7 +1996,7 @@ namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
|
||||
|
||||
if(client.SceneAgent is ScenePresence sp && sp.IsViewerUIGod)
|
||||
{
|
||||
Services.Interfaces.PresenceInfo[] pi = Scene.PresenceService?.GetAgents([agent.ToString()]);
|
||||
Services.Interfaces.PresenceInfo[] pi = Scene.PresenceService?.GetAgents(new string[] { agent.ToString() });
|
||||
return pi is not null && pi.Length > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1319,6 +1319,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
|
||||
protected virtual bool CreateAgent(ScenePresence sp, GridRegion reg, GridRegion finalDestination, AgentCircuitData agentCircuit, uint teleportFlags, EntityTransferContext ctx, out string reason, out bool logout)
|
||||
{
|
||||
if (!sp.GotAttachmentsData)
|
||||
{
|
||||
logout = false;
|
||||
reason = "Cannot leave region yet, attachments are still loading";
|
||||
return false;
|
||||
}
|
||||
|
||||
GridRegion source = new(m_sceneRegionInfo)
|
||||
{
|
||||
RawServerURI = m_thisGridInfo.GateKeeperURL
|
||||
@@ -2854,7 +2861,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
return true;
|
||||
}
|
||||
|
||||
private int GetStateSource(SceneObjectGroup sog)
|
||||
public int GetStateSource(SceneObjectGroup sog)
|
||||
{
|
||||
ScenePresence sp = m_scene.GetScenePresence(sog.OwnerID);
|
||||
|
||||
|
||||
@@ -651,17 +651,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
if (OwnerID.IsZero())
|
||||
{
|
||||
m_log.DebugFormat(
|
||||
"[HG TRANSFER MODULE]: Denied object {0}({1}) entry into {2} because ownerID is zero",
|
||||
"[HG ENTITY TRANSFER MODULE]: Denied object {0}({1}) entry into {2} because ownerID is zero",
|
||||
so.Name, so.UUID, m_sceneName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_sceneRegionInfo.EstateSettings.IsBanned(OwnerID))
|
||||
if (!m_scene.Permissions.IsAdministrator(so.OwnerID) && m_sceneRegionInfo.EstateSettings.IsBanned(OwnerID))
|
||||
{
|
||||
m_log.DebugFormat(
|
||||
"[HG TRANSFER MODULE]: Denied prim crossing of {0} {1} into {2} for banned avatar {3}",
|
||||
so.Name, so.UUID, m_sceneName, so.OwnerID);
|
||||
|
||||
$"[HG ENTITY TRANSFER MODULE]: Denied {so.Name} {so.UUID} into { m_sceneName} of banned owner {so.OwnerID}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -676,8 +674,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
if (m_scene.GetScenePresence(OwnerID) == null)
|
||||
{
|
||||
m_log.DebugFormat(
|
||||
"[HG TRANSFER MODULE]: Denied attachment {0}({1}) owner {2} not in region {3}",
|
||||
so.Name, so.UUID, OwnerID, m_sceneName);
|
||||
$"[HG ENTITY TRANSFER MODULE]: Denied attachment {so.Name}({so.UUID}) owner {so.OwnerID} not in region {m_sceneName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -803,72 +800,115 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aCircuit.ServiceURLs != null && aCircuit.ServiceURLs.ContainsKey("AssetServerURI"))
|
||||
// Upstream does let the avi in, even if their assetserver can't be reached
|
||||
// The visitor will obviously be without visible attachments in this case
|
||||
if (aCircuit.ServiceURLs == null || !aCircuit.ServiceURLs.ContainsKey("AssetServerURI"))
|
||||
{
|
||||
ScenePresence defsp = sp;
|
||||
List<SceneObjectGroup> deftatt = attachments;
|
||||
List<SceneObjectGroup> toadd = new List<SceneObjectGroup>(deftatt.Count);
|
||||
m_incomingSceneObjectEngine.QueueJob(
|
||||
string.Format("HG UUID Gather attachments {0}", defsp.Name), () =>
|
||||
sp.GotAttachmentsData = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ScenePresence defsp = sp;
|
||||
List<SceneObjectGroup> deftatt = new(attachments.Count);
|
||||
List<SceneObjectGroup> defhuds = new();
|
||||
int requestStartTick = Environment.TickCount;
|
||||
|
||||
// Prioritize non-HUDS, as AOs, dance huds etc can hold a lot of assets
|
||||
foreach (SceneObjectGroup sog in attachments)
|
||||
{
|
||||
if (sog.AttachmentPoint >= 31 && sog.AttachmentPoint <= 38)
|
||||
{
|
||||
defhuds.Add(sog);
|
||||
}
|
||||
else
|
||||
{
|
||||
deftatt.Add(sog);
|
||||
}
|
||||
}
|
||||
deftatt.AddRange(defhuds);
|
||||
|
||||
IClientAPI remoteClient = sp.ControllingClient;
|
||||
UUID groupID = remoteClient.ActiveGroupId;
|
||||
|
||||
m_incomingSceneObjectEngine.QueueJob(
|
||||
string.Format("HG UUID Gather attachments {0}", defsp.Name), () =>
|
||||
{
|
||||
string url = aCircuit.ServiceURLs["AssetServerURI"].ToString();
|
||||
IDictionary<UUID, sbyte> ids = new Dictionary<UUID, sbyte>();
|
||||
HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, url, ids);
|
||||
|
||||
foreach (SceneObjectGroup sog in deftatt)
|
||||
{
|
||||
string url = aCircuit.ServiceURLs["AssetServerURI"].ToString();
|
||||
IDictionary<UUID, sbyte> ids = new Dictionary<UUID, sbyte>();
|
||||
HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, url, ids);
|
||||
|
||||
foreach (SceneObjectGroup defso in deftatt)
|
||||
if(sog.OwnerID.NotEqual(defsp.UUID))
|
||||
{
|
||||
if(defso.OwnerID.NotEqual(defsp.UUID))
|
||||
{
|
||||
m_log.ErrorFormat(
|
||||
"[HG TRANSFER MODULE] attachment {0}({1} owner {2} does not match HG avatarID {3}",
|
||||
defso.Name, defso.UUID, defso.OwnerID, defsp.UUID);
|
||||
continue;
|
||||
}
|
||||
uuidGatherer.AddForInspection(defso);
|
||||
while (!uuidGatherer.Complete)
|
||||
{
|
||||
if (sp.IsDeleted)
|
||||
{
|
||||
deftatt = null;
|
||||
defsp = null;
|
||||
uuidGatherer = null;
|
||||
toadd = null;
|
||||
return;
|
||||
}
|
||||
uuidGatherer.GatherNext();
|
||||
}
|
||||
toadd.Add(defso);
|
||||
m_log.ErrorFormat(
|
||||
"[HG ENTITY TRANSFER] Owner ({0}) of attachment '{1}' does not match HG visitor's ID ({2})",
|
||||
sog.OwnerID, sog.Name, defsp.UUID);
|
||||
continue;
|
||||
}
|
||||
deftatt = null;
|
||||
|
||||
// get asset uuids for scene object
|
||||
uuidGatherer.AddForInspection(sog);
|
||||
while (!uuidGatherer.Complete)
|
||||
{
|
||||
if (sp.IsDeleted)
|
||||
{
|
||||
deftatt = null;
|
||||
defsp = null;
|
||||
uuidGatherer = null;
|
||||
return;
|
||||
}
|
||||
uuidGatherer.GatherNext();
|
||||
}
|
||||
// fetch assets for scene object
|
||||
foreach (UUID id in ids.Keys)
|
||||
{
|
||||
int tickStart = Util.EnvironmentTickCount();
|
||||
|
||||
uuidGatherer.FetchAsset(id);
|
||||
|
||||
int ticksElapsed = Util.EnvironmentTickCountSubtract(tickStart);
|
||||
|
||||
if (sp.IsDeleted || ticksElapsed > 30000)
|
||||
{
|
||||
m_log.WarnFormat(
|
||||
"[HG ENTITY TRANSFER]: Aborting fetch attachments assets for HG user {0}", sp.Name);
|
||||
"[HG ENTITY TRANSFER]: Aborting fetching assets for for HG visitor {0}", defsp.Name);
|
||||
|
||||
deftatt = null;
|
||||
defsp = null;
|
||||
uuidGatherer = null;
|
||||
toadd = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ids.Clear(); // this scene object is done fetching
|
||||
|
||||
base.HandleIncomingAttachments(sp, toadd);
|
||||
// add scene object
|
||||
if (!m_scene.AddSceneObject(sog))
|
||||
{
|
||||
m_log.DebugFormat(
|
||||
"[HG ENTITY TRANSFER]: Could not add attachment '{0}' for HG visitor {1}",
|
||||
sog.Name, defsp.Name);
|
||||
} else {
|
||||
sog.SetGroup(groupID, remoteClient);
|
||||
if (sog.ContainsScripts())
|
||||
{
|
||||
sog.RootPart.ParentGroup.CreateScriptInstances(
|
||||
0, false, Scene.DefaultScriptEngine, GetStateSource(sog));
|
||||
sog.aggregateScriptEvents();
|
||||
sog.ResumeScripts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defsp = null;
|
||||
uuidGatherer = null;
|
||||
toadd = null;
|
||||
},
|
||||
OwnerID.ToString());
|
||||
}
|
||||
deftatt = null;
|
||||
defsp = null;
|
||||
uuidGatherer = null;
|
||||
|
||||
sp.GotAttachmentsData = true;
|
||||
string loadtime = Convert.ToString(Environment.TickCount - requestStartTick);
|
||||
m_log.DebugFormat(
|
||||
"[HG TRANSFER MODULE]: Attachments of avatar {0} took {1}ms. to load", sp.Name, loadtime);
|
||||
|
||||
// at this point the job engine is done
|
||||
},
|
||||
OwnerID.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1214,11 +1214,16 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement
|
||||
{
|
||||
if (m_thisGridInfo.IsLocalGrid(homeuri.URL) == 1) // local
|
||||
{
|
||||
oldUser.FirstName = firstname;
|
||||
oldUser.LastName = lastname;
|
||||
oldUser.IsLocal = true;
|
||||
oldUser.HomeURL = string.Empty;
|
||||
oldUser.HasGridUserTried = true;
|
||||
UserAccount account = m_userAccountService.GetUserAccount(UUID.Zero, firstname, lastname);
|
||||
if (account != null)
|
||||
{
|
||||
oldUser.FirstName = firstname;
|
||||
oldUser.LastName = lastname;
|
||||
oldUser.IsLocal = true;
|
||||
oldUser.HomeURL = string.Empty;
|
||||
oldUser.HasGridUserTried = true;
|
||||
}
|
||||
else return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1232,11 +1237,16 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement
|
||||
{
|
||||
if (string.IsNullOrEmpty(homeuri.Host)) // take this as local
|
||||
{
|
||||
oldUser.FirstName = firstname;
|
||||
oldUser.LastName = lastname;
|
||||
oldUser.IsLocal = true;
|
||||
oldUser.HomeURL = string.Empty;
|
||||
oldUser.HasGridUserTried = true;
|
||||
UserAccount account = m_userAccountService.GetUserAccount(UUID.Zero, firstname, lastname);
|
||||
if (account != null && account.PrincipalID == id)
|
||||
{
|
||||
oldUser.FirstName = firstname;
|
||||
oldUser.LastName = lastname;
|
||||
oldUser.IsLocal = true;
|
||||
oldUser.HomeURL = string.Empty;
|
||||
oldUser.HasGridUserTried = true;
|
||||
}
|
||||
else return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -32,7 +32,9 @@ using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using log4net;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Search;
|
||||
using MimeKit;
|
||||
using Nini.Config;
|
||||
using OpenMetaverse;
|
||||
@@ -40,6 +42,7 @@ using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using Mono.Addins;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
{
|
||||
@@ -68,8 +71,14 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
private string SMTP_SERVER_LOGIN = null;
|
||||
private string SMTP_SERVER_PASSWORD = null;
|
||||
|
||||
private bool m_enableEmailToExternalObjects = true;
|
||||
private bool m_enableEmailToSMTP = true;
|
||||
private bool IMAP_SERVER_TLS = false;
|
||||
private string IMAP_SERVER_HOSTNAME = null;
|
||||
private int IMAP_SERVER_PORT = 143;
|
||||
private string IMAP_SERVER_LOGIN = null;
|
||||
private string IMAP_SERVER_PASSWORD = null;
|
||||
|
||||
private bool m_enableEmailToExternalObjects = false;
|
||||
private bool m_enableEmailToSMTP = false;
|
||||
|
||||
private ParserOptions m_mailParseOptions;
|
||||
|
||||
@@ -102,6 +111,7 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
private int m_MaxEmailSize = 4096; // largest email allowed by default, as per lsl docs.
|
||||
|
||||
private static SslPolicyErrors m_SMTP_SslPolicyErrorsMask;
|
||||
private static SslPolicyErrors m_IMAP_SslPolicyErrorsMask;
|
||||
private bool m_checkSpecName;
|
||||
|
||||
private object m_queuesLock = new object();
|
||||
@@ -123,22 +133,22 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
if(startupConfig.GetString("emailmodule", "DefaultEmailModule") != "DefaultEmailModule")
|
||||
return;
|
||||
|
||||
//Load SMTP SERVER config
|
||||
//Load Email config
|
||||
try
|
||||
{
|
||||
IConfig SMTPConfig = config.Configs["SMTP"];
|
||||
if (SMTPConfig == null)
|
||||
IConfig EmailConfig = config.Configs["Email"];
|
||||
if (EmailConfig == null)
|
||||
return;
|
||||
|
||||
if(!SMTPConfig.GetBoolean("enabled", false))
|
||||
if(!EmailConfig.GetBoolean("enabled", true))
|
||||
return;
|
||||
|
||||
m_enableEmailToExternalObjects = SMTPConfig.GetBoolean("enableEmailToExternalObjects", m_enableEmailToExternalObjects);
|
||||
m_enableEmailToSMTP = SMTPConfig.GetBoolean("enableEmailToSMTP", m_enableEmailToSMTP);
|
||||
m_enableEmailToExternalObjects = EmailConfig.GetBoolean("enableEmailToExternalObjects", m_enableEmailToExternalObjects);
|
||||
m_enableEmailToSMTP = EmailConfig.GetBoolean("enableEmailToSMTP", m_enableEmailToSMTP);
|
||||
|
||||
m_MailsToPrimAddressPerHour = SMTPConfig.GetInt("MailsToPrimAddressPerHour", m_MailsToPrimAddressPerHour);
|
||||
m_MailsToPrimAddressPerHour = EmailConfig.GetInt("MailsToPrimAddressPerHour", m_MailsToPrimAddressPerHour);
|
||||
m_MailsToPrimAddressRate = m_MailsToPrimAddressPerHour / 3600.0;
|
||||
m_MailsFromOwnerPerHour = SMTPConfig.GetInt("MailsFromOwnerPerHour", m_MailsFromOwnerPerHour);
|
||||
m_MailsFromOwnerPerHour = EmailConfig.GetInt("MailsFromOwnerPerHour", m_MailsFromOwnerPerHour);
|
||||
m_MailsFromOwnerRate = m_MailsFromOwnerPerHour / 3600.0;
|
||||
|
||||
m_mailParseOptions = new ParserOptions()
|
||||
@@ -146,17 +156,17 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
AllowAddressesWithoutDomain = false,
|
||||
};
|
||||
|
||||
m_InterObjectHostname = SMTPConfig.GetString("internal_object_host", m_InterObjectHostname);
|
||||
m_InterObjectHostname = EmailConfig.GetString("internal_object_host", m_InterObjectHostname);
|
||||
m_checkSpecName = !m_InterObjectHostname.Equals("lsl.secondlife.com");
|
||||
|
||||
if (m_enableEmailToSMTP)
|
||||
{
|
||||
m_SMTP_MailsPerDay = SMTPConfig.GetInt("SMTP_MailsPerDay", m_SMTP_MailsPerDay);
|
||||
m_SMTP_MailsPerDay = EmailConfig.GetInt("SMTP_MailsPerDay", m_SMTP_MailsPerDay);
|
||||
m_SMTP_MailsRate = m_SMTP_MailsPerDay / 86400.0;
|
||||
m_MailsToSMTPAddressPerHour = SMTPConfig.GetInt("MailsToSMTPAddressPerHour", m_MailsToPrimAddressPerHour);
|
||||
m_MailsToSMTPAddressPerHour = EmailConfig.GetInt("MailsToSMTPAddressPerHour", m_MailsToPrimAddressPerHour);
|
||||
m_MailsToSMTPAddressRate = m_MailsToPrimAddressPerHour / 3600.0;
|
||||
|
||||
SMTP_SERVER_HOSTNAME = SMTPConfig.GetString("SMTP_SERVER_HOSTNAME", SMTP_SERVER_HOSTNAME);
|
||||
SMTP_SERVER_HOSTNAME = EmailConfig.GetString("SMTP_SERVER_HOSTNAME", SMTP_SERVER_HOSTNAME);
|
||||
OSHHTPHost hosttmp = new OSHHTPHost(SMTP_SERVER_HOSTNAME, true);
|
||||
if(!hosttmp.IsResolvedHost)
|
||||
{
|
||||
@@ -164,22 +174,22 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
return;
|
||||
}
|
||||
|
||||
SMTP_SERVER_PORT = SMTPConfig.GetInt("SMTP_SERVER_PORT", SMTP_SERVER_PORT);
|
||||
SMTP_SERVER_TLS = SMTPConfig.GetBoolean("SMTP_SERVER_TLS", SMTP_SERVER_TLS);
|
||||
SMTP_SERVER_PORT = EmailConfig.GetInt("SMTP_SERVER_PORT", SMTP_SERVER_PORT);
|
||||
SMTP_SERVER_TLS = EmailConfig.GetBoolean("SMTP_SERVER_TLS", SMTP_SERVER_TLS);
|
||||
|
||||
string smtpfrom = SMTPConfig.GetString("SMTP_SERVER_FROM", string.Empty);
|
||||
m_HostName = SMTPConfig.GetString("host_domain_header_from", m_HostName);
|
||||
string smtpfrom = EmailConfig.GetString("SMTP_SERVER_FROM", string.Empty);
|
||||
m_HostName = EmailConfig.GetString("host_domain_header_from", m_HostName);
|
||||
if (!string.IsNullOrEmpty(smtpfrom) && !MailboxAddress.TryParse(m_mailParseOptions, smtpfrom, out SMTP_MAIL_FROM))
|
||||
{
|
||||
m_log.ErrorFormat("[EMAIL]: Invalid SMTP_SERVER_FROM {0}", smtpfrom);
|
||||
return;
|
||||
}
|
||||
|
||||
SMTP_SERVER_LOGIN = SMTPConfig.GetString("SMTP_SERVER_LOGIN", SMTP_SERVER_LOGIN);
|
||||
SMTP_SERVER_PASSWORD = SMTPConfig.GetString("SMTP_SERVER_PASSWORD", SMTP_SERVER_PASSWORD);
|
||||
SMTP_SERVER_LOGIN = EmailConfig.GetString("SMTP_SERVER_LOGIN", SMTP_SERVER_LOGIN);
|
||||
SMTP_SERVER_PASSWORD = EmailConfig.GetString("SMTP_SERVER_PASSWORD", SMTP_SERVER_PASSWORD);
|
||||
|
||||
bool VerifyCertChain = SMTPConfig.GetBoolean("SMTP_VerifyCertChain", true);
|
||||
bool VerifyCertNames = SMTPConfig.GetBoolean("SMTP_VerifyCertNames", true);
|
||||
bool VerifyCertChain = EmailConfig.GetBoolean("SMTP_VerifyCertChain", true);
|
||||
bool VerifyCertNames = EmailConfig.GetBoolean("SMTP_VerifyCertNames", true);
|
||||
m_SMTP_SslPolicyErrorsMask = VerifyCertChain ? 0 : SslPolicyErrors.RemoteCertificateChainErrors;
|
||||
if (!VerifyCertNames)
|
||||
m_SMTP_SslPolicyErrorsMask |= SslPolicyErrors.RemoteCertificateNameMismatch;
|
||||
@@ -191,7 +201,35 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
m_log.Warn("[EMAIL]: SMTP disabled, set enableEmailSMTP to enable");
|
||||
}
|
||||
|
||||
m_MaxEmailSize = SMTPConfig.GetInt("email_max_size", m_MaxEmailSize);
|
||||
if (m_enableEmailToExternalObjects)
|
||||
{
|
||||
IMAP_SERVER_HOSTNAME = EmailConfig.GetString("IMAP_SERVER_HOSTNAME", IMAP_SERVER_HOSTNAME);
|
||||
OSHHTPHost hosttmp = new OSHHTPHost(IMAP_SERVER_HOSTNAME, true);
|
||||
if(!hosttmp.IsResolvedHost)
|
||||
{
|
||||
m_log.ErrorFormat("[EMAIL]: could not resolve IMAP_SERVER_HOSTNAME {0}", IMAP_SERVER_HOSTNAME);
|
||||
return;
|
||||
}
|
||||
|
||||
IMAP_SERVER_PORT = EmailConfig.GetInt("IMAP_SERVER_PORT", IMAP_SERVER_PORT);
|
||||
IMAP_SERVER_TLS = EmailConfig.GetBoolean("IMAP_SERVER_TLS", IMAP_SERVER_TLS);
|
||||
IMAP_SERVER_LOGIN = EmailConfig.GetString("IMAP_SERVER_LOGIN", IMAP_SERVER_LOGIN);
|
||||
IMAP_SERVER_PASSWORD = EmailConfig.GetString("IMAP_SERVER_PASSWORD", IMAP_SERVER_PASSWORD);
|
||||
|
||||
bool VerifyCertChain = EmailConfig.GetBoolean("IMAP_VerifyCertChain", true);
|
||||
bool VerifyCertNames = EmailConfig.GetBoolean("IMAP_VerifyCertNames", true);
|
||||
m_IMAP_SslPolicyErrorsMask = VerifyCertChain ? 0 : SslPolicyErrors.RemoteCertificateChainErrors;
|
||||
if (!VerifyCertNames)
|
||||
m_IMAP_SslPolicyErrorsMask |= SslPolicyErrors.RemoteCertificateNameMismatch;
|
||||
m_IMAP_SslPolicyErrorsMask = ~m_IMAP_SslPolicyErrorsMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_IMAP_SslPolicyErrorsMask = ~SslPolicyErrors.None;
|
||||
m_log.Warn("[EMAIL]: IMAP disabled, set enableEmailToExternalObjects to enable");
|
||||
}
|
||||
|
||||
m_MaxEmailSize = EmailConfig.GetInt("email_max_size", m_MaxEmailSize);
|
||||
if(m_MaxEmailSize < 256 || m_MaxEmailSize > 1000000)
|
||||
{
|
||||
m_log.Warn("[EMAIL]: email_max_size out of range [256, 1000000], Changed to default 4096");
|
||||
@@ -369,6 +407,11 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
{
|
||||
return (sslPolicyErrors & m_SMTP_SslPolicyErrorsMask) == SslPolicyErrors.None;
|
||||
}
|
||||
public static bool imapValidateServerCertificate(object sender, X509Certificate certificate,
|
||||
X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
return (sslPolicyErrors & m_IMAP_SslPolicyErrorsMask) == SslPolicyErrors.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendMail function utilized by llEMail
|
||||
@@ -500,7 +543,7 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
client.Connect(SMTP_SERVER_HOSTNAME, SMTP_SERVER_PORT, MailKit.Security.SecureSocketOptions.StartTls);
|
||||
}
|
||||
else
|
||||
client.Connect(SMTP_SERVER_HOSTNAME, SMTP_SERVER_PORT);
|
||||
client.Connect(SMTP_SERVER_HOSTNAME, SMTP_SERVER_PORT, MailKit.Security.SecureSocketOptions.None);
|
||||
|
||||
if (!string.IsNullOrEmpty(SMTP_SERVER_LOGIN) && !string.IsNullOrEmpty(SMTP_SERVER_PASSWORD))
|
||||
client.Authenticate(SMTP_SERVER_LOGIN, SMTP_SERVER_PASSWORD);
|
||||
@@ -549,25 +592,60 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
if (!UUID.TryParse(address.Substring(0, indx), out UUID toID))
|
||||
return;
|
||||
|
||||
Email email = new Email();
|
||||
email.time = Util.UnixTimeSinceEpoch().ToString();
|
||||
email.subject = subject;
|
||||
email.sender = objectID.ToString() + "@" + m_InterObjectHostname;
|
||||
email.message = "Object-Name: " + LastObjectName +
|
||||
"\nRegion: " + LastObjectRegionName + "\nLocal-Position: " +
|
||||
LastObjectPosition + "\n\n" + body;
|
||||
|
||||
if (IsLocal(toID))
|
||||
{
|
||||
// object in this instance
|
||||
|
||||
Email email = new Email();
|
||||
email.time = Util.UnixTimeSinceEpoch().ToString();
|
||||
email.subject = subject;
|
||||
email.sender = objectID.ToString() + "@" + m_InterObjectHostname;
|
||||
email.message = "Object-Name: " + LastObjectName +
|
||||
"\nRegion: " + LastObjectRegionName + "\nLocal-Position: " +
|
||||
LastObjectPosition + "\n\n" + body;
|
||||
|
||||
InsertEmail(toID, email);
|
||||
}
|
||||
else
|
||||
{
|
||||
// object on another region
|
||||
|
||||
if (!m_enableEmailToExternalObjects)
|
||||
return;
|
||||
// object on another region
|
||||
// TODO FIX
|
||||
|
||||
// Insert mail into IMAP inbox
|
||||
using (var client = new ImapClient ()) {
|
||||
if (IMAP_SERVER_TLS)
|
||||
{
|
||||
client.ServerCertificateValidationCallback = imapValidateServerCertificate;
|
||||
client.Connect(IMAP_SERVER_HOSTNAME, IMAP_SERVER_PORT, MailKit.Security.SecureSocketOptions.StartTls);
|
||||
}
|
||||
else
|
||||
client.Connect(IMAP_SERVER_HOSTNAME, IMAP_SERVER_PORT, MailKit.Security.SecureSocketOptions.None);
|
||||
|
||||
if (client.IsConnected && !string.IsNullOrEmpty(IMAP_SERVER_LOGIN) && !string.IsNullOrEmpty(IMAP_SERVER_PASSWORD))
|
||||
{
|
||||
client.Authenticate(IMAP_SERVER_LOGIN, IMAP_SERVER_PASSWORD);
|
||||
client.Inbox.Open(FolderAccess.ReadWrite);
|
||||
|
||||
var builder = new BodyBuilder();
|
||||
builder.TextBody = "Object-Name: " + LastObjectName +
|
||||
"\nRegion: " + LastObjectRegionName +
|
||||
"\nLocal-Position: " + LastObjectPosition +
|
||||
"\n\n" + body;
|
||||
|
||||
MimeMessage email = new()
|
||||
{
|
||||
Date = DateTimeOffset.UtcNow,
|
||||
Subject = subject,
|
||||
Sender = new MailboxAddress(LastObjectName, objectID.ToString() + "@" + m_InterObjectHostname),
|
||||
Body = builder.ToMessageBody()
|
||||
};
|
||||
email.To.Add(MailboxAddress.Parse(address));
|
||||
client.Inbox.Append(email);
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -637,6 +715,50 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch mail from imap inbox here
|
||||
if (m_enableEmailToExternalObjects) {
|
||||
using (var client = new ImapClient ()) {
|
||||
if (IMAP_SERVER_TLS)
|
||||
{
|
||||
client.ServerCertificateValidationCallback = imapValidateServerCertificate;
|
||||
client.Connect(IMAP_SERVER_HOSTNAME, IMAP_SERVER_PORT, MailKit.Security.SecureSocketOptions.StartTls);
|
||||
}
|
||||
else
|
||||
client.Connect(IMAP_SERVER_HOSTNAME, IMAP_SERVER_PORT, MailKit.Security.SecureSocketOptions.None);
|
||||
|
||||
if (client.IsConnected && !string.IsNullOrEmpty(IMAP_SERVER_LOGIN) && !string.IsNullOrEmpty(IMAP_SERVER_PASSWORD))
|
||||
{
|
||||
client.Authenticate(IMAP_SERVER_LOGIN, IMAP_SERVER_PASSWORD);
|
||||
|
||||
client.Inbox.Open(FolderAccess.ReadWrite);
|
||||
|
||||
SearchQuery query = SearchQuery.ToContains(objectID.ToString()+"@"+m_InterObjectHostname).And(SearchQuery.NotDeleted);
|
||||
if (!string.IsNullOrEmpty(sender))
|
||||
query = query.And(SearchQuery.FromContains(sender));
|
||||
if (!string.IsNullOrEmpty(subject))
|
||||
query = query.And(SearchQuery.SubjectContains(subject));
|
||||
var uids = client.Inbox.Search(query);
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
var message = client.Inbox.GetMessage(uid);
|
||||
|
||||
Email email = new()
|
||||
{
|
||||
time = message.Date.ToString(),
|
||||
subject = message.Subject,
|
||||
sender = message.From.ToString(),
|
||||
message = message.GetTextBody(MimeKit.Text.TextFormat.Plain)
|
||||
};
|
||||
InsertEmail(objectID, email);
|
||||
client.Inbox.AddFlags(uid, MessageFlags.Deleted, true);
|
||||
}
|
||||
client.Inbox.Expunge();
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (m_queuesLock)
|
||||
{
|
||||
m_LastGetEmailCall[objectID] = now + m_QueueTimeout;
|
||||
|
||||
@@ -644,8 +644,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||
else
|
||||
len = -1;
|
||||
|
||||
using CancellationTokenSource cts = new(30000);
|
||||
Stream resStream = responseMessage.Content.ReadAsStream(cts.Token);
|
||||
Stream resStream = responseMessage.Content.ReadAsStream();
|
||||
|
||||
if(resStream is not null)
|
||||
{
|
||||
int maxBytes = (len < 0 || len > HttpBodyMaxLen) ? HttpBodyMaxLen : len;
|
||||
|
||||
@@ -951,7 +951,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
{
|
||||
if (parcel.GroupID.IsZero())
|
||||
{
|
||||
parcel.OwnerID = m_rootScene.RegionInfo.EstateSettings.EstateOwner;
|
||||
parcel.OwnerID = m_defaultUser;
|
||||
parcel.IsGroupOwned = false;
|
||||
}
|
||||
else
|
||||
@@ -963,7 +963,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
else
|
||||
{
|
||||
if (!ResolveUserUuid(scene, parcel.OwnerID))
|
||||
parcel.OwnerID = m_rootScene.RegionInfo.EstateSettings.EstateOwner;
|
||||
parcel.OwnerID = m_defaultUser;
|
||||
}
|
||||
|
||||
List<LandAccessEntry> accessList = new();
|
||||
|
||||
@@ -155,7 +155,6 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
if (options.TryGetValue("checkPermissions", out Object temp))
|
||||
FilterContent = (string)temp;
|
||||
|
||||
|
||||
// Find the regions to archive
|
||||
ArchiveScenesGroup scenesGroup = new ArchiveScenesGroup();
|
||||
if (MultiRegionFormat)
|
||||
@@ -189,7 +188,15 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
scenesGroup.ForEachScene(delegate(Scene scene)
|
||||
{
|
||||
string regionDir = MultiRegionFormat ? scenesGroup.GetRegionDir(scene.RegionInfo.RegionID) : "";
|
||||
ArchiveOneRegion(scene, regionDir, assetUuids, failedIDs, uncertainAssetsUUIDs);
|
||||
|
||||
UUID userId = scene.RegionInfo.EstateSettings.EstateOwner;
|
||||
if (options.TryGetValue("tenant", out Object temp))
|
||||
{
|
||||
if (!UUID.TryParse((string)temp, out userId) || userId.IsZero())
|
||||
userId = scene.RegionInfo.EstateSettings.EstateOwner;
|
||||
}
|
||||
|
||||
ArchiveOneRegion(scene, regionDir, assetUuids, failedIDs, uncertainAssetsUUIDs, userId);
|
||||
});
|
||||
|
||||
// Archive the assets
|
||||
@@ -227,7 +234,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
}
|
||||
|
||||
private void ArchiveOneRegion(Scene scene, string regionDir, Dictionary<UUID, sbyte> assetUuids,
|
||||
HashSet<UUID> failedIDs, HashSet<UUID> uncertainAssetsUUIDs)
|
||||
HashSet<UUID> failedIDs, HashSet<UUID> uncertainAssetsUUIDs, UUID userId)
|
||||
{
|
||||
m_log.InfoFormat("[ARCHIVER]: Writing region {0}", scene.Name);
|
||||
|
||||
@@ -248,7 +255,12 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
|
||||
if (!sceneObject.IsDeleted && !sceneObject.IsAttachment && !sceneObject.IsTemporary && !sceneObject.inTransit)
|
||||
{
|
||||
if (!CanUserArchiveObject(scene.RegionInfo.EstateSettings.EstateOwner, sceneObject, FilterContent, permissionsModule))
|
||||
if (userId != scene.RegionInfo.EstateSettings.EstateOwner && sceneObject.OwnerID != userId)
|
||||
{
|
||||
// A tenant= option was specified, skip object if not owned by tenant with userid
|
||||
++numObjectsSkippedPermissions;
|
||||
}
|
||||
else if (!CanUserArchiveObject(userId, sceneObject, FilterContent, permissionsModule))
|
||||
{
|
||||
// The user isn't allowed to copy/transfer this object, so it will not be included in the OAR.
|
||||
++numObjectsSkippedPermissions;
|
||||
|
||||
@@ -280,6 +280,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver
|
||||
ops.Add("publish", v => options["wipe-owners"] = v != null);
|
||||
ops.Add("perm=", delegate(string v) { options["checkPermissions"] = v; });
|
||||
ops.Add("all", delegate(string v) { options["all"] = v != null; });
|
||||
ops.Add("tenant=", delegate(string v) { options["tenant"] = v; });
|
||||
|
||||
List<string> mainParams = ops.Parse(cmdparams);
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ using OSDMap = OpenMetaverse.StructuredData.OSDMap;
|
||||
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
|
||||
|
||||
using Extension = Mono.Addins.ExtensionAttribute;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace OpenSim.Region.CoreModules.World.Land
|
||||
{
|
||||
// used for caching
|
||||
@@ -1694,10 +1696,28 @@ namespace OpenSim.Region.CoreModules.World.Land
|
||||
|
||||
if (m_scene.Permissions.CanAbandonParcel(remote_client.AgentId, land))
|
||||
{
|
||||
string LastOwnerName;
|
||||
if (land.LandData.IsGroupOwned)
|
||||
{
|
||||
if (m_groupManager is not null)
|
||||
{
|
||||
GroupRecord groupRecord = m_groupManager.GetGroupRecord(land.LandData.OwnerID);
|
||||
LastOwnerName = "group " + groupRecord.GroupName;
|
||||
}
|
||||
else
|
||||
LastOwnerName = "a group";
|
||||
}
|
||||
else
|
||||
LastOwnerName = m_userManager.GetUserName(land.LandData.OwnerID);
|
||||
|
||||
string date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
land.LandData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner;
|
||||
land.LandData.GroupID = UUID.Zero;
|
||||
land.LandData.IsGroupOwned = false;
|
||||
land.LandData.Flags &= ~(uint) (ParcelFlags.ForSale | ParcelFlags.ForSaleObjects | ParcelFlags.SellParcelObjects | ParcelFlags.ShowDirectory);
|
||||
land.LandData.Flags &= ~(uint) (ParcelFlags.ForSale | ParcelFlags.ForSaleObjects | ParcelFlags.SellParcelObjects | ParcelFlags.ShowDirectory | ParcelFlags.CreateObjects);
|
||||
land.LandData.Name = "Abandoned Land";
|
||||
land.LandData.OtherCleanTime = 5;
|
||||
land.LandData.Description = "Land abandoned by " + LastOwnerName + " on " + date;
|
||||
|
||||
UpdateLandObject(land.LandData.LocalID, land.LandData);
|
||||
m_scene.ForEachClient(SendParcelOverlay);
|
||||
@@ -1725,7 +1745,9 @@ namespace OpenSim.Region.CoreModules.World.Land
|
||||
land.LandData.SeeAVs = true;
|
||||
land.LandData.AnyAVSounds = true;
|
||||
land.LandData.GroupAVSounds = true;
|
||||
land.LandData.Flags &= ~(uint) (ParcelFlags.ForSale | ParcelFlags.ForSaleObjects | ParcelFlags.SellParcelObjects | ParcelFlags.ShowDirectory);
|
||||
land.LandData.Flags &= ~(uint) (ParcelFlags.ForSale | ParcelFlags.ForSaleObjects | ParcelFlags.SellParcelObjects | ParcelFlags.ShowDirectory | ParcelFlags.CreateObjects);
|
||||
land.LandData.Name = "Reclaimed Land";
|
||||
land.LandData.OtherCleanTime = 0;
|
||||
UpdateLandObject(land.LandData.LocalID, land.LandData);
|
||||
m_scene.ForEachClient(SendParcelOverlay);
|
||||
land.SendLandUpdateToAvatars();
|
||||
|
||||
@@ -858,12 +858,12 @@ namespace OpenSim.Region.CoreModules.World.Land
|
||||
|
||||
private bool IsRestrictedFromLand_inner(UUID avatar)
|
||||
{
|
||||
if ((LandData.Flags & (uint) ParcelFlags.UseAccessList) == 0)
|
||||
if ((LandData.Flags & (uint)ParcelFlags.UseAccessList) == 0)
|
||||
{
|
||||
bool adults = m_estateSettings.DoDenyMinors &&
|
||||
(m_estateSettings.DenyMinors || ((LandData.Flags & (uint)ParcelFlags.DenyAgeUnverified) != 0));
|
||||
bool anonymous = m_estateSettings.DoDenyAnonymous &&
|
||||
(m_estateSettings.DenyAnonymous || ((LandData.Flags & (uint)ParcelFlags.DenyAnonymous) != 0));
|
||||
bool adults = m_estateSettings.DoDenyMinors ||
|
||||
(m_estateSettings.DenyMinors || ((LandData.Flags & (uint)ParcelFlags.DenyAgeUnverified) != 0));
|
||||
bool anonymous = m_estateSettings.DoDenyAnonymous ||
|
||||
(m_estateSettings.DenyAnonymous || ((LandData.Flags & (uint)ParcelFlags.DenyAnonymous) != 0));
|
||||
if(adults || anonymous)
|
||||
{
|
||||
int userflags;
|
||||
|
||||
@@ -1003,6 +1003,12 @@ namespace OpenSim.Region.CoreModules.World.LightShare
|
||||
return (float)Utils.Clamp(dayfrac, 0, 1);
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public float GetDayFractionTime(Vector3 pos)
|
||||
{
|
||||
return GetDayFractionTime(GetEnvironment(pos));
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public float GetRegionDayFractionTime()
|
||||
{
|
||||
|
||||
@@ -89,11 +89,9 @@ namespace OpenSim.Region.CoreModules.World.Permissions
|
||||
private bool m_propagatePermissions = false;
|
||||
private bool m_debugPermissions = false;
|
||||
private bool m_allowGridAdmins = false;
|
||||
private bool m_RegionOwnerIsAdmin = false;
|
||||
private bool m_RegionManagerIsAdmin = false;
|
||||
private bool m_forceGridAdminsOnly;
|
||||
private bool m_forceAdminModeAlwaysOn;
|
||||
private bool m_allowAdminActionsWithoutGodMode;
|
||||
private bool m_takeCopyRestricted = false;
|
||||
|
||||
/// <value>
|
||||
/// The set of users that are allowed to create scripts. This is only active if permissions are not being
|
||||
@@ -160,19 +158,10 @@ namespace OpenSim.Region.CoreModules.World.Permissions
|
||||
|
||||
string[] sections = new string[] { "Startup", "Permissions" };
|
||||
|
||||
m_allowGridAdmins = Util.GetConfigVarFromSections<bool>(config, "allow_grid_gods", sections, false);
|
||||
m_allowGridAdmins = Util.GetConfigVarFromSections<bool>(config, "allow_grid_gods", sections, true);
|
||||
m_bypassPermissions = !Util.GetConfigVarFromSections<bool>(config, "serverside_object_permissions", sections, true);
|
||||
m_propagatePermissions = Util.GetConfigVarFromSections<bool>(config, "propagate_permissions", sections, true);
|
||||
|
||||
m_forceGridAdminsOnly = Util.GetConfigVarFromSections<bool>(config, "force_grid_gods_only", sections, false);
|
||||
if(!m_forceGridAdminsOnly)
|
||||
{
|
||||
m_RegionOwnerIsAdmin = Util.GetConfigVarFromSections<bool>(config, "region_owner_is_god",sections, true);
|
||||
m_RegionManagerIsAdmin = Util.GetConfigVarFromSections<bool>(config, "region_manager_is_god",sections, false);
|
||||
}
|
||||
else
|
||||
m_allowGridAdmins = true;
|
||||
|
||||
m_forceAdminModeAlwaysOn = Util.GetConfigVarFromSections<bool>(config, "automatic_gods", sections, false);
|
||||
m_allowAdminActionsWithoutGodMode = Util.GetConfigVarFromSections<bool>(config, "implicit_gods", sections, false);
|
||||
if(m_allowAdminActionsWithoutGodMode)
|
||||
@@ -188,6 +177,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions
|
||||
else
|
||||
m_log.Debug("[PERMISSIONS]: Enabling all region service permission checks");
|
||||
|
||||
m_takeCopyRestricted = Util.GetConfigVarFromSections<bool>(config, "take_copy_restricted", sections, false);
|
||||
|
||||
string grant = Util.GetConfigVarFromSections<string>(config, "GrantLSL",
|
||||
new string[] { "Startup", "Permissions" }, string.Empty);
|
||||
if (grant.Length > 0)
|
||||
@@ -625,12 +616,6 @@ namespace OpenSim.Region.CoreModules.World.Permissions
|
||||
if (user.IsZero())
|
||||
return false;
|
||||
|
||||
if (m_RegionOwnerIsAdmin && m_scene.RegionInfo.EstateSettings.EstateOwner.Equals(user))
|
||||
return true;
|
||||
|
||||
if (m_RegionManagerIsAdmin && IsEstateManager(user))
|
||||
return true;
|
||||
|
||||
if (IsGridAdministrator(user))
|
||||
return true;
|
||||
|
||||
@@ -2022,6 +2007,12 @@ namespace OpenSim.Region.CoreModules.World.Permissions
|
||||
|
||||
if(sog.OwnerID.NotEqual(sp.UUID) && (perms & (uint)PermissionMask.Transfer) == 0)
|
||||
return false;
|
||||
|
||||
if (sog.OwnerID.NotEqual(sp.UUID) && !IsFriendWithPerms(sp.UUID, sog.OwnerID) && !sp.IsGod)
|
||||
{
|
||||
if (m_takeCopyRestricted)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2380,7 +2371,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions
|
||||
|
||||
bool spNotOwner = sp.UUID.NotEqual(destsog.OwnerID);
|
||||
|
||||
// scripts can't be droped
|
||||
// scripts can't be dropped
|
||||
if(spNotOwner && item.InvType == (int)InventoryType.LSL)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -84,6 +84,8 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap
|
||||
private float m_renderMinHeight = -100f;
|
||||
private float m_renderMaxHeight = 4096f;
|
||||
|
||||
private string m_tilesPath = ".";
|
||||
|
||||
private bool m_Enabled = false;
|
||||
|
||||
#region Region Module interface
|
||||
@@ -132,6 +134,10 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap
|
||||
m_renderMinHeight = -100f;
|
||||
else if (m_renderMinHeight > m_renderMaxHeight - 10f)
|
||||
m_renderMinHeight = m_renderMaxHeight - 10f;
|
||||
|
||||
m_tilesPath = Util.GetConfigVarFromSections<string>(source, "GenTilesDirectory", configSections, m_tilesPath);
|
||||
if (!Directory.Exists(m_tilesPath))
|
||||
Directory.CreateDirectory(m_tilesPath);
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
@@ -208,7 +214,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap
|
||||
|
||||
Bitmap tile = GenImage();
|
||||
// image may be reloaded elsewhere, so no compression format
|
||||
string filename = "MAP-" + m_scene.RegionInfo.RegionID.ToString() + ".png";
|
||||
string filename = System.IO.Path.Combine(m_tilesPath, "MAP-" + m_scene.RegionInfo.RegionID.ToString() + ".png");
|
||||
tile.Save(filename,ImageFormat.Png);
|
||||
m_primMesher = null;
|
||||
return tile;
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||
private string m_regionName;
|
||||
|
||||
private byte[] myMapImageJPEG;
|
||||
private string m_tilesPath = ".";
|
||||
protected volatile bool m_Enabled = false;
|
||||
|
||||
private ManualResetEvent m_mapBlockRequestEvent = new ManualResetEvent(false);
|
||||
@@ -158,6 +159,10 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||
Util.GetConfigVarFromSections<bool>(config, "ExportMapAddRegionName", configSections, m_exportPrintRegionName);
|
||||
m_localV1MapAssets =
|
||||
Util.GetConfigVarFromSections<bool>(config, "LocalV1MapAssets", configSections, m_localV1MapAssets);
|
||||
m_tilesPath =
|
||||
Util.GetConfigVarFromSections<string>(config, "GenTilesDirectory", configSections, m_tilesPath);
|
||||
if (!Directory.Exists(m_tilesPath))
|
||||
Directory.CreateDirectory(m_tilesPath);
|
||||
}
|
||||
|
||||
public virtual void AddRegion(Scene scene)
|
||||
@@ -916,9 +921,6 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||
}
|
||||
|
||||
|
||||
private const double SPAMBLOCKTIMEms = 30000;
|
||||
private Dictionary<UUID,double> spamBlocked = new Dictionary<UUID,double>();
|
||||
|
||||
/// <summary>
|
||||
/// Requests map blocks in area of minX, maxX, minY, MaxY in world cordinates
|
||||
/// </summary>
|
||||
@@ -928,50 +930,10 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||
/// <param name="maxY"></param>
|
||||
public void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag)
|
||||
{
|
||||
// anti spam because of FireStorm 4.7.7 absurd request repeat rates
|
||||
// possible others
|
||||
|
||||
double now = Util.GetTimeStampMS();
|
||||
UUID agentID = remoteClient.AgentId;
|
||||
|
||||
lock (m_mapBlockRequestEvent)
|
||||
{
|
||||
if(spamBlocked.ContainsKey(agentID))
|
||||
{
|
||||
if(spamBlocked[agentID] < now &&
|
||||
(!m_mapBlockRequests.ContainsKey(agentID) ||
|
||||
m_mapBlockRequests[agentID].Count == 0 ))
|
||||
{
|
||||
spamBlocked.Remove(agentID);
|
||||
m_log.DebugFormat("[WoldMapModule] RequestMapBlocks release spammer {0}", agentID);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ugly slow expire spammers
|
||||
if(spamBlocked.Count > 0)
|
||||
{
|
||||
UUID k = UUID.Zero;
|
||||
bool expireone = false;
|
||||
foreach(UUID k2 in spamBlocked.Keys)
|
||||
{
|
||||
if(spamBlocked[k2] < now &&
|
||||
(!m_mapBlockRequests.ContainsKey(k2) ||
|
||||
m_mapBlockRequests[k2].Count == 0 ))
|
||||
{
|
||||
m_log.DebugFormat("[WoldMapModule] RequestMapBlocks release spammer {0}", k2);
|
||||
k = k2;
|
||||
expireone = true;
|
||||
}
|
||||
break; // doing one at a time
|
||||
}
|
||||
if(expireone)
|
||||
spamBlocked.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
// m_log.DebugFormat("[WoldMapModule] RequestMapBlocks {0}={1}={2}={3} {4}", minX, minY, maxX, maxY, flag);
|
||||
|
||||
MapBlockRequestData req = new MapBlockRequestData()
|
||||
@@ -990,13 +952,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||
agentq = new Queue<MapBlockRequestData>();
|
||||
m_mapBlockRequests[agentID] = agentq;
|
||||
}
|
||||
if(agentq.Count < 150 )
|
||||
agentq.Enqueue(req);
|
||||
else
|
||||
{
|
||||
spamBlocked[agentID] = now + SPAMBLOCKTIMEms;
|
||||
m_log.DebugFormat("[WoldMapModule] RequestMapBlocks blocking spammer {0} for {1} s",agentID, SPAMBLOCKTIMEms/1000.0);
|
||||
}
|
||||
agentq.Enqueue(req);
|
||||
m_mapBlockRequestEvent.Set();
|
||||
}
|
||||
}
|
||||
@@ -1338,7 +1294,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||
startY--;
|
||||
|
||||
bool doneLocal = false;
|
||||
string filename = "MAP-" + m_scene.RegionInfo.RegionID.ToString() + ".png";
|
||||
string filename = Path.Combine(m_tilesPath, "MAP-" + m_scene.RegionInfo.RegionID.ToString() + ".png");
|
||||
try
|
||||
{
|
||||
using(Image localMap = Bitmap.FromFile(filename))
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace OpenSim.Region.Framework.Interfaces
|
||||
|
||||
ViewerEnvironment GetRegionEnvironment();
|
||||
|
||||
float GetDayFractionTime(Vector3 pos);
|
||||
float GetRegionDayFractionTime();
|
||||
int GetRegionDayLength();
|
||||
int GetRegionDayOffset();
|
||||
|
||||
@@ -25,9 +25,8 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using static OpenMetaverse.Primitive.RenderMaterials;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenSim.Region.Framework.Interfaces
|
||||
{
|
||||
@@ -37,7 +36,5 @@ namespace OpenSim.Region.Framework.Interfaces
|
||||
FaceMaterial GetMaterialCopy(UUID ID);
|
||||
UUID AddNewMaterial(FaceMaterial fm);
|
||||
void RemoveMaterial(UUID id);
|
||||
bool CleanMaterialOverrides(ref RenderMaterialOverrideEntry[] overrides, int side, bool removeTransforms = false);
|
||||
bool RemoveMaterialEntry(ref RenderMaterialEntry[] entries, int side);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
return;
|
||||
}
|
||||
|
||||
m_log.DebugFormat("Setting override for {0} to {1}", state, animID);
|
||||
//m_log.DebugFormat("Setting override for {0} to {1}", state, animID);
|
||||
|
||||
lock (MAOLock)
|
||||
m_overrides[state] = animID;
|
||||
|
||||
@@ -87,7 +87,10 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
if (permissionToDelete)
|
||||
{
|
||||
foreach (SceneObjectGroup g in objectGroups)
|
||||
{
|
||||
g.LastOwnerID = g.OwnerID;
|
||||
g.DeleteGroupFromScene(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(Monitor.TryEnter(m_threadLock))
|
||||
|
||||
@@ -46,9 +46,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
ScenePresence m_scenePresence;
|
||||
Scene m_scene;
|
||||
protected bool m_allowGridGods;
|
||||
protected bool m_forceGridGodsOnly;
|
||||
protected bool m_regionOwnerIsGod;
|
||||
protected bool m_regionManagerIsGod;
|
||||
protected bool m_forceGodModeAlwaysOn;
|
||||
protected bool m_allowGodActionsWithoutGodMode;
|
||||
|
||||
@@ -76,29 +73,8 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
// gods are god everywhere.
|
||||
m_allowGridGods =
|
||||
Util.GetConfigVarFromSections<bool>(config,
|
||||
"allow_grid_gods", sections, false);
|
||||
"allow_grid_gods", sections, true);
|
||||
|
||||
// If grid gods are active, dont allow any other gods
|
||||
m_forceGridGodsOnly =
|
||||
Util.GetConfigVarFromSections<bool>(config,
|
||||
"force_grid_gods_only", sections, false);
|
||||
|
||||
if(!m_forceGridGodsOnly)
|
||||
{
|
||||
// The owner of a region is a god in his region only.
|
||||
m_regionOwnerIsGod =
|
||||
Util.GetConfigVarFromSections<bool>(config,
|
||||
"region_owner_is_god", sections, true);
|
||||
|
||||
// Region managers are gods in the regions they manage.
|
||||
m_regionManagerIsGod =
|
||||
Util.GetConfigVarFromSections<bool>(config,
|
||||
"region_manager_is_god", sections, false);
|
||||
|
||||
}
|
||||
else
|
||||
m_allowGridGods = true; // reduce potencial user mistakes
|
||||
|
||||
// God mode should be turned on in the viewer whenever
|
||||
// the user has god rights somewhere. They may choose
|
||||
// to turn it off again, though.
|
||||
@@ -140,18 +116,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
if (m_allowGridGods && m_userLevel >= 200)
|
||||
level = m_userLevel;
|
||||
|
||||
if(m_forceGridGodsOnly || level >= (int)ImplicitGodLevels.RegionOwner)
|
||||
return level;
|
||||
|
||||
if (m_regionOwnerIsGod && m_scene.RegionInfo.EstateSettings.IsEstateOwner(m_scenePresence.UUID))
|
||||
level = (int)ImplicitGodLevels.RegionOwner;
|
||||
|
||||
if(level >= (int)ImplicitGodLevels.EstateManager)
|
||||
return level;
|
||||
|
||||
if (m_regionManagerIsGod && m_scene.Permissions.IsEstateManager(m_scenePresence.UUID))
|
||||
level = (int)ImplicitGodLevels.EstateManager;
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ using System.IO;
|
||||
using System.Runtime;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
using Nini.Config;
|
||||
@@ -267,8 +268,9 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
get { return m_minRegionViewDistance; }
|
||||
}
|
||||
|
||||
private readonly List<string> m_AllowedViewers = new();
|
||||
private readonly List<string> m_BannedViewers = new();
|
||||
private static string m_AllowedViewers = string.Empty;
|
||||
private static string m_BannedViewers = string.Empty;
|
||||
private static string m_ViewerDeniedMsg = "Access denied, your viewer is banned";
|
||||
|
||||
// TODO: need to figure out how allow client agents but deny
|
||||
// root agents when ACL denies access to root agent
|
||||
@@ -1039,29 +1041,12 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
|
||||
string[] possibleAccessControlConfigSections = new string[] { "Startup", "AccessControl"};
|
||||
|
||||
string grant = Util.GetConfigVarFromSections<string>(
|
||||
config, "AllowedClients", possibleAccessControlConfigSections, string.Empty);
|
||||
|
||||
if (grant.Length > 0)
|
||||
{
|
||||
foreach (string viewer in grant.Split(','))
|
||||
{
|
||||
m_AllowedViewers.Add(viewer.Trim().ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
grant = Util.GetConfigVarFromSections<string>(config, "DeniedClients", possibleAccessControlConfigSections, string.Empty);
|
||||
// Deal with the mess of someone having used a different word at some point
|
||||
if (string.IsNullOrWhiteSpace(grant))
|
||||
grant = Util.GetConfigVarFromSections<string>(config, "BannedClients", possibleAccessControlConfigSections, string.Empty);
|
||||
|
||||
if (grant.Length > 0)
|
||||
{
|
||||
foreach (string viewer in grant.Split(','))
|
||||
{
|
||||
m_BannedViewers.Add(viewer.Trim().ToLower());
|
||||
}
|
||||
}
|
||||
m_AllowedViewers = Util.GetConfigVarFromSections<string>(
|
||||
config, "AllowedClients", possibleAccessControlConfigSections, string.Empty);
|
||||
m_BannedViewers = Util.GetConfigVarFromSections<string>(
|
||||
config, "DeniedClients", possibleAccessControlConfigSections, string.Empty);
|
||||
m_ViewerDeniedMsg = Util.GetConfigVarFromSections<string>(
|
||||
config, "ViewerDeniedMsg", possibleAccessControlConfigSections, m_ViewerDeniedMsg);
|
||||
|
||||
FrameTime = startupConfig.GetFloat( "FrameTime", FrameTime);
|
||||
FrameTimeWarnPercent = startupConfig.GetInt( "FrameTimeWarnPercent", FrameTimeWarnPercent);
|
||||
@@ -1293,12 +1278,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
if (!string.IsNullOrEmpty(SceneGridInfo.EconomyURL))
|
||||
fm.AddOpenSimExtraFeature("currency-base-uri", SceneGridInfo.EconomyURL);
|
||||
}
|
||||
|
||||
if (SceneGridInfo.StunServers is not null)
|
||||
{
|
||||
string stuns = string.Join(',', SceneGridInfo.StunServers);
|
||||
fm.AddFeature("stun-servers", stuns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2973,7 +2952,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IncomingAttechments(ScenePresence sp, List<SceneObjectGroup> attachments)
|
||||
public bool IncomingAttachments(ScenePresence sp, List<SceneObjectGroup> attachments)
|
||||
{
|
||||
//m_log.DebugFormat(" >>> IncomingCreateObject(sog) <<< {0} deleted? {1} isAttach? {2}", ((SceneObjectGroup)sog).AbsolutePosition,
|
||||
// ((SceneObjectGroup)sog).IsDeleted, ((SceneObjectGroup)sog).RootPart.IsAttachment);
|
||||
@@ -3983,20 +3962,14 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
//Check if the viewer is banned or in the viewer access list
|
||||
//We check if the substring is listed for higher flexebility
|
||||
bool ViewerDenied = true;
|
||||
string cV = null;
|
||||
|
||||
//Check if the specific viewer is listed in the allowed viewer list
|
||||
if (m_AllowedViewers.Count > 0)
|
||||
if (!String.IsNullOrWhiteSpace(m_AllowedViewers))
|
||||
{
|
||||
cV = curViewer.Trim().ToLower();
|
||||
foreach (string viewer in m_AllowedViewers)
|
||||
{
|
||||
if (viewer == cV[..Math.Min(viewer.Length, curViewer.Length)])
|
||||
{
|
||||
ViewerDenied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Regex arx = new Regex(m_AllowedViewers);
|
||||
Match am = arx.Match(curViewer);
|
||||
|
||||
if (am.Success) ViewerDenied = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4004,17 +3977,12 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
}
|
||||
|
||||
//Check if the viewer is in the banned list
|
||||
if (m_BannedViewers.Count > 0)
|
||||
if (!String.IsNullOrWhiteSpace(m_BannedViewers))
|
||||
{
|
||||
cV ??= curViewer.Trim().ToLower();
|
||||
foreach (string viewer in m_BannedViewers)
|
||||
{
|
||||
if (viewer == cV[..Math.Min(viewer.Length, curViewer.Length)])
|
||||
{
|
||||
ViewerDenied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Regex drx = new Regex(m_BannedViewers);
|
||||
Match dm = drx.Match(curViewer);
|
||||
|
||||
if (dm.Success) ViewerDenied = true;
|
||||
}
|
||||
|
||||
if (ViewerDenied)
|
||||
@@ -4022,7 +3990,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
m_log.DebugFormat(
|
||||
"[SCENE]: Access denied for {0} {1} using {2}",
|
||||
acd.firstname, acd.lastname, curViewer);
|
||||
reason = "Access denied, your viewer is banned";
|
||||
reason = m_ViewerDeniedMsg;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -712,10 +712,8 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
// now that position is changed tell it to scripts
|
||||
if (triggerScriptEvent && (ScriptEvents & scriptEvents.changed) != 0)
|
||||
{
|
||||
foreach (SceneObjectPart part in parts)
|
||||
{
|
||||
part.TriggerScriptChangedEvent(Changed.POSITION);
|
||||
}
|
||||
// Only send this to the rootprim instead of all childprims
|
||||
m_rootPart.TriggerScriptChangedEvent(Changed.POSITION);
|
||||
}
|
||||
|
||||
Scene?.EventManager.TriggerParcelPrimCountTainted();
|
||||
|
||||
@@ -2073,7 +2073,15 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
return;
|
||||
|
||||
if (localGlobalTF)
|
||||
impulse *= GetWorldRotation();
|
||||
{
|
||||
if (ParentGroup.IsAttachment)
|
||||
{
|
||||
ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar);
|
||||
if (sp != null)
|
||||
impulse *= sp.GetWorldRotation();
|
||||
} else
|
||||
impulse *= GetWorldRotation();
|
||||
}
|
||||
|
||||
ParentGroup.applyImpulse(impulse);
|
||||
}
|
||||
@@ -2084,9 +2092,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
if (ParentGroup == null || ParentGroup.IsDeleted || ParentGroup.inTransit)
|
||||
return;
|
||||
|
||||
if (ParentGroup.IsAttachment)
|
||||
return; // don't work on attachments (for now ??)
|
||||
|
||||
SceneObjectPart root = ParentGroup.RootPart;
|
||||
|
||||
if (root.VehicleType != (int)Vehicle.TYPE_NONE) // don't mess with vehicles
|
||||
@@ -2098,7 +2103,16 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
return;
|
||||
|
||||
if (localGlobalTF)
|
||||
pVel *= GetWorldRotation();
|
||||
{
|
||||
if (ParentGroup.IsAttachment)
|
||||
{
|
||||
ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar);
|
||||
if (sp != null)
|
||||
pVel *= sp.GetWorldRotation();
|
||||
}
|
||||
else
|
||||
pVel *= GetWorldRotation();
|
||||
}
|
||||
|
||||
ParentGroup.Velocity = pVel;
|
||||
}
|
||||
@@ -2109,9 +2123,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
if (ParentGroup == null || ParentGroup.IsDeleted || ParentGroup.inTransit)
|
||||
return;
|
||||
|
||||
if (ParentGroup.IsAttachment)
|
||||
return; // don't work on attachments (for now ??)
|
||||
|
||||
SceneObjectPart root = ParentGroup.RootPart;
|
||||
|
||||
if (root.VehicleType != (int)Vehicle.TYPE_NONE) // don't mess with vehicles
|
||||
@@ -2123,7 +2134,16 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
return;
|
||||
|
||||
if (localGlobalTF)
|
||||
pAngVel *= GetWorldRotation();
|
||||
{
|
||||
if (ParentGroup.IsAttachment)
|
||||
{
|
||||
ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar);
|
||||
if (sp != null)
|
||||
pAngVel *= sp.GetWorldRotation();
|
||||
}
|
||||
else
|
||||
pAngVel *= GetWorldRotation();
|
||||
}
|
||||
|
||||
root.AngularVelocity = pAngVel;
|
||||
}
|
||||
@@ -2142,7 +2162,16 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
return;
|
||||
|
||||
if (localGlobalTF)
|
||||
ParentGroup.ApplyAngularImpulse(impulse * GetWorldRotation());
|
||||
{
|
||||
if (ParentGroup.IsAttachment)
|
||||
{
|
||||
ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar);
|
||||
if (sp != null)
|
||||
ParentGroup.ApplyAngularImpulse(impulse * sp.GetWorldRotation());
|
||||
}
|
||||
else
|
||||
ParentGroup.ApplyAngularImpulse(impulse * GetWorldRotation());
|
||||
}
|
||||
else
|
||||
ParentGroup.ApplyAngularImpulse(impulse);
|
||||
}
|
||||
@@ -2161,7 +2190,16 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
Vector3 torque = torquei;
|
||||
|
||||
if (localGlobalTF)
|
||||
torque *= GetWorldRotation();
|
||||
{
|
||||
if (ParentGroup.IsAttachment)
|
||||
{
|
||||
ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar);
|
||||
if (sp != null)
|
||||
torque *= sp.GetWorldRotation();
|
||||
}
|
||||
else
|
||||
torque *= GetWorldRotation();
|
||||
}
|
||||
|
||||
Torque = torque;
|
||||
}
|
||||
|
||||
@@ -655,11 +655,17 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
|
||||
public string Grouptitle
|
||||
{
|
||||
get { return m_groupTitle; }
|
||||
get { return UseFakeGroupTitle ? "(Loading)" : m_groupTitle; }
|
||||
set { m_groupTitle = value; }
|
||||
}
|
||||
private string m_groupTitle;
|
||||
|
||||
/// <summary>
|
||||
/// When this is 'true', return a dummy group title instead of the real group title. This is
|
||||
/// used as part of a hack to force viewers to update the displayed avatar name.
|
||||
/// </summary>
|
||||
public bool UseFakeGroupTitle { get; set; }
|
||||
|
||||
// Agent's Draw distance.
|
||||
private float m_drawDistance = 255f;
|
||||
public float DrawDistance
|
||||
@@ -1075,7 +1081,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
|
||||
// velocities
|
||||
private const float AgentControlStopSlowVel = 0.2f * 4.096f;
|
||||
public const float AgentControlMidVel = 0.6f * 4.096f;
|
||||
public const float AgentControlMidVel = 1.0f * 4.096f;
|
||||
public const float AgentControlNormalVel = 1.0f * 4.096f;
|
||||
|
||||
// old normal speed was tuned to match sl normal plus Fast modifiers
|
||||
@@ -1590,9 +1596,12 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
// Resume scripts
|
||||
foreach (SceneObjectGroup sog in attachments)
|
||||
{
|
||||
sog.RootPart.ParentGroup.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, GetStateSource());
|
||||
sog.ResumeScripts();
|
||||
sog.ScheduleGroupForFullUpdate();
|
||||
if (sog.ContainsScripts())
|
||||
{
|
||||
sog.RootPart.ParentGroup.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, GetStateSource());
|
||||
sog.ResumeScripts();
|
||||
sog.ScheduleGroupForFullUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1611,7 +1620,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
/// Group Title. So the following trick makes viewers update the avatar's name by briefly changing
|
||||
/// the group title (to "(Loading)"), and then restoring it.
|
||||
/// </remarks>
|
||||
/*
|
||||
public void ForceViewersUpdateName()
|
||||
{
|
||||
m_log.DebugFormat("[SCENE PRESENCE]: Forcing viewers to update the avatar name for " + Name);
|
||||
@@ -1628,10 +1636,10 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
Thread.Sleep(5000);
|
||||
|
||||
UseFakeGroupTitle = false;
|
||||
SendAvatarDataToAllClients(false);
|
||||
SendAvatarDataToAllAgents();
|
||||
}, null, "Scenepresence.ForceViewersUpdateName");
|
||||
}
|
||||
*/
|
||||
|
||||
public int GetStateSource()
|
||||
{
|
||||
return m_teleportFlags == TeleportFlags.Default ? 2 : 5; // StateSource.PrimCrossing : StateSource.Teleporting
|
||||
@@ -2239,6 +2247,16 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
m_log.DebugFormat("[CompleteMovement]: Missing COF for {0} is {1}", client.AgentId, COF);
|
||||
*/
|
||||
}
|
||||
|
||||
if ((m_teleportFlags & TeleportFlags.ViaHGLogin) != 0)
|
||||
{
|
||||
// The avatar is arriving from another grid. This means that we may have changed the
|
||||
// avatar's name to or from the special Hypergrid format ("First.Last @grid.example.com").
|
||||
// Unfortunately, due to a viewer bug, viewers don't always show the new name.
|
||||
// But we have a trick that can force them to update the name anyway.
|
||||
ForceViewersUpdateName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (m_teleportFlags > 0)
|
||||
@@ -2355,6 +2373,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
// attachments
|
||||
if (IsNPC || IsRealLogin(m_teleportFlags))
|
||||
{
|
||||
GotAttachmentsData = true;
|
||||
if (Scene.AttachmentsModule != null)
|
||||
{
|
||||
if(IsNPC)
|
||||
@@ -3070,14 +3089,22 @@ namespace OpenSim.Region.Framework.Scenes
|
||||
if(IsNPC)
|
||||
{
|
||||
if (!Flying)
|
||||
shouldfly = !noFly && (pos.Z > terrainHeight + Appearance.AvatarHeight);
|
||||
shouldfly = !noFly && (pos.Z > AbsolutePosition.Z + (Appearance.AvatarHeight*2));
|
||||
LandAtTarget = landAtTarget && shouldfly;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have no control on viewer fly state
|
||||
shouldfly = Flying || (pos.Z > terrainHeight + Appearance.AvatarHeight);
|
||||
LandAtTarget = false;
|
||||
if (pos.Z < AbsolutePosition.Z)
|
||||
{
|
||||
shouldfly = false;
|
||||
LandAtTarget = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have no control on viewer fly state
|
||||
shouldfly = Flying || (pos.Z > AbsolutePosition.Z + (Appearance.AvatarHeight*2));
|
||||
LandAtTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
// m_log.DebugFormat("[SCENE PRESENCE]: Local vector to target is {0},[1}", localVectorToTarget3D.X,localVectorToTarget3D.Y);
|
||||
|
||||
@@ -824,6 +824,21 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
|
||||
UUID agentID = GetRequestingAgentID(remoteClient);
|
||||
m_groupData.SetAgentActiveGroup(agentID, agentID, groupID);
|
||||
|
||||
ScenePresence sp = ((Scene)(remoteClient.Scene)).GetScenePresence(remoteClient.AgentId);
|
||||
List<SceneObjectGroup> attachments = sp.GetAttachments();
|
||||
|
||||
foreach(SceneObjectGroup so in attachments)
|
||||
{
|
||||
//m_log.DebugFormat("[GROUPS MODULE]: Setting new group and checking scripts to run in attachment {0} for {1}", so.Name, so.OwnerID);
|
||||
so.SetGroup(groupID, remoteClient);
|
||||
if (so.ContainsScripts() && so.RunningScriptCount() == 0)
|
||||
{
|
||||
so.RootPart.ParentGroup.CreateScriptInstances(
|
||||
0, false, sp.Scene.DefaultScriptEngine, sp.GetStateSource());
|
||||
so.ResumeScripts();
|
||||
}
|
||||
}
|
||||
|
||||
// llClientView does this
|
||||
SendAgentGroupDataUpdate(remoteClient, true);
|
||||
}
|
||||
@@ -1080,11 +1095,16 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
|
||||
{
|
||||
if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
|
||||
m_groupData.SetAgentActiveGroupRole(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID, titleRoleID);
|
||||
UUID agentID = remoteClient.AgentId;
|
||||
m_groupData.SetAgentActiveGroupRole(agentID, agentID, groupID, titleRoleID);
|
||||
|
||||
// TODO: Not sure what all is needed here, but if the active group role change is for the group
|
||||
// the client currently has set active, then we need to do a scene presence update too
|
||||
// if (m_groupData.GetAgentActiveMembership(GetRequestingAgentID(remoteClient)).GroupID == GroupID)
|
||||
// If the active group role change is for the group
|
||||
// the client currently has set active, then we need to
|
||||
// set the active group again for the tag to update
|
||||
if (m_groupData.GetAgentActiveMembership(agentID, agentID).GroupID == groupID)
|
||||
{
|
||||
m_groupData.SetAgentActiveGroup(agentID, agentID, groupID);
|
||||
}
|
||||
|
||||
SendDataUpdate(remoteClient, true);
|
||||
}
|
||||
|
||||
@@ -1025,8 +1025,7 @@ namespace OpenSim.Region.OptionalModules.Materials
|
||||
|
||||
public static readonly byte[] XMLkeyMaterialSucess = osUTF8.GetASCIIBytes("<llsd><map><key>success</key><integer>1</integer></map></llsd>\r\n");
|
||||
public static readonly byte[] XMLkeyMaterialFail = osUTF8.GetASCIIBytes("<llsd><map><key>success</key><integer>0</integer></map></llsd>\r\n");
|
||||
|
||||
public bool RemoveMaterialEntry(ref RenderMaterialEntry[] entries, int side)
|
||||
private static bool RemoveMaterialEntry(ref RenderMaterialEntry[] entries, int side)
|
||||
{
|
||||
if (entries is null || entries.Length == 0)
|
||||
return false;
|
||||
@@ -1387,90 +1386,5 @@ namespace OpenSim.Region.OptionalModules.Materials
|
||||
overrides[indx].data = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ClearOverrideData(OSDMap facemat, out OSDMap changedmat)
|
||||
{
|
||||
if(facemat is not null && facemat.TryGetOSDArray("ti", out OSDArray transforms))
|
||||
{
|
||||
changedmat = new OSDMap()
|
||||
{
|
||||
["ti"] = transforms
|
||||
};
|
||||
return facemat.Count > 1;
|
||||
}
|
||||
|
||||
changedmat = null;
|
||||
return facemat is not null;
|
||||
}
|
||||
|
||||
public bool CleanMaterialOverrides(ref RenderMaterialOverrideEntry[] overrides, int side, bool removeTransforms = false)
|
||||
{
|
||||
if (overrides is null || overrides.Length == 0)
|
||||
return false;
|
||||
|
||||
bool changed = false;
|
||||
if(removeTransforms && side < 0)
|
||||
{
|
||||
overrides = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
List<RenderMaterialOverrideEntry> newoverrides = new(overrides.Length);
|
||||
|
||||
for(int indx = 0; indx < overrides.Length; indx++)
|
||||
{
|
||||
if(side >= 0 && side != overrides[indx].te_index)
|
||||
{
|
||||
newoverrides.Add(overrides[indx]);
|
||||
}
|
||||
else if (removeTransforms)
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(string.IsNullOrEmpty(overrides[indx].data))
|
||||
{
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
int tiindx = overrides[indx].data.IndexOf("ti");
|
||||
if(tiindx < 0)
|
||||
{
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
StringBuilder sb = osStringBuilderCache.Acquire();
|
||||
sb.Append("{'");
|
||||
int brk = 0;
|
||||
do
|
||||
{
|
||||
char c = overrides[indx].data[tiindx];
|
||||
sb.Append(c);
|
||||
if(c=='[')
|
||||
brk++;
|
||||
else if(c==']')
|
||||
{
|
||||
brk--;
|
||||
if(brk == 0)
|
||||
break;
|
||||
}
|
||||
tiindx++;
|
||||
}
|
||||
while (tiindx < overrides[indx].data.Length);
|
||||
|
||||
sb.Append('}');
|
||||
string newdata = osStringBuilderCache.GetStringAndRelease(sb);
|
||||
changed = !newdata.Equals(overrides[indx].data);
|
||||
overrides[indx].data = newdata;
|
||||
newoverrides.Add(overrides[indx]);
|
||||
}
|
||||
}
|
||||
|
||||
overrides = newoverrides.Count > 0 ? [.. newoverrides] : null;
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
protected float m_recoilScaleFactor = 0.0f;
|
||||
protected bool m_AllowGodFunctions;
|
||||
|
||||
protected string m_SlurlPrefix = "http://slurl.opensim.local";
|
||||
|
||||
protected double m_timer = Util.GetTimeStamp();
|
||||
protected bool m_waitingForScriptAnswer = false;
|
||||
protected bool m_automaticLinkPermission = false;
|
||||
@@ -443,6 +445,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
m_recoilScaleFactor = seConfig.GetFloat("RecoilScaleFactor", m_recoilScaleFactor);
|
||||
m_AllowGodFunctions = seConfig.GetBoolean("AllowGodFunctions", false);
|
||||
|
||||
m_SlurlPrefix = seConfig.GetString("SlurlPrefix", m_SlurlPrefix);
|
||||
|
||||
m_disable_underground_movement = seConfig.GetBoolean("DisableUndergroundMovement", true);
|
||||
|
||||
m_linksetDataLimit = seConfig.GetInt("LinksetDataLimit", m_linksetDataLimit);
|
||||
@@ -516,7 +520,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
m_useMeshCacheInCastRay = lslConfig.GetBoolean("UseMeshCacheInLlCastRay", m_useMeshCacheInCastRay);
|
||||
}
|
||||
|
||||
IConfig smtpConfig = seConfigSource.Configs["SMTP"];
|
||||
IConfig smtpConfig = seConfigSource.Configs["Email"];
|
||||
if (smtpConfig != null)
|
||||
{
|
||||
// there's an smtp config, so load in the snooze time.
|
||||
@@ -525,7 +529,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
m_internalObjectHost = smtpConfig.GetString("internal_object_host", m_internalObjectHost);
|
||||
}
|
||||
|
||||
IConfig chatConfig = seConfigSource.Configs["SMTP"];
|
||||
IConfig chatConfig = seConfigSource.Configs["Chat"];
|
||||
if(chatConfig != null)
|
||||
{
|
||||
m_whisperdistance = chatConfig.GetInt("whisper_distance", m_whisperdistance);
|
||||
@@ -2799,7 +2803,30 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
|
||||
public LSL_Float llGetTimeOfDay()
|
||||
{
|
||||
return (double)((DateTime.Now.TimeOfDay.TotalMilliseconds / 1000) % (3600 * 4));
|
||||
if (m_envModule is null)
|
||||
{
|
||||
return (float)((DateTime.Now.TimeOfDay.TotalMilliseconds / 1000) % (3600 * 4));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Similar as osGetApparentTime(), taking into account parcel environment
|
||||
float frac = m_envModule.GetDayFractionTime(m_host.GetWorldPosition());
|
||||
return 86400 * frac;
|
||||
}
|
||||
}
|
||||
|
||||
public LSL_Float llGetRegionTimeOfDay()
|
||||
{
|
||||
if (m_envModule is null)
|
||||
{
|
||||
return (float)((DateTime.Now.TimeOfDay.TotalMilliseconds / 1000) % (3600 * 4));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same as osGetApparentTime(), taking into account region environment
|
||||
float frac = m_envModule.GetRegionDayFractionTime();
|
||||
return 86400 * frac;
|
||||
}
|
||||
}
|
||||
|
||||
public LSL_Float llGetWallclock()
|
||||
@@ -5035,7 +5062,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
if (account is null)
|
||||
{
|
||||
GridUserInfo info = World.GridUserService.GetGridUserInfo(destId.ToString());
|
||||
if(info is null || info.Online == false)
|
||||
if(info is null)
|
||||
{
|
||||
Error("llGiveInventory", "Can't find destination '" + destId.ToString() + "'");
|
||||
return;
|
||||
@@ -5237,7 +5264,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data == 7)
|
||||
if (data == 7 || data == 8)
|
||||
reply = "0";
|
||||
}
|
||||
break;
|
||||
@@ -6472,6 +6499,25 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
case "shout_range":
|
||||
return m_shoutdistance.ToString();
|
||||
|
||||
case "region_rating":
|
||||
switch(World.RegionInfo.RegionSettings.Maturity)
|
||||
{
|
||||
case 0: return "PG";
|
||||
case 1: return "MATURE";
|
||||
case 2: return "ADULT";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
|
||||
case "grid":
|
||||
return World.SceneGridInfo == null ? string.Empty : World.SceneGridInfo.GridName;
|
||||
|
||||
case "objectmail_hostname":
|
||||
goto case "mailname";
|
||||
case "mailname":
|
||||
if (m_emailModule is not null)
|
||||
return m_internalObjectHost;
|
||||
return "";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -8119,8 +8165,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
GridInstantMessage msg = new(World, m_host.OwnerID, m_host.Name, destID,
|
||||
(byte)InstantMessageDialog.TaskInventoryOffered,
|
||||
m_host.OwnerID.Equals(m_host.GroupID),
|
||||
string.Format("'{0}'", category),
|
||||
//string.Format("'{0}' ( http://slurl.com/secondlife/{1}/{2}/{3}/{4} )", category, World.Name, (int)pos.X, (int)pos.Y, (int)pos.Z),
|
||||
string.Format("'{0}' ( {1}/{2}/{3}/{4}/{5}/ )", category, m_SlurlPrefix, World.Name, (int)pos.X, (int)pos.Y, (int)pos.Z),
|
||||
folderID, false, pos,
|
||||
bucket, false);
|
||||
|
||||
@@ -10751,8 +10796,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
|
||||
float repeatX = Math.Clamp((float)mnrepeat.x,-100.0f, 100.0f);
|
||||
float repeatY = Math.Clamp((float)mnrepeat.y,-100.0f, 100.0f);
|
||||
float offsetX = Math.Clamp((float)mnoffset.x, 0f, 1.0f);
|
||||
float offsetY = Math.Clamp((float)mnoffset.y, 0f, 1.0f);
|
||||
float offsetX = Math.Clamp((float)mnoffset.x, -1.0f, 1.0f);
|
||||
float offsetY = Math.Clamp((float)mnoffset.y, -1.0f, 1.0f);
|
||||
|
||||
materialChanged |= SetMaterialNormalMap(part, face, mapID, repeatX, repeatY, offsetX, offsetY, mnrot);
|
||||
break;
|
||||
@@ -19612,123 +19657,6 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void llSetRenderMaterial(LSL_String materialstr, LSL_Integer lsl_face)
|
||||
{
|
||||
if(m_materialsModule is null)
|
||||
return;
|
||||
|
||||
if(string.IsNullOrEmpty(materialstr.m_string))
|
||||
{
|
||||
Error("llSetRenderMaterial", "material \"\" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
int face = lsl_face.value;
|
||||
bool changed;
|
||||
|
||||
if(UUID.ZeroString.Equals(materialstr.m_string, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if(m_host.Shape.RenderMaterials is null || m_host.Shape.RenderMaterials.entries is null || m_host.Shape.RenderMaterials.entries.Length == 0)
|
||||
return;
|
||||
|
||||
changed = m_materialsModule.CleanMaterialOverrides(ref m_host.Shape.RenderMaterials.overrides, face);
|
||||
if(face == ScriptBaseClass.ALL_SIDES)
|
||||
{
|
||||
m_host.Shape.RenderMaterials.entries = null;
|
||||
changed = true;
|
||||
}
|
||||
else
|
||||
changed |= m_materialsModule.RemoveMaterialEntry(ref m_host.Shape.RenderMaterials.entries, face);
|
||||
|
||||
if(changed)
|
||||
{
|
||||
m_host.ParentGroup.HasGroupChanged = true;
|
||||
m_host.ScheduleUpdate(PrimUpdateFlags.MaterialOvr | PrimUpdateFlags.FullUpdate);
|
||||
m_host.TriggerScriptChangedEvent(Changed.MATERIAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UUID matID = ScriptUtils.GetAssetIdFromItemName(m_host, materialstr.m_string, (int)AssetType.Material);
|
||||
if (matID.IsZero())
|
||||
{
|
||||
if (!UUID.TryParse(materialstr.m_string, out matID) || matID.IsZero())
|
||||
{
|
||||
Error("llSetRenderMaterial", $"material \"{materialstr.m_string}\" not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int nsides = GetNumberOfSides(m_host);
|
||||
if(face >= nsides)
|
||||
return;
|
||||
|
||||
m_host.Shape.RenderMaterials ??= new();
|
||||
m_host.Shape.RenderMaterials.entries ??= new Primitive.RenderMaterials.RenderMaterialEntry[1];
|
||||
|
||||
changed = m_materialsModule.CleanMaterialOverrides(ref m_host.Shape.RenderMaterials.overrides, face);
|
||||
if(face == ScriptBaseClass.ALL_SIDES)
|
||||
{
|
||||
if(m_host.Shape.RenderMaterials.entries is null || m_host.Shape.RenderMaterials.entries.Length != nsides)
|
||||
{
|
||||
m_host.Shape.RenderMaterials.entries = new Primitive.RenderMaterials.RenderMaterialEntry[nsides];
|
||||
for (int i = 0; i < m_host.Shape.RenderMaterials.entries.Length; i++)
|
||||
{
|
||||
m_host.Shape.RenderMaterials.entries[i] = new()
|
||||
{
|
||||
te_index = (byte)i,
|
||||
id = matID
|
||||
};
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < m_host.Shape.RenderMaterials.entries.Length; i++)
|
||||
{
|
||||
if(matID.NotEqual(m_host.Shape.RenderMaterials.entries[i].id))
|
||||
{
|
||||
changed = true;
|
||||
m_host.Shape.RenderMaterials.entries[i].id = matID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int indx = 0;
|
||||
for( ; indx < m_host.Shape.RenderMaterials.entries.Length; indx++)
|
||||
{
|
||||
if (m_host.Shape.RenderMaterials.entries[indx].te_index == face)
|
||||
{
|
||||
if(matID.NotEqual(m_host.Shape.RenderMaterials.entries[indx].id))
|
||||
{
|
||||
changed = true;
|
||||
m_host.Shape.RenderMaterials.entries[indx].id = matID;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(indx == m_host.Shape.RenderMaterials.entries.Length)
|
||||
{
|
||||
Array.Resize(ref m_host.Shape.RenderMaterials.entries, m_host.Shape.RenderMaterials.entries.Length + 1);
|
||||
|
||||
m_host.Shape.RenderMaterials.entries[indx] = new()
|
||||
{
|
||||
te_index = (byte)face,
|
||||
id = matID
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if(changed)
|
||||
{
|
||||
m_host.ParentGroup.HasGroupChanged = true;
|
||||
m_host.ScheduleUpdate(PrimUpdateFlags.MaterialOvr | PrimUpdateFlags.FullUpdate);
|
||||
m_host.TriggerScriptChangedEvent(Changed.MATERIAL);
|
||||
}
|
||||
}
|
||||
|
||||
public LSL_Vector llWorldPosToHUD(LSL_Vector wp)
|
||||
{
|
||||
if(!m_host.ParentGroup.IsAttachment)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
using Nini.Config;
|
||||
using OpenSim;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Region.ScriptEngine.Shared;
|
||||
using OpenSim.Region.ScriptEngine.Shared.Api.Plugins;
|
||||
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
|
||||
using OpenSim.Region.ScriptEngine.Interfaces;
|
||||
using OpenSim.Region.ScriptEngine.Shared.Api.Interfaces;
|
||||
|
||||
using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
|
||||
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
|
||||
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
|
||||
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
|
||||
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
|
||||
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
|
||||
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
|
||||
|
||||
#pragma warning disable IDE1006
|
||||
|
||||
namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||
{
|
||||
[Serializable]
|
||||
public class Lickx_Api : ILickx_Api, IScriptApi
|
||||
{
|
||||
internal IScriptEngine m_ScriptEngine;
|
||||
internal SceneObjectPart m_host;
|
||||
internal bool m_LickxFunctionsEnabled = true;
|
||||
internal IScriptModuleComms m_comms = null;
|
||||
|
||||
public void Initialize(IScriptEngine scriptEngine, SceneObjectPart host, TaskInventoryItem item)
|
||||
{
|
||||
m_ScriptEngine = scriptEngine;
|
||||
m_host = host;
|
||||
|
||||
m_comms = m_ScriptEngine.World.RequestModuleInterface<IScriptModuleComms>();
|
||||
if (m_comms == null)
|
||||
m_LickxFunctionsEnabled = false;
|
||||
}
|
||||
|
||||
public Scene World
|
||||
{
|
||||
get { return m_ScriptEngine.World; }
|
||||
}
|
||||
|
||||
internal static void lxError(string msg)
|
||||
{
|
||||
throw new ScriptException("Lickx Runtime Error: " + msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the viewer name of an agent
|
||||
/// </summary>
|
||||
/// <param name="avKey">The UUID of the target agent</param>
|
||||
/// <returns>A string containing the viewer name</returns>
|
||||
public LSL_String lxGetAgentViewer(LSL_Key avkey)
|
||||
{
|
||||
if (!UUID.TryParse(avkey.m_string, out UUID avId))
|
||||
return string.Empty;
|
||||
|
||||
AgentCircuitData aCircuit = World.AuthenticateHandler.GetAgentCircuitData(avId);
|
||||
if (aCircuit != null)
|
||||
return Util.GetViewerName(aCircuit);
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces
|
||||
LSL_Vector llGetTextureScale(int side);
|
||||
LSL_Float llGetTime();
|
||||
LSL_Float llGetTimeOfDay();
|
||||
LSL_Float llGetRegionTimeOfDay();
|
||||
LSL_String llGetTimestamp();
|
||||
LSL_Vector llGetTorque();
|
||||
LSL_Integer llGetUnixTime();
|
||||
@@ -537,6 +538,5 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces
|
||||
LSL_String llGetRenderMaterial(LSL_Integer face);
|
||||
LSL_Integer llIsLinkGLTFMaterial(LSL_Integer linknum, LSL_Integer face);
|
||||
LSL_Vector llWorldPosToHUD(LSL_Vector WorldPosition);
|
||||
void llSetRenderMaterial(LSL_String material, LSL_Integer face);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -25,30 +25,24 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System.Threading.Tasks;
|
||||
#pragma warning disable IDE1006
|
||||
|
||||
using OMV = OpenMetaverse;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using OpenSim.Region.ScriptEngine.Interfaces;
|
||||
|
||||
namespace osWebRtcVoice
|
||||
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
|
||||
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
|
||||
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
|
||||
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
|
||||
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
|
||||
using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
|
||||
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
|
||||
|
||||
namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the interface for the viewer session. It is used to store the
|
||||
/// state of the viewer session and to disconnect the session when needed.
|
||||
/// </summary>
|
||||
public interface IVoiceViewerSession
|
||||
public interface ILickx_Api
|
||||
{
|
||||
// This ID is passed to and from the viewer to identify the session
|
||||
public string ViewerSessionID { get; set; }
|
||||
public IWebRtcVoiceService VoiceService { get; set; }
|
||||
// THis ID is passed between us and the voice service to idetify the session
|
||||
public string VoiceServiceSessionId { get; set; }
|
||||
// The UUID of the region that is being connected to
|
||||
public OMV.UUID RegionId { get; set; }
|
||||
|
||||
// The simulator has a GUID to identify the user
|
||||
public OMV.UUID AgentId { get; set; }
|
||||
|
||||
// Disconnect the connection to the voice service for this session
|
||||
public Task Shutdown();
|
||||
LSL_String lxGetAgentViewer(LSL_Key avkey);
|
||||
}
|
||||
}
|
||||
@@ -1051,6 +1051,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
|
||||
public const int SOUND_TRIGGER = 2;
|
||||
public const int SOUND_SYNC = 4;
|
||||
|
||||
// returned by llRequestAgentData
|
||||
public const int PAYMENT_INFO_ON_FILE = 1;
|
||||
public const int PAYMENT_INFO_USED = 2;
|
||||
|
||||
//llRezObjectWithParams Parameters
|
||||
public const int REZ_PARAM = 0;
|
||||
public const int REZ_FLAGS = 1;
|
||||
|
||||
@@ -1040,6 +1040,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
|
||||
return m_LSL_Functions.llGetTimeOfDay();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public LSL_Float llGetRegionTimeOfDay()
|
||||
{
|
||||
return m_LSL_Functions.llGetRegionTimeOfDay();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public LSL_String llGetTimestamp()
|
||||
{
|
||||
@@ -2899,11 +2905,5 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
|
||||
{
|
||||
return m_LSL_Functions.llWorldPosToHUD(WorldPosition);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void llSetRenderMaterial(LSL_String material, LSL_Integer face)
|
||||
{
|
||||
m_LSL_Functions.llSetRenderMaterial(material, face);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
@@ -25,31 +25,37 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace osWebRtcVoice
|
||||
using OpenSim.Region.ScriptEngine.Interfaces;
|
||||
using OpenSim.Region.ScriptEngine.Shared.Api.Interfaces;
|
||||
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
|
||||
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
|
||||
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
|
||||
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
|
||||
using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
|
||||
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
|
||||
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
|
||||
|
||||
#pragma warning disable IDE1006
|
||||
|
||||
namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
|
||||
{
|
||||
/// <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
|
||||
public partial class ScriptBaseClass
|
||||
{
|
||||
// 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.
|
||||
public ILickx_Api m_Lickx_Functions;
|
||||
|
||||
// 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);
|
||||
public void ApiTypeLickx(IScriptApi api)
|
||||
{
|
||||
if (api is ILickx_Api p)
|
||||
m_Lickx_Functions = p;
|
||||
}
|
||||
|
||||
// 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);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public LSL_String lxGetAgentViewer(LSL_Key avkey)
|
||||
{
|
||||
return m_Lickx_Functions.lxGetAgentViewer(avkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,12 +89,13 @@ namespace OpenSim.Server.Handlers.Grid
|
||||
if (stats_available)
|
||||
{
|
||||
stats_available = false;
|
||||
string gridService = m_Config.Configs["GridService"].GetString("LocalServiceModule", string.Empty);
|
||||
if(!string.IsNullOrEmpty(gridService))
|
||||
string gridService = gridCfg.GetString("GridService", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(gridService))
|
||||
{
|
||||
m_GridService = ServerUtils.LoadPlugin<IGridService>(gridService, [m_Config]);
|
||||
if(m_GridService != null)
|
||||
{
|
||||
object[] args = new object[] { configSource };
|
||||
m_GridService = ServerUtils.LoadPlugin<IGridService>(gridService, args);
|
||||
if (m_GridService != null)
|
||||
{
|
||||
IConfig dbConfig = configSource.Configs["DatabaseService"];
|
||||
if (dbConfig is not null)
|
||||
{
|
||||
@@ -282,7 +283,7 @@ namespace OpenSim.Server.Handlers.Grid
|
||||
try
|
||||
{
|
||||
// Fetch region data
|
||||
if(m_GridService is not null)
|
||||
if (m_GridService is not null)
|
||||
{
|
||||
List<GridRegion> regions = m_GridService.GetOnlineRegions(UUID.Zero, 0, 0, int.MaxValue);
|
||||
foreach (GridRegion region in regions)
|
||||
|
||||
@@ -69,24 +69,18 @@ namespace OpenSim.Server.Handlers.Presence
|
||||
string method = string.Empty;
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> request = ServerUtils.ParseQueryString(body);
|
||||
Dictionary<string, object> request =
|
||||
ServerUtils.ParseQueryString(body);
|
||||
|
||||
if (!request.TryGetValue("METHOD", out object tmpobj) || tmpobj is not string)
|
||||
if (!request.ContainsKey("METHOD"))
|
||||
return FailureResult();
|
||||
|
||||
method = (string)tmpobj;
|
||||
method = request["METHOD"].ToString();
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case "login":
|
||||
{
|
||||
//return LoginAgent(request); this is ilegal
|
||||
if (request.TryGetValue("UserID", out object uo) && uo is string user)
|
||||
m_log.Debug($"[PRESENCE HANDLER]: ilegal login try from {httpRequest.RemoteIPEndPoint} for userID {user}");
|
||||
else
|
||||
m_log.Debug($"[PRESENCE HANDLER]: ilegal login try from {httpRequest.RemoteIPEndPoint} for unkown user");
|
||||
|
||||
return FailureResult();
|
||||
}
|
||||
return LoginAgent(request);
|
||||
case "logout":
|
||||
return LogoutAgent(request);
|
||||
case "logoutregion":
|
||||
@@ -98,11 +92,11 @@ namespace OpenSim.Server.Handlers.Presence
|
||||
case "getagents":
|
||||
return GetAgents(request);
|
||||
}
|
||||
m_log.Debug($"[PRESENCE HANDLER]: unknown method request: {method}");
|
||||
m_log.DebugFormat("[PRESENCE HANDLER]: unknown method request: {0}", method);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Debug($"[PRESENCE HANDLER]: Exception in method {method}: {e.Message}");
|
||||
m_log.DebugFormat("[PRESENCE HANDLER]: Exception in method {0}: {1}", method, e);
|
||||
}
|
||||
|
||||
return FailureResult();
|
||||
|
||||
@@ -32,6 +32,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Collections.Generic;
|
||||
using OpenSim.Framework;
|
||||
@@ -50,6 +51,7 @@ namespace OpenSim.Server
|
||||
protected static List<IServiceConnector> m_ServiceConnectors = new();
|
||||
|
||||
protected static PluginLoader loader;
|
||||
private static PosixSignalRegistration m_signalReg;
|
||||
private static bool m_NoVerifyCertChain = false;
|
||||
private static bool m_NoVerifyCertHostname = false;
|
||||
|
||||
@@ -95,13 +97,20 @@ namespace OpenSim.Server
|
||||
Culture.SetCurrentCulture();
|
||||
Culture.SetDefaultCurrentCulture();
|
||||
|
||||
m_signalReg = PosixSignalRegistration.Create(PosixSignal.SIGTERM, context =>
|
||||
{
|
||||
m_log.Info("Received SIGTERM, shutting down");
|
||||
m_Server?.Shutdown();
|
||||
Util.StopThreadPool();
|
||||
});
|
||||
|
||||
ServicePointManager.DefaultConnectionLimit = 64;
|
||||
ServicePointManager.MaxServicePointIdleTime = 30000;
|
||||
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
ServicePointManager.UseNagleAlgorithm = false;
|
||||
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
|
||||
|
||||
|
||||
m_Server = new HttpServerBase("R.O.B.U.S.T.", args);
|
||||
|
||||
string registryLocation;
|
||||
|
||||
@@ -432,6 +432,12 @@ namespace OpenSim.Services.HypergridService
|
||||
if (m_ForeignAgentsAllowed && IsException(aCircuit, m_ForeignsAllowedExceptions))
|
||||
allowed = false;
|
||||
|
||||
string iponly = @"http\:\/\/\d+\.\d+\.\d+\.\d+\:\d+";
|
||||
Regex r = new Regex(iponly, RegexOptions.IgnoreCase);
|
||||
Match m = r.Match(aCircuit.ServiceURLs["HomeURI"].ToString());
|
||||
if (m_ForeignAgentsAllowed && m.Success)
|
||||
allowed = false; // HomeURI is an ip address, not a proper hostname
|
||||
|
||||
if (!m_ForeignAgentsAllowed && IsException(aCircuit, m_ForeignsDisallowedExceptions))
|
||||
allowed = true;
|
||||
|
||||
|
||||
@@ -104,6 +104,11 @@ namespace OpenSim.Services.HypergridService
|
||||
throw new Exception("No PresenceService in " + m_ConfigName);
|
||||
m_PresenceService = ServerUtils.LoadPlugin<IPresenceService>(theService, args);
|
||||
|
||||
theService = serverConfig.GetString("GridUserService", string.Empty);
|
||||
if (theService.Length == 0)
|
||||
throw new Exception("No GridUserService in " + m_ConfigName);
|
||||
m_GridUserService = ServerUtils.LoadPlugin<IGridUserService>(theService, args);
|
||||
|
||||
m_FriendsSimConnector = new FriendsSimConnector();
|
||||
|
||||
m_log.DebugFormat("[HGFRIENDS SERVICE]: Starting...");
|
||||
@@ -133,6 +138,9 @@ namespace OpenSim.Services.HypergridService
|
||||
|
||||
m_log.DebugFormat("[HGFRIENDS SERVICE]: New friendship {0} {1} ({2})", friend.PrincipalID, friend.Friend, verified);
|
||||
|
||||
// Mantis 9199: Make sure the new HG friend is in the GridUser table
|
||||
m_GridUserService?.LoggedIn(friendID.ToString()+";"+url+";"+first+" "+last);
|
||||
|
||||
// Does the friendship already exist?
|
||||
FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID);
|
||||
foreach (FriendInfo finfo in finfos)
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace OpenSim.Services.HypergridService
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_log.WarnFormat("[HG IM SERVICE]: Unable to load PresenceService");
|
||||
m_log.WarnFormat("[HG IM SERVICE]: Unable to load UserAgentService");
|
||||
}
|
||||
|
||||
m_InGatekeeper = serverConfig.GetBoolean("InGatekeeper", false);
|
||||
@@ -122,7 +122,7 @@ namespace OpenSim.Services.HypergridService
|
||||
IConfig cnf = config.Configs["Messaging"];
|
||||
if (cnf == null)
|
||||
{
|
||||
m_log.Debug("[HG IM SERVICE]: Starting (without [MEssaging])");
|
||||
m_log.Debug("[HG IM SERVICE]: Starting (without [Messaging])");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@ namespace OpenSim.Services.HypergridService
|
||||
{
|
||||
public class UserAccountCache : IUserAccountService
|
||||
{
|
||||
private const double CACHE_EXPIRATION_SECONDS = 120000.0; // 33 hours!
|
||||
private const double CACHE_ALIEN_EXPIRATION_SECONDS = 7200.0; // 2 hours
|
||||
private const double CACHE_EXPIRATION_SECONDS = 3600; // 1 hour // 120000.0; // 33 hours!
|
||||
private const double CACHE_NULL_EXPIRATION_SECONDS = 600; // 10 minutes
|
||||
|
||||
// private static readonly ILog m_log =
|
||||
// LogManager.GetLogger(
|
||||
// MethodBase.GetCurrentMethod().DeclaringType);
|
||||
//private static readonly ILog m_log =
|
||||
// LogManager.GetLogger(
|
||||
// MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private ExpiringCache<UUID, UserAccount> m_UUIDCache;
|
||||
|
||||
@@ -40,7 +42,20 @@ namespace OpenSim.Services.HypergridService
|
||||
public void Cache(UUID userID, UserAccount account)
|
||||
{
|
||||
// Cache even null accounts
|
||||
m_UUIDCache.AddOrUpdate(userID, account, CACHE_EXPIRATION_SECONDS);
|
||||
if (account is null)
|
||||
{
|
||||
m_UUIDCache.AddOrUpdate(userID, account, CACHE_NULL_EXPIRATION_SECONDS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (account.LocalToGrid)
|
||||
{
|
||||
m_UUIDCache.AddOrUpdate(userID, account, CACHE_EXPIRATION_SECONDS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Foreigners
|
||||
m_UUIDCache.AddOrUpdate(userID, account, CACHE_ALIEN_EXPIRATION_SECONDS);
|
||||
|
||||
//m_log.DebugFormat("[USER CACHE]: cached user {0}", userID);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace OpenSim.Services.PresenceService
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
protected bool m_allowDuplicatePresences = false;
|
||||
protected bool m_useCache = true;
|
||||
const int EXPIREMS = 300000;
|
||||
static ExpiringCacheOS<UUID, PresenceData> BySessionCache = new ExpiringCacheOS<UUID, PresenceData>(60000);
|
||||
static ExpiringCacheOS<string, PresenceData> ByUserCache = new ExpiringCacheOS<string, PresenceData>(60000);
|
||||
@@ -57,23 +58,32 @@ namespace OpenSim.Services.PresenceService
|
||||
if (presenceConfig != null)
|
||||
{
|
||||
m_allowDuplicatePresences = presenceConfig.GetBoolean("AllowDuplicatePresences", m_allowDuplicatePresences);
|
||||
m_useCache = presenceConfig.GetBoolean("UseCache", m_useCache);
|
||||
}
|
||||
}
|
||||
|
||||
public bool LoginAgent(string userID, UUID sessionID, UUID secureSessionID)
|
||||
{
|
||||
bool inCache = ByUserCache.TryGetValue(userID, out PresenceData prevUser);
|
||||
if (!inCache)
|
||||
bool inCache = false;
|
||||
PresenceData prevUser;
|
||||
|
||||
if (m_useCache)
|
||||
{
|
||||
PresenceData[] dataprv = m_Database.Get("UserID", userID);
|
||||
if (dataprv.Length > 0)
|
||||
prevUser = dataprv[0];
|
||||
inCache = ByUserCache.TryGetValue(userID, out prevUser);
|
||||
if (!inCache)
|
||||
{
|
||||
PresenceData[] dataprv = m_Database.Get("UserID", userID);
|
||||
if (dataprv.Length > 0)
|
||||
prevUser = dataprv[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
prevUser = GetUser(userID);
|
||||
|
||||
if (!m_allowDuplicatePresences && (prevUser != null))
|
||||
{
|
||||
m_Database.Delete("UserID", userID.ToString());
|
||||
if(inCache)
|
||||
if(m_useCache && inCache)
|
||||
{
|
||||
BySessionCache.Remove(prevUser.SessionID);
|
||||
ByUserCache.Remove(userID);
|
||||
@@ -89,8 +99,11 @@ namespace OpenSim.Services.PresenceService
|
||||
data.Data["SecureSessionID"] = secureSessionID.ToString();
|
||||
|
||||
m_Database.Store(data);
|
||||
BySessionCache.Add(sessionID, data, EXPIREMS);
|
||||
ByUserCache.Add(userID, data, EXPIREMS);
|
||||
if (m_useCache)
|
||||
{
|
||||
BySessionCache.Add(sessionID, data, EXPIREMS);
|
||||
ByUserCache.Add(userID, data, EXPIREMS);
|
||||
}
|
||||
|
||||
string prevUserStr = "";
|
||||
if (prevUser != null)
|
||||
@@ -104,8 +117,16 @@ namespace OpenSim.Services.PresenceService
|
||||
|
||||
public bool LogoutAgent(UUID sessionID)
|
||||
{
|
||||
bool inCache = BySessionCache.TryGetValue(sessionID, out PresenceData presence);
|
||||
if(!inCache)
|
||||
bool inCache = false;
|
||||
PresenceData presence;
|
||||
|
||||
if (m_useCache)
|
||||
{
|
||||
inCache = BySessionCache.TryGetValue(sessionID, out presence);
|
||||
if(!inCache)
|
||||
presence = m_Database.Get(sessionID);
|
||||
}
|
||||
else
|
||||
presence = m_Database.Get(sessionID);
|
||||
|
||||
m_log.DebugFormat("[PRESENCE SERVICE]: LogoutAgent: session {0}, user {1}, region {2}",
|
||||
@@ -114,7 +135,7 @@ namespace OpenSim.Services.PresenceService
|
||||
(presence == null) ? null : presence.RegionID.ToString());
|
||||
|
||||
bool ret = m_Database.Delete("SessionID", sessionID.ToString());
|
||||
if(inCache)
|
||||
if(m_useCache && inCache)
|
||||
{
|
||||
BySessionCache.Remove(sessionID);
|
||||
if(presence is not null)
|
||||
@@ -130,11 +151,14 @@ namespace OpenSim.Services.PresenceService
|
||||
return true;
|
||||
|
||||
m_log.DebugFormat("[PRESENCE SERVICE]: Logout users in region {0}", regionID);
|
||||
for (int i = 0; i < prevSessions.Length; ++i)
|
||||
if (m_useCache)
|
||||
{
|
||||
PresenceData pd = prevSessions[i];
|
||||
BySessionCache.Remove(pd.SessionID);
|
||||
ByUserCache.Remove(pd.UserID);
|
||||
for (int i = 0; i < prevSessions.Length; ++i)
|
||||
{
|
||||
PresenceData pd = prevSessions[i];
|
||||
BySessionCache.Remove(pd.SessionID);
|
||||
ByUserCache.Remove(pd.UserID);
|
||||
}
|
||||
}
|
||||
|
||||
// There's a small chance that LogoutRegionAgents() will logout different users than the
|
||||
@@ -149,8 +173,16 @@ namespace OpenSim.Services.PresenceService
|
||||
{
|
||||
try
|
||||
{
|
||||
bool inCache = BySessionCache.TryGetValue(sessionID, out PresenceData presence);
|
||||
if(!inCache)
|
||||
bool inCache = false;
|
||||
PresenceData presence;
|
||||
|
||||
if (m_useCache)
|
||||
{
|
||||
inCache = BySessionCache.TryGetValue(sessionID, out presence);
|
||||
if(!inCache)
|
||||
presence = m_Database.Get(sessionID);
|
||||
}
|
||||
else
|
||||
presence = m_Database.Get(sessionID);
|
||||
|
||||
bool success;
|
||||
@@ -164,16 +196,19 @@ namespace OpenSim.Services.PresenceService
|
||||
sessionID, (presence == null) ? null : presence.UserID, regionID,
|
||||
(presence == null) ? "not logged-in" : "region " + presence.RegionID);
|
||||
|
||||
if (success)
|
||||
if (m_useCache)
|
||||
{
|
||||
presence.RegionID = regionID;
|
||||
BySessionCache.Add(sessionID, presence, EXPIREMS); // lastseen seems unused
|
||||
ByUserCache.Add(presence.UserID, presence, EXPIREMS); // lastseen seems unused
|
||||
}
|
||||
else if (inCache)
|
||||
{
|
||||
BySessionCache.Remove(sessionID);
|
||||
ByUserCache.Remove(presence.UserID);
|
||||
if (success)
|
||||
{
|
||||
presence.RegionID = regionID;
|
||||
BySessionCache.Add(sessionID, presence, EXPIREMS); // lastseen seems unused
|
||||
ByUserCache.Add(presence.UserID, presence, EXPIREMS); // lastseen seems unused
|
||||
}
|
||||
else if (inCache)
|
||||
{
|
||||
BySessionCache.Remove(sessionID);
|
||||
ByUserCache.Remove(presence.UserID);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -187,14 +222,24 @@ namespace OpenSim.Services.PresenceService
|
||||
|
||||
public PresenceInfo GetAgent(UUID sessionID)
|
||||
{
|
||||
if(!BySessionCache.TryGetValue(sessionID, out PresenceData data))
|
||||
PresenceData data;
|
||||
|
||||
if (m_useCache)
|
||||
{
|
||||
if(!BySessionCache.TryGetValue(sessionID, out data))
|
||||
data = m_Database.Get(sessionID);
|
||||
}
|
||||
else
|
||||
data = m_Database.Get(sessionID);
|
||||
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
BySessionCache.Add(sessionID, data, EXPIREMS);
|
||||
ByUserCache.Add(data.UserID, data, EXPIREMS);
|
||||
if (m_useCache)
|
||||
{
|
||||
BySessionCache.Add(sessionID, data, EXPIREMS);
|
||||
ByUserCache.Add(data.UserID, data, EXPIREMS);
|
||||
}
|
||||
|
||||
var ret = new PresenceInfo()
|
||||
{
|
||||
@@ -210,8 +255,9 @@ namespace OpenSim.Services.PresenceService
|
||||
PresenceInfo ret;
|
||||
foreach (string userIDStr in userIDs)
|
||||
{
|
||||
if(ByUserCache.TryGetValue(userIDStr, out PresenceData pd))
|
||||
if(m_useCache && ByUserCache.TryGetValue(userIDStr, out PresenceData pd))
|
||||
{
|
||||
// cache hit
|
||||
ByUserCache.Add(pd.UserID, pd, EXPIREMS);
|
||||
BySessionCache.Add(pd.SessionID, pd, EXPIREMS);
|
||||
ret = new PresenceInfo()
|
||||
@@ -223,12 +269,16 @@ namespace OpenSim.Services.PresenceService
|
||||
}
|
||||
else
|
||||
{
|
||||
// no cache used or cache miss
|
||||
PresenceData[] data = m_Database.Get("UserID", userIDStr);
|
||||
if(data.Length == 0)
|
||||
continue;
|
||||
PresenceData d = data[0];
|
||||
ByUserCache.Add(d.UserID, d, EXPIREMS);
|
||||
BySessionCache.Add(d.SessionID, d, EXPIREMS);
|
||||
if (m_useCache)
|
||||
{
|
||||
ByUserCache.Add(d.UserID, d, EXPIREMS);
|
||||
BySessionCache.Add(d.SessionID, d, EXPIREMS);
|
||||
}
|
||||
ret = new PresenceInfo()
|
||||
{
|
||||
UserID = d.UserID,
|
||||
@@ -243,6 +293,18 @@ namespace OpenSim.Services.PresenceService
|
||||
return info.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the user's Presence. This only really works well if !AllowDuplicatePresences, but that's the default.
|
||||
/// </summary>
|
||||
private PresenceData GetUser(string userID)
|
||||
{
|
||||
PresenceData[] data = m_Database.Get("UserID", userID);
|
||||
if (data.Length > 0)
|
||||
return data[0];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private PresenceData[] GetRegionAgents(UUID regionID)
|
||||
{
|
||||
return m_Database.Get("RegionID", regionID.ToString());
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace OpenSim.Services.UserAccountService
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static bool m_Initialized;
|
||||
protected bool m_useCache = true;
|
||||
|
||||
public GridUserService(IConfigSource config) : base(config)
|
||||
{
|
||||
@@ -69,6 +70,13 @@ namespace OpenSim.Services.UserAccountService
|
||||
"This number may not be accurate as a region may crash or not be cleanly shutdown and leave grid users shown as online\n."
|
||||
+ "For this reason, users online for more than 5 days are not currently counted",
|
||||
HandleShowGridUsersOnline);
|
||||
|
||||
IConfig griduserConfig = config.Configs["GridUserService"];
|
||||
if (griduserConfig != null)
|
||||
{
|
||||
m_useCache = griduserConfig.GetBoolean("UseCache", m_useCache);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,13 +140,13 @@ namespace OpenSim.Services.UserAccountService
|
||||
if (userID.Length > 36)
|
||||
userID = userID.Substring(0, 36);
|
||||
|
||||
if (cache.TryGetValue(userID, out GridUserData d))
|
||||
return d;
|
||||
if (m_useCache && cache.TryGetValue(userID, out GridUserData d))
|
||||
return d;
|
||||
|
||||
GridUserData[] ds = m_Database.GetAll(userID);
|
||||
if (ds == null || ds.Length == 0)
|
||||
{
|
||||
cache.Add(userID, null, 300000);
|
||||
if (m_useCache) cache.Add(userID, null, 300000);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -168,7 +176,7 @@ namespace OpenSim.Services.UserAccountService
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
cache.Add(userID, d, 300000);
|
||||
if (m_useCache) cache.Add(userID, d, 300000);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -249,7 +257,7 @@ namespace OpenSim.Services.UserAccountService
|
||||
d.Data["Login"] = Util.UnixTimeSinceEpoch().ToString();
|
||||
|
||||
m_Database.Store(d);
|
||||
if (userID.Length >= 36)
|
||||
if (m_useCache && userID.Length >= 36)
|
||||
cache.Add(userID.Substring(0, 36), d, 300000);
|
||||
|
||||
return ToInfo(d);
|
||||
@@ -275,7 +283,7 @@ namespace OpenSim.Services.UserAccountService
|
||||
|
||||
if(m_Database.Store(d))
|
||||
{
|
||||
if (userID.Length >= 36)
|
||||
if (m_useCache && userID.Length >= 36)
|
||||
cache.Add(userID.Substring(0, 36), d, 300000);
|
||||
return true;
|
||||
}
|
||||
@@ -298,7 +306,7 @@ namespace OpenSim.Services.UserAccountService
|
||||
|
||||
if(m_Database.Store(d))
|
||||
{
|
||||
if (userID.Length >= 36)
|
||||
if (m_useCache && userID.Length >= 36)
|
||||
cache.Add(userID.Substring(0, 36), d, 300000);
|
||||
return true;
|
||||
}
|
||||
@@ -323,7 +331,7 @@ namespace OpenSim.Services.UserAccountService
|
||||
|
||||
if(m_Database.Store(d))
|
||||
{
|
||||
if (userID.Length >= 36)
|
||||
if (m_useCache && userID.Length >= 36)
|
||||
cache.Add(userID.Substring(0, 36), d, 300000);
|
||||
return true;
|
||||
}
|
||||
|
||||
732
addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs
Normal file
732
addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs
Normal file
@@ -0,0 +1,732 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using OpenMetaverse;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using Nwc.XmlRpc;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Framework.Interfaces;
|
||||
using OpenSim.Region.Framework.Scenes;
|
||||
using OpenSim.Services.Interfaces;
|
||||
using Mono.Addins;
|
||||
|
||||
using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags;
|
||||
|
||||
[assembly: Addin("OpenSimSearch", OpenSim.VersionInfo.VersionNumber + "0.4")]
|
||||
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
|
||||
[assembly: AddinDescription("OpenSimSearch module.")]
|
||||
[assembly: AddinAuthor("Unknown")]
|
||||
|
||||
|
||||
namespace OpenSimSearch.Modules.OpenSearch
|
||||
{
|
||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "OpenSimSearch")]
|
||||
public class OpenSearchModule : ISearchModule, ISharedRegionModule
|
||||
{
|
||||
//
|
||||
// Log module
|
||||
//
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
//
|
||||
// Module vars
|
||||
//
|
||||
private List<Scene> m_Scenes = new();
|
||||
private string m_SearchServer = "";
|
||||
private bool m_Enabled = true;
|
||||
|
||||
#region IRegionModuleBase implementation
|
||||
public void Initialise(IConfigSource config)
|
||||
{
|
||||
IConfig searchConfig = config.Configs["Search"];
|
||||
|
||||
if (searchConfig is null)
|
||||
{
|
||||
m_Enabled = false;
|
||||
return;
|
||||
}
|
||||
if (searchConfig.GetString("Module", "OpenSimSearch") != "OpenSimSearch")
|
||||
{
|
||||
m_Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_SearchServer = searchConfig.GetString("SearchURL", "");
|
||||
if (m_SearchServer == "")
|
||||
{
|
||||
m_Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_log.Info("[SEARCH] OpenSimSearch module is active");
|
||||
m_Enabled = true;
|
||||
}
|
||||
|
||||
public void AddRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
// Hook up events
|
||||
scene.EventManager.OnNewClient += OnNewClient;
|
||||
|
||||
// Take ownership of the ISearchModule service
|
||||
scene.RegisterModuleInterface<ISearchModule>(this);
|
||||
|
||||
// Add our scene to our list...
|
||||
lock(m_Scenes)
|
||||
{
|
||||
m_Scenes.Add(scene);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRegion(Scene scene)
|
||||
{
|
||||
if (!m_Enabled)
|
||||
return;
|
||||
|
||||
scene.UnregisterModuleInterface<ISearchModule>(this);
|
||||
|
||||
scene.EventManager.OnNewClient -= OnNewClient;
|
||||
|
||||
lock(m_Scenes)
|
||||
{
|
||||
m_Scenes.Remove(scene);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegionLoaded(Scene scene)
|
||||
{
|
||||
}
|
||||
|
||||
public Type ReplaceableInterface
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "OpenSimSearch"; }
|
||||
}
|
||||
|
||||
public static bool IsSharedModule
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// New Client Event Handler
|
||||
private void OnNewClient(IClientAPI client)
|
||||
{
|
||||
// Subscribe to messages
|
||||
client.OnDirPlacesQuery += DirPlacesQuery;
|
||||
client.OnDirFindQuery += DirFindQuery;
|
||||
client.OnDirPopularQuery += DirPopularQuery;
|
||||
client.OnDirLandQuery += DirLandQuery;
|
||||
client.OnDirClassifiedQuery += DirClassifiedQuery;
|
||||
// Response after Directory Queries
|
||||
client.OnEventInfoRequest += EventInfoRequest;
|
||||
client.OnClassifiedInfoRequest += ClassifiedInfoRequest;
|
||||
client.OnMapItemRequest += HandleMapItemRequest;
|
||||
}
|
||||
|
||||
//
|
||||
// Make external XMLRPC request
|
||||
//
|
||||
private Hashtable GenericXMLRPCRequest(Hashtable ReqParams, string method)
|
||||
{
|
||||
ArrayList SendParams = new()
|
||||
{
|
||||
ReqParams
|
||||
};
|
||||
|
||||
// Send Request
|
||||
XmlRpcResponse Resp;
|
||||
try
|
||||
{
|
||||
XmlRpcRequest Req = new(method, SendParams);
|
||||
Resp = Req.Send(m_SearchServer, 30000);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
m_log.ErrorFormat("[SEARCH]: Unable to connect to Search " +
|
||||
"Server {0}. Exception {1}", m_SearchServer, ex);
|
||||
|
||||
Hashtable ErrorHash = new()
|
||||
{
|
||||
["success"] = false,
|
||||
["errorMessage"] = "Unable to search at this time. ",
|
||||
["errorURI"] = ""
|
||||
};
|
||||
|
||||
return ErrorHash;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
m_log.ErrorFormat(
|
||||
"[SEARCH]: Unable to connect to Search Server {0}. " +
|
||||
"Exception {1}", m_SearchServer, ex);
|
||||
|
||||
Hashtable ErrorHash = new()
|
||||
{
|
||||
["success"] = false,
|
||||
["errorMessage"] = "Unable to search at this time. ",
|
||||
["errorURI"] = ""
|
||||
};
|
||||
|
||||
return ErrorHash;
|
||||
}
|
||||
catch (XmlException ex)
|
||||
{
|
||||
m_log.ErrorFormat(
|
||||
"[SEARCH]: Unable to connect to Search Server {0}. " +
|
||||
"Exception {1}", m_SearchServer, ex);
|
||||
|
||||
Hashtable ErrorHash = new()
|
||||
{
|
||||
["success"] = false,
|
||||
["errorMessage"] = "Unable to search at this time. ",
|
||||
["errorURI"] = ""
|
||||
};
|
||||
|
||||
return ErrorHash;
|
||||
}
|
||||
if (Resp.IsFault)
|
||||
{
|
||||
Hashtable ErrorHash = new()
|
||||
{
|
||||
["success"] = false,
|
||||
["errorMessage"] = "Unable to search at this time. ",
|
||||
["errorURI"] = ""
|
||||
};
|
||||
return ErrorHash;
|
||||
}
|
||||
Hashtable RespData = (Hashtable)Resp.Value;
|
||||
|
||||
return RespData;
|
||||
}
|
||||
|
||||
protected void DirPlacesQuery(IClientAPI remoteClient, UUID queryID,
|
||||
string queryText, int queryFlags, int category, string simName,
|
||||
int queryStart)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["text"] = queryText,
|
||||
["flags"] = queryFlags.ToString(),
|
||||
["category"] = category.ToString(),
|
||||
["sim_name"] = simName,
|
||||
["query_start"] = queryStart.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_places_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
|
||||
int count = (dataArray.Count > 100) ? 101 : dataArray.Count;
|
||||
|
||||
DirPlacesReplyData[] data = new DirPlacesReplyData[count];
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
data[i] = new DirPlacesReplyData
|
||||
{
|
||||
parcelID = new UUID(d["parcel_id"].ToString()),
|
||||
name = d["name"].ToString(),
|
||||
forSale = Convert.ToBoolean(d["for_sale"]),
|
||||
auction = Convert.ToBoolean(d["auction"]),
|
||||
dwell = Convert.ToSingle(d["dwell"])
|
||||
};
|
||||
|
||||
if (++i >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
remoteClient.SendDirPlacesReply(queryID, data);
|
||||
}
|
||||
|
||||
public void DirPopularQuery(IClientAPI remoteClient, UUID queryID, uint queryFlags)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["flags"] = queryFlags.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_popular_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
|
||||
int count = (dataArray.Count > 100) ? 101 : dataArray.Count;
|
||||
|
||||
DirPopularReplyData[] data = new DirPopularReplyData[count];
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
data[i] = new DirPopularReplyData
|
||||
{
|
||||
parcelID = new UUID(d["parcel_id"].ToString()),
|
||||
name = d["name"].ToString(),
|
||||
dwell = Convert.ToSingle(d["dwell"])
|
||||
};
|
||||
|
||||
if (++i >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
remoteClient.SendDirPopularReply(queryID, data);
|
||||
}
|
||||
|
||||
public void DirLandQuery(IClientAPI remoteClient, UUID queryID,
|
||||
uint queryFlags, uint searchType, int price, int area,
|
||||
int queryStart)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["flags"] = queryFlags.ToString(),
|
||||
["type"] = searchType.ToString(),
|
||||
["price"] = price.ToString(),
|
||||
["area"] = area.ToString(),
|
||||
["query_start"] = queryStart.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_land_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
int count = 0;
|
||||
|
||||
/* Count entries in dataArray with valid region name to */
|
||||
/* prevent allocating data array with too many entries. */
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
if (d["name"] is not null)
|
||||
++count;
|
||||
}
|
||||
|
||||
count = (count > 100) ? 101 : count;
|
||||
|
||||
DirLandReplyData[] data = new DirLandReplyData[count];
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
if (d["name"] is null)
|
||||
continue;
|
||||
|
||||
data[i] = new DirLandReplyData
|
||||
{
|
||||
parcelID = new UUID(d["parcel_id"].ToString()),
|
||||
name = d["name"].ToString(),
|
||||
auction = Convert.ToBoolean(d["auction"]),
|
||||
forSale = Convert.ToBoolean(d["for_sale"]),
|
||||
salePrice = Convert.ToInt32(d["sale_price"]),
|
||||
actualArea = Convert.ToInt32(d["area"])
|
||||
};
|
||||
|
||||
if (++i >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
remoteClient.SendDirLandReply(queryID, data);
|
||||
}
|
||||
|
||||
public void DirFindQuery(IClientAPI remoteClient, UUID queryID,
|
||||
string queryText, uint queryFlags, int queryStart)
|
||||
{
|
||||
if (((DirFindFlags)queryFlags & DirFindFlags.DateEvents) == DirFindFlags.DateEvents)
|
||||
{
|
||||
DirEventsQuery(remoteClient, queryID, queryText, queryFlags,
|
||||
queryStart);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void DirEventsQuery(IClientAPI remoteClient, UUID queryID,
|
||||
string queryText, uint queryFlags, int queryStart)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["text"] = queryText,
|
||||
["flags"] = queryFlags.ToString(),
|
||||
["query_start"] = queryStart.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_events_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
|
||||
int count = (dataArray.Count > 100) ? 101 : dataArray.Count;
|
||||
|
||||
DirEventsReplyData[] data = new DirEventsReplyData[count];
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
data[i] = new DirEventsReplyData
|
||||
{
|
||||
ownerID = new UUID(d["owner_id"].ToString()),
|
||||
name = d["name"].ToString(),
|
||||
eventID = Convert.ToUInt32(d["event_id"]),
|
||||
date = d["date"].ToString(),
|
||||
unixTime = Convert.ToUInt32(d["unix_time"]),
|
||||
eventFlags = Convert.ToUInt32(d["event_flags"])
|
||||
};
|
||||
|
||||
if (++i >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
remoteClient.SendDirEventsReply(queryID, data);
|
||||
}
|
||||
|
||||
public void DirClassifiedQuery(IClientAPI remoteClient, UUID queryID,
|
||||
string queryText, uint queryFlags, uint category,
|
||||
int queryStart)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["text"] = queryText,
|
||||
["flags"] = queryFlags.ToString(),
|
||||
["category"] = category.ToString(),
|
||||
["query_start"] = queryStart.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_classified_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
|
||||
int count = (dataArray.Count > 100) ? 101 : dataArray.Count;
|
||||
|
||||
DirClassifiedReplyData[] data = new DirClassifiedReplyData[count];
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
data[i] = new DirClassifiedReplyData
|
||||
{
|
||||
classifiedID = new UUID(d["classifiedid"].ToString()),
|
||||
name = d["name"].ToString(),
|
||||
classifiedFlags = Convert.ToByte(d["classifiedflags"]),
|
||||
creationDate = Convert.ToUInt32(d["creation_date"]),
|
||||
expirationDate = Convert.ToUInt32(d["expiration_date"]),
|
||||
price = Convert.ToInt32(d["priceforlisting"])
|
||||
};
|
||||
|
||||
if (++i >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
remoteClient.SendDirClassifiedReply(queryID, data);
|
||||
}
|
||||
|
||||
public void EventInfoRequest(IClientAPI remoteClient, uint queryEventID)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["eventID"] = queryEventID.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "event_info_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
if (dataArray.Count == 0)
|
||||
{
|
||||
// something bad happened here, if we could return an
|
||||
// event after the search,
|
||||
// we should be able to find it here
|
||||
// TODO do some (more) sensible error-handling here
|
||||
remoteClient.SendAgentAlertMessage("Couldn't find this event.",
|
||||
false);
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable d = (Hashtable)dataArray[0];
|
||||
EventData data = new()
|
||||
{
|
||||
eventID = Convert.ToUInt32(d["event_id"]),
|
||||
creator = d["creator"].ToString(),
|
||||
name = d["name"].ToString(),
|
||||
category = d["category"].ToString(),
|
||||
description = d["description"].ToString(),
|
||||
date = d["date"].ToString(),
|
||||
dateUTC = Convert.ToUInt32(d["dateUTC"]),
|
||||
duration = Convert.ToUInt32(d["duration"]),
|
||||
cover = Convert.ToUInt32(d["covercharge"]),
|
||||
amount = Convert.ToUInt32(d["coveramount"]),
|
||||
simName = d["simname"].ToString()
|
||||
};
|
||||
data.globalPos = (Vector3.TryParse(d["globalposition"].ToString(), out data.globalPos)) ? data.globalPos : new();
|
||||
data.eventFlags = Convert.ToUInt32(d["eventflags"]);
|
||||
|
||||
remoteClient.SendEventInfoReply(data);
|
||||
}
|
||||
|
||||
public void ClassifiedInfoRequest(UUID queryClassifiedID, IClientAPI remoteClient)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["classifiedID"] = queryClassifiedID.ToString()
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "classifieds_info_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
//The viewer seems to issue an info request even when it is
|
||||
//creating a new classified which means the data hasn't been
|
||||
//saved to the database yet so there is no info to find.
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
if (dataArray.Count == 0)
|
||||
{
|
||||
// Something bad happened here if we could not return an
|
||||
// event after the search. We should be able to find it here.
|
||||
// TODO do some (more) sensible error-handling here
|
||||
// remoteClient.SendAgentAlertMessage("Couldn't find data for classified ad.",
|
||||
// false);
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable d = (Hashtable)dataArray[0];
|
||||
|
||||
Vector3 globalPos = (Vector3.TryParse(d["posglobal"].ToString(), out globalPos)) ? globalPos : new();
|
||||
|
||||
remoteClient.SendClassifiedInfoReply(
|
||||
new UUID(d["classifieduuid"].ToString()),
|
||||
new UUID(d["creatoruuid"].ToString()),
|
||||
Convert.ToUInt32(d["creationdate"]),
|
||||
Convert.ToUInt32(d["expirationdate"]),
|
||||
Convert.ToUInt32(d["category"]),
|
||||
d["name"].ToString(),
|
||||
d["description"].ToString(),
|
||||
new UUID(d["parceluuid"].ToString()),
|
||||
Convert.ToUInt32(d["parentestate"]),
|
||||
new UUID(d["snapshotuuid"].ToString()),
|
||||
d["simname"].ToString(),
|
||||
globalPos,
|
||||
d["parcelname"].ToString(),
|
||||
Convert.ToByte(d["classifiedflags"]),
|
||||
Convert.ToInt32(d["priceforlisting"]));
|
||||
}
|
||||
|
||||
public void HandleMapItemRequest(IClientAPI remoteClient, uint flags,
|
||||
uint EstateID, bool godlike,
|
||||
uint itemtype, ulong regionhandle)
|
||||
{
|
||||
//The following constant appears to be from GridLayerType enum
|
||||
//defined in OpenMetaverse/GridManager.cs of libopenmetaverse.
|
||||
if (itemtype == (uint)OpenMetaverse.GridItemType.LandForSale)
|
||||
{
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
//The flags are: SortAsc (1 << 15), PerMeterSort (1 << 17)
|
||||
["flags"] = "163840",
|
||||
["type"] = "4294967295", //This is -1 in 32 bits
|
||||
["price"] = "0",
|
||||
["area"] = "0",
|
||||
["query_start"] = "0"
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_land_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
|
||||
List<mapItemReply> mapitems = new();
|
||||
string ParcelRegionUUID;
|
||||
string[] landingpoint;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
if (d["name"] is null)
|
||||
continue;
|
||||
|
||||
mapItemReply mapitem = new();
|
||||
|
||||
ParcelRegionUUID = d["region_UUID"].ToString();
|
||||
|
||||
foreach (Scene scene in m_Scenes)
|
||||
{
|
||||
if (scene.RegionInfo.RegionID.ToString() == ParcelRegionUUID)
|
||||
{
|
||||
landingpoint = d["landing_point"].ToString().Split('/');
|
||||
|
||||
mapitem.x = (uint)((scene.RegionInfo.RegionLocX * 256) +
|
||||
Convert.ToDecimal(landingpoint[0]));
|
||||
mapitem.y = (uint)((scene.RegionInfo.RegionLocY * 256) +
|
||||
Convert.ToDecimal(landingpoint[1]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mapitem.id = new UUID(d["parcel_id"].ToString());
|
||||
mapitem.Extra = Convert.ToInt32(d["area"]);
|
||||
mapitem.Extra2 = Convert.ToInt32(d["sale_price"]);
|
||||
mapitem.name = d["name"].ToString();
|
||||
|
||||
mapitems.Add(mapitem);
|
||||
}
|
||||
|
||||
remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
|
||||
mapitems.Clear();
|
||||
}
|
||||
|
||||
if (itemtype == (uint)OpenMetaverse.GridItemType.PgEvent ||
|
||||
itemtype == (uint)OpenMetaverse.GridItemType.MatureEvent ||
|
||||
itemtype == (uint)OpenMetaverse.GridItemType.AdultEvent)
|
||||
{
|
||||
|
||||
//Find the maturity level
|
||||
int maturity = (1 << 24);
|
||||
|
||||
//Find the maturity level
|
||||
if (itemtype == (uint)OpenMetaverse.GridItemType.MatureEvent)
|
||||
maturity = (1 << 25);
|
||||
else
|
||||
{
|
||||
if (itemtype == (uint)OpenMetaverse.GridItemType.AdultEvent)
|
||||
maturity = (1 << 26);
|
||||
}
|
||||
|
||||
//The flags are: SortAsc (1 << 15), PerMeterSort (1 << 17)
|
||||
maturity |= 163840;
|
||||
|
||||
//When character before | is a u get upcoming/in-progress events
|
||||
//Character before | is number of days before/after current date
|
||||
//Characters after | is the number for a category
|
||||
Hashtable ReqHash = new()
|
||||
{
|
||||
["text"] = "u|0",
|
||||
["flags"] = maturity.ToString(),
|
||||
["query_start"] = "0"
|
||||
};
|
||||
|
||||
Hashtable result = GenericXMLRPCRequest(ReqHash, "dir_events_query");
|
||||
|
||||
if (!Convert.ToBoolean(result["success"]))
|
||||
{
|
||||
remoteClient.SendAgentAlertMessage(result["errorMessage"].ToString(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList dataArray = (ArrayList)result["data"];
|
||||
|
||||
List<mapItemReply> mapitems = new();
|
||||
int event_id;
|
||||
string[] landingpoint;
|
||||
|
||||
foreach (Object o in dataArray)
|
||||
{
|
||||
Hashtable d = (Hashtable)o;
|
||||
|
||||
if (d["name"] is null)
|
||||
continue;
|
||||
|
||||
mapItemReply mapitem = new();
|
||||
|
||||
//Events use a comma separator in the landing point
|
||||
landingpoint = d["landing_point"].ToString().Split(',');
|
||||
mapitem.x = Convert.ToUInt32(landingpoint[0]);
|
||||
mapitem.y = Convert.ToUInt32(landingpoint[1]);
|
||||
|
||||
//This is a crazy way to pass the event ID back to the
|
||||
//viewer but that is the way it wants the information.
|
||||
event_id = Convert.ToInt32(d["event_id"]);
|
||||
mapitem.id = new UUID("00000000-0000-0000-0000-0000" +
|
||||
event_id.ToString("X8"));
|
||||
|
||||
mapitem.Extra = Convert.ToInt32(d["unix_time"]);
|
||||
mapitem.Extra2 = 0; //FIXME: No idea what to do here
|
||||
mapitem.name = d["name"].ToString();
|
||||
|
||||
mapitems.Add(mapitem);
|
||||
}
|
||||
|
||||
remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
|
||||
mapitems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
30
addon-modules/OpenSimSearch/prebuild.xml
Normal file
30
addon-modules/OpenSimSearch/prebuild.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project name="OpenSimSearch.Modules" path="addon-modules/OpenSimSearch/Modules" type="Library" version="0.5.0-$Rev$" frameworkVersion="net8_0">
|
||||
<Configuration name="Debug">
|
||||
<Options>
|
||||
<OutputPath>../../../bin/</OutputPath>
|
||||
</Options>
|
||||
</Configuration>
|
||||
<Configuration name="Release">
|
||||
<Options>
|
||||
<OutputPath>../../../bin/</OutputPath>
|
||||
</Options>
|
||||
</Configuration>
|
||||
|
||||
<ReferencePath>../../../bin/</ReferencePath>
|
||||
<Reference name="OpenMetaverseTypes"/>
|
||||
<Reference name="OpenMetaverse"/>
|
||||
<Reference name="OpenSim.Framework"/>
|
||||
<Reference name="OpenSim.Region.Framework"/>
|
||||
<Reference name="OpenSim.Services.Interfaces"/>
|
||||
<Reference name="Nini"/>
|
||||
<Reference name="log4net"/>
|
||||
<Reference name="XMLRPC"/>
|
||||
<Reference name="Mono.Addins"/>
|
||||
|
||||
<Files>
|
||||
<Match pattern="*.cs" recurse="true">
|
||||
<Exclude name="obj" pattern="obj"/>
|
||||
</Match>
|
||||
</Files>
|
||||
</Project>
|
||||
BIN
bin/CSJ2K.dll
BIN
bin/CSJ2K.dll
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
<configuration>
|
||||
<dllmap os="windows" cpu="x86-64" dll="sqlite3" target="lib64/sqlite3.dll" />
|
||||
<dllmap os="!windows,osx" cpu="x86-64" dll="sqlite3" target="lib64/libsqlite3_64.so" />
|
||||
<dllmap os="!windows,osx" cpu="arm64" dll="sqlite3" target="lib64/libsqlite3-arm64.so" />
|
||||
</configuration>
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
; from outside their networks, using firewalls
|
||||
PrivatePort = "8003"
|
||||
|
||||
|
||||
|
||||
[Startup]
|
||||
;# {ConsolePrompt} {} {ConsolePrompt} {} "Region (\R) "
|
||||
;; Console prompt
|
||||
@@ -103,7 +101,7 @@
|
||||
;# {ConsoleHistoryTimeStamp} {} {Time stamp commands in history file} {} false
|
||||
;; Time stamp commands in history file (default false)
|
||||
; ConsoleHistoryTimeStamp = false
|
||||
|
||||
|
||||
;# {save_crashes} {} {Save crashes to disk?} {true false} false
|
||||
;; Set this to true if you want to log crashes to disk
|
||||
;; this can be useful when submitting bug reports.
|
||||
@@ -268,7 +266,7 @@
|
||||
;# {DefaultScriptEngine} {} {Default script engine} {YEngine}
|
||||
;; Default script engine to use. Currently, we only have YEngine
|
||||
; DefaultScriptEngine = "YEngine"
|
||||
|
||||
|
||||
;# {HttpProxy} {} {Proxy URL for llHTTPRequest and dynamic texture loading} {} http://proxy.com:8080
|
||||
;; Http proxy setting for llHTTPRequest and dynamic texture loading, if
|
||||
;; required
|
||||
@@ -283,8 +281,11 @@
|
||||
; HttpProxyExceptions = ".mydomain.com;localhost"
|
||||
|
||||
;# {emailmodule} {} {Provide llEmail and llGetNextEmail functionality? (requires SMTP server)} {DefaultEmailModule} none
|
||||
;; The email module requires some configuration. It needs an SMTP
|
||||
;; server to send mail through.
|
||||
;; By default email only works within the boundaries of a sim
|
||||
;; For intersim mail, all sims involved need to use the same IMAP mailbox
|
||||
;; and enableEmailToExternalObjects = true in [Email]
|
||||
;; If you want to also allow mail to enter and leave the grid, SMTP will
|
||||
;; also need to be setup
|
||||
; emailmodule = DefaultEmailModule
|
||||
|
||||
;# {SpawnPointRouting} {} {Set routing method for Telehub Spawnpoints} {closest random sequence} closest
|
||||
@@ -364,12 +365,9 @@
|
||||
;; you can also bypass the hostname or domain verification
|
||||
;# {NoVerifyCertHostname} {} {do not verify SSL Cert name versus peer name} {true false} true
|
||||
; NoVerifyCertHostname = true
|
||||
;; having both options true does provide encryption but with lower security
|
||||
;; having both options true does provide encryption but with low security
|
||||
;; set both true if you don't care to use SSL, they are needed to contact regions or grids that do use it.
|
||||
|
||||
;; comma separated list of stun servers (in host:port format), for webrtc support, possible other uses in future
|
||||
StunServers = stun.l.google.com:19302, stun.cloudflare.com:3478
|
||||
|
||||
[AccessControl]
|
||||
;# {AllowedClients} {} {Bar (|) separated list of allowed clients} {}
|
||||
;; Bar (|) separated list of viewers which may gain access to the regions.
|
||||
@@ -392,6 +390,16 @@
|
||||
;;
|
||||
; DeniedClients = ""
|
||||
|
||||
; It is strongly recommended to block at least these viewers by uncommenting ;DeniedClients below:
|
||||
; The first 4 viewers are problematic/bugged official Firestorm releases,
|
||||
; the last 7 viewers are copybots pretending to be (a very outdated) Firestorm and Singularity
|
||||
; The + is needed for older OpenSim versions (<0.9.3.1), after the space before the version (it means 'one or more spaces')
|
||||
; DeniedClients = "Firestorm-Release(x64)? +4.7.7.48706|Firestorm-Release(x64)? +6.6.8.68380|Firestorm-Release(x64)? +6.6.16.70339|Firestorm-Releasex64 +7.1.9.74745|Firestorm-Release(x64)? +6.2.0|Firestorm-Release(x64)? +4.6.8.42696|Firestorm-Release(x64)? +6.3.2.58086|Firestorm-Release(x64)? +6.3.8.58105|Firestorm-Release(x64)? +6.4.13.63251|Firestorm-Release(x64)? +6.4.23.64823|Singularity Alpha( 64)? +1.8.7.7610"
|
||||
|
||||
;# {ViewerDeniedMsg} {} {A message string} {"Access denied, your viewer is banned"}
|
||||
; The message people see who attempt to teleport with a denied viewer
|
||||
;ViewerDeniedMsg = "Access denied, your viewer is banned"
|
||||
|
||||
|
||||
[Map]
|
||||
;# {GenerateMaptiles} {} {Generate map tiles?} {true false} true
|
||||
@@ -471,27 +479,20 @@
|
||||
;; If set to true, then all permissions checks are carried out
|
||||
; serverside_object_permissions = true
|
||||
|
||||
; if next 2 are false, several admin powers are only active if god powers requested on viewer
|
||||
; this reduces mistakes
|
||||
; set both to true to enable previous behaviour
|
||||
; if next 2 are false, several admin powers are only active if god powers requested on viewer
|
||||
; this reduces mistakes
|
||||
; set both to true to enable previous behaviour
|
||||
automatic_gods = false
|
||||
implicit_gods = false
|
||||
|
||||
;# {allow_grid_gods} {} {Allow grid gods?} {true false} false
|
||||
|
||||
;# {allow_grid_gods} {} {Allow grid gods?} {true false} false
|
||||
;; This allows users with a UserLevel of 200 or more to assume god
|
||||
;; powers in the regions in this simulator.
|
||||
;; if you don't trust grid admins, what are you doing there?
|
||||
;; if you don't trust grid admins, what are you doing there?
|
||||
allow_grid_gods = true
|
||||
|
||||
;; This allows some control over permissions
|
||||
;; please note that this still doesn't duplicate SL, and is not intended to
|
||||
;# {region_owner_is_god} {} {Allow region owner gods} {true false} true
|
||||
;; Allow region owners to assume god powers in their regions
|
||||
; region_owner_is_god = true
|
||||
|
||||
;# {region_manager_is_god} {} {Allow region manager gods} {true false} false
|
||||
;; Allow region managers to assume god powers in regions they manage
|
||||
; region_manager_is_god = false
|
||||
|
||||
;# {simple_build_permissions} {} {Allow building in parcel by access list (no groups)} {true false} false
|
||||
;; More control over permissions
|
||||
@@ -503,6 +504,9 @@
|
||||
;; without having to use the Groups feature
|
||||
; simple_build_permissions = false
|
||||
|
||||
;# {take_copy_restricted} {} {Disallow "take copy"} {true false} false
|
||||
;; Disallow "take copy" of ANY object set as "Allow anyone to take a copy"
|
||||
;take_copy_restricted = false
|
||||
|
||||
[Estates]
|
||||
; If these values are commented out then the user will be asked for estate details when required (this is the normal case).
|
||||
@@ -535,18 +539,10 @@
|
||||
;; Password for the default estate owner
|
||||
; DefaultEstateOwnerPassword = password
|
||||
|
||||
[SMTP]
|
||||
;; The SMTP server enabled the email module to send email to external
|
||||
;; destinations.
|
||||
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable module?} {true false} false
|
||||
[Email]
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable module?} {true false} true
|
||||
;; Enable the email module
|
||||
; enabled = false
|
||||
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable send to objects to regions not on instance?} {true false} true
|
||||
;; Enable sending email to regions not on current opensimulator instance. Currently does not work
|
||||
; enableEmailToExternalObjects = true
|
||||
|
||||
; enabled = true
|
||||
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {maximum number of emails from a object owner per hour} {} 500
|
||||
; MailsFromOwnerPerHour = 500
|
||||
@@ -561,11 +557,11 @@
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {maximum number of emails to a SMTP address per hour} {} 10
|
||||
; MailsToSMTPAddressPerHour = 10
|
||||
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable send to the world?} {true false} true
|
||||
;; Enable sending email to the world.
|
||||
; enableEmailToSMTP = true
|
||||
|
||||
;# {internal_object_host} {[Startup]emailmodule:DefaultEmailModule enabled:true} {Host name to treat as internal (object to object) email?} {} lsl.opensim.local
|
||||
; Messages with this domain are considered grid-local
|
||||
; Internal messages don't use SMTP.
|
||||
; - If the destination exists on THIS sim, delivers directly
|
||||
; - If the destination exists on ANOTHER sim, delivers through IMAP
|
||||
; internal_object_host = lsl.opensim.local
|
||||
|
||||
;# {host_domain_header_from} {[Startup]emailmodule:DefaultEmailModule enabled:true} {From address to use in the sent email header?} {} 127.0.0.1
|
||||
@@ -573,6 +569,14 @@
|
||||
; if not set SMTP_SERVER_FROM below
|
||||
; host_domain_header_from = "127.0.0.1"
|
||||
|
||||
; SMTP configuration: (off-grid mail)
|
||||
;; The SMTP server enabled the email module to send email to external
|
||||
;; destinations.
|
||||
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable send to the world?} {true false} false
|
||||
;; Enable sending email to the world.
|
||||
; enableEmailToSMTP = false
|
||||
|
||||
;# {SMTP_SERVER_FROM} {[Startup]emailmodule:DefaultEmailModule enabled:true} {SMTP server from name?} {}
|
||||
; some smtp servers require a known From email address or will give Error 500 - Envelope from address is not authorised
|
||||
; set to a valid email address that smtp will accept (in some cases must be like SMTP_SERVER_LOGIN)
|
||||
@@ -605,6 +609,38 @@
|
||||
;# {SMTP_VerifyCertNames} {[Startup]emailmodule:DefaultEmailModule enabled:true} {SMTP Verify cert chain option} {} true
|
||||
; SMTP_VerifyCertNames = true
|
||||
|
||||
; Imap configuration: (grid-local mail)
|
||||
|
||||
;# {enabled} {[Startup]emailmodule:DefaultEmailModule} {Enable send to objects to regions not on instance?} {true false} false
|
||||
;; Enable sending email to regions not on current opensimulator instance.
|
||||
;; Requires a dedicated IMAP account, locally or remote
|
||||
; enableEmailToExternalObjects = false
|
||||
|
||||
;# {IMAP_SERVER_TLS} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP use TLS?} {} false
|
||||
; IMAP_SERVER_TLS = false
|
||||
|
||||
;# {IMAP_SERVER_HOSTNAME} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP server name?} {} 127.0.0.1
|
||||
; IMAP_SERVER_HOSTNAME = 127.0.0.1
|
||||
|
||||
;# {IMAP_SERVER_PORT} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP server port?} {} 143
|
||||
; If using IMAP with TLS, it is port 993
|
||||
; IMAP_SERVER_PORT = 143
|
||||
|
||||
;# {IMAP_SERVER_LOGIN} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP server user name?} {}
|
||||
; The user needs to have a Maildir folder in its homedirectory, with perms 700
|
||||
; This can be made with: maildirmake.courier ~/Maildir
|
||||
; IMAP_SERVER_LOGIN = foo
|
||||
|
||||
;# {IMAP_SERVER_PASSWORD} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP server password} {}
|
||||
; IMAP_SERVER_PASSWORD = bar
|
||||
|
||||
; using TLS and an IMAP server with a self signed certificate you will need to set next two options false
|
||||
;# {IMAP_VerifyCertChain} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP Verify cert chain option} {} true
|
||||
; IMAP_VerifyCertChain = true
|
||||
;# {IMAP_VerifyCertNames} {[Startup]emailmodule:DefaultEmailModule enabled:true} {IMAP Verify cert chain option} {} true
|
||||
; IMAP_VerifyCertNames = true
|
||||
|
||||
|
||||
[Network]
|
||||
;# {ConsoleUser} {} {User name for console account} {}
|
||||
;; Configure the remote console user here. This will not actually be used
|
||||
@@ -628,15 +664,15 @@
|
||||
; to use others like self signed certificates with those viewers,
|
||||
; their debug option NoVerifySSLCert needs to be set true, You need to inform users about this
|
||||
; the main unsecure port will still open for some services. this may change in future.
|
||||
|
||||
|
||||
; set http_listener_ssl to enable main server ssl. it will replace unsecure port on most functions
|
||||
;# {http_listener_ssl}{} {enable main server ssl port} {} false
|
||||
;http_listener_ssl = false
|
||||
|
||||
|
||||
; Set port for main SSL connections
|
||||
;# {http_listener_sslport}{} {main server ssl port} {} 9001
|
||||
;http_listener_sslport = 9001 ;
|
||||
|
||||
|
||||
; currently if using ssl, regions ExternalHostName must the the same and equal to http_listener_cn
|
||||
; this may be removed in future
|
||||
;# {http_listener_cn}{} {main server ssl externalHostName} {} ""
|
||||
@@ -690,8 +726,8 @@
|
||||
;# {ExternalHostNameForLSL} {} {Hostname to use for HTTP-IN URLs. This should be reachable from the internet.} {}
|
||||
;; Hostname to use in llRequestURL/llRequestSecureURL
|
||||
;; if not defined - llRequestURL/llRequestSecureURL are disabled
|
||||
;; this should be reachable from internet and point this machine
|
||||
;; for standalones it can be
|
||||
;; this should be reachable from internet and point this machine
|
||||
;; for standalones it can be
|
||||
ExternalHostNameForLSL = ${Const|BaseHostname}
|
||||
|
||||
;# {shard} {} {Name to use for X-Secondlife-Shard header? (press enter if unsure)} {} OpenSim
|
||||
@@ -1144,7 +1180,7 @@
|
||||
|
||||
[InterestManagement]
|
||||
;; This section controls how state updates are prioritized for each client
|
||||
|
||||
|
||||
;# {UpdatePrioritizationScheme} {} {Update prioritization scheme?} {BestAvatarResponsiveness SimpleAngularDistance} BestAvatarResponsiveness
|
||||
;; Valid values are BestAvatarResponsiveness and SimpleAngularDistance
|
||||
;; SimpleAngularDistance does use more cpu
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
; Time stamp commands in history file (default false)
|
||||
; ConsoleHistoryTimeStamp = false
|
||||
|
||||
|
||||
; Set this to true if you want to log crashes to disk
|
||||
; this can be useful when submitting bug reports.
|
||||
; However, this will only log crashes within OpenSimulator that cause the entire program to exit
|
||||
@@ -112,12 +112,12 @@
|
||||
;; Allow child agents to see into the region even if their root counterpart isn't allowed in here
|
||||
see_into_region = true
|
||||
|
||||
;; use legacy math for sittarget offset "correction"
|
||||
;; set this option to false to use improved math more compatibility with SL.
|
||||
;; keep it true if you have many old objects with sits set by scripts.
|
||||
;; the offset in question is added to the SitTarget to find the sitting avatar position.
|
||||
;; acording to its size, etc.
|
||||
; LegacySitOffsets = true
|
||||
;; use legacy math for sittarget offset "correction"
|
||||
;; set this option to false to use improved math more compatibility with SL.
|
||||
;; keep it true if you have many old objects with sits set by scripts.
|
||||
;; the offset in question is added to the SitTarget to find the sitting avatar position.
|
||||
;; acording to its size, etc.
|
||||
; LegacySitOffsets = true
|
||||
|
||||
; Maximum number of position, rotation and scale changes for each prim that the simulator will store for later undos
|
||||
; Increasing this number will increase memory usage.
|
||||
@@ -176,22 +176,22 @@
|
||||
InworldRestartShutsDown = false
|
||||
|
||||
; Use of normalized 55FPS statistics
|
||||
; Opensim does not have a frame rate control like other simulators.
|
||||
; Most parameters that control timing can be configurable region by region.
|
||||
; To achieve closer compatibility with values expected by viewers, scripts and users
|
||||
; some parameters are converted to a equivalent per frame value.
|
||||
; Additionally, they are scaled to values they would have on a system running at a nominal 55 frames per second rate.
|
||||
; The scale factor it 55 * FrameTime, corresponding to 5 with default configuration
|
||||
; You can choose to show the true physics FPS to viewers by setting Normalized55FPS to false.
|
||||
; Normalized55FPS = true
|
||||
; Opensim does not have a frame rate control like other simulators.
|
||||
; Most parameters that control timing can be configurable region by region.
|
||||
; To achieve closer compatibility with values expected by viewers, scripts and users
|
||||
; some parameters are converted to a equivalent per frame value.
|
||||
; Additionally, they are scaled to values they would have on a system running at a nominal 55 frames per second rate.
|
||||
; The scale factor it 55 * FrameTime, corresponding to 5 with default configuration
|
||||
; You can choose to show the true physics FPS to viewers by setting Normalized55FPS to false.
|
||||
; Normalized55FPS = true
|
||||
|
||||
; Main Frame time
|
||||
; This defines the rate of several simulation events.
|
||||
; Default value should meet most needs.
|
||||
; It can be reduced to improve the simulation of moving objects, with possible increase of cpu and network loads.
|
||||
; It should not be less than the physics engine step time.
|
||||
; Being a integer multiple of it may reduce some jitter in reported physics FPS.
|
||||
; changing this value, you need to change some of the following *EveryNFrames so their actions timing remains the same
|
||||
; This defines the rate of several simulation events.
|
||||
; Default value should meet most needs.
|
||||
; It can be reduced to improve the simulation of moving objects, with possible increase of cpu and network loads.
|
||||
; It should not be less than the physics engine step time.
|
||||
; Being a integer multiple of it may reduce some jitter in reported physics FPS.
|
||||
; changing this value, you need to change some of the following *EveryNFrames so their actions timing remains the same
|
||||
FrameTime = 0.0909
|
||||
|
||||
; The values below represent the percentage of the target frame time that,
|
||||
@@ -314,9 +314,12 @@
|
||||
; ##
|
||||
; ## EMAIL MODULE
|
||||
; ##
|
||||
;; The email module requires some configuration. It needs an SMTP
|
||||
;; server to send mail through.
|
||||
;emailmodule = DefaultEmailModule
|
||||
;; By default email only works within the boundaries of a sim
|
||||
;; For intersim mail, all sims involved need to use the same IMAP mailbox
|
||||
;; and enableEmailToExternalObjects = true in [Email]
|
||||
;; If you want to also allow mail to enter and leave the grid, SMTP will
|
||||
;; also need to be setup
|
||||
emailmodule = DefaultEmailModule
|
||||
|
||||
; ##
|
||||
; ## ANIMATIONS
|
||||
@@ -443,7 +446,7 @@
|
||||
; #
|
||||
; # SSL certificates validation options
|
||||
; #
|
||||
|
||||
|
||||
; SSL certificate validation options
|
||||
; you can allow selfsigned certificates or no official CA with next option set to true
|
||||
; NoVerifyCertChain = true
|
||||
@@ -491,18 +494,21 @@
|
||||
|
||||
; Attempt to render meshes and sculpties on the map
|
||||
RenderMeshes = false
|
||||
|
||||
; warp3D rendering height limits for prims (relative to rez position not bounding box)
|
||||
; prims above RenderMaxHeight are excluded
|
||||
; valid values: 100 to 4086
|
||||
;RenderMaxHeight = 4086
|
||||
|
||||
; prims below RenderMinHeight are excluded
|
||||
; valid values: -100 to RenderMaxHeight - 10
|
||||
;RenderMinHeight = -100
|
||||
|
||||
|
||||
; warp3D rendering height limits for prims (relative to rez position not bounding box)
|
||||
; prims above RenderMaxHeight are excluded
|
||||
; valid values: 100 to 4086
|
||||
;RenderMaxHeight = 4086
|
||||
|
||||
; prims below RenderMinHeight are excluded
|
||||
; valid values: -100 to RenderMaxHeight - 10
|
||||
;RenderMinHeight = -100
|
||||
|
||||
; Show NPCs as green world map dots
|
||||
;ShowNPCs = true
|
||||
|
||||
; Where to store generated map tiles in the form of MAP-RegionUUID.png
|
||||
;GenTilesDirectory = "."
|
||||
|
||||
[Permissions]
|
||||
; ##
|
||||
@@ -525,15 +531,9 @@
|
||||
|
||||
; This allows grid users with a UserLevel of 200 or more to assume god
|
||||
; powers in the regions in this simulator.
|
||||
; if you don't trust grid admins, what are you doing there?
|
||||
; if you don't trust grid admins, what are you doing there?
|
||||
allow_grid_gods = true
|
||||
|
||||
; Allow region owners to assume god powers in their regions
|
||||
;region_owner_is_god = true
|
||||
|
||||
; Allow region managers to assume god powers in regions they manage
|
||||
;region_manager_is_god = false
|
||||
|
||||
; God mode should be turned on in the viewer whenever
|
||||
; the user has god rights somewhere. They may choose
|
||||
; to turn it off again, though.
|
||||
@@ -572,6 +572,8 @@
|
||||
; Minimum user level required to upload assets
|
||||
;LevelUpload = 0
|
||||
|
||||
; Disallow "take copy" of ANY object set as "Allow anyone to take a copy"
|
||||
;take_copy_restricted = false
|
||||
|
||||
[RegionReady]
|
||||
; Enable this module to get notified once all items and scripts in the region have been completely loaded and compiled
|
||||
@@ -621,11 +623,13 @@
|
||||
; AllowUserProfileWebURLs = true
|
||||
|
||||
|
||||
[SMTP]
|
||||
enabled = false
|
||||
[Email]
|
||||
enabled = true
|
||||
|
||||
;enabled = true
|
||||
;internal_object_host = lsl.opensim.local
|
||||
|
||||
;enableEmailToSMTP = false
|
||||
;host_domain_header_from = 127.0.0.1
|
||||
;SMTP_SERVER_FROM = ""
|
||||
;SMTP_SERVER_HOSTNAME = 127.0.0.1
|
||||
@@ -636,6 +640,16 @@
|
||||
;SMTP_VerifyCertChain = true
|
||||
;SMTP_VerifyCertNames = true
|
||||
|
||||
;enableEmailToExternalObjects = false
|
||||
;IMAP_SERVER_HOSTNAME = 127.0.0.1
|
||||
;IMAP_SERVER_PORT = 143
|
||||
;IMAP_SERVER_TLS = false
|
||||
;IMAP_SERVER_LOGIN = foo
|
||||
;IMAP_SERVER_PASSWORD = bar
|
||||
;IMAP_VerifyCertChain = true
|
||||
;IMAP_VerifyCertNames = true
|
||||
|
||||
|
||||
[Network]
|
||||
ConsoleUser = "Test"
|
||||
ConsolePass = "secret"
|
||||
@@ -645,12 +659,12 @@
|
||||
; ssl config: Experimental!
|
||||
http_listener_ssl = false ; if set to true main server is replaced by a ssl one
|
||||
http_listener_sslport = 9001 ; Use this port for SSL connections
|
||||
; currently if using ssl, regions ExternalHostName must the the same and equal to http_listener_cn
|
||||
; this will change is future
|
||||
; currently if using ssl, regions ExternalHostName must the the same and equal to http_listener_cn
|
||||
; this will change is future
|
||||
http_listener_cn = "myRegionsExternalHostName"
|
||||
; if the cert doesnt have a oficial CA or is selfsigned viewers option NoVerifySSLCert need to be set true
|
||||
; if the cert doesnt have a oficial CA or is selfsigned viewers option NoVerifySSLCert need to be set true
|
||||
http_listener_cert_path = "mycert.p12" ; path for the cert file that is valid for the ExternalHostName
|
||||
http_listener_cert_pass = "mycertpass" ; the cert passwork
|
||||
http_listener_cert_pass = "mycertpass" ; the cert passwork
|
||||
|
||||
; addicional HTTPS for "Out of band" management applications such as the remote
|
||||
; admin module or scripts
|
||||
@@ -673,8 +687,8 @@
|
||||
; HttpBodyMaxLenMAX=16384
|
||||
|
||||
; Hostname to use in llRequestURL/llRequestSecureURL
|
||||
; must be a valid hostname for the ssl cert.
|
||||
; if not defined - llRequestURL/llRequestSecureURL are disabled
|
||||
; must be a valid hostname for the ssl cert.
|
||||
; if not defined - llRequestURL/llRequestSecureURL are disabled
|
||||
; ExternalHostNameForLSL=127.0.0.1
|
||||
|
||||
; Disallow the following address ranges for user scripting calls (e.g. llHttpRequest())
|
||||
@@ -714,32 +728,54 @@
|
||||
;MaxRequestConcurrency = 30
|
||||
|
||||
[ScriptsHttpRequestModule]
|
||||
; options for llHttpRequest
|
||||
|
||||
; max number of concurrent connections per instance (all scenes), default 8
|
||||
; MaxPoolThreads = 8
|
||||
|
||||
; options for llHttpRequest
|
||||
|
||||
; max number of concurrent connections per instance (all scenes), default 8
|
||||
; MaxPoolThreads = 8
|
||||
|
||||
; max requests per second for all scripts on a prim, default 1
|
||||
;PrimRequestsPerSec = 1.0
|
||||
; initial unthrottled burst for all scripts on a prim, default 3
|
||||
; initial unthrottled burst for all scripts on a prim, default 3
|
||||
;PrimRequestsBurst = 3.0
|
||||
|
||||
; max requests per second for the objects owner (per instance), default 25
|
||||
;PrimOwnerRequestsPerSec = 25.0
|
||||
; initial unthrottled burst for the objects owner (per instance), default 5
|
||||
; initial unthrottled burst for the objects owner (per instance), default 5
|
||||
;PrimOwnerRequestsBurst = 5.0
|
||||
|
||||
; requests timeout in miliseconds, range 200 to 60000, default 30000
|
||||
|
||||
; requests timeout in miliseconds, range 200 to 60000, default 30000
|
||||
;RequestsTimeOut = 30000
|
||||
|
||||
|
||||
[AccessControl]
|
||||
; Viewer-based access control. |-separated list of allowed viewers.
|
||||
;# {AllowedClients} {} {Bar (|) separated list of allowed clients} {}
|
||||
;; Bar (|) separated list of viewers which may gain access to the regions.
|
||||
;; One can use a substring of the viewer name to enable only certain
|
||||
;; versions
|
||||
;; Example: Agent uses the viewer "Imprudence 1.3.2.0"
|
||||
;; - "Imprudence" has access
|
||||
;; - "Imprudence 1.3" has access
|
||||
;; - "Imprudence 1.3.1" has no access
|
||||
; AllowedClients = ""
|
||||
|
||||
; Viewer-based access control. |-separated list of denied viewers.
|
||||
; No restrictions by default.
|
||||
;# {DeniedClients} {} {Bar (|) separated list of denied clients} {}
|
||||
;; Bar (|) separated list of viewers which may not gain access to the regions.
|
||||
;; One can use a Substring of the viewer name to disable only certain
|
||||
;; versions
|
||||
;; Example: Agent uses the viewer "Imprudence 1.3.2.0"
|
||||
;; - "Imprudence" has no access
|
||||
;; - "Imprudence 1.3" has no access
|
||||
;; - "Imprudence 1.3.1" has access
|
||||
; DeniedClients = ""
|
||||
|
||||
; It is strongly recommended to block at least these viewers by uncommenting ;DeniedClients below:
|
||||
; The first 4 viewers are problematic/bugged official Firestorm releases,
|
||||
; the last 7 viewers are copybots pretending to be (a very outdated) Firestorm and Singularity
|
||||
; The + is needed for older OpenSim versions (<0.9.3.1), after the space before the version (it means 'one or more spaces')
|
||||
; DeniedClients = "Firestorm-Release(x64)? +4.7.7.48706|Firestorm-Release(x64)? +6.6.8.68380|Firestorm-Release(x64)? +6.6.16.70339|Firestorm-Releasex64 +7.1.9.74745|Firestorm-Release(x64)? +6.2.0|Firestorm-Release(x64)? +4.6.8.42696|Firestorm-Release(x64)? +6.3.2.58086|Firestorm-Release(x64)? +6.3.8.58105|Firestorm-Release(x64)? +6.4.13.63251|Firestorm-Release(x64)? +6.4.23.64823|Singularity Alpha( 64)? +1.8.7.7610"
|
||||
|
||||
;# {ViewerDeniedMsg} {} {A message string} {"Access denied, your viewer is banned"}
|
||||
; The message people see who attempt to teleport with a denied viewer
|
||||
;ViewerDeniedMsg = "Access denied, your viewer is banned"
|
||||
|
||||
[ClientStack.LindenUDP]
|
||||
; Maximum outbound bytes per second for a single scene. This can be used to
|
||||
@@ -799,11 +835,11 @@
|
||||
;
|
||||
;PausedAckTimeout = 300
|
||||
|
||||
; Support viewers object cache, default true
|
||||
; users may need to reduce viewer bandwitdh if some prims or terrain parts fail to rez.
|
||||
; change to false if you need to use old viewers that do not support this feature
|
||||
;
|
||||
; SupportViewerObjectsCache = true
|
||||
; Support viewers object cache, default true
|
||||
; users may need to reduce viewer bandwitdh if some prims or terrain parts fail to rez.
|
||||
; change to false if you need to use old viewers that do not support this feature
|
||||
;
|
||||
; SupportViewerObjectsCache = true
|
||||
|
||||
[ClientStack.LindenCaps]
|
||||
;; Long list of capabilities taken from
|
||||
@@ -904,9 +940,9 @@
|
||||
; Allow avatars to cross into and out of the region.
|
||||
AllowAvatarCrossing = true
|
||||
|
||||
; This disables border transfers for objects. When true, objects can be placed outside
|
||||
; the region's border without being transferred to another simulator.
|
||||
DisableObjectTransfer = false
|
||||
; This disables border transfers for objects. When true, objects can be placed outside
|
||||
; the region's border without being transferred to another simulator.
|
||||
DisableObjectTransfer = false
|
||||
|
||||
; Minimum user level required for HyperGrid teleports
|
||||
LevelHGTeleport = 0
|
||||
@@ -916,10 +952,10 @@
|
||||
; Disabling cancellation can be okay in small closed grids where all teleports are highly likely to suceed.
|
||||
DisableInterRegionTeleportCancellation = false
|
||||
|
||||
;; This option exists to control the behavior of teleporting gods into places that have landing points
|
||||
;; and telehubs. Historically, there has been a difference: OpenSim (OS) has honored landing points and telehubs even for
|
||||
;; avatars with god permissions; SL lets gods land wherever they want.
|
||||
LandingPointBehavior = LandingPointBehavior_OS
|
||||
;; This option exists to control the behavior of teleporting gods into places that have landing points
|
||||
;; and telehubs. Historically, there has been a difference: OpenSim (OS) has honored landing points and telehubs even for
|
||||
;; avatars with god permissions; SL lets gods land wherever they want.
|
||||
LandingPointBehavior = LandingPointBehavior_OS
|
||||
|
||||
|
||||
[Messaging]
|
||||
@@ -1063,14 +1099,14 @@
|
||||
avatar_terminal_velocity = 54
|
||||
|
||||
; World Step size.
|
||||
; with legacy ODE this value needs to be close to 0.02s
|
||||
; with ubOde this value can be reduced to improve simulation quality with the cost of higher cpu load
|
||||
; you will need to test acording to you needs
|
||||
; choosing a value that is a integer sub multiple of FrameRate reduces some jitter on reported physics FPS
|
||||
; with legacy ODE this value needs to be close to 0.02s
|
||||
; with ubOde this value can be reduced to improve simulation quality with the cost of higher cpu load
|
||||
; you will need to test acording to you needs
|
||||
; choosing a value that is a integer sub multiple of FrameRate reduces some jitter on reported physics FPS
|
||||
world_stepsize = 0.01818
|
||||
; number of iterations of constrains solver, higher should improve results
|
||||
; up to a point where acumulated math errors eliminate the improvement
|
||||
; more steps may increase CPU load. No real gain in changing
|
||||
; number of iterations of constrains solver, higher should improve results
|
||||
; up to a point where acumulated math errors eliminate the improvement
|
||||
; more steps may increase CPU load. No real gain in changing
|
||||
world_solver_iterations = 10
|
||||
|
||||
|
||||
@@ -1707,13 +1743,13 @@
|
||||
;PriceObjectRent = 0
|
||||
;PriceObjectScaleFactor = 10
|
||||
;PriceParcelRent = 0
|
||||
|
||||
; Mesh upload settings, independent of economymodule
|
||||
|
||||
; Create inventory entries for textures uploaded with a model
|
||||
; default is false, ie, do not create
|
||||
; MeshModelAllowTextureToInventory = true
|
||||
|
||||
|
||||
; Mesh upload settings, independent of economymodule
|
||||
|
||||
; Create inventory entries for textures uploaded with a model
|
||||
; default is false, ie, do not create
|
||||
; MeshModelAllowTextureToInventory = true
|
||||
|
||||
|
||||
[YEngine]
|
||||
;; implements non preemptive microthreading, so fixing problems like llSleep or long events handlers
|
||||
@@ -1721,10 +1757,10 @@
|
||||
;; warning: scripts state is lost on TP or cross to Xengine regions (cars stop, etc)
|
||||
;; ignore its extensions (subset of original XMRengine), those are still undefined.
|
||||
Enabled = true
|
||||
|
||||
|
||||
; maximum stack a script can use in KB
|
||||
;ScriptStackSize = 2048
|
||||
|
||||
|
||||
; maximum heap memory a script can use in KB
|
||||
;ScriptHeapSize = 1024
|
||||
|
||||
@@ -1776,6 +1812,15 @@
|
||||
; scripts states and cache parent folder location
|
||||
;ScriptEnginesPath="ScriptEngines"
|
||||
|
||||
; Used in inventory offer messages to show the giving object origin
|
||||
; To make /decline work for RLVa inventory offers, this has to
|
||||
; start with "http://slurl", however a webpage does not need to
|
||||
; exist at given location. Your grid may choose to host a slurl.com-like
|
||||
; webmap if desired, for example at http://slurl.yourgrid.com
|
||||
; The dialog will display it inworld as: SlurlPrefix/regionname/x/y/z/
|
||||
; In SL the prefix is: http://slurl.com/secondlife
|
||||
;SlurlPrefix="http://slurl.opensim.local"
|
||||
|
||||
|
||||
[Concierge]
|
||||
; Enable concierge module
|
||||
@@ -2037,18 +2082,18 @@
|
||||
LimitParcelLayerUpdateDistance = true
|
||||
ParcelLayerViewDistance = 128
|
||||
|
||||
; set this to false to not display parcel ban lines
|
||||
ShowParcelBansLines = true
|
||||
|
||||
; Parcel Bans max height above ground. Default 100m
|
||||
; range 20m to 5000m
|
||||
; BanLineSafeHeight = 100
|
||||
; set this to false to not display parcel ban lines
|
||||
ShowParcelBansLines = true
|
||||
|
||||
; setting the parcel to admin content, It works as setting the land to Linden content in SL.
|
||||
; To use it, activate god mode in the viewer, select the parcel and go to: Admin -> Parcel -> Set to Linden Content ( CTRL+ALT+SHIFT+C)
|
||||
; DefaultAdministratorParcelName = "Admin Parcel"
|
||||
; DefaultAdministratorGroupUUID = "00000000-0000-0000-0000-000000000000"
|
||||
; DefaultAdministratorOwnerUUID = "00000000-0000-0000-0000-000000000000"
|
||||
; Parcel Bans max height above ground. Default 100m
|
||||
; range 20m to 5000m
|
||||
; BanLineSafeHeight = 100
|
||||
|
||||
; setting the parcel to admin content, It works as setting the land to Linden content in SL.
|
||||
; To use it, activate god mode in the viewer, select the parcel and go to: Admin -> Parcel -> Set to Linden Content ( CTRL+ALT+SHIFT+C)
|
||||
; DefaultAdministratorParcelName = "Admin Parcel"
|
||||
; DefaultAdministratorGroupUUID = "00000000-0000-0000-0000-000000000000"
|
||||
; DefaultAdministratorOwnerUUID = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
|
||||
;;
|
||||
@@ -2103,9 +2148,9 @@
|
||||
DwellModule = DefaultDwellModule
|
||||
|
||||
[ServerReleaseNotes]
|
||||
;; Comment or set to "" to disable
|
||||
ServerReleaseNotesURL = "http://opensimulator.org/wiki/0.9.3.1_Release"
|
||||
|
||||
;; Comment or set to "" to disable
|
||||
ServerReleaseNotesURL = "https://github.com/lickx/opensim-lickx"
|
||||
|
||||
[Modules]
|
||||
Include-modules = "addon-modules/*/config/*.ini"
|
||||
|
||||
|
||||
@@ -490,6 +490,7 @@
|
||||
[GridUserService]
|
||||
; for the server connector
|
||||
LocalServiceModule = "OpenSim.Services.UserAccountService.dll:GridUserService"
|
||||
;UseCache = true
|
||||
|
||||
|
||||
[AgentPreferencesService]
|
||||
@@ -500,6 +501,8 @@
|
||||
[PresenceService]
|
||||
; for the server connector
|
||||
LocalServiceModule = "OpenSim.Services.PresenceService.dll:PresenceService"
|
||||
;AllowDuplicatePresences = false
|
||||
;UseCache = true
|
||||
|
||||
[AvatarService]
|
||||
; for the server connector
|
||||
@@ -637,6 +640,8 @@
|
||||
|
||||
|
||||
[GridInfoService]
|
||||
GridService = "OpenSim.Services.GridService.dll:GridService"
|
||||
|
||||
; These settings are used to return information on a get_grid_info call.
|
||||
; Client launcher scripts and third-party clients make use of this to
|
||||
; autoconfigure the client and to provide a nice user experience. If you
|
||||
@@ -838,6 +843,7 @@
|
||||
UserAccountService = "OpenSim.Services.UserAccountService.dll:UserAccountService"
|
||||
GridService = "OpenSim.Services.GridService.dll:GridService"
|
||||
PresenceService = "OpenSim.Services.PresenceService.dll:PresenceService"
|
||||
GridUserService = "OpenSim.Services.UserAccountService.dll:GridUserService"
|
||||
|
||||
|
||||
[HGInstantMessageService]
|
||||
|
||||
@@ -431,6 +431,7 @@
|
||||
[GridUserService]
|
||||
; for the server connector
|
||||
LocalServiceModule = "OpenSim.Services.UserAccountService.dll:GridUserService"
|
||||
;UseCache = true
|
||||
|
||||
|
||||
[AgentPreferencesService]
|
||||
@@ -441,6 +442,8 @@
|
||||
[PresenceService]
|
||||
; for the server connector
|
||||
LocalServiceModule = "OpenSim.Services.PresenceService.dll:PresenceService"
|
||||
;AllowDuplicatePresences = false
|
||||
;UseCache = true
|
||||
|
||||
[AvatarService]
|
||||
; for the server connector
|
||||
@@ -558,6 +561,8 @@
|
||||
|
||||
|
||||
[GridInfoService]
|
||||
GridService = "OpenSim.Services.GridService.dll:GridService"
|
||||
|
||||
; These settings are used to return information on a get_grid_info call.
|
||||
; Client launcher scripts and third-party clients make use of this to
|
||||
; autoconfigure the client and to provide a nice user experience. If you
|
||||
|
||||
@@ -1858,6 +1858,14 @@
|
||||
<key>value</key><string>0x2</string>
|
||||
<key>tooltip</key><string>osTeleportObject flag: stop at jump point if tp fails</string>
|
||||
</map>
|
||||
<key>PAYMENT_INFO_ON_FILE</key><map>
|
||||
<key>type</key><string>integer</string>
|
||||
<key>value</key><string>1</string>
|
||||
</map>
|
||||
<key>PAYMENT_INFO_USED</key><map>
|
||||
<key>type</key><string>integer</string>
|
||||
<key>value</key><string>2</string>
|
||||
</map>
|
||||
<key>PARCEL_COUNT_GROUP</key><map>
|
||||
<key>type</key><string>integer</string>
|
||||
<key>value</key><string>2</string>
|
||||
@@ -5108,6 +5116,13 @@
|
||||
<map>
|
||||
<key>return</key><string>float</string>
|
||||
<key>arguments</key><undef/>
|
||||
<key>tooltip</key><string>Returns parcel time in seconds since midnight.\n- Sleep: 0 seconds.</string>
|
||||
</map>
|
||||
<key>llGetRegionTimeOfDay</key>
|
||||
<map>
|
||||
<key>return</key><string>float</string>
|
||||
<key>arguments</key><undef/>
|
||||
<key>tooltip</key><string>Returns region time in seconds since midnight.\n- Sleep: 0 seconds.</string>
|
||||
</map>
|
||||
<key>llGetTimestamp</key>
|
||||
<map>
|
||||
@@ -9548,5 +9563,12 @@
|
||||
<key>arguments</key><undef/>
|
||||
<key>tooltip</key><string>Returns the name of the currently enabled wind plugin.</string>
|
||||
</map>
|
||||
<key>lxGetAgentViewer</key>
|
||||
<map>
|
||||
<key>return</key><string>string</string>
|
||||
<key>arguments</key><array>
|
||||
<map><key>avkey</key><map><key>type</key><string>key</string></map></map>
|
||||
</array>
|
||||
</map>
|
||||
</map>
|
||||
</map></llsd>
|
||||
</map></llsd>
|
||||
|
||||
BIN
bin/assets/SoundsAssetSet/FSAlertSound.ogg
Normal file
BIN
bin/assets/SoundsAssetSet/FSAlertSound.ogg
Normal file
Binary file not shown.
@@ -161,4 +161,10 @@
|
||||
<Key Name="assetType" Value="1" />
|
||||
<Key Name="fileName" Value="OSSndWindowOpen.ogg" />
|
||||
</Section>
|
||||
<Section Name="FSAlertSound">
|
||||
<Key Name="assetID" Value="a3f48b85-c29f-1f97-ebb6-644b7c053512" />
|
||||
<Key Name="name" Value="FSAlertSound" />
|
||||
<Key Name="assetType" Value="1" />
|
||||
<Key Name="fileName" Value="FSAlertSound.ogg" />
|
||||
</Section>
|
||||
</Nini>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -270,14 +270,14 @@
|
||||
<Key Name="fileName" Value="water3.jp2" />
|
||||
</Section>
|
||||
<Section Name="Sea">
|
||||
<Key Name="assetID" Value="482E671B-9244-4B86-A036-68953A898D11" />
|
||||
<Key Name="assetID" Value="482e671b-9244-4b86-a036-68953a898d11" />
|
||||
<Key Name="name" Value="Sea" />
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="water3.jp2" />
|
||||
</Section>
|
||||
<Section Name="SeaWaterTrasp">
|
||||
<Section Name="SeaWaterTransp">
|
||||
<Key Name="assetID" Value="2bfd3884-7e27-69b9-ba3a-3e673f680004" />
|
||||
<Key Name="name" Value="SeaWaterTrasp" />
|
||||
<Key Name="name" Value="SeaWaterTransp" />
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="seaWaterTransp.j2c" />
|
||||
</Section>
|
||||
@@ -375,26 +375,51 @@
|
||||
</Section>
|
||||
|
||||
<Section Name="Terrain Dirt">
|
||||
<Key Name="assetID" Value="b8d3965a-ad78-bf43-699b-bff8eca6c975"/>
|
||||
<Key Name="assetID" Value="0bc58228-74a0-7e83-89bc-5c23464bcec5"/>
|
||||
<Key Name="name" Value="Terrain Dirt"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="0bc58228-74a0-7e83-89bc-5c23464bcec5.j2c" />
|
||||
</Section>
|
||||
<Section Name="Terrain Grass">
|
||||
<Key Name="assetID" Value="63338ede-0037-c4fd-855b-015d77112fc8"/>
|
||||
<Key Name="name" Value="Terrain Grass"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="63338ede-0037-c4fd-855b-015d77112fc8.j2c" />
|
||||
</Section>
|
||||
<Section Name="Terrain Mountain">
|
||||
<Key Name="assetID" Value="303cd381-8560-7579-23f1-f0a880799740"/>
|
||||
<Key Name="name" Value="Terrain Mountain"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="303cd381-8560-7579-23f1-f0a880799740.j2c" />
|
||||
</Section>
|
||||
<Section Name="Terrain Rock">
|
||||
<Key Name="assetID" Value="53a2f406-4895-1d13-d541-d2e3b86bc19c"/>
|
||||
<Key Name="name" Value="Terrain Rock"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="53a2f406-4895-1d13-d541-d2e3b86bc19c.j2c" />
|
||||
</Section>
|
||||
|
||||
<Section Name="Terrain Dirt (legacy)">
|
||||
<Key Name="assetID" Value="b8d3965a-ad78-bf43-699b-bff8eca6c975"/>
|
||||
<Key Name="name" Value="Terrain Dirt (legacy)"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="Terrain Dirt-b8d3965a-ad78-bf43-699b-bff8eca6c975.texture" />
|
||||
</Section>
|
||||
<Section Name="Terrain Grass">
|
||||
<Section Name="Terrain Grass (legacy)">
|
||||
<Key Name="assetID" Value="abb783e6-3e93-26c0-248a-247666855da3"/>
|
||||
<Key Name="name" Value="Terrain Grass"/>
|
||||
<Key Name="name" Value="Terrain Grass (legacy)"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="Terrain Grass-abb783e6-3e93-26c0-248a-247666855da3.texture" />
|
||||
</Section>
|
||||
<Section Name="Terrain Mountain">
|
||||
<Section Name="Terrain Mountain (legacy)">
|
||||
<Key Name="assetID" Value="179cdabd-398a-9b6b-1391-4dc333ba321f"/>
|
||||
<Key Name="name" Value="Terrain Mountain"/>
|
||||
<Key Name="name" Value="Terrain Mountain (legacy)"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="Terrain Mountain-179cdabd-398a-9b6b-1391-4dc333ba321f.texture" />
|
||||
</Section>
|
||||
<Section Name="Terrain Rock">
|
||||
<Section Name="Terrain Rock (legacy)">
|
||||
<Key Name="assetID" Value="beb169c7-11ea-fff2-efe5-0f24dc881df2"/>
|
||||
<Key Name="name" Value="Terrain Rock"/>
|
||||
<Key Name="name" Value="Terrain Rock (legacy)"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="Terrain Rock-beb169c7-11ea-fff2-efe5-0f24dc881df2.texture" />
|
||||
</Section>
|
||||
@@ -755,13 +780,13 @@
|
||||
Texture expected by viewers for properly displaying stars in the night sky
|
||||
Name derives from label in viewer code
|
||||
-->
|
||||
<Section Name="IMG_BLOOM1 Texture">
|
||||
<Section Name="IMG_BLOOM1">
|
||||
<Key Name="assetID" Value="3c59f7fe-9dc8-47f9-8aaf-a9dd1fbc3bef"/>
|
||||
<Key Name="name" Value="IMG_BLOOM1 Texture"/>
|
||||
<Key Name="name" Value="IMG_BLOOM1"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="IMG_BLOOM1.jp2" />
|
||||
<Key Name="fileName" Value="3c59f7fe-9dc8-47f9-8aaf-a9dd1fbc3bef.j2c" />
|
||||
</Section>
|
||||
|
||||
|
||||
<Section Name="Smoke">
|
||||
<Key Name="assetID" Value="b4ba225c-373f-446d-9f7e-6cb7b5cf9b3d"/>
|
||||
<Key Name="name" Value="Smoke"/>
|
||||
@@ -769,8 +794,39 @@
|
||||
<Key Name="fileName" Value="b4ba225c-373f-446d-9f7e-6cb7b5cf9b3d.j2c"/>
|
||||
</Section>
|
||||
|
||||
<!-- Sprite used to generate collision visual effect -->
|
||||
<Section Name="IMG_SHOT">
|
||||
<Key Name="assetID" Value="35f217a3-f618-49cf-bbca-c86d486551a9"/>
|
||||
<Key Name="name" Value="IMG_SHOT"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="35f217a3-f618-49cf-bbca-c86d486551a9.j2c" />
|
||||
</Section>
|
||||
|
||||
<!-- Not sure what this is used for but it looks like a smokey cloud in Photoshop -->
|
||||
<Section Name="IMG_DEFAULT">
|
||||
<Key Name="assetID" Value="d2114404-dd59-4a4d-8e6c-49359e91bbf0"/>
|
||||
<Key Name="name" Value="IMG_DEFAULT"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="d2114404-dd59-4a4d-8e6c-49359e91bbf0.j2c" />
|
||||
</Section>
|
||||
|
||||
<!-- Pre-EEP sun -->
|
||||
<Section Name="IMG_SUN">
|
||||
<Key Name="assetID" Value="cce0f112-878f-4586-a2e2-a8f104bba271"/>
|
||||
<Key Name="name" Value="IMG_SUN"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="sun.j2c" />
|
||||
</Section>
|
||||
|
||||
<Section Name="IMG_SMOKE_POOF">
|
||||
<Key Name="assetID" Value="1e63e323-5fe0-452e-92f8-b98bd0f764e3"/>
|
||||
<Key Name="name" Value="IMG_SMOKE_POOF"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="1e63e323-5fe0-452e-92f8-b98bd0f764e3.j2c" />
|
||||
</Section>
|
||||
|
||||
<Section Name="Water">
|
||||
<Key Name="assetID" Value="DB814CAF-26AC-4D75-977B-A68578FE643D"/>
|
||||
<Key Name="assetID" Value="db814caf-26ac-4d75-977b-a68578fe643d"/>
|
||||
<Key Name="name" Value="Water"/>
|
||||
<Key Name="assetType" Value="0" />
|
||||
<Key Name="fileName" Value="water.jp2" />
|
||||
|
||||
Binary file not shown.
BIN
bin/assets/TexturesAssetSet/sun.j2c
Normal file
BIN
bin/assets/TexturesAssetSet/sun.j2c
Normal file
Binary file not shown.
@@ -223,7 +223,7 @@
|
||||
Allow_osTeleportObject = ${OSSL|osslParcelO}ESTATE_MANAGER,ESTATE_OWNER
|
||||
|
||||
; ThreatLevel Severe with additional internal restrictions
|
||||
Allow_osGetAgentIP = true ; always restricted to Administrators (true or false to disable)
|
||||
Allow_osGetAgentIP = false ; always restricted to Administrators (true or false to disable)
|
||||
Allow_osSetContentType = false
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user