diff options
309 files changed, 21643 insertions, 1870 deletions
@@ -72,35 +72,35 @@ b53a0576eec80614d7767ed72b40ed67aeff27c9 DRTVWR-38_2.5.2-release 461c8c65b5c799ddfe365422f9be9c0095d91e7d 2.6.0-beta1-tip 9e4641f4a7870c0f565a25a2971368d5a29516a1 2.6.0-beta2 9e4641f4a7870c0f565a25a2971368d5a29516a1 DRTVWR-41_2.6.0-beta2 +42f32494bac475d0737799346f6831558ae8bf5d 2.6.0-release +42f32494bac475d0737799346f6831558ae8bf5d DRTVWR-39_2.6.0-release c5bdef3aaa2744626aef3c217ce29e1900d357b3 2.6.1-beta1 c5bdef3aaa2744626aef3c217ce29e1900d357b3 2.6.1-start c5bdef3aaa2744626aef3c217ce29e1900d357b3 DRTVWR-43_2.6.1-beta1 +c9182ed77d427c759cfacf49a7b71a2e20d522aa 2.6.1-release +c9182ed77d427c759cfacf49a7b71a2e20d522aa DRTVWR-42_2.6.1-release 56b2778c743c2a964d82e1caf11084d76a87de2c 2.6.2-start d1203046bb653b763f835b04d184646949d8dd5c 2.6.2-beta1 d1203046bb653b763f835b04d184646949d8dd5c DRTVWR-45_2.6.2-beta1 -42f32494bac475d0737799346f6831558ae8bf5d 2.6.0-release -42f32494bac475d0737799346f6831558ae8bf5d DRTVWR-39_2.6.0-release -c9182ed77d427c759cfacf49a7b71a2e20d522aa 2.6.1-release -c9182ed77d427c759cfacf49a7b71a2e20d522aa DRTVWR-42_2.6.1-release +214180ad5714ce8392b82bbebcc92f4babd98300 2.6.2-release +214180ad5714ce8392b82bbebcc92f4babd98300 DRTVWR-44_2.6.2-release 52b2263ab28f0976c689fd0b76c55a9eb027cdbf end-of-develop.py ec32f1045e7c2644015245df3a9933620aa194b8 2.6.3-start d7fcefabdf32bb61a9ea6d6037c1bb26190a85bc 2.6.3-beta1 d7fcefabdf32bb61a9ea6d6037c1bb26190a85bc DRTVWR-47_2.6.3-beta1 0630e977504af5ea320c58d33cae4e1ddee793e9 2.6.3-beta2 0630e977504af5ea320c58d33cae4e1ddee793e9 DRTVWR-48_2.6.3-beta2 +8f2da1701c81a62352df2b8d413d27fb2cade9a6 2.6.3-release +8f2da1701c81a62352df2b8d413d27fb2cade9a6 DRTVWR-46_2.6.3-release 3178e311da3a8739a85363665006ea3c4610cad4 dons-headless-hackathon-work -214180ad5714ce8392b82bbebcc92f4babd98300 2.6.2-release -214180ad5714ce8392b82bbebcc92f4babd98300 DRTVWR-44_2.6.2-release 7db558aaa7c176f2022b3e9cfe38ac72f6d1fccd 2.6.5-beta1 7db558aaa7c176f2022b3e9cfe38ac72f6d1fccd DRTVWR-50_2.6.5-beta1 -8f2da1701c81a62352df2b8d413d27fb2cade9a6 2.6.3-release -8f2da1701c81a62352df2b8d413d27fb2cade9a6 DRTVWR-46_2.6.3-release 800cefce8d364ffdd2f383cbecb91294da3ea424 2.6.6-start bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 2.6.6-beta1 bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 DRTVWR-52_2.6.6-beta1 -5e349dbe9cc84ea5795af8aeb6d473a0af9d4953 2.6.8-start dac76a711da5f1489a01c1fa62ec97d99c25736d 2.6.6-release dac76a711da5f1489a01c1fa62ec97d99c25736d DRTVWR-51_2.6.6-release +5e349dbe9cc84ea5795af8aeb6d473a0af9d4953 2.6.8-start beafa8a9bd1d1b670b7523d865204dc4a4b38eef 2.6.8-beta1 beafa8a9bd1d1b670b7523d865204dc4a4b38eef DRTVWR-55_2.6.8-beta1 be2000b946f8cb3de5f44b2d419287d4c48ec4eb 2.6.8-release @@ -119,50 +119,50 @@ e67da2c6e3125966dd49eef98b36317afac1fcfe 2.6.9-start 9f79a6ed8fdcd2f3dac33ea6b3236eeb278dccfe 2.7.2-start e0dc8b741eaa27dcdfbc9e956bb2579b954d15eb 2.7.2-beta1 e0dc8b741eaa27dcdfbc9e956bb2579b954d15eb DRTVWR-63_2.7.2-beta1 -6a3e7e403bd19e45fdfc2fcc716867af3ab80861 2.7.3-start fe3a8e7973072ea62043c08b19b66626c1a720eb 2.7.1-release fe3a8e7973072ea62043c08b19b66626c1a720eb 2.7.2-release fe3a8e7973072ea62043c08b19b66626c1a720eb DRTVWR-60_2.7.1-release fe3a8e7973072ea62043c08b19b66626c1a720eb DRTVWR-62_2.7.2-release +6a3e7e403bd19e45fdfc2fcc716867af3ab80861 2.7.3-start 6af10678de4736222b2c3f7e010e984fb5b327de 2.7.4-start be963a4eef635542f9617d7f5fd22ba48fb71958 2.7.4-beta1 be963a4eef635542f9617d7f5fd22ba48fb71958 DRTVWR-67_2.7.4-beta1 +057f319dd8eccdf63a54d99686c68cdcb31b6abc 2.7.4-release +057f319dd8eccdf63a54d99686c68cdcb31b6abc DRTVWR-66_2.7.4-release 19a498fa62570f352d7d246f17e3c81cc1d82d8b 2.7.5-start 09984bfa6cae17e0f72d02b75c1b7393c65eecfc 2.7.5-beta1 09984bfa6cae17e0f72d02b75c1b7393c65eecfc DRTVWR-69_2.7.5-beta1 +6866d9df6efbd441c66451debd376d21211de39c 2.7.5-release +6866d9df6efbd441c66451debd376d21211de39c DRTVWR-68_2.7.5-release e1ed60913230dd64269a7f7fc52cbc6004f6d52c 2.8.0-beta1 e1ed60913230dd64269a7f7fc52cbc6004f6d52c 2.8.0-start e1ed60913230dd64269a7f7fc52cbc6004f6d52c DRTVWR-71_2.8.0-beta1 -057f319dd8eccdf63a54d99686c68cdcb31b6abc 2.7.4-release -057f319dd8eccdf63a54d99686c68cdcb31b6abc DRTVWR-66_2.7.4-release -6866d9df6efbd441c66451debd376d21211de39c 2.7.5-release -6866d9df6efbd441c66451debd376d21211de39c DRTVWR-68_2.7.5-release +493d9127ee50e84ba08a736a65a23ca86f7a5b01 2.8.0-release +493d9127ee50e84ba08a736a65a23ca86f7a5b01 DRTVWR-70_2.8.0-release 502f6a5deca9365ddae57db4f1e30172668e171e 2.8.1-start 2c7e459e0c883f8e406b932e41e60097e9ee077e 2.8.1-beta1 2c7e459e0c883f8e406b932e41e60097e9ee077e DRTVWR-73_2.8.1-beta1 -493d9127ee50e84ba08a736a65a23ca86f7a5b01 2.8.0-release -493d9127ee50e84ba08a736a65a23ca86f7a5b01 DRTVWR-70_2.8.0-release -54bc7823ad4e3a436fef79710f685a7372bbf795 2.8.2-start -ac0f1a132d35c02a58861d37cca75b0429ac9137 2.8.3-start 29e93d7e19991011bd12b5748142b11a5dcb4370 2.8.1-release 29e93d7e19991011bd12b5748142b11a5dcb4370 DRTVWR-72_2.8.1-release 4780e3bd2b3042f91be3426151f28c30d199bb3b 2.8.1-hotfix 4780e3bd2b3042f91be3426151f28c30d199bb3b DRTVWR-76_2.8.1-hotfix +54bc7823ad4e3a436fef79710f685a7372bbf795 2.8.2-start +ac0f1a132d35c02a58861d37cca75b0429ac9137 2.8.3-start 599677276b227357140dda35bea4a2c18e2e67b5 2.8.3-beta1 599677276b227357140dda35bea4a2c18e2e67b5 DRTVWR-75_2.8.3-beta1 +fb85792b84bf28428889c4cc966469d92e5dac4c 2.8.3-release +fb85792b84bf28428889c4cc966469d92e5dac4c DRTVWR-74_2.8.3-release 6b678ea52f90d5c14181661dcd2546e25bde483e 3.0.0-start b0be6ce3adfef3a014a2389d360539f8a86c5439 3.0.0-beta1 b0be6ce3adfef3a014a2389d360539f8a86c5439 DRTVWR-78_3.0.0-beta1 -fb85792b84bf28428889c4cc966469d92e5dac4c 2.8.3-release -fb85792b84bf28428889c4cc966469d92e5dac4c DRTVWR-74_2.8.3-release +1778f26b6d0ae762dec3ca37140f66620f2485d9 3.0.0-release +1778f26b6d0ae762dec3ca37140f66620f2485d9 DRTVWR-77_3.0.0-release 82a2079ffcb57ecb1b3849cb41376b443e1eb912 3.0.1-start 364fd63517fbacbbcb9129d096187171ba8c9e48 3.0.1-beta1 364fd63517fbacbbcb9129d096187171ba8c9e48 DRTVWR-81_3.0.1-beta1 f2412ecd6740803ea9452f1d17fd872e263a0df7 3.0.2-start 42784bf50fa01974bada2a1af3892ee09c93fcda 3.0.2-beta1 42784bf50fa01974bada2a1af3892ee09c93fcda DRTVWR-83_3.0.2-beta1 -1778f26b6d0ae762dec3ca37140f66620f2485d9 3.0.0-release -1778f26b6d0ae762dec3ca37140f66620f2485d9 DRTVWR-77_3.0.0-release e5c9af2d7980a99a71650be3a0cf7b2b3c3b897e 3.0.2-beta2 e5c9af2d7980a99a71650be3a0cf7b2b3c3b897e DRTVWR-86_3.0.2-beta2 b95ddac176ac944efdc85cbee94ac2e1eab44c79 3.0.3-start @@ -170,9 +170,9 @@ b95ddac176ac944efdc85cbee94ac2e1eab44c79 3.0.3-start 6694f3f062aa45f64ab391d25a3eb3d5eb1b0871 DRTVWR-85_3.0.3-beta1 61aa7974df089e8621fe9a4c69bcdefdb3cc208a 3.0.3-beta2 61aa7974df089e8621fe9a4c69bcdefdb3cc208a DRTVWR-89_3.0.3-beta2 -586907287be581817b2422b5137971b22d54ea48 3.0.4-start 0496d2f74043cf4e6058e76ac3db03d44cff42ce 3.0.3-release 0496d2f74043cf4e6058e76ac3db03d44cff42ce DRTVWR-84_3.0.3-release +586907287be581817b2422b5137971b22d54ea48 3.0.4-start 92a3aa04775438226399b19deee12ac3b5a62838 3.0.5-start c7282e59f374ee904bd793c3c444455e3399b0c5 3.1.0-start 2657fa785bbfac115852c41bd0adaff74c2ad5da 3.1.0-beta1 @@ -193,11 +193,11 @@ e440cd1dfbd128d7d5467019e497f7f803640ad6 DRTVWR-95_3.2.0-beta1 c4911ec8cd81e676dfd2af438b3e065407a94a7a 3.2.1-start 9e390d76807fa70d356b8716fb83b8ce42a629ef 3.2.1-beta1 9e390d76807fa70d356b8716fb83b8ce42a629ef DRTVWR-100_3.2.1-beta1 +a8c7030d6845186fac7c188be4323a0e887b4184 3.2.1-release +a8c7030d6845186fac7c188be4323a0e887b4184 DRTVWR-99_3.2.1-release 40b46edba007d15d0059c80864b708b99c1da368 3.2.2-start 523df3e67378541498d516d52af4402176a26bac 3.2.2-beta1 523df3e67378541498d516d52af4402176a26bac DRTVWR-102_3.2.2-beta1 -a8c7030d6845186fac7c188be4323a0e887b4184 3.2.1-release -a8c7030d6845186fac7c188be4323a0e887b4184 DRTVWR-99_3.2.1-release 80f3e30d8aa4d8f674a48bd742aaa6d8e9eae0b5 3.2.3-start 3fe994349fae64fc40874bb59db387131eb35a41 3.2.4-beta1 3fe994349fae64fc40874bb59db387131eb35a41 3.2.4-start @@ -248,57 +248,67 @@ bb9932a7a5fd00edf52d95f354e3b37ae6a942db DRTVWR-156 6414ecdabc5d89515b08d1f872cf923ed3a5523a DRTVWR-148 2a3965b3ad202df7ea25d2be689291bb14a1280e DRTVWR-155 24a7281bef42bd4430ceb25db8b195449c2c7de3 DRTVWR-153 +a716684aa7c07c440b1de5815b8a1f3dd3fd8bfb DRTVWR-159 +9a78ac13f047056f788c4734dd91aebfe30970e3 DRTVWR-157 5910f8063a7e1ddddf504c2f35ca831cc5e8f469 DRTVWR-160 f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1 f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 -2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164 +089e5c84b2dece68f2b016c842ef9b5de4786842 DRTVWR-161 600f3b3920d94de805ac6dc8bb6def9c069dd360 DRTVWR-162 +c08e2ac17a99973b2a94477659220b99b8847ae2 DRTVWR-163 +2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164 80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165 fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2 af5f3e43e6e4424b1da19d9e16f6b853a7b822ed DRTVWR-169 4b3c68199a86cabaa5d9466d7b0f7e141e901d7a 3.3.3-beta3 6428242e124b523813bfaf4c45b3d422f0298c81 3.3.3-release -a716684aa7c07c440b1de5815b8a1f3dd3fd8bfb DRTVWR-159 -9a78ac13f047056f788c4734dd91aebfe30970e3 DRTVWR-157 -089e5c84b2dece68f2b016c842ef9b5de4786842 DRTVWR-161 -c08e2ac17a99973b2a94477659220b99b8847ae2 DRTVWR-163 b9d0170b62eb1c7c3adaa37a0b13a833e5e659f9 DRTVWR-171 050e48759337249130f684b4a21080b683f61732 DRTVWR-168 09ef7fd1b0781f33b8a3a9af6236b7bcb4831910 DRTVWR-170 f87bfbe0b62d26f451d02a47c80ebef6b9168fc2 DRTVWR-158 f91d003091a61937a044652c4c674447f7dcbb7a 3.3.4-beta1 +005dfe5c4c377207d065fb27858d2eb0b53b143a DRTVWR-167 bce218b2b45b730b22cc51e4807aa8b571cadef3 DRTVWR-173 cbea6356ce9cb0c313b6777f10c5c14783264fcc DRTVWR-174 82b5330bc8b17d0d4b598832e9c5a92e90075682 3.3.4-beta2 57d221de3df94f90b55204313c2cef044a3c0ae2 DRTVWR-176 eb539c65e6ee26eea2bf373af2d0f4b52dc91289 DRTVWR-177 a8057e1b9a1246b434a27405be35e030f7d28b0c 3.3.4-beta3 +888768f162d2c0a8de1dcc5fb9a08bd8bd120a6b DRTVWR-175 4281aa899fb2cedb7a9ca7ce91c5c29d4aa69594 DRTVWR-180 5c08e1d8edd871807153603b690e3ee9dbb548aa DRTVWR-183 6c75f220b103db1420919c8b635fe53e2177f318 3.3.4-beta4 9cd174d3a54d93d409a7c346a15b8bfb40fc58f4 DRTVWR-184 ab2ffc547c8a8950ff187c4f6c95e5334fab597b 3.3.4-beta5 28e100d0379a2b0710c57647a28fc5239d3d7b99 3.3.4-release -005dfe5c4c377207d065fb27858d2eb0b53b143a DRTVWR-167 -888768f162d2c0a8de1dcc5fb9a08bd8bd120a6b DRTVWR-175 a8b3eca451a9eaab59987efb0ab1c4217e3f2dcc DRTVWR-182 1f27cdfdc54246484f8afbbe42ce48e954175cbd 3.4.0-beta1 -9ee9387789701d597130f879d9011a4958753862 DRTVWR-189 +81f6b745ef27f5915fd07f988fdec9944f2bb73e DRTVWR-186 47f0d08ba7ade0a3905074009067c6d3df7e16ae DRTVWR-190 +cc953f00956be52cc64c30637bbeec310eea603f DRTVWR-181 +c04e68e1b0034fd0a20815ae24c77e5f8428e822 DRTVWR-188 +9ee9387789701d597130f879d9011a4958753862 DRTVWR-189 421126293dcbde918e0da027ca0ab9deb5b4fbf2 DRTVWR-192 +4b2c52aecb7a75de31dbb12d9f5b9a251d8707be DRTVWR-191 33a2fc7a910ae29ff8b4850316ed7fbff9f64d33 DRTVWR-195 e9732c739c8a72a590216951505ea9c76a526a84 DRTVWR-193 +78ca0bbf43a92e8914d4cfa87d69a6717ef7d4cf DRTVWR-194 7602f61c804a512764e349c034c02ddabeefebc4 DRTVWR-196 ae5c83dd61d2d37c45f1d5b8bf2b036d87599f1b DRTVWR-198 507bdfbd6bf844a511c1ffeda4baa80016ed1346 DRTVWR-197 b1dbb1a83f48f93f6f878cff9e52d2cb635e145c 3.4.0-beta2 37402e2b19af970d51b0a814d79892cc5647532b DRTVWR-200 182a9bf30e81070361bb020a78003b1cf398e79c 3.4.0-beta3 +248f4acd92a706c79e842bc83d80baa7369c0c2e DRTVWR-203 6dfb0fba782c9233dd95f24ec48146db0d3f210b DRTVWR-199 7c9102fb998885621919f2474a002c35b583539b 3.3.4-release2 7649a3dff5ec22d3727377e5f02efd0f421e4cb5 DRTVWR-201 84fb70dfe3444e75a44fb4bee43e2fc8221cebdd 3.4.0-beta4 +de3be913f68813a9bac7d1c671fef96d1159bcd6 DRTVWR-202 573e863be2f26d3687161def4b9fea9b7038dda8 3.4.0-beta5 +34dbbe2b00afe90352d3acf8290eb10ab90d1c8b oz-build-test-tag +6ee71714935ffcd159db3d4f5800c1929aac54e1 DRTVWR-205 +7b22c612fc756e0ea63b10b163e81d107f85dbf8 DRTVWR-206 8c9085066c78ed5f6c9379dc054c82a6fcdb1851 DRTVWR-207 351eea5f9dc192fc5ddea3b02958de97677a0a12 3.3.4-release3 af7b28e75bd5a629cd9e0dc46fb3f1757626f493 DRTVWR-212 @@ -309,6 +319,7 @@ ceed0b65a69f1eac20d523e0203320a32f9a3f3c DRTVWR-215 97977c67245f52db20eb15f1918cc0f24778cabc 3.4.0-release 5adb2b8f96c3cac88ad7c7d996d707f1b29df336 3.4.1-beta1 b3f74858a1c8720c82d0978f3877a3fc8ba459ec 3.4.1-beta1a +b61afe175b829c149d369524a4e974dfda99facf DRTVWR-219 2b779f233ee6f38c89cb921650c773a96e63da92 DRTVWR-220 0b9d95f4bfb6867cbf56eaec51633b0da2f1262d DRTVWR-221 e6e553761829dc0270eaaa712b7cb0622535b076 3.4.1-beta3 @@ -333,16 +344,14 @@ baf97f06ae17223614c5e31aa42e71d87cff07fe DRTVWR-236 b2f21e3442542283a80e7eaebae9f833e5a927b6 DRTVWR-237 3f9be82de642d468c5fc272cb9d96b46b5498402 3.4.1-beta12 e59ffd3fe0838ae6b09b242a6e9df71761b88f41 3.4.1-release -81f6b745ef27f5915fd07f988fdec9944f2bb73e DRTVWR-186 -cc953f00956be52cc64c30637bbeec310eea603f DRTVWR-181 -c04e68e1b0034fd0a20815ae24c77e5f8428e822 DRTVWR-188 -4b2c52aecb7a75de31dbb12d9f5b9a251d8707be DRTVWR-191 -78ca0bbf43a92e8914d4cfa87d69a6717ef7d4cf DRTVWR-194 -248f4acd92a706c79e842bc83d80baa7369c0c2e DRTVWR-203 -de3be913f68813a9bac7d1c671fef96d1159bcd6 DRTVWR-202 -34dbbe2b00afe90352d3acf8290eb10ab90d1c8b oz-build-test-tag -6ee71714935ffcd159db3d4f5800c1929aac54e1 DRTVWR-205 -7b22c612fc756e0ea63b10b163e81d107f85dbf8 DRTVWR-206 -b61afe175b829c149d369524a4e974dfda99facf DRTVWR-219 32896d5e920ca9a29256ff3b747c2e99752aa5ae DRTVWR-217 704bbae7b182a1f2811a47a054e680522966f54a 3.4.2-beta1 +d799593b53ed733862e9a13871e318e886469377 DRTVWR-208 +e497dcde7a3653e384eb223a8a460030e89c294c DRTVWR-223 +288539fc0408ed4b69a99665de33bbbc2c3c08fe DRTVWR-216 +e664473c16df1d82ffaff382e7b3e023da202d52 3.4.2-beta2 +93ab02d83f51e30a3cabad98aff89601befd9413 DRTVWR-240 +0891d7a773a31397dcad48be3fa66531d567a821 DRTVWR-242 +710785535362b3cb801b6a3dc4703be3373bd0cd 3.4.2-beta3 +2aa72e3372a83dece4df9cf72fb1e7c34f90b5e3 DRTVWR-209 +f7bedce18ad52283e6072814db23318907261487 DRTVWR-238 diff --git a/autobuild.xml b/autobuild.xml index 05e0ac28ae..9a92614dfd 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -186,9 +186,9 @@ <key>archive</key> <map> <key>hash</key> - <string>d98078791ce345bf6168ce9ba53ca2d7</string> + <string>36aa500e13cdde61607b6e93065206ec</string> <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-boost/rev/222752/arch/Darwin/installer/boost-1.45.0-darwin-20110304.tar.bz2</string> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-boost/rev/261457/arch/Darwin/installer/boost-1.48.0-darwin-20120710.tar.bz2</string> </map> <key>name</key> <string>darwin</string> @@ -198,9 +198,9 @@ <key>archive</key> <map> <key>hash</key> - <string>a34e7fffdb94a6a4d8a2966b1f216da3</string> + <string>18602d44bd435eb0d7189f436ff2cb0f</string> <key>url</key> - <string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.45.0-linux-20110310.tar.bz2</string> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-boost/rev/261457/arch/Linux/installer/boost-1.48.0-linux-20120710.tar.bz2</string> </map> <key>name</key> <string>linux</string> @@ -210,9 +210,9 @@ <key>archive</key> <map> <key>hash</key> - <string>98be22c8833aa2bca184b9fa09fbb82b</string> + <string>dc8f5dc6be04c64bf3460b4932b18457</string> <key>url</key> - <string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.45.0-windows-20110124.tar.bz2</string> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-boost/rev/261457/arch/CYGWIN/installer/boost-1.48.0-windows-20120710.tar.bz2</string> </map> <key>name</key> <string>windows</string> diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 1cebb53a07..24c98bfada 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -43,6 +43,7 @@ add_subdirectory(cmake) add_subdirectory(${LIBS_OPEN_PREFIX}llaudio) add_subdirectory(${LIBS_OPEN_PREFIX}llcharacter) add_subdirectory(${LIBS_OPEN_PREFIX}llcommon) +add_subdirectory(${LIBS_OPEN_PREFIX}llcorehttp) add_subdirectory(${LIBS_OPEN_PREFIX}llimage) add_subdirectory(${LIBS_OPEN_PREFIX}llkdu) add_subdirectory(${LIBS_OPEN_PREFIX}llimagej2coj) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 2135f0584c..2af0bc1b30 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -12,12 +12,13 @@ if (STANDALONE) set(BOOST_SIGNALS_LIBRARY boost_signals-mt) set(BOOST_SYSTEM_LIBRARY boost_system-mt) set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt) + set(BOOST_THREAD_LIBRARY boost_thread-mt) else (STANDALONE) use_prebuilt_binary(boost) set(Boost_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include) if (WINDOWS) - set(BOOST_VERSION 1_45) + set(BOOST_VERSION 1_48) if(MSVC80) set(BOOST_PROGRAM_OPTIONS_LIBRARY optimized libboost_program_options-vc80-mt-${BOOST_VERSION} @@ -37,22 +38,55 @@ else (STANDALONE) else(MSVC80) # MSVC 10.0 config set(BOOST_PROGRAM_OPTIONS_LIBRARY - optimized libboost_program_options-vc100-mt-${BOOST_VERSION} - debug libboost_program_options-vc100-mt-gd-${BOOST_VERSION}) + optimized libboost_program_options-mt + debug libboost_program_options-mt-gd) set(BOOST_REGEX_LIBRARY - optimized libboost_regex-vc100-mt-${BOOST_VERSION} - debug libboost_regex-vc100-mt-gd-${BOOST_VERSION}) + optimized libboost_regex-mt + debug libboost_regex-mt-gd) set(BOOST_SYSTEM_LIBRARY - optimized libboost_system-vc100-mt-${BOOST_VERSION} - debug libboost_system-vc100-mt-gd-${BOOST_VERSION}) + optimized libboost_system-mt + debug libboost_system-mt-gd) set(BOOST_FILESYSTEM_LIBRARY - optimized libboost_filesystem-vc100-mt-${BOOST_VERSION} - debug libboost_filesystem-vc100-mt-gd-${BOOST_VERSION}) + optimized libboost_filesystem-mt + debug libboost_filesystem-mt-gd) + set(BOOST_THREAD_LIBRARY + optimized libboost_thread-mt + debug libboost_thread-mt-gd) endif (MSVC80) - elseif (DARWIN OR LINUX) - set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options) - set(BOOST_REGEX_LIBRARY boost_regex) - set(BOOST_SYSTEM_LIBRARY boost_system) - set(BOOST_FILESYSTEM_LIBRARY boost_filesystem) + elseif (LINUX) + set(BOOST_PROGRAM_OPTIONS_LIBRARY + optimized boost_program_options-mt + debug boost_program_options-mt-d) + set(BOOST_REGEX_LIBRARY + optimized boost_regex-mt + debug boost_regex-mt-d) + set(BOOST_SYSTEM_LIBRARY + optimized boost_system-mt + debug boost_system-mt-d) + set(BOOST_FILESYSTEM_LIBRARY + optimized boost_filesystem-mt + debug boost_filesystem-mt-d) + set(BOOST_THREAD_LIBRARY + optimized boost_thread-mt + debug boost_thread-mt-d) + elseif (DARWIN) + set(BOOST_PROGRAM_OPTIONS_LIBRARY + optimized boost_program_options-mt + debug boost_program_options-mt-d) + set(BOOST_PROGRAM_OPTIONS_LIBRARY + optimized boost_program_options-mt + debug boost_program_options-mt-d) + set(BOOST_REGEX_LIBRARY + optimized boost_regex-mt + debug boost_regex-mt-d) + set(BOOST_SYSTEM_LIBRARY + optimized boost_system-mt + debug boost_system-mt-d) + set(BOOST_FILESYSTEM_LIBRARY + optimized boost_filesystem-mt + debug boost_filesystem-mt-d) + set(BOOST_THREAD_LIBRARY + optimized boost_thread-mt + debug boost_thread-mt-d) endif (WINDOWS) endif (STANDALONE) diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 9f05c4cff2..a5483ba678 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -254,6 +254,12 @@ elseif(LINUX) libapr-1.so.0 libaprutil-1.so.0 libatk-1.0.so + libboost_program_options-mt.so.1.48.0 + libboost_regex-mt.so.1.48.0 + libboost_thread-mt.so.1.48.0 + libboost_filesystem-mt.so.1.48.0 + libboost_signals-mt.so.1.48.0 + libboost_system-mt.so.1.48.0 libbreakpad_client.so.0 libcollada14dom.so libcrypto.so.1.0.0 diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 543075db5b..543075db5b 100755..100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake index d4694ad37a..8f7bb296ce 100644 --- a/indra/cmake/LLCommon.cmake +++ b/indra/cmake/LLCommon.cmake @@ -24,7 +24,7 @@ endif (LINUX) add_definitions(${TCMALLOC_FLAG}) -set(LLCOMMON_LINK_SHARED OFF CACHE BOOL "Build the llcommon target as a shared library.") +set(LLCOMMON_LINK_SHARED OFF CACHE BOOL "Build the llcommon target as a static library.") if(LLCOMMON_LINK_SHARED) add_definitions(-DLL_COMMON_LINK_SHARED=1) endif(LLCOMMON_LINK_SHARED) diff --git a/indra/cmake/LLCoreHttp.cmake b/indra/cmake/LLCoreHttp.cmake new file mode 100644 index 0000000000..61e4b23d98 --- /dev/null +++ b/indra/cmake/LLCoreHttp.cmake @@ -0,0 +1,16 @@ +# -*- cmake -*- + +include(CARes) +include(CURL) +include(OpenSSL) +include(Boost) + +set(LLCOREHTTP_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/llcorehttp + ${CARES_INCLUDE_DIRS} + ${CURL_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIRS} + ) + +set(LLCOREHTTP_LIBRARIES llcorehttp) diff --git a/indra/cmake/LLPrimitive.cmake b/indra/cmake/LLPrimitive.cmake index f15a2c2649..ab39cbb6be 100644 --- a/indra/cmake/LLPrimitive.cmake +++ b/indra/cmake/LLPrimitive.cmake @@ -15,10 +15,10 @@ if (WINDOWS) optimized llprimitive debug libcollada14dom22-d optimized libcollada14dom22 - debug libboost_filesystem-vc100-mt-gd-1_45 - optimized libboost_filesystem-vc100-mt-1_45 - debug libboost_system-vc100-mt-gd-1_45 - optimized libboost_system-vc100-mt-1_45 + debug libboost_filesystem-mt-gd + optimized libboost_filesystem-mt + debug libboost_system-mt-gd + optimized libboost_system-mt ) else (WINDOWS) set(LLPRIMITIVE_LIBRARIES diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index bfcb259709..97cc31bba0 100644 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -626,6 +626,23 @@ class LLManifest(object): d = src_re.sub(d_template, s.replace('\\', '/')) yield os.path.normpath(s), os.path.normpath(d) + def path2basename(self, path, file): + """ + It is a common idiom to write: + self.path(os.path.join(somedir, somefile), somefile) + + So instead you can write: + self.path2basename(somedir, somefile) + + Note that this is NOT the same as: + self.path(os.path.join(somedir, somefile)) + + which is the same as: + temppath = os.path.join(somedir, somefile) + self.path(temppath, temppath) + """ + return self.path(os.path.join(path, file), file) + def path(self, src, dst=None): sys.stdout.write("Processing %s => %s ... " % (src, dst)) sys.stdout.flush() @@ -671,6 +688,10 @@ class LLManifest(object): print "%d files" % count + # Let caller check whether we processed as many files as expected. In + # particular, let caller notice 0. + return count + def do(self, *actions): self.actions = actions self.construct() diff --git a/indra/llcharacter/lleditingmotion.cpp b/indra/llcharacter/lleditingmotion.cpp index 66b3c2bd25..0d0b85ba60 100644 --- a/indra/llcharacter/lleditingmotion.cpp +++ b/indra/llcharacter/lleditingmotion.cpp @@ -214,8 +214,10 @@ BOOL LLEditingMotion::onUpdate(F32 time, U8* joint_mask) target = target * target_dist; if (!target.isFinite()) { - llerrs << "Non finite target in editing motion with target distance of " << target_dist << + // Don't error out here, set a fail-safe target vector + llwarns << "Non finite target in editing motion with target distance of " << target_dist << " and focus point " << focus_pt << llendl; + target.setVec(1.f, 1.f, 1.f); } mTarget.setPosition( target + mParentJoint.getPosition()); diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index 5cfcdab41c..f0a5603d06 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -59,4 +59,8 @@ #include "llerror.h" #include "llfile.h" +// Boost 1.45 had version 2 as the default for the filesystem library, +// 1.48 has version 3 as the default. Keep compatibility for now. +#define BOOST_FILESYSTEM_VERSION 2 + #endif diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index af33ce666f..034546c3f3 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -168,7 +168,7 @@ public: void operator -=(Type x) { apr_atomic_sub32(&mData, apr_uint32_t(x)); } void operator +=(Type x) { apr_atomic_add32(&mData, apr_uint32_t(x)); } Type operator ++(int) { return apr_atomic_inc32(&mData); } // Type++ - Type operator --(int) { return apr_atomic_dec32(&mData); } // Type-- + Type operator --(int) { return apr_atomic_dec32(&mData); } // approximately --Type (0 if final is 0, non-zero otherwise) private: apr_uint32_t mData; diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 7f4f670ed0..6b549e4b6f 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -1451,9 +1451,12 @@ S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 option } case LLSD::TypeUUID: + { ostr.put('u'); - ostr.write((const char*)(&(data.asUUID().mData)), UUID_BYTES); + LLSD::UUID value = data.asUUID(); + ostr.write((const char*)(&value.mData), UUID_BYTES); break; + } case LLSD::TypeString: ostr.put('s'); diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index fa0eb9f72c..0c32679744 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -47,7 +47,8 @@ std::string ll_safe_string(const char* in) std::string ll_safe_string(const char* in, S32 maxlen) { - if(in) return std::string(in, maxlen); + if(in && maxlen > 0 ) return std::string(in, maxlen); + return std::string(); } diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index c2fbb544a8..1d56a52c32 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -71,6 +71,13 @@ LL_COMMON_API void assert_main_thread() } } +void LLThread::registerThreadID() +{ +#if !LL_DARWIN + sThreadID = ++sIDIter; +#endif +} + // // Handed to the APR thread creation function // diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 115bf47553..5c8bbca2ca 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -88,6 +88,11 @@ public: U32 getID() const { return mID; } + // Called by threads *not* created via LLThread to register some + // internal state used by LLMutex. You must call this once early + // in the running thread to prevent collisions with the main thread. + static void registerThreadID(); + private: BOOL mPaused; diff --git a/indra/llcommon/tests/bitpack_test.cpp b/indra/llcommon/tests/bitpack_test.cpp index 05289881d0..4c3bc674af 100644 --- a/indra/llcommon/tests/bitpack_test.cpp +++ b/indra/llcommon/tests/bitpack_test.cpp @@ -95,6 +95,7 @@ namespace tut ensure("bitPack: individual unpack: 5", unpackbuffer[0] == (U8) str[5]); unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, 8*4); // Life ensure_memory_matches("bitPack: 4 bytes unpack:", unpackbuffer, 4, str+6, 4); + ensure("keep compiler quiet", unpack_bufsize == unpack_bufsize); } // U32 packing diff --git a/indra/llcommon/tests/reflection_test.cpp b/indra/llcommon/tests/reflection_test.cpp index 59491cd1fe..8980ebb1f1 100644 --- a/indra/llcommon/tests/reflection_test.cpp +++ b/indra/llcommon/tests/reflection_test.cpp @@ -207,7 +207,7 @@ namespace tut const LLReflective* reflective = property->get(aggregated_data); // Wrong reflective type, should throw exception. // useless op to get rid of compiler warning. - reflective = NULL; + reflective = reflective; } catch(...) { diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt new file mode 100644 index 0000000000..8632a2b722 --- /dev/null +++ b/indra/llcorehttp/CMakeLists.txt @@ -0,0 +1,184 @@ +# -*- cmake -*- + +project(llcorehttp) + +include(00-Common) +include(GoogleMock) +include(CURL) +include(CARes) +include(OpenSSL) +include(ZLIB) +include(LLCoreHttp) +include(LLAddBuildTest) +include(LLMessage) +include(LLCommon) +include(Tut) + +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories( + ${LLMESSAGE_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLCOREHTTP_INCLUDE_DIRS} + ) + +set(llcorehttp_SOURCE_FILES + bufferarray.cpp + bufferstream.cpp + httpcommon.cpp + httpheaders.cpp + httpoptions.cpp + httprequest.cpp + httpresponse.cpp + _httplibcurl.cpp + _httpopcancel.cpp + _httpoperation.cpp + _httpoprequest.cpp + _httpopsetget.cpp + _httpopsetpriority.cpp + _httppolicy.cpp + _httppolicyclass.cpp + _httppolicyglobal.cpp + _httpreplyqueue.cpp + _httprequestqueue.cpp + _httpservice.cpp + _refcounted.cpp + ) + +set(llcorehttp_HEADER_FILES + CMakeLists.txt + + bufferarray.h + bufferstream.h + httpcommon.h + httphandler.h + httpheaders.h + httpoptions.h + httprequest.h + httpresponse.h + _httpinternal.h + _httplibcurl.h + _httpopcancel.h + _httpoperation.h + _httpoprequest.h + _httpopsetget.h + _httpopsetpriority.h + _httppolicy.h + _httppolicyclass.h + _httppolicyglobal.h + _httpreadyqueue.h + _httpreplyqueue.h + _httprequestqueue.h + _httpservice.h + _mutex.h + _refcounted.h + _thread.h + ) + +set_source_files_properties(${llcorehttp_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) +if (DARWIN OR LINUX) + # Boost headers define unused members in condition_variable so... + set_source_files_properties(${llcorehttp_SOURCE_FILES} + PROPERTIES COMPILE_FLAGS -Wno-unused-variable) +endif (DARWIN OR LINUX) + +list(APPEND llcorehttp_SOURCE_FILES ${llcorehttp_HEADER_FILES}) + +add_library (llcorehttp ${llcorehttp_SOURCE_FILES}) +target_link_libraries( + llcorehttp + ${CURL_LIBRARIES} + ${CARES_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${BOOST_THREAD_LIBRARY} + ) + +# tests +if (LL_TESTS) + SET(llcorehttp_TEST_SOURCE_FILES + tests/test_allocator.cpp + ) + + set(llcorehttp_TEST_HEADER_FILS + tests/test_httpstatus.hpp + tests/test_refcounted.hpp + tests/test_httpoperation.hpp + tests/test_httprequest.hpp + tests/test_httprequestqueue.hpp + tests/test_httpheaders.hpp + tests/test_bufferarray.hpp + tests/test_bufferstream.hpp + ) + + set_source_files_properties(${llcorehttp_TEST_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + + list(APPEND llcorehttp_TEST_SOURCE_FILES ${llcorehttp_TEST_HEADER_FILES}) + + # LL_ADD_PROJECT_UNIT_TESTS(llcorehttp "${llcorehttp_TEST_SOURCE_FILES}") + + # set(TEST_DEBUG on) + set(test_libs + ${LLCOREHTTP_LIBRARIES} + ${WINDOWS_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} + ${CURL_LIBRARIES} + ${CARES_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${BOOST_THREAD_LIBRARY} + ) + + LL_ADD_INTEGRATION_TEST(llcorehttp + "${llcorehttp_TEST_SOURCE_FILES}" + "${test_libs}" + ${PYTHON_EXECUTABLE} + "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llcorehttp_peer.py" + ) + + # + # Example Programs + # + SET(llcorehttp_EXAMPLE_SOURCE_FILES + examples/http_texture_load.cpp + ) + + set(example_libs + ${LLCOREHTTP_LIBRARIES} + ${WINDOWS_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} + ${CURL_LIBRARIES} + ${CARES_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${BOOST_THREAD_LIBRARY} + ) + + add_executable(http_texture_load + ${llcorehttp_EXAMPLE_SOURCE_FILES} + ) + set_target_properties(http_texture_load + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${EXE_STAGING_DIR}" + ) + + if (WINDOWS) + # The following come from LLAddBuildTest.cmake's INTEGRATION_TEST_xxxx target. + set_target_properties(http_texture_load + PROPERTIES + LINK_FLAGS "/debug /NODEFAULTLIB:LIBCMT /SUBSYSTEM:WINDOWS /INCLUDE:__tcmalloc" + LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMT;LIBCMTD;MSVCRT\" /INCREMENTAL:NO" + LINK_FLAGS_RELEASE "" + ) + endif (WINDOWS) + + target_link_libraries(http_texture_load ${example_libs}) + +endif (LL_TESTS) + diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h new file mode 100644 index 0000000000..465e2036b3 --- /dev/null +++ b/indra/llcorehttp/_httpinternal.h @@ -0,0 +1,146 @@ +/** + * @file _httpinternal.h + * @brief Implementation constants and magic numbers + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_INTERNAL_H_ +#define _LLCORE_HTTP_INTERNAL_H_ + + +// If you find this included in a public interface header, +// something wrong is probably happening. + + +// -------------------------------------------------------------------- +// General library to-do list +// +// - Implement policy classes. Structure is mostly there just didn't +// need it for the first consumer. +// - Consider Removing 'priority' from the request interface. Its use +// in an always active class can lead to starvation of low-priority +// requests. Requires coodination of priority values across all +// components that share a class. Changing priority across threads +// is slightly expensive (relative to gain) and hasn't been completely +// implemented. And the major user of priority, texture fetches, +// may not really need it. +// - Set/get for global policy and policy classes is clumsy. Rework +// it heading in a direction that allows for more dynamic behavior. +// - Move HttpOpRequest::prepareRequest() to HttpLibcurl for the +// pedantic. +// - Update downloader and other long-duration services are going to +// need a progress notification. Initial idea is to introduce a +// 'repeating request' which can piggyback on another request and +// persist until canceled or carrier completes. Current queue +// structures allow an HttpOperation object to be enqueued +// repeatedly, so... +// - Investigate making c-ares' re-implementation of a resolver library +// more resilient or more intelligent on Mac. Part of the DNS failure +// lies in here. The mechanism also looks a little less dynamic +// than needed in an environments where networking is changing. +// - Global optimizations: 'borrowing' connections from other classes, +// HTTP pipelining. +// - Dynamic/control system stuff: detect problems and self-adjust. +// This won't help in the face of the router problems we've looked +// at, however. Detect starvation due to UDP activity and provide +// feedback to it. +// +// Integration to-do list +// - LLTextureFetch still needs a major refactor. The use of +// LLQueuedThread makes it hard to inspect workers and do the +// resource waiting we're now doing. Rebuild along simpler lines +// some of which are suggested in new commentary at the top of +// the main source file. +// - Expand areas of usage eventually leading to the removal of LLCurl. +// Rough order of expansion: +// . Mesh fetch +// . Avatar names +// . Group membership lists +// . Caps access in general +// . 'The rest' +// - Adapt texture cache, image decode and other image consumers to +// the BufferArray model to reduce data copying. Alternatively, +// adapt this library to something else. +// +// -------------------------------------------------------------------- + + +// If '1', internal ready queues will not order ready +// requests by priority, instead it's first-come-first-served. +// Reprioritization requests have the side-effect of then +// putting the modified request at the back of the ready queue. + +#define LLCORE_HTTP_READY_QUEUE_IGNORES_PRIORITY 1 + + +namespace LLCore +{ + +// Maxium number of policy classes that can be defined. +// *TODO: Currently limited to the default class, extend. +const int HTTP_POLICY_CLASS_LIMIT = 1; + +// Debug/informational tracing. Used both +// as a global option and in per-request traces. +const int HTTP_TRACE_OFF = 0; +const int HTTP_TRACE_LOW = 1; +const int HTTP_TRACE_CURL_HEADERS = 2; +const int HTTP_TRACE_CURL_BODIES = 3; + +const int HTTP_TRACE_MIN = HTTP_TRACE_OFF; +const int HTTP_TRACE_MAX = HTTP_TRACE_CURL_BODIES; + +// Request retry limits +const int HTTP_RETRY_COUNT_DEFAULT = 5; +const int HTTP_RETRY_COUNT_MIN = 0; +const int HTTP_RETRY_COUNT_MAX = 100; + +const int HTTP_REDIRECTS_DEFAULT = 10; + +// Timeout value used for both connect and protocol exchange. +// Retries and time-on-queue are not included and aren't +// accounted for. +const long HTTP_REQUEST_TIMEOUT_DEFAULT = 30L; +const long HTTP_REQUEST_TIMEOUT_MIN = 0L; +const long HTTP_REQUEST_TIMEOUT_MAX = 3600L; + +// Limits on connection counts +const int HTTP_CONNECTION_LIMIT_DEFAULT = 8; +const int HTTP_CONNECTION_LIMIT_MIN = 1; +const int HTTP_CONNECTION_LIMIT_MAX = 256; + +// Tuning parameters + +// Time worker thread sleeps after a pass through the +// request, ready and active queues. +const int HTTP_SERVICE_LOOP_SLEEP_NORMAL_MS = 2; + +// Block allocation size (a tuning parameter) is found +// in bufferarray.h. + +// Compatibility controls +const bool HTTP_ENABLE_LINKSYS_WRT54G_V5_DNS_FIX = true; + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_INTERNAL_H_ diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp new file mode 100644 index 0000000000..6fe0bfc7d1 --- /dev/null +++ b/indra/llcorehttp/_httplibcurl.cpp @@ -0,0 +1,373 @@ +/** + * @file _httplibcurl.cpp + * @brief Internal definitions of the Http libcurl thread + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httplibcurl.h" + +#include "httpheaders.h" +#include "bufferarray.h" +#include "_httpoprequest.h" +#include "_httppolicy.h" + +#include "llhttpstatuscodes.h" + + +namespace LLCore +{ + + +HttpLibcurl::HttpLibcurl(HttpService * service) + : mService(service), + mPolicyCount(0), + mMultiHandles(NULL) +{} + + +HttpLibcurl::~HttpLibcurl() +{ + shutdown(); + + mService = NULL; +} + + +void HttpLibcurl::shutdown() +{ + while (! mActiveOps.empty()) + { + HttpOpRequest * op(* mActiveOps.begin()); + mActiveOps.erase(mActiveOps.begin()); + + cancelRequest(op); + op->release(); + } + + if (mMultiHandles) + { + for (int policy_class(0); policy_class < mPolicyCount; ++policy_class) + { + if (mMultiHandles[policy_class]) + { + curl_multi_cleanup(mMultiHandles[policy_class]); + mMultiHandles[policy_class] = 0; + } + } + + delete [] mMultiHandles; + mMultiHandles = NULL; + } + + mPolicyCount = 0; +} + + +void HttpLibcurl::start(int policy_count) +{ + llassert_always(policy_count <= HTTP_POLICY_CLASS_LIMIT); + llassert_always(! mMultiHandles); // One-time call only + + mPolicyCount = policy_count; + mMultiHandles = new CURLM * [mPolicyCount]; + for (int policy_class(0); policy_class < mPolicyCount; ++policy_class) + { + mMultiHandles[policy_class] = curl_multi_init(); + } +} + + +// Give libcurl some cycles, invoke it's callbacks, process +// completed requests finalizing or issuing retries as needed. +// +// If active list goes empty *and* we didn't queue any +// requests for retry, we return a request for a hard +// sleep otherwise ask for a normal polling interval. +HttpService::ELoopSpeed HttpLibcurl::processTransport() +{ + HttpService::ELoopSpeed ret(HttpService::REQUEST_SLEEP); + + // Give libcurl some cycles to do I/O & callbacks + for (int policy_class(0); policy_class < mPolicyCount; ++policy_class) + { + if (! mMultiHandles[policy_class]) + continue; + + int running(0); + CURLMcode status(CURLM_CALL_MULTI_PERFORM); + do + { + running = 0; + status = curl_multi_perform(mMultiHandles[policy_class], &running); + } + while (0 != running && CURLM_CALL_MULTI_PERFORM == status); + + // Run completion on anything done + CURLMsg * msg(NULL); + int msgs_in_queue(0); + while ((msg = curl_multi_info_read(mMultiHandles[policy_class], &msgs_in_queue))) + { + if (CURLMSG_DONE == msg->msg) + { + CURL * handle(msg->easy_handle); + CURLcode result(msg->data.result); + + if (completeRequest(mMultiHandles[policy_class], handle, result)) + { + // Request is still active, don't get too sleepy + ret = HttpService::NORMAL; + } + handle = NULL; // No longer valid on return + } + else if (CURLMSG_NONE == msg->msg) + { + // Ignore this... it shouldn't mean anything. + ; + } + else + { + LL_WARNS_ONCE("CoreHttp") << "Unexpected message from libcurl. Msg code: " + << msg->msg + << LL_ENDL; + } + msgs_in_queue = 0; + } + } + + if (! mActiveOps.empty()) + { + ret = HttpService::NORMAL; + } + return ret; +} + + +// Caller has provided us with a ref count on op. +void HttpLibcurl::addOp(HttpOpRequest * op) +{ + llassert_always(op->mReqPolicy < mPolicyCount); + llassert_always(mMultiHandles[op->mReqPolicy] != NULL); + + // Create standard handle + if (! op->prepareRequest(mService)) + { + // Couldn't issue request, fail with notification + // *TODO: Need failure path + return; + } + + // Make the request live + curl_multi_add_handle(mMultiHandles[op->mReqPolicy], op->mCurlHandle); + op->mCurlActive = true; + + if (op->mTracing > HTTP_TRACE_OFF) + { + HttpPolicy & policy(mService->getPolicy()); + + LL_INFOS("CoreHttp") << "TRACE, ToActiveQueue, Handle: " + << static_cast<HttpHandle>(op) + << ", Actives: " << mActiveOps.size() + << ", Readies: " << policy.getReadyCount(op->mReqPolicy) + << LL_ENDL; + } + + // On success, make operation active + mActiveOps.insert(op); +} + + +// Implements the transport part of any cancel operation. +// See if the handle is an active operation and if so, +// use the more complicated transport-based cancelation +// method to kill the request. +bool HttpLibcurl::cancel(HttpHandle handle) +{ + HttpOpRequest * op(static_cast<HttpOpRequest *>(handle)); + active_set_t::iterator it(mActiveOps.find(op)); + if (mActiveOps.end() == it) + { + return false; + } + + // Cancel request + cancelRequest(op); + + // Drop references + mActiveOps.erase(it); + op->release(); + + return true; +} + + +// *NOTE: cancelRequest logic parallels completeRequest logic. +// Keep them synchronized as necessary. Caller is expected to +// remove the op from the active list and release the op *after* +// calling this method. It must be called first to deliver the +// op to the reply queue with refcount intact. +void HttpLibcurl::cancelRequest(HttpOpRequest * op) +{ + // Deactivate request + op->mCurlActive = false; + + // Detach from multi and recycle handle + curl_multi_remove_handle(mMultiHandles[op->mReqPolicy], op->mCurlHandle); + curl_easy_cleanup(op->mCurlHandle); + op->mCurlHandle = NULL; + + // Tracing + if (op->mTracing > HTTP_TRACE_OFF) + { + LL_INFOS("CoreHttp") << "TRACE, RequestCanceled, Handle: " + << static_cast<HttpHandle>(op) + << ", Status: " << op->mStatus.toHex() + << LL_ENDL; + } + + // Cancel op and deliver for notification + op->cancel(); +} + + +// *NOTE: cancelRequest logic parallels completeRequest logic. +// Keep them synchronized as necessary. +bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode status) +{ + HttpOpRequest * op(NULL); + curl_easy_getinfo(handle, CURLINFO_PRIVATE, &op); + + if (handle != op->mCurlHandle || ! op->mCurlActive) + { + LL_WARNS("CoreHttp") << "libcurl handle and HttpOpRequest handle in disagreement or inactive request." + << " Handle: " << static_cast<HttpHandle>(handle) + << LL_ENDL; + return false; + } + + active_set_t::iterator it(mActiveOps.find(op)); + if (mActiveOps.end() == it) + { + LL_WARNS("CoreHttp") << "libcurl completion for request not on active list. Continuing." + << " Handle: " << static_cast<HttpHandle>(handle) + << LL_ENDL; + return false; + } + + // Deactivate request + mActiveOps.erase(it); + op->mCurlActive = false; + + // Set final status of request if it hasn't failed by other mechanisms yet + if (op->mStatus) + { + op->mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, status); + } + if (op->mStatus) + { + int http_status(HTTP_OK); + + curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &http_status); + if (http_status >= 100 && http_status <= 999) + { + char * cont_type(NULL); + curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &cont_type); + if (cont_type) + { + op->mReplyConType = cont_type; + } + op->mStatus = HttpStatus(http_status); + } + else + { + LL_WARNS("CoreHttp") << "Invalid HTTP response code (" + << http_status << ") received from server." + << LL_ENDL; + op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INVALID_HTTP_STATUS); + } + } + + // Detach from multi and recycle handle + curl_multi_remove_handle(multi_handle, handle); + curl_easy_cleanup(handle); + op->mCurlHandle = NULL; + + // Tracing + if (op->mTracing > HTTP_TRACE_OFF) + { + LL_INFOS("CoreHttp") << "TRACE, RequestComplete, Handle: " + << static_cast<HttpHandle>(op) + << ", Status: " << op->mStatus.toHex() + << LL_ENDL; + } + + // Dispatch to next stage + HttpPolicy & policy(mService->getPolicy()); + bool still_active(policy.stageAfterCompletion(op)); + + return still_active; +} + + +int HttpLibcurl::getActiveCount() const +{ + return mActiveOps.size(); +} + + +int HttpLibcurl::getActiveCountInClass(int policy_class) const +{ + int count(0); + + for (active_set_t::const_iterator iter(mActiveOps.begin()); + mActiveOps.end() != iter; + ++iter) + { + if ((*iter)->mReqPolicy == policy_class) + { + ++count; + } + } + + return count; +} + + +// --------------------------------------- +// Free functions +// --------------------------------------- + + +struct curl_slist * append_headers_to_slist(const HttpHeaders * headers, struct curl_slist * slist) +{ + for (HttpHeaders::container_t::const_iterator it(headers->mHeaders.begin()); + + headers->mHeaders.end() != it; + ++it) + { + slist = curl_slist_append(slist, (*it).c_str()); + } + return slist; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httplibcurl.h b/indra/llcorehttp/_httplibcurl.h new file mode 100644 index 0000000000..611f029ef5 --- /dev/null +++ b/indra/llcorehttp/_httplibcurl.h @@ -0,0 +1,129 @@ +/** + * @file _httplibcurl.h + * @brief Declarations for internal class providing libcurl transport. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_LIBCURL_H_ +#define _LLCORE_HTTP_LIBCURL_H_ + +#include "linden_common.h" // Modifies curl/curl.h interfaces + +#include <curl/curl.h> +#include <curl/multi.h> + +#include <set> + +#include "httprequest.h" +#include "_httpservice.h" +#include "_httpinternal.h" + + +namespace LLCore +{ + + +class HttpPolicy; +class HttpOpRequest; +class HttpHeaders; + + +/// Implements libcurl-based transport for an HttpService instance. +/// +/// Threading: Single-threaded. Other than for construction/destruction, +/// all methods are expected to be invoked in a single thread, typically +/// a worker thread of some sort. + +class HttpLibcurl +{ +public: + HttpLibcurl(HttpService * service); + virtual ~HttpLibcurl(); + +private: + HttpLibcurl(const HttpLibcurl &); // Not defined + void operator=(const HttpLibcurl &); // Not defined + +public: + /// Give cycles to libcurl to run active requests. Completed + /// operations (successful or failed) will be retried or handed + /// over to the reply queue as final responses. + /// + /// @return Indication of how long this method is + /// willing to wait for next service call. + HttpService::ELoopSpeed processTransport(); + + /// Add request to the active list. Caller is expected to have + /// provided us with a reference count on the op to hold the + /// request. (No additional references will be added.) + void addOp(HttpOpRequest * op); + + /// One-time call to set the number of policy classes to be + /// serviced and to create the resources for each. Value + /// must agree with HttpPolicy::setPolicies() call. + void start(int policy_count); + + /// Synchronously stop libcurl operations. All active requests + /// are canceled and removed from libcurl's handling. Easy + /// handles are detached from their multi handles and released. + /// Multi handles are also released. Canceled requests are + /// completed with canceled status and made available on their + /// respective reply queues. + /// + /// Can be restarted with a start() call. + void shutdown(); + + /// Return global and per-class counts of active requests. + int getActiveCount() const; + int getActiveCountInClass(int policy_class) const; + + /// Attempt to cancel a request identified by handle. + /// + /// Interface shadows HttpService's method. + /// + /// @return True if handle was found and operation canceled. + /// + bool cancel(HttpHandle handle); + +protected: + /// Invoked when libcurl has indicated a request has been processed + /// to completion and we need to move the request to a new state. + bool completeRequest(CURLM * multi_handle, CURL * handle, CURLcode status); + + /// Invoked to cancel an active request, mainly during shutdown + /// and destroy. + void cancelRequest(HttpOpRequest * op); + +protected: + typedef std::set<HttpOpRequest *> active_set_t; + +protected: + HttpService * mService; // Simple reference, not owner + active_set_t mActiveOps; + int mPolicyCount; + CURLM ** mMultiHandles; +}; // end class HttpLibcurl + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_LIBCURL_H_ diff --git a/indra/llcorehttp/_httpopcancel.cpp b/indra/llcorehttp/_httpopcancel.cpp new file mode 100644 index 0000000000..c1912eb3db --- /dev/null +++ b/indra/llcorehttp/_httpopcancel.cpp @@ -0,0 +1,73 @@ +/** + * @file _httpopcancel.cpp + * @brief Definitions for internal class HttpOpCancel + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpopcancel.h" + +#include "httpcommon.h" +#include "httphandler.h" +#include "httpresponse.h" + +#include "_httpservice.h" + + +namespace LLCore +{ + + +// ================================== +// HttpOpCancel +// ================================== + + +HttpOpCancel::HttpOpCancel(HttpHandle handle) + : HttpOperation(), + mHandle(handle) +{} + + +HttpOpCancel::~HttpOpCancel() +{} + + +// Immediately search for the request on various queues +// and cancel operations if found. Return the status of +// the search and cancel as the status of this request. +// The canceled request will return a canceled status to +// its handler. +void HttpOpCancel::stageFromRequest(HttpService * service) +{ + if (! service->cancel(mHandle)) + { + mStatus = HttpStatus(HttpStatus::LLCORE, HE_HANDLE_NOT_FOUND); + } + + addAsReply(); +} + + +} // end namespace LLCore + + diff --git a/indra/llcorehttp/_httpopcancel.h b/indra/llcorehttp/_httpopcancel.h new file mode 100644 index 0000000000..336dfdc573 --- /dev/null +++ b/indra/llcorehttp/_httpopcancel.h @@ -0,0 +1,78 @@ +/** + * @file _httpopcancel.h + * @brief Internal declarations for the HttpOpCancel subclass + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_OPCANCEL_H_ +#define _LLCORE_HTTP_OPCANCEL_H_ + + +#include "linden_common.h" // Modifies curl/curl.h interfaces + +#include "httpcommon.h" + +#include <curl/curl.h> + +#include "_httpoperation.h" +#include "_refcounted.h" + + +namespace LLCore +{ + + +/// HttpOpCancel requests that a previously issued request +/// be canceled, if possible. This includes active requests +/// that may be in the middle of an HTTP transaction. Any +/// completed request will not be canceled and will return +/// its final status unchanged and *this* request will complete +/// with an HE_HANDLE_NOT_FOUND error status. + +class HttpOpCancel : public HttpOperation +{ +public: + /// @param handle Handle of previously-issued request to + /// be canceled. + HttpOpCancel(HttpHandle handle); + +protected: + virtual ~HttpOpCancel(); // Use release() + +private: + HttpOpCancel(const HttpOpCancel &); // Not defined + void operator=(const HttpOpCancel &); // Not defined + +public: + virtual void stageFromRequest(HttpService *); + +public: + // Request data + HttpHandle mHandle; +}; // end class HttpOpCancel + + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_OPCANCEL_H_ + diff --git a/indra/llcorehttp/_httpoperation.cpp b/indra/llcorehttp/_httpoperation.cpp new file mode 100644 index 0000000000..5cf5bc5930 --- /dev/null +++ b/indra/llcorehttp/_httpoperation.cpp @@ -0,0 +1,248 @@ +/** + * @file _httpoperation.cpp + * @brief Definitions for internal classes based on HttpOperation + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpoperation.h" + +#include "httphandler.h" +#include "httpresponse.h" +#include "httprequest.h" + +#include "_httprequestqueue.h" +#include "_httpreplyqueue.h" +#include "_httpservice.h" +#include "_httpinternal.h" + +#include "lltimer.h" + + +namespace LLCore +{ + + +// ================================== +// HttpOperation +// ================================== + + +HttpOperation::HttpOperation() + : LLCoreInt::RefCounted(true), + mReplyQueue(NULL), + mUserHandler(NULL), + mReqPolicy(HttpRequest::DEFAULT_POLICY_ID), + mReqPriority(0U), + mTracing(0) +{ + mMetricCreated = totalTime(); +} + + +HttpOperation::~HttpOperation() +{ + setReplyPath(NULL, NULL); +} + + +void HttpOperation::setReplyPath(HttpReplyQueue * reply_queue, + HttpHandler * user_handler) +{ + if (reply_queue != mReplyQueue) + { + if (mReplyQueue) + { + mReplyQueue->release(); + } + + if (reply_queue) + { + reply_queue->addRef(); + } + + mReplyQueue = reply_queue; + } + + // Not refcounted + mUserHandler = user_handler; +} + + + +void HttpOperation::stageFromRequest(HttpService *) +{ + // Default implementation should never be called. This + // indicates an operation making a transition that isn't + // defined. + LL_ERRS("HttpCore") << "Default stageFromRequest method may not be called." + << LL_ENDL; +} + + +void HttpOperation::stageFromReady(HttpService *) +{ + // Default implementation should never be called. This + // indicates an operation making a transition that isn't + // defined. + LL_ERRS("HttpCore") << "Default stageFromReady method may not be called." + << LL_ENDL; +} + + +void HttpOperation::stageFromActive(HttpService *) +{ + // Default implementation should never be called. This + // indicates an operation making a transition that isn't + // defined. + LL_ERRS("HttpCore") << "Default stageFromActive method may not be called." + << LL_ENDL; +} + + +void HttpOperation::visitNotifier(HttpRequest *) +{ + if (mUserHandler) + { + HttpResponse * response = new HttpResponse(); + + response->setStatus(mStatus); + mUserHandler->onCompleted(static_cast<HttpHandle>(this), response); + + response->release(); + } +} + + +HttpStatus HttpOperation::cancel() +{ + HttpStatus status; + + return status; +} + + +void HttpOperation::addAsReply() +{ + if (mTracing > HTTP_TRACE_OFF) + { + LL_INFOS("CoreHttp") << "TRACE, ToReplyQueue, Handle: " + << static_cast<HttpHandle>(this) + << LL_ENDL; + } + + if (mReplyQueue) + { + addRef(); + mReplyQueue->addOp(this); + } +} + + +// ================================== +// HttpOpStop +// ================================== + + +HttpOpStop::HttpOpStop() + : HttpOperation() +{} + + +HttpOpStop::~HttpOpStop() +{} + + +void HttpOpStop::stageFromRequest(HttpService * service) +{ + // Do operations + service->stopRequested(); + + // Prepare response if needed + addAsReply(); +} + + +// ================================== +// HttpOpNull +// ================================== + + +HttpOpNull::HttpOpNull() + : HttpOperation() +{} + + +HttpOpNull::~HttpOpNull() +{} + + +void HttpOpNull::stageFromRequest(HttpService * service) +{ + // Perform op + // Nothing to perform. This doesn't fall into the libcurl + // ready/active queues, it just bounces over to the reply + // queue directly. + + // Prepare response if needed + addAsReply(); +} + + +// ================================== +// HttpOpSpin +// ================================== + + +HttpOpSpin::HttpOpSpin(int mode) + : HttpOperation(), + mMode(mode) +{} + + +HttpOpSpin::~HttpOpSpin() +{} + + +void HttpOpSpin::stageFromRequest(HttpService * service) +{ + if (0 == mMode) + { + // Spin forever + while (true) + { + ms_sleep(100); + } + } + else + { + ms_sleep(1); // backoff interlock plumbing a bit + this->addRef(); + if (! service->getRequestQueue().addOp(this)) + { + this->release(); + } + } +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httpoperation.h b/indra/llcorehttp/_httpoperation.h new file mode 100644 index 0000000000..914627fad0 --- /dev/null +++ b/indra/llcorehttp/_httpoperation.h @@ -0,0 +1,262 @@ +/** + * @file _httpoperation.h + * @brief Internal declarations for HttpOperation and sub-classes + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_OPERATION_H_ +#define _LLCORE_HTTP_OPERATION_H_ + + +#include "httpcommon.h" +#include "httprequest.h" +#include "_refcounted.h" + + +namespace LLCore +{ + +class HttpReplyQueue; +class HttpHandler; +class HttpService; + +/// HttpOperation is the base class for all request/reply +/// pairs. +/// +/// Operations are expected to be of two types: immediate +/// and queued. Immediate requests go to the singleton +/// request queue and when picked up by the worker thread +/// are executed immediately and there results placed on +/// the supplied reply queue. Queued requests (namely for +/// HTTP operations), go to the request queue, are picked +/// up and moved to a ready queue where they're ordered by +/// priority and managed by the policy component, are +/// then activated issuing HTTP requests and moved to an +/// active list managed by the transport (libcurl) component +/// and eventually finalized when a response is available +/// and status and data return via reply queue. +/// +/// To manage these transitions, derived classes implement +/// three methods: stageFromRequest, stageFromReady and +/// stageFromActive. Immediate requests will only override +/// stageFromRequest which will perform the operation and +/// return the result by invoking addAsReply() to put the +/// request on a reply queue. Queued requests will involve +/// all three stage methods. +/// +/// Threading: not thread-safe. Base and derived classes +/// provide no locking. Instances move across threads +/// via queue-like interfaces that are thread compatible +/// and those interfaces establish the access rules. + +class HttpOperation : public LLCoreInt::RefCounted +{ +public: + /// Threading: called by a consumer/application thread. + HttpOperation(); + +protected: + /// Threading: called by any thread. + virtual ~HttpOperation(); // Use release() + +private: + HttpOperation(const HttpOperation &); // Not defined + void operator=(const HttpOperation &); // Not defined + +public: + /// Register a reply queue and a handler for completion notifications. + /// + /// Invokers of operations that want to receive notification that an + /// operation has been completed do so by binding a reply queue and + /// a handler object to the request. + /// + /// @param reply_queue Pointer to the reply queue where completion + /// notifications are to be queued (typically + /// by addAsReply()). This will typically be + /// the reply queue referenced by the request + /// object. This method will increment the + /// refcount on the queue holding the queue + /// until delivery is complete. Using a reply_queue + /// even if the handler is NULL has some benefits + /// for memory deallocation by keeping it in the + /// originating thread. + /// + /// @param handler Possibly NULL pointer to a non-refcounted + //// handler object to be invoked (onCompleted) + /// when the operation is finished. Note that + /// the handler object is never dereferenced + /// by the worker thread. This is passible data + /// until notification is performed. + /// + /// Threading: called by application thread. + /// + void setReplyPath(HttpReplyQueue * reply_queue, + HttpHandler * handler); + + /// The three possible staging steps in an operation's lifecycle. + /// Asynchronous requests like HTTP operations move from the + /// request queue to the ready queue via stageFromRequest. Then + /// from the ready queue to the active queue by stageFromReady. And + /// when complete, to the reply queue via stageFromActive and the + /// addAsReply utility. + /// + /// Immediate mode operations (everything else) move from the + /// request queue to the reply queue directly via stageFromRequest + /// and addAsReply with no existence on the ready or active queues. + /// + /// These methods will take out a reference count on the request, + /// caller only needs to dispose of its reference when done with + /// the request. + /// + /// Threading: called by worker thread. + /// + virtual void stageFromRequest(HttpService *); + virtual void stageFromReady(HttpService *); + virtual void stageFromActive(HttpService *); + + /// Delivers a notification to a handler object on completion. + /// + /// Once a request is complete and it has been removed from its + /// reply queue, a handler notification may be delivered by a + /// call to HttpRequest::update(). This method does the necessary + /// dispatching. + /// + /// Threading: called by application thread. + /// + virtual void visitNotifier(HttpRequest *); + + /// Cancels the operation whether queued or active. + /// Final status of the request becomes canceled (an error) and + /// that will be delivered to caller via notification scheme. + /// + /// Threading: called by worker thread. + /// + virtual HttpStatus cancel(); + +protected: + /// Delivers request to reply queue on completion. After this + /// call, worker thread no longer accesses the object and it + /// is owned by the reply queue. + /// + /// Threading: called by worker thread. + /// + void addAsReply(); + +protected: + HttpReplyQueue * mReplyQueue; // Have refcount + HttpHandler * mUserHandler; // Naked pointer + +public: + // Request Data + HttpRequest::policy_t mReqPolicy; + HttpRequest::priority_t mReqPriority; + + // Reply Data + HttpStatus mStatus; + + // Tracing, debug and metrics + HttpTime mMetricCreated; + int mTracing; +}; // end class HttpOperation + + +/// HttpOpStop requests the servicing thread to shutdown +/// operations, cease pulling requests from the request +/// queue and release shared resources (particularly +/// those shared via reference count). The servicing +/// thread will then exit. The underlying thread object +/// remains so that another thread can join on the +/// servicing thread prior to final cleanup. The +/// request *does* generate a reply on the response +/// queue, if requested. + +class HttpOpStop : public HttpOperation +{ +public: + HttpOpStop(); + +protected: + virtual ~HttpOpStop(); + +private: + HttpOpStop(const HttpOpStop &); // Not defined + void operator=(const HttpOpStop &); // Not defined + +public: + virtual void stageFromRequest(HttpService *); + +}; // end class HttpOpStop + + +/// HttpOpNull is a do-nothing operation used for testing via +/// a basic loopback pattern. It's executed immediately by +/// the servicing thread which bounces a reply back to the +/// caller without any further delay. + +class HttpOpNull : public HttpOperation +{ +public: + HttpOpNull(); + +protected: + virtual ~HttpOpNull(); + +private: + HttpOpNull(const HttpOpNull &); // Not defined + void operator=(const HttpOpNull &); // Not defined + +public: + virtual void stageFromRequest(HttpService *); + +}; // end class HttpOpNull + + +/// HttpOpSpin is a test-only request that puts the worker +/// thread into a cpu spin. Used for unit tests and cleanup +/// evaluation. You do not want to use this in production. +class HttpOpSpin : public HttpOperation +{ +public: + // 0 does a hard spin in the operation + // 1 does a soft spin continuously requeuing itself + HttpOpSpin(int mode); + +protected: + virtual ~HttpOpSpin(); + +private: + HttpOpSpin(const HttpOpSpin &); // Not defined + void operator=(const HttpOpSpin &); // Not defined + +public: + virtual void stageFromRequest(HttpService *); + +protected: + int mMode; +}; // end class HttpOpSpin + + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_OPERATION_H_ + diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp new file mode 100644 index 0000000000..7db19b1841 --- /dev/null +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -0,0 +1,906 @@ +/** + * @file _httpoprequest.cpp + * @brief Definitions for internal class HttpOpRequest + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpoprequest.h" + +#include <cstdio> +#include <algorithm> + +#include "httpcommon.h" +#include "httphandler.h" +#include "httpresponse.h" +#include "bufferarray.h" +#include "httpheaders.h" +#include "httpoptions.h" + +#include "_httprequestqueue.h" +#include "_httpreplyqueue.h" +#include "_httpservice.h" +#include "_httppolicy.h" +#include "_httppolicyglobal.h" +#include "_httplibcurl.h" +#include "_httpinternal.h" + +#include "llhttpstatuscodes.h" +#include "llproxy.h" + +namespace +{ + +// Attempts to parse a 'Content-Range:' header. Caller must already +// have verified that the header tag is present. The 'buffer' argument +// will be processed by strtok_r calls which will modify the buffer. +// +// @return -1 if invalid and response should be dropped, 0 if valid an +// correct, 1 if couldn't be parsed. If 0, the first, last, +// and length arguments are also written. 'length' may be +// 0 if the length wasn't available to the server. +// +int parse_content_range_header(char * buffer, + unsigned int * first, + unsigned int * last, + unsigned int * length); + + +// Take data from libcurl's CURLOPT_DEBUGFUNCTION callback and +// escape and format it for a tracing line in logging. Absolutely +// anything including NULs can be in the data. If @scrub is true, +// non-printing or non-ascii characters are replaced with spaces +// otherwise a %XX form of escaping is used. +void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, + std::string & safe_line); + + +// OS-neutral string comparisons of various types +int os_strncasecmp(const char *s1, const char *s2, size_t n); +int os_strcasecmp(const char *s1, const char *s2); +char * os_strtok_r(char *str, const char *delim, char **saveptr); + + +static const char * const hdr_whitespace(" \t"); +static const char * const hdr_separator(": \t"); + +} // end anonymous namespace + + +namespace LLCore +{ + + +HttpOpRequest::HttpOpRequest() + : HttpOperation(), + mProcFlags(0U), + mReqMethod(HOR_GET), + mReqBody(NULL), + mReqOffset(0), + mReqLength(0), + mReqHeaders(NULL), + mReqOptions(NULL), + mCurlActive(false), + mCurlHandle(NULL), + mCurlService(NULL), + mCurlHeaders(NULL), + mCurlBodyPos(0), + mReplyBody(NULL), + mReplyOffset(0), + mReplyLength(0), + mReplyFullLength(0), + mReplyHeaders(NULL), + mPolicyRetries(0), + mPolicyRetryAt(HttpTime(0)), + mPolicyRetryLimit(HTTP_RETRY_COUNT_DEFAULT) +{ + // *NOTE: As members are added, retry initialization/cleanup + // may need to be extended in @see prepareRequest(). +} + + + +HttpOpRequest::~HttpOpRequest() +{ + if (mReqBody) + { + mReqBody->release(); + mReqBody = NULL; + } + + if (mReqOptions) + { + mReqOptions->release(); + mReqOptions = NULL; + } + + if (mReqHeaders) + { + mReqHeaders->release(); + mReqHeaders = NULL; + } + + if (mCurlHandle) + { + curl_easy_cleanup(mCurlHandle); + mCurlHandle = NULL; + } + + mCurlService = NULL; + + if (mCurlHeaders) + { + curl_slist_free_all(mCurlHeaders); + mCurlHeaders = NULL; + } + + if (mReplyBody) + { + mReplyBody->release(); + mReplyBody = NULL; + } + + if (mReplyHeaders) + { + mReplyHeaders->release(); + mReplyHeaders = NULL; + } +} + + +void HttpOpRequest::stageFromRequest(HttpService * service) +{ + addRef(); + service->getPolicy().addOp(this); // transfers refcount +} + + +void HttpOpRequest::stageFromReady(HttpService * service) +{ + addRef(); + service->getTransport().addOp(this); // transfers refcount +} + + +void HttpOpRequest::stageFromActive(HttpService * service) +{ + if (mReplyLength) + { + // If non-zero, we received and processed a Content-Range + // header with the response. Verify that what it says + // is consistent with the received data. + if (mReplyLength != mReplyBody->size()) + { + // Not as expected, fail the request + mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); + } + } + + if (mCurlHeaders) + { + // We take these headers out of the request now as they were + // allocated originally in this thread and the notifier doesn't + // need them. This eliminates one source of heap moving across + // threads. + + curl_slist_free_all(mCurlHeaders); + mCurlHeaders = NULL; + } + + addAsReply(); +} + + +void HttpOpRequest::visitNotifier(HttpRequest * request) +{ + if (mUserHandler) + { + HttpResponse * response = new HttpResponse(); + response->setStatus(mStatus); + response->setBody(mReplyBody); + response->setHeaders(mReplyHeaders); + if (mReplyOffset || mReplyLength) + { + // Got an explicit offset/length in response + response->setRange(mReplyOffset, mReplyLength, mReplyFullLength); + } + response->setContentType(mReplyConType); + + mUserHandler->onCompleted(static_cast<HttpHandle>(this), response); + + response->release(); + } +} + + +HttpStatus HttpOpRequest::cancel() +{ + mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED); + + addAsReply(); + + return HttpStatus(); +} + + +HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers) +{ + setupCommon(policy_id, priority, url, NULL, options, headers); + mReqMethod = HOR_GET; + + return HttpStatus(); +} + + +HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + size_t offset, + size_t len, + HttpOptions * options, + HttpHeaders * headers) +{ + setupCommon(policy_id, priority, url, NULL, options, headers); + mReqMethod = HOR_GET; + mReqOffset = offset; + mReqLength = len; + if (offset || len) + { + mProcFlags |= PF_SCAN_RANGE_HEADER; + } + + return HttpStatus(); +} + + +HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers) +{ + setupCommon(policy_id, priority, url, body, options, headers); + mReqMethod = HOR_POST; + + return HttpStatus(); +} + + +HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers) +{ + setupCommon(policy_id, priority, url, body, options, headers); + mReqMethod = HOR_PUT; + + return HttpStatus(); +} + + +void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers) +{ + mProcFlags = 0U; + mReqPolicy = policy_id; + mReqPriority = priority; + mReqURL = url; + if (body) + { + body->addRef(); + mReqBody = body; + } + if (headers && ! mReqHeaders) + { + headers->addRef(); + mReqHeaders = headers; + } + if (options && ! mReqOptions) + { + options->addRef(); + mReqOptions = options; + if (options->getWantHeaders()) + { + mProcFlags |= PF_SAVE_HEADERS; + } + mPolicyRetryLimit = options->getRetries(); + mPolicyRetryLimit = llclamp(mPolicyRetryLimit, HTTP_RETRY_COUNT_MIN, HTTP_RETRY_COUNT_MAX); + mTracing = (std::max)(mTracing, llclamp(options->getTrace(), HTTP_TRACE_MIN, HTTP_TRACE_MAX)); + } +} + + +// Sets all libcurl options and data for a request. +// +// Used both for initial requests and to 'reload' for +// a retry, generally with a different CURL handle. +// Junk may be left around from a failed request and that +// needs to be cleaned out. +// +HttpStatus HttpOpRequest::prepareRequest(HttpService * service) +{ + // Scrub transport and result data for retried op case + mCurlActive = false; + mCurlHandle = NULL; + mCurlService = NULL; + if (mCurlHeaders) + { + curl_slist_free_all(mCurlHeaders); + mCurlHeaders = NULL; + } + mCurlBodyPos = 0; + + if (mReplyBody) + { + mReplyBody->release(); + mReplyBody = NULL; + } + mReplyOffset = 0; + mReplyLength = 0; + mReplyFullLength = 0; + if (mReplyHeaders) + { + mReplyHeaders->release(); + mReplyHeaders = NULL; + } + mReplyConType.clear(); + + // *FIXME: better error handling later + HttpStatus status; + + // Get policy options + HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions()); + + mCurlHandle = curl_easy_init(); + curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); + curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + + if (HTTP_ENABLE_LINKSYS_WRT54G_V5_DNS_FIX) + { + // The Linksys WRT54G V5 router has an issue with frequent + // DNS lookups from LAN machines. If they happen too often, + // like for every HTTP request, the router gets annoyed after + // about 700 or so requests and starts issuing TCP RSTs to + // new connections. Reuse the DNS lookups for even a few + // seconds and no RSTs. + curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15); + } + else + { + // *TODO: Revisit this old DNS timeout setting - may no longer be valid + // I don't think this is valid anymore, the Multi shared DNS + // cache is working well. For the case of naked easy handles, + // consider using a shared DNS object. + curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); + } + curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT); + curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this); + curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback); + curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this); + curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); + + const std::string * opt_value(NULL); + long opt_long(0L); + policy.get(HttpRequest::GP_LLPROXY, &opt_long); + if (opt_long) + { + // Use the viewer-based thread-safe API which has a + // fast/safe check for proxy enable. Would like to + // encapsulate this someway... + LLProxy::getInstance()->applyProxySettings(mCurlHandle); + } + else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value)) + { + // *TODO: This is fine for now but get fuller socks5/ + // authentication thing going later.... + curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + } + if (policy.get(HttpRequest::GP_CA_PATH, &opt_value)) + { + curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str()); + } + if (policy.get(HttpRequest::GP_CA_FILE, &opt_value)) + { + curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str()); + } + + switch (mReqMethod) + { + case HOR_GET: + curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); + break; + + case HOR_POST: + { + curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + long data_size(0); + if (mReqBody) + { + data_size = mReqBody->size(); + } + curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL)); + curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); + } + break; + + case HOR_PUT: + { + curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1); + long data_size(0); + if (mReqBody) + { + data_size = mReqBody->size(); + } + curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size); + curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); + } + break; + + default: + LL_ERRS("CoreHttp") << "Invalid HTTP method in request: " + << int(mReqMethod) << ". Can't recover." + << LL_ENDL; + break; + } + + // Tracing + if (mTracing >= HTTP_TRACE_CURL_HEADERS) + { + curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this); + curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback); + } + + // There's a CURLOPT for this now... + if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod) + { + static const char * const fmt1("Range: bytes=%lu-%lu"); + static const char * const fmt2("Range: bytes=%lu-"); + + char range_line[64]; + +#if LL_WINDOWS + _snprintf_s(range_line, sizeof(range_line), sizeof(range_line) - 1, + (mReqLength ? fmt1 : fmt2), + (unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1)); +#else + snprintf(range_line, sizeof(range_line), + (mReqLength ? fmt1 : fmt2), + (unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1)); +#endif // LL_WINDOWS + range_line[sizeof(range_line) - 1] = '\0'; + mCurlHeaders = curl_slist_append(mCurlHeaders, range_line); + } + + mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:"); + + // Request options + long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT); + if (mReqOptions) + { + timeout = mReqOptions->getTimeout(); + timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX); + } + curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout); + + // Request headers + if (mReqHeaders) + { + // Caller's headers last to override + mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders); + } + curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); + + if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS)) + { + curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); + curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this); + } + + if (status) + { + mCurlService = service; + } + return status; +} + + +size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void * userdata) +{ + HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata)); + + if (! op->mReplyBody) + { + op->mReplyBody = new BufferArray(); + } + const size_t req_size(size * nmemb); + const size_t write_size(op->mReplyBody->append(static_cast<char *>(data), req_size)); + return write_size; +} + + +size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void * userdata) +{ + HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata)); + + if (! op->mReqBody) + { + return 0; + } + const size_t req_size(size * nmemb); + const size_t body_size(op->mReqBody->size()); + if (body_size <= op->mCurlBodyPos) + { + LL_WARNS("HttpCore") << "Request body position beyond body size. Aborting request." + << LL_ENDL; + return 0; + } + + const size_t do_size((std::min)(req_size, body_size - op->mCurlBodyPos)); + const size_t read_size(op->mReqBody->read(op->mCurlBodyPos, static_cast<char *>(data), do_size)); + op->mCurlBodyPos += read_size; + return read_size; +} + + +size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, void * userdata) +{ + static const char status_line[] = "HTTP/"; + static const size_t status_line_len = sizeof(status_line) - 1; + + static const char con_ran_line[] = "content-range:"; + static const size_t con_ran_line_len = sizeof(con_ran_line) - 1; + + HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata)); + + const size_t hdr_size(size * nmemb); + const char * hdr_data(static_cast<const char *>(data)); // Not null terminated + + if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len)) + { + // One of possibly several status lines. Reset what we know and start over + // taking results from the last header stanza we receive. + op->mReplyOffset = 0; + op->mReplyLength = 0; + op->mReplyFullLength = 0; + op->mStatus = HttpStatus(); + if (op->mReplyHeaders) + { + op->mReplyHeaders->mHeaders.clear(); + } + } + + // Nothing in here wants a final CR/LF combination. Remove + // it as much as possible. + size_t wanted_hdr_size(hdr_size); + if (wanted_hdr_size && '\n' == hdr_data[wanted_hdr_size - 1]) + { + if (--wanted_hdr_size && '\r' == hdr_data[wanted_hdr_size - 1]) + { + --wanted_hdr_size; + } + } + + // Save header if caller wants them in the response + if (op->mProcFlags & PF_SAVE_HEADERS) + { + // Save headers in response + if (! op->mReplyHeaders) + { + op->mReplyHeaders = new HttpHeaders; + } + op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_hdr_size)); + } + + // Detect and parse 'Content-Range' headers + if (op->mProcFlags & PF_SCAN_RANGE_HEADER) + { + char hdr_buffer[128]; // Enough for a reasonable header + size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1)); + + memcpy(hdr_buffer, hdr_data, frag_size); + hdr_buffer[frag_size] = '\0'; + if (frag_size > con_ran_line_len && + ! os_strncasecmp(hdr_buffer, con_ran_line, con_ran_line_len)) + { + unsigned int first(0), last(0), length(0); + int status; + + if (! (status = parse_content_range_header(hdr_buffer, &first, &last, &length))) + { + // Success, record the fragment position + op->mReplyOffset = first; + op->mReplyLength = last - first + 1; + op->mReplyFullLength = length; + } + else if (-1 == status) + { + // Response is badly formed and shouldn't be accepted + op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); + } + else + { + // Ignore the unparsable. + LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header: '" + << std::string(hdr_data, frag_size) + << "'. Ignoring." + << LL_ENDL; + } + } + } + + return hdr_size; +} + + +int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata) +{ + HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata)); + + std::string safe_line; + std::string tag; + bool logit(false); + len = (std::min)(len, size_t(256)); // Keep things reasonable in all cases + + switch (info) + { + case CURLINFO_TEXT: + if (op->mTracing >= HTTP_TRACE_CURL_HEADERS) + { + tag = "TEXT"; + escape_libcurl_debug_data(buffer, len, true, safe_line); + logit = true; + } + break; + + case CURLINFO_HEADER_IN: + if (op->mTracing >= HTTP_TRACE_CURL_HEADERS) + { + tag = "HEADERIN"; + escape_libcurl_debug_data(buffer, len, true, safe_line); + logit = true; + } + break; + + case CURLINFO_HEADER_OUT: + if (op->mTracing >= HTTP_TRACE_CURL_HEADERS) + { + tag = "HEADEROUT"; + escape_libcurl_debug_data(buffer, 2 * len, true, safe_line); // Goes out as one line + logit = true; + } + break; + + case CURLINFO_DATA_IN: + if (op->mTracing >= HTTP_TRACE_CURL_HEADERS) + { + tag = "DATAIN"; + logit = true; + if (op->mTracing >= HTTP_TRACE_CURL_BODIES) + { + escape_libcurl_debug_data(buffer, len, false, safe_line); + } + else + { + std::ostringstream out; + out << len << " Bytes"; + safe_line = out.str(); + } + } + break; + + case CURLINFO_DATA_OUT: + if (op->mTracing >= HTTP_TRACE_CURL_HEADERS) + { + tag = "DATAOUT"; + logit = true; + if (op->mTracing >= HTTP_TRACE_CURL_BODIES) + { + escape_libcurl_debug_data(buffer, len, false, safe_line); + } + else + { + std::ostringstream out; + out << len << " Bytes"; + safe_line = out.str(); + } + } + break; + + default: + logit = false; + break; + } + + if (logit) + { + LL_INFOS("CoreHttp") << "TRACE, LibcurlDebug, Handle: " + << static_cast<HttpHandle>(op) + << ", Type: " << tag + << ", Data: " << safe_line + << LL_ENDL; + } + + return 0; +} + + +} // end namespace LLCore + + +// ======================================= +// Anonymous Namespace +// ======================================= + +namespace +{ + +int parse_content_range_header(char * buffer, + unsigned int * first, + unsigned int * last, + unsigned int * length) +{ + char * tok_state(NULL), * tok(NULL); + bool match(true); + + if (! os_strtok_r(buffer, hdr_separator, &tok_state)) + match = false; + if (match && (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state))) + match = 0 == os_strcasecmp("bytes", tok); + if (match && ! (tok = os_strtok_r(NULL, " \t", &tok_state))) + match = false; + if (match) + { + unsigned int lcl_first(0), lcl_last(0), lcl_len(0); + +#if LL_WINDOWS + if (3 == sscanf_s(tok, "%u-%u/%u", &lcl_first, &lcl_last, &lcl_len)) +#else + if (3 == sscanf(tok, "%u-%u/%u", &lcl_first, &lcl_last, &lcl_len)) +#endif // LL_WINDOWS + { + if (lcl_first > lcl_last || lcl_last >= lcl_len) + return -1; + *first = lcl_first; + *last = lcl_last; + *length = lcl_len; + return 0; + } +#if LL_WINDOWS + if (2 == sscanf_s(tok, "%u-%u/*", &lcl_first, &lcl_last)) +#else + if (2 == sscanf(tok, "%u-%u/*", &lcl_first, &lcl_last)) +#endif // LL_WINDOWS + { + if (lcl_first > lcl_last) + return -1; + *first = lcl_first; + *last = lcl_last; + *length = 0; + return 0; + } + } + + // Header is there but badly/unexpectedly formed, try to ignore it. + return 1; +} + + +void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line) +{ + std::string out; + len = (std::min)(len, size_t(200)); + out.reserve(3 * len); + for (int i(0); i < len; ++i) + { + unsigned char uc(static_cast<unsigned char>(buffer[i])); + + if (uc < 32 || uc > 126) + { + if (scrub) + { + out.append(1, ' '); + } + else + { + static const char hex[] = "0123456789ABCDEF"; + char convert[4]; + + convert[0] = '%'; + convert[1] = hex[(uc >> 4) % 16]; + convert[2] = hex[uc % 16]; + convert[3] = '\0'; + out.append(convert); + } + } + else + { + out.append(1, buffer[i]); + } + } + safe_line.swap(out); +} + + +int os_strncasecmp(const char *s1, const char *s2, size_t n) +{ +#if LL_WINDOWS + return _strnicmp(s1, s2, n); +#else + return strncasecmp(s1, s2, n); +#endif // LL_WINDOWS +} + + +int os_strcasecmp(const char *s1, const char *s2) +{ +#if LL_WINDOWS + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif // LL_WINDOWS +} + + +char * os_strtok_r(char *str, const char *delim, char ** savestate) +{ +#if LL_WINDOWS + return strtok_s(str, delim, savestate); +#else + return strtok_r(str, delim, savestate); +#endif +} + + +} // end anonymous namespace + + diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h new file mode 100644 index 0000000000..7b65d17783 --- /dev/null +++ b/indra/llcorehttp/_httpoprequest.h @@ -0,0 +1,219 @@ +/** + * @file _httpoprequest.h + * @brief Internal declarations for the HttpOpRequest subclass + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_OPREQUEST_H_ +#define _LLCORE_HTTP_OPREQUEST_H_ + + +#include "linden_common.h" // Modifies curl/curl.h interfaces + +#include <string> +#include <curl/curl.h> + +#include "httpcommon.h" +#include "httprequest.h" +#include "_httpoperation.h" +#include "_refcounted.h" + + +namespace LLCore +{ + + +class BufferArray; +class HttpHeaders; +class HttpOptions; + + +/// HttpOpRequest requests a supported HTTP method invocation with +/// option and header overrides. +/// +/// Essentially an RPC to get an HTTP GET, POST or PUT executed +/// asynchronously with options to override behaviors and HTTP +/// headers. +/// +/// Constructor creates a raw object incapable of useful work. +/// A subsequent call to one of the setupXXX() methods provides +/// the information needed to make a working request which can +/// then be enqueued to a request queue. +/// + +class HttpOpRequest : public HttpOperation +{ +public: + HttpOpRequest(); + +protected: + virtual ~HttpOpRequest(); // Use release() + +private: + HttpOpRequest(const HttpOpRequest &); // Not defined + void operator=(const HttpOpRequest &); // Not defined + +public: + enum EMethod + { + HOR_GET, + HOR_POST, + HOR_PUT + }; + + virtual void stageFromRequest(HttpService *); + virtual void stageFromReady(HttpService *); + virtual void stageFromActive(HttpService *); + + virtual void visitNotifier(HttpRequest * request); + +public: + /// Setup Methods + /// + /// Basically an RPC setup for each type of HTTP method + /// invocation with one per method type. These are + /// generally invoked right after construction. + /// + /// Threading: called by application thread + /// + HttpStatus setupGet(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers); + + HttpStatus setupGetByteRange(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + size_t offset, + size_t len, + HttpOptions * options, + HttpHeaders * headers); + + HttpStatus setupPost(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers); + + HttpStatus setupPut(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers); + + // Internal method used to setup the libcurl options for a request. + // Does all the libcurl handle setup in one place. + // + // Threading: called by worker thread + // + HttpStatus prepareRequest(HttpService * service); + + virtual HttpStatus cancel(); + +protected: + // Common setup for all the request methods. + // + // Threading: called by application thread + // + void setupCommon(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers); + + // libcurl operational callbacks + // + // Threading: called by worker thread + // + static size_t writeCallback(void * data, size_t size, size_t nmemb, void * userdata); + static size_t readCallback(void * data, size_t size, size_t nmemb, void * userdata); + static size_t headerCallback(void * data, size_t size, size_t nmemb, void * userdata); + static int debugCallback(CURL *, curl_infotype info, char * buffer, size_t len, void * userdata); + +protected: + unsigned int mProcFlags; + static const unsigned int PF_SCAN_RANGE_HEADER = 0x00000001U; + static const unsigned int PF_SAVE_HEADERS = 0x00000002U; + +public: + // Request data + EMethod mReqMethod; + std::string mReqURL; + BufferArray * mReqBody; + off_t mReqOffset; + size_t mReqLength; + HttpHeaders * mReqHeaders; + HttpOptions * mReqOptions; + + // Transport data + bool mCurlActive; + CURL * mCurlHandle; + HttpService * mCurlService; + curl_slist * mCurlHeaders; + size_t mCurlBodyPos; + + // Result data + HttpStatus mStatus; + BufferArray * mReplyBody; + off_t mReplyOffset; + size_t mReplyLength; + size_t mReplyFullLength; + HttpHeaders * mReplyHeaders; + std::string mReplyConType; + + // Policy data + int mPolicyRetries; + HttpTime mPolicyRetryAt; + int mPolicyRetryLimit; +}; // end class HttpOpRequest + + +/// HttpOpRequestCompare isn't an operation but a uniform comparison +/// functor for STL containers that order by priority. Mainly +/// used for the ready queue container but defined here. +class HttpOpRequestCompare +{ +public: + bool operator()(const HttpOpRequest * lhs, const HttpOpRequest * rhs) + { + return lhs->mReqPriority > rhs->mReqPriority; + } +}; // end class HttpOpRequestCompare + + +// --------------------------------------- +// Free functions +// --------------------------------------- + +// Internal function to append the contents of an HttpHeaders +// instance to a curl_slist object. +curl_slist * append_headers_to_slist(const HttpHeaders *, curl_slist * slist); + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_OPREQUEST_H_ + diff --git a/indra/llcorehttp/_httpopsetget.cpp b/indra/llcorehttp/_httpopsetget.cpp new file mode 100644 index 0000000000..8198528a9b --- /dev/null +++ b/indra/llcorehttp/_httpopsetget.cpp @@ -0,0 +1,97 @@ +/** + * @file _httpopsetget.cpp + * @brief Definitions for internal class HttpOpSetGet + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpopsetget.h" + +#include "httpcommon.h" + +#include "_httpservice.h" +#include "_httppolicy.h" + + +namespace LLCore +{ + + +// ================================== +// HttpOpSetget +// ================================== + + +HttpOpSetGet::HttpOpSetGet() + : HttpOperation(), + mIsGlobal(false), + mDoSet(false), + mSetting(-1), // Nothing requested + mLongValue(0L) +{} + + +HttpOpSetGet::~HttpOpSetGet() +{} + + +void HttpOpSetGet::setupGet(HttpRequest::EGlobalPolicy setting) +{ + mIsGlobal = true; + mSetting = setting; +} + + +void HttpOpSetGet::setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value) +{ + mIsGlobal = true; + mDoSet = true; + mSetting = setting; + mStrValue = value; +} + + +void HttpOpSetGet::stageFromRequest(HttpService * service) +{ + HttpPolicyGlobal & pol_opt(service->getPolicy().getGlobalOptions()); + HttpRequest::EGlobalPolicy setting(static_cast<HttpRequest::EGlobalPolicy>(mSetting)); + + if (mDoSet) + { + mStatus = pol_opt.set(setting, mStrValue); + } + if (mStatus) + { + const std::string * value(NULL); + if ((mStatus = pol_opt.get(setting, &value))) + { + mStrValue = *value; + } + } + + addAsReply(); +} + + +} // end namespace LLCore + + diff --git a/indra/llcorehttp/_httpopsetget.h b/indra/llcorehttp/_httpopsetget.h new file mode 100644 index 0000000000..6966b9d94e --- /dev/null +++ b/indra/llcorehttp/_httpopsetget.h @@ -0,0 +1,83 @@ +/** + * @file _httpopsetget.h + * @brief Internal declarations for the HttpOpSetGet subclass + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_OPSETGET_H_ +#define _LLCORE_HTTP_OPSETGET_H_ + + +#include "linden_common.h" // Modifies curl/curl.h interfaces + +#include "httpcommon.h" + +#include <curl/curl.h> + +#include "_httpoperation.h" +#include "_refcounted.h" + + +namespace LLCore +{ + + +/// HttpOpSetGet requests dynamic changes to policy and +/// configuration settings. +/// +/// *NOTE: Expect this to change. Don't really like it yet. + +class HttpOpSetGet : public HttpOperation +{ +public: + HttpOpSetGet(); + +protected: + virtual ~HttpOpSetGet(); // Use release() + +private: + HttpOpSetGet(const HttpOpSetGet &); // Not defined + void operator=(const HttpOpSetGet &); // Not defined + +public: + /// Threading: called by application thread + void setupGet(HttpRequest::EGlobalPolicy setting); + void setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value); + + virtual void stageFromRequest(HttpService *); + +public: + // Request data + bool mIsGlobal; + bool mDoSet; + int mSetting; + long mLongValue; + std::string mStrValue; + +}; // end class HttpOpSetGet + + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_OPSETGET_H_ + diff --git a/indra/llcorehttp/_httpopsetpriority.cpp b/indra/llcorehttp/_httpopsetpriority.cpp new file mode 100644 index 0000000000..d48c7a0b7d --- /dev/null +++ b/indra/llcorehttp/_httpopsetpriority.cpp @@ -0,0 +1,63 @@ +/** + * @file _httpopsetpriority.cpp + * @brief Definitions for internal classes based on HttpOpSetPriority + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpopsetpriority.h" + +#include "httpresponse.h" +#include "httphandler.h" +#include "_httpservice.h" + + +namespace LLCore +{ + + +HttpOpSetPriority::HttpOpSetPriority(HttpHandle handle, HttpRequest::priority_t priority) + : HttpOperation(), + mHandle(handle), + mPriority(priority) +{} + + +HttpOpSetPriority::~HttpOpSetPriority() +{} + + +void HttpOpSetPriority::stageFromRequest(HttpService * service) +{ + // Do operations + if (! service->changePriority(mHandle, mPriority)) + { + // Request not found, fail the final status + mStatus = HttpStatus(HttpStatus::LLCORE, HE_HANDLE_NOT_FOUND); + } + + // Move directly to response queue + addAsReply(); +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httpopsetpriority.h b/indra/llcorehttp/_httpopsetpriority.h new file mode 100644 index 0000000000..31706b737c --- /dev/null +++ b/indra/llcorehttp/_httpopsetpriority.h @@ -0,0 +1,73 @@ +/** + * @file _httpsetpriority.h + * @brief Internal declarations for HttpSetPriority + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_SETPRIORITY_H_ +#define _LLCORE_HTTP_SETPRIORITY_H_ + + +#include "httpcommon.h" +#include "httprequest.h" +#include "_httpoperation.h" +#include "_refcounted.h" + + +namespace LLCore +{ + + +/// HttpOpSetPriority is an immediate request that +/// searches the various queues looking for a given +/// request handle and changing it's priority if +/// found. +/// +/// *NOTE: This will very likely be removed in the near future +/// when priority is removed from the library. + +class HttpOpSetPriority : public HttpOperation +{ +public: + HttpOpSetPriority(HttpHandle handle, HttpRequest::priority_t priority); + +protected: + virtual ~HttpOpSetPriority(); + +private: + HttpOpSetPriority(const HttpOpSetPriority &); // Not defined + void operator=(const HttpOpSetPriority &); // Not defined + +public: + virtual void stageFromRequest(HttpService *); + +protected: + // Request Data + HttpHandle mHandle; + HttpRequest::priority_t mPriority; +}; // end class HttpOpSetPriority + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_SETPRIORITY_H_ + diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp new file mode 100644 index 0000000000..76c1e22431 --- /dev/null +++ b/indra/llcorehttp/_httppolicy.cpp @@ -0,0 +1,387 @@ +/** + * @file _httppolicy.cpp + * @brief Internal definitions of the Http policy thread + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "_httppolicy.h" + +#include "_httpoprequest.h" +#include "_httpservice.h" +#include "_httplibcurl.h" +#include "_httppolicyclass.h" + +#include "lltimer.h" + + +namespace LLCore +{ + + +// Per-policy-class data for a running system. +// Collection of queues, parameters, history, metrics, etc. +// for a single policy class. +// +// Threading: accessed only by worker thread +struct HttpPolicy::State +{ +public: + State() + : mConnMax(HTTP_CONNECTION_LIMIT_DEFAULT), + mConnAt(HTTP_CONNECTION_LIMIT_DEFAULT), + mConnMin(1), + mNextSample(0), + mErrorCount(0), + mErrorFactor(0) + {} + + HttpReadyQueue mReadyQueue; + HttpRetryQueue mRetryQueue; + + HttpPolicyClass mOptions; + + long mConnMax; + long mConnAt; + long mConnMin; + + HttpTime mNextSample; + unsigned long mErrorCount; + unsigned long mErrorFactor; +}; + + +HttpPolicy::HttpPolicy(HttpService * service) + : mActiveClasses(0), + mState(NULL), + mService(service) +{} + + +HttpPolicy::~HttpPolicy() +{ + shutdown(); + + mService = NULL; +} + + +void HttpPolicy::shutdown() +{ + for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + { + HttpRetryQueue & retryq(mState[policy_class].mRetryQueue); + while (! retryq.empty()) + { + HttpOpRequest * op(retryq.top()); + retryq.pop(); + + op->cancel(); + op->release(); + } + + HttpReadyQueue & readyq(mState[policy_class].mReadyQueue); + while (! readyq.empty()) + { + HttpOpRequest * op(readyq.top()); + readyq.pop(); + + op->cancel(); + op->release(); + } + } + delete [] mState; + mState = NULL; + mActiveClasses = 0; +} + + +void HttpPolicy::start(const HttpPolicyGlobal & global, + const std::vector<HttpPolicyClass> & classes) +{ + llassert_always(! mState); + + mGlobalOptions = global; + mActiveClasses = classes.size(); + mState = new State [mActiveClasses]; + for (int i(0); i < mActiveClasses; ++i) + { + mState[i].mOptions = classes[i]; + mState[i].mConnMax = classes[i].mConnectionLimit; + mState[i].mConnAt = mState[i].mConnMax; + mState[i].mConnMin = 2; + } +} + + +void HttpPolicy::addOp(HttpOpRequest * op) +{ + const int policy_class(op->mReqPolicy); + + op->mPolicyRetries = 0; + mState[policy_class].mReadyQueue.push(op); +} + + +void HttpPolicy::retryOp(HttpOpRequest * op) +{ + static const HttpTime retry_deltas[] = + { + 250000, // 1st retry in 0.25 S, etc... + 500000, + 1000000, + 2000000, + 5000000 // ... to every 5.0 S. + }; + static const int delta_max(int(LL_ARRAY_SIZE(retry_deltas)) - 1); + + const HttpTime now(totalTime()); + const int policy_class(op->mReqPolicy); + + const HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]); + op->mPolicyRetryAt = now + delta; + ++op->mPolicyRetries; + LL_WARNS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op) + << " retry " << op->mPolicyRetries + << " scheduled for +" << (delta / HttpTime(1000)) + << " mS. Status: " << op->mStatus.toHex() + << LL_ENDL; + if (op->mTracing > 0) + { + LL_INFOS("CoreHttp") << "TRACE, ToRetryQueue, Handle: " + << static_cast<HttpHandle>(op) + << LL_ENDL; + } + mState[policy_class].mRetryQueue.push(op); +} + + +// Attempt to deliver requests to the transport layer. +// +// Tries to find HTTP requests for each policy class with +// available capacity. Starts with the retry queue first +// looking for requests that have waited long enough then +// moves on to the ready queue. +// +// If all queues are empty, will return an indication that +// the worker thread may sleep hard otherwise will ask for +// normal polling frequency. +// +HttpService::ELoopSpeed HttpPolicy::processReadyQueue() +{ + const HttpTime now(totalTime()); + HttpService::ELoopSpeed result(HttpService::REQUEST_SLEEP); + HttpLibcurl & transport(mService->getTransport()); + + for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + { + State & state(mState[policy_class]); + int active(transport.getActiveCountInClass(policy_class)); + int needed(state.mConnAt - active); // Expect negatives here + + HttpRetryQueue & retryq(state.mRetryQueue); + HttpReadyQueue & readyq(state.mReadyQueue); + + if (needed > 0) + { + // First see if we have any retries... + while (needed > 0 && ! retryq.empty()) + { + HttpOpRequest * op(retryq.top()); + if (op->mPolicyRetryAt > now) + break; + + retryq.pop(); + + op->stageFromReady(mService); + op->release(); + + --needed; + } + + // Now go on to the new requests... + while (needed > 0 && ! readyq.empty()) + { + HttpOpRequest * op(readyq.top()); + readyq.pop(); + + op->stageFromReady(mService); + op->release(); + + --needed; + } + } + + if (! readyq.empty() || ! retryq.empty()) + { + // If anything is ready, continue looping... + result = HttpService::NORMAL; + } + } // end foreach policy_class + + return result; +} + + +bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t priority) +{ + for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + { + State & state(mState[policy_class]); + // We don't scan retry queue because a priority change there + // is meaningless. The request will be issued based on retry + // intervals not priority value, which is now moot. + + // Scan ready queue for requests that match policy + HttpReadyQueue::container_type & c(state.mReadyQueue.get_container()); + for (HttpReadyQueue::container_type::iterator iter(c.begin()); c.end() != iter;) + { + HttpReadyQueue::container_type::iterator cur(iter++); + + if (static_cast<HttpHandle>(*cur) == handle) + { + HttpOpRequest * op(*cur); + c.erase(cur); // All iterators are now invalidated + op->mReqPriority = priority; + state.mReadyQueue.push(op); // Re-insert using adapter class + return true; + } + } + } + + return false; +} + + +bool HttpPolicy::cancel(HttpHandle handle) +{ + for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + { + State & state(mState[policy_class]); + + // Scan retry queue + HttpRetryQueue::container_type & c1(state.mRetryQueue.get_container()); + for (HttpRetryQueue::container_type::iterator iter(c1.begin()); c1.end() != iter;) + { + HttpRetryQueue::container_type::iterator cur(iter++); + + if (static_cast<HttpHandle>(*cur) == handle) + { + HttpOpRequest * op(*cur); + c1.erase(cur); // All iterators are now invalidated + op->cancel(); + op->release(); + return true; + } + } + + // Scan ready queue + HttpReadyQueue::container_type & c2(state.mReadyQueue.get_container()); + for (HttpReadyQueue::container_type::iterator iter(c2.begin()); c2.end() != iter;) + { + HttpReadyQueue::container_type::iterator cur(iter++); + + if (static_cast<HttpHandle>(*cur) == handle) + { + HttpOpRequest * op(*cur); + c2.erase(cur); // All iterators are now invalidated + op->cancel(); + op->release(); + return true; + } + } + } + + return false; +} + + +bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op) +{ + static const HttpStatus cant_connect(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); + static const HttpStatus cant_res_proxy(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_PROXY); + static const HttpStatus cant_res_host(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_HOST); + static const HttpStatus send_error(HttpStatus::EXT_CURL_EASY, CURLE_SEND_ERROR); + static const HttpStatus recv_error(HttpStatus::EXT_CURL_EASY, CURLE_RECV_ERROR); + static const HttpStatus upload_failed(HttpStatus::EXT_CURL_EASY, CURLE_UPLOAD_FAILED); + static const HttpStatus op_timedout(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); + static const HttpStatus post_error(HttpStatus::EXT_CURL_EASY, CURLE_HTTP_POST_ERROR); + + // Retry or finalize + if (! op->mStatus) + { + // If this failed, we might want to retry. Have to inspect + // the status a little more deeply for those reasons worth retrying... + if (op->mPolicyRetries < op->mPolicyRetryLimit && + ((op->mStatus.isHttpStatus() && op->mStatus.mType >= 499 && op->mStatus.mType <= 599) || + cant_connect == op->mStatus || + cant_res_proxy == op->mStatus || + cant_res_host == op->mStatus || + send_error == op->mStatus || + recv_error == op->mStatus || + upload_failed == op->mStatus || + op_timedout == op->mStatus || + post_error == op->mStatus)) + { + // Okay, worth a retry. We include 499 in this test as + // it's the old 'who knows?' error from many grid services... + retryOp(op); + return true; // still active/ready + } + } + + // This op is done, finalize it delivering it to the reply queue... + if (! op->mStatus) + { + LL_WARNS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op) + << " failed after " << op->mPolicyRetries + << " retries. Reason: " << op->mStatus.toString() + << " (" << op->mStatus.toHex() << ")" + << LL_ENDL; + } + else if (op->mPolicyRetries) + { + LL_WARNS("CoreHttp") << "HTTP request " << static_cast<HttpHandle>(op) + << " succeeded on retry " << op->mPolicyRetries << "." + << LL_ENDL; + } + + op->stageFromActive(mService); + op->release(); + return false; // not active +} + + +int HttpPolicy::getReadyCount(HttpRequest::policy_t policy_class) const +{ + if (policy_class < mActiveClasses) + { + return (mState[policy_class].mReadyQueue.size() + + mState[policy_class].mRetryQueue.size()); + } + return 0; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h new file mode 100644 index 0000000000..03d92c0b8e --- /dev/null +++ b/indra/llcorehttp/_httppolicy.h @@ -0,0 +1,161 @@ +/** + * @file _httppolicy.h + * @brief Declarations for internal class enforcing policy decisions. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_POLICY_H_ +#define _LLCORE_HTTP_POLICY_H_ + + +#include "httprequest.h" +#include "_httpservice.h" +#include "_httpreadyqueue.h" +#include "_httpretryqueue.h" +#include "_httppolicyglobal.h" +#include "_httppolicyclass.h" +#include "_httpinternal.h" + + +namespace LLCore +{ + +class HttpReadyQueue; +class HttpOpRequest; + + +/// Implements class-based queuing policies for an HttpService instance. +/// +/// Threading: Single-threaded. Other than for construction/destruction, +/// all methods are expected to be invoked in a single thread, typically +/// a worker thread of some sort. +class HttpPolicy +{ +public: + HttpPolicy(HttpService *); + virtual ~HttpPolicy(); + +private: + HttpPolicy(const HttpPolicy &); // Not defined + void operator=(const HttpPolicy &); // Not defined + +public: + /// Cancel all ready and retry requests sending them to + /// their notification queues. Release state resources + /// making further request handling impossible. + /// + /// Threading: called by worker thread + void shutdown(); + + /// Deliver policy definitions and enable handling of + /// requests. One-time call invoked before starting + /// the worker thread. + /// + /// Threading: called by application thread + void start(const HttpPolicyGlobal & global, + const std::vector<HttpPolicyClass> & classes); + + /// Give the policy layer some cycles to scan the ready + /// queue promoting higher-priority requests to active + /// as permited. + /// + /// @return Indication of how soon this method + /// should be called again. + /// + /// Threading: called by worker thread + HttpService::ELoopSpeed processReadyQueue(); + + /// Add request to a ready queue. Caller is expected to have + /// provided us with a reference count to hold the request. (No + /// additional references will be added.) + /// + /// OpRequest is owned by the request queue after this call + /// and should not be modified by anyone until retrieved + /// from queue. + /// + /// Threading: called by any thread + void addOp(HttpOpRequest *); + + /// Similar to addOp, used when a caller wants to retry a + /// request that has failed. It's placed on a special retry + /// queue but ordered by retry time not priority. Otherwise, + /// handling is the same and retried operations are considered + /// before new ones but that doesn't guarantee completion + /// order. + /// + /// Threading: called by worker thread + void retryOp(HttpOpRequest *); + + /// Attempt to change the priority of an earlier request. + /// Request that Shadows HttpService's method + /// + /// Threading: called by worker thread + bool changePriority(HttpHandle handle, HttpRequest::priority_t priority); + + /// Attempt to cancel a previous request. + /// Shadows HttpService's method as well + /// + /// Threading: called by worker thread + bool cancel(HttpHandle handle); + + /// When transport is finished with an op and takes it off the + /// active queue, it is delivered here for dispatch. Policy + /// may send it back to the ready/retry queues if it needs another + /// go or we may finalize it and send it on to the reply queue. + /// + /// @return Returns true of the request is still active + /// or ready after staging, false if has been + /// sent on to the reply queue. + /// + /// Threading: called by worker thread + bool stageAfterCompletion(HttpOpRequest * op); + + // Get pointer to global policy options. Caller is expected + // to do context checks like no setting once running. + /// + /// Threading: called by any thread *but* the object may + /// only be modified by the worker thread once running. + /// + HttpPolicyGlobal & getGlobalOptions() + { + return mGlobalOptions; + } + + /// Get ready counts for a particular policy class + /// + /// Threading: called by worker thread + int getReadyCount(HttpRequest::policy_t policy_class) const; + +protected: + struct State; + + int mActiveClasses; + State * mState; + HttpService * mService; // Naked pointer, not refcounted, not owner + HttpPolicyGlobal mGlobalOptions; + +}; // end class HttpPolicy + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_POLICY_H_ diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp new file mode 100644 index 0000000000..a23b81322c --- /dev/null +++ b/indra/llcorehttp/_httppolicyclass.cpp @@ -0,0 +1,125 @@ +/** + * @file _httppolicyclass.cpp + * @brief Definitions for internal class defining class policy option. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httppolicyclass.h" + +#include "_httpinternal.h" + + +namespace LLCore +{ + + +HttpPolicyClass::HttpPolicyClass() + : mSetMask(0UL), + mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), + mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), + mPipelining(0) +{} + + +HttpPolicyClass::~HttpPolicyClass() +{} + + +HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other) +{ + if (this != &other) + { + mSetMask = other.mSetMask; + mConnectionLimit = other.mConnectionLimit; + mPerHostConnectionLimit = other.mPerHostConnectionLimit; + mPipelining = other.mPipelining; + } + return *this; +} + + +HttpPolicyClass::HttpPolicyClass(const HttpPolicyClass & other) + : mSetMask(other.mSetMask), + mConnectionLimit(other.mConnectionLimit), + mPerHostConnectionLimit(other.mPerHostConnectionLimit), + mPipelining(other.mPipelining) +{} + + +HttpStatus HttpPolicyClass::set(HttpRequest::EClassPolicy opt, long value) +{ + switch (opt) + { + case HttpRequest::CP_CONNECTION_LIMIT: + mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX)); + break; + + case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT: + mPerHostConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), mConnectionLimit); + break; + + case HttpRequest::CP_ENABLE_PIPELINING: + mPipelining = llclamp(value, 0L, 1L); + break; + + default: + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + + mSetMask |= 1UL << int(opt); + return HttpStatus(); +} + + +HttpStatus HttpPolicyClass::get(HttpRequest::EClassPolicy opt, long * value) +{ + static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); + long * src(NULL); + + switch (opt) + { + case HttpRequest::CP_CONNECTION_LIMIT: + src = &mConnectionLimit; + break; + + case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT: + src = &mPerHostConnectionLimit; + break; + + case HttpRequest::CP_ENABLE_PIPELINING: + src = &mPipelining; + break; + + default: + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + + if (! (mSetMask & (1UL << int(opt)))) + return not_set; + + *value = *src; + return HttpStatus(); +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httppolicyclass.h b/indra/llcorehttp/_httppolicyclass.h new file mode 100644 index 0000000000..d175413cbd --- /dev/null +++ b/indra/llcorehttp/_httppolicyclass.h @@ -0,0 +1,59 @@ +/** + * @file _httppolicyclass.h + * @brief Declarations for internal class defining policy class options. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_POLICY_CLASS_H_ +#define _LLCORE_HTTP_POLICY_CLASS_H_ + + +#include "httprequest.h" + + +namespace LLCore +{ + +class HttpPolicyClass +{ +public: + HttpPolicyClass(); + ~HttpPolicyClass(); + + HttpPolicyClass & operator=(const HttpPolicyClass &); + HttpPolicyClass(const HttpPolicyClass &); // Not defined + +public: + HttpStatus set(HttpRequest::EClassPolicy opt, long value); + HttpStatus get(HttpRequest::EClassPolicy opt, long * value); + +public: + unsigned long mSetMask; + long mConnectionLimit; + long mPerHostConnectionLimit; + long mPipelining; +}; // end class HttpPolicyClass + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_POLICY_CLASS_H_ diff --git a/indra/llcorehttp/_httppolicyglobal.cpp b/indra/llcorehttp/_httppolicyglobal.cpp new file mode 100644 index 0000000000..72f409d3b1 --- /dev/null +++ b/indra/llcorehttp/_httppolicyglobal.cpp @@ -0,0 +1,175 @@ +/** + * @file _httppolicyglobal.cpp + * @brief Definitions for internal class defining global policy option. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httppolicyglobal.h" + +#include "_httpinternal.h" + + +namespace LLCore +{ + + +HttpPolicyGlobal::HttpPolicyGlobal() + : mSetMask(0UL), + mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), + mTrace(HTTP_TRACE_OFF), + mUseLLProxy(0) +{} + + +HttpPolicyGlobal::~HttpPolicyGlobal() +{} + + +HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other) +{ + if (this != &other) + { + mSetMask = other.mSetMask; + mConnectionLimit = other.mConnectionLimit; + mCAPath = other.mCAPath; + mCAFile = other.mCAFile; + mHttpProxy = other.mHttpProxy; + mTrace = other.mTrace; + mUseLLProxy = other.mUseLLProxy; + } + return *this; +} + + +HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value) +{ + switch (opt) + { + case HttpRequest::GP_CONNECTION_LIMIT: + mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX)); + break; + + case HttpRequest::GP_TRACE: + mTrace = llclamp(value, long(HTTP_TRACE_MIN), long(HTTP_TRACE_MAX)); + break; + + case HttpRequest::GP_LLPROXY: + mUseLLProxy = llclamp(value, 0L, 1L); + break; + + default: + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + + mSetMask |= 1UL << int(opt); + return HttpStatus(); +} + + +HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::string & value) +{ + switch (opt) + { + case HttpRequest::GP_CA_PATH: + mCAPath = value; + break; + + case HttpRequest::GP_CA_FILE: + mCAFile = value; + break; + + case HttpRequest::GP_HTTP_PROXY: + mCAFile = value; + break; + + default: + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + + mSetMask |= 1UL << int(opt); + return HttpStatus(); +} + + +HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long * value) +{ + static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); + long * src(NULL); + + switch (opt) + { + case HttpRequest::GP_CONNECTION_LIMIT: + src = &mConnectionLimit; + break; + + case HttpRequest::GP_TRACE: + src = &mTrace; + break; + + case HttpRequest::GP_LLPROXY: + src = &mUseLLProxy; + break; + + default: + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + + if (! (mSetMask & (1UL << int(opt)))) + return not_set; + + *value = *src; + return HttpStatus(); +} + + +HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, const std::string ** value) +{ + static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); + const std::string * src(NULL); + + switch (opt) + { + case HttpRequest::GP_CA_PATH: + src = &mCAPath; + break; + + case HttpRequest::GP_CA_FILE: + src = &mCAFile; + break; + + case HttpRequest::GP_HTTP_PROXY: + src = &mHttpProxy; + break; + + default: + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + + if (! (mSetMask & (1UL << int(opt)))) + return not_set; + + *value = src; + return HttpStatus(); +} + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httppolicyglobal.h b/indra/llcorehttp/_httppolicyglobal.h new file mode 100644 index 0000000000..a50d0e4188 --- /dev/null +++ b/indra/llcorehttp/_httppolicyglobal.h @@ -0,0 +1,66 @@ +/** + * @file _httppolicyglobal.h + * @brief Declarations for internal class defining global policy option. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_POLICY_GLOBAL_H_ +#define _LLCORE_HTTP_POLICY_GLOBAL_H_ + + +#include "httprequest.h" + + +namespace LLCore +{ + +class HttpPolicyGlobal +{ +public: + HttpPolicyGlobal(); + ~HttpPolicyGlobal(); + + HttpPolicyGlobal & operator=(const HttpPolicyGlobal &); + +private: + HttpPolicyGlobal(const HttpPolicyGlobal &); // Not defined + +public: + HttpStatus set(HttpRequest::EGlobalPolicy opt, long value); + HttpStatus set(HttpRequest::EGlobalPolicy opt, const std::string & value); + HttpStatus get(HttpRequest::EGlobalPolicy opt, long * value); + HttpStatus get(HttpRequest::EGlobalPolicy opt, const std::string ** value); + +public: + unsigned long mSetMask; + long mConnectionLimit; + std::string mCAPath; + std::string mCAFile; + std::string mHttpProxy; + long mTrace; + long mUseLLProxy; +}; // end class HttpPolicyGlobal + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_POLICY_GLOBAL_H_ diff --git a/indra/llcorehttp/_httpreadyqueue.h b/indra/llcorehttp/_httpreadyqueue.h new file mode 100644 index 0000000000..5f19a9c5f9 --- /dev/null +++ b/indra/llcorehttp/_httpreadyqueue.h @@ -0,0 +1,124 @@ +/** + * @file _httpreadyqueue.h + * @brief Internal declaration for the operation ready queue + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_READY_QUEUE_H_ +#define _LLCORE_HTTP_READY_QUEUE_H_ + + +#include <queue> + +#include "_httpinternal.h" +#include "_httpoprequest.h" + + +namespace LLCore +{ + +/// HttpReadyQueue provides a simple priority queue for HttpOpRequest objects. +/// +/// This uses the priority_queue adaptor class to provide the queue +/// as well as the ordering scheme while allowing us access to the +/// raw container if we follow a few simple rules. One of the more +/// important of those rules is that any iterator becomes invalid +/// on element erasure. So pay attention. +/// +/// If LLCORE_HTTP_READY_QUEUE_IGNORES_PRIORITY tests true, the class +/// implements a std::priority_queue interface but on std::deque +/// behavior to eliminate sensitivity to priority. In the future, +/// this will likely become the only behavior or it may become +/// a run-time election. +/// +/// Threading: not thread-safe. Expected to be used entirely by +/// a single thread, typically a worker thread of some sort. + +#if LLCORE_HTTP_READY_QUEUE_IGNORES_PRIORITY + +typedef std::deque<HttpOpRequest *> HttpReadyQueueBase; + +#else + +typedef std::priority_queue<HttpOpRequest *, + std::deque<HttpOpRequest *>, + LLCore::HttpOpRequestCompare> HttpReadyQueueBase; + +#endif // LLCORE_HTTP_READY_QUEUE_IGNORES_PRIORITY + +class HttpReadyQueue : public HttpReadyQueueBase +{ +public: + HttpReadyQueue() + : HttpReadyQueueBase() + {} + + ~HttpReadyQueue() + {} + +protected: + HttpReadyQueue(const HttpReadyQueue &); // Not defined + void operator=(const HttpReadyQueue &); // Not defined + +public: + +#if LLCORE_HTTP_READY_QUEUE_IGNORES_PRIORITY + // Types and methods needed to make a std::deque look + // more like a std::priority_queue, at least for our + // purposes. + typedef HttpReadyQueueBase container_type; + + const_reference top() const + { + return front(); + } + + void pop() + { + pop_front(); + } + + void push(const value_type & v) + { + push_back(v); + } + +#endif // LLCORE_HTTP_READY_QUEUE_IGNORES_PRIORITY + + const container_type & get_container() const + { + return *this; + } + + container_type & get_container() + { + return *this; + } + +}; // end class HttpReadyQueue + + +} // end namespace LLCore + + +#endif // _LLCORE_HTTP_READY_QUEUE_H_ diff --git a/indra/llcorehttp/_httpreplyqueue.cpp b/indra/llcorehttp/_httpreplyqueue.cpp new file mode 100644 index 0000000000..558b7bdee9 --- /dev/null +++ b/indra/llcorehttp/_httpreplyqueue.cpp @@ -0,0 +1,107 @@ +/** + * @file _httpreplyqueue.cpp + * @brief Internal definitions for the operation reply queue + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpreplyqueue.h" + + +#include "_mutex.h" +#include "_thread.h" +#include "_httpoperation.h" + +using namespace LLCoreInt; + + +namespace LLCore +{ + + +HttpReplyQueue::HttpReplyQueue() + : RefCounted(true) +{ +} + + +HttpReplyQueue::~HttpReplyQueue() +{ + while (! mQueue.empty()) + { + HttpOperation * op = mQueue.back(); + mQueue.pop_back(); + op->release(); + } +} + + +void HttpReplyQueue::addOp(HttpOperation * op) +{ + { + HttpScopedLock lock(mQueueMutex); + + mQueue.push_back(op); + } + mQueueCV.notify_all(); +} + + +HttpOperation * HttpReplyQueue::fetchOp() +{ + HttpOperation * result(NULL); + + { + HttpScopedLock lock(mQueueMutex); + + if (mQueue.empty()) + return NULL; + + result = mQueue.front(); + mQueue.erase(mQueue.begin()); + } + + // Caller also acquires the reference count + return result; +} + + +void HttpReplyQueue::fetchAll(OpContainer & ops) +{ + // Not valid putting something back on the queue... + llassert_always(ops.empty()); + + { + HttpScopedLock lock(mQueueMutex); + + if (! mQueue.empty()) + { + mQueue.swap(ops); + } + } + + // Caller also acquires the reference counts on each op. + return; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httpreplyqueue.h b/indra/llcorehttp/_httpreplyqueue.h new file mode 100644 index 0000000000..4220a09a3b --- /dev/null +++ b/indra/llcorehttp/_httpreplyqueue.h @@ -0,0 +1,108 @@ +/** + * @file _httpreplyqueue.h + * @brief Internal declarations for the operation reply queue. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_REPLY_QUEUE_H_ +#define _LLCORE_HTTP_REPLY_QUEUE_H_ + + +#include "_refcounted.h" +#include "_mutex.h" + + +namespace LLCore +{ + + +class HttpOperation; + + +/// Almost identical to the HttpRequestQueue class but +/// whereas that class is a singleton and is known to the +/// HttpService object, this queue is 1:1 with HttpRequest +/// instances and isn't explicitly referenced by the +/// service object. Instead, HttpOperation objects that +/// want to generate replies back to their creators also +/// keep references to the corresponding HttpReplyQueue. +/// The HttpService plumbing then simply delivers replies +/// to the requested reply queue. +/// +/// One result of that is that the fetch operations do +/// not have a wait forever option. The service object +/// doesn't keep handles on everything it would need to +/// notify so it can't wake up sleepers should it need to +/// shutdown. So only non-blocking or timed-blocking modes +/// are anticipated. These are how most application consumers +/// will be coded anyway so it shouldn't be too much of a +/// burden. + +class HttpReplyQueue : public LLCoreInt::RefCounted +{ +public: + /// Caller acquires a Refcount on construction + HttpReplyQueue(); + +protected: + virtual ~HttpReplyQueue(); // Use release() + +private: + HttpReplyQueue(const HttpReplyQueue &); // Not defined + void operator=(const HttpReplyQueue &); // Not defined + +public: + typedef std::vector<HttpOperation *> OpContainer; + + /// Insert an object at the back of the reply queue. + /// + /// Library also takes possession of one reference count to pass + /// through the queue. + /// + /// Threading: callable by any thread. + void addOp(HttpOperation * op); + + /// Fetch an operation from the head of the queue. Returns + /// NULL if none exists. + /// + /// Caller acquires reference count on returned operation. + /// + /// Threading: callable by any thread. + HttpOperation * fetchOp(); + + /// Caller acquires reference count on each returned operation + /// + /// Threading: callable by any thread. + void fetchAll(OpContainer & ops); + +protected: + OpContainer mQueue; + LLCoreInt::HttpMutex mQueueMutex; + LLCoreInt::HttpConditionVariable mQueueCV; + +}; // end class HttpReplyQueue + +} // end namespace LLCore + + +#endif // _LLCORE_HTTP_REPLY_QUEUE_H_ diff --git a/indra/llcorehttp/_httprequestqueue.cpp b/indra/llcorehttp/_httprequestqueue.cpp new file mode 100644 index 0000000000..c16966d078 --- /dev/null +++ b/indra/llcorehttp/_httprequestqueue.cpp @@ -0,0 +1,161 @@ +/** + * @file _httprequestqueue.cpp + * @brief + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httprequestqueue.h" + +#include "_httpoperation.h" +#include "_mutex.h" + + +using namespace LLCoreInt; + +namespace LLCore +{ + +HttpRequestQueue * HttpRequestQueue::sInstance(NULL); + + +HttpRequestQueue::HttpRequestQueue() + : RefCounted(true), + mQueueStopped(false) +{ +} + + +HttpRequestQueue::~HttpRequestQueue() +{ + while (! mQueue.empty()) + { + HttpOperation * op = mQueue.back(); + mQueue.pop_back(); + op->release(); + } +} + + +void HttpRequestQueue::init() +{ + llassert_always(! sInstance); + sInstance = new HttpRequestQueue(); +} + + +void HttpRequestQueue::term() +{ + if (sInstance) + { + sInstance->release(); + sInstance = NULL; + } +} + + +HttpStatus HttpRequestQueue::addOp(HttpOperation * op) +{ + bool wake(false); + { + HttpScopedLock lock(mQueueMutex); + + if (mQueueStopped) + { + // Return op and error to caller + return HttpStatus(HttpStatus::LLCORE, HE_SHUTTING_DOWN); + } + wake = mQueue.empty(); + mQueue.push_back(op); + } + if (wake) + { + mQueueCV.notify_all(); + } + return HttpStatus(); +} + + +HttpOperation * HttpRequestQueue::fetchOp(bool wait) +{ + HttpOperation * result(NULL); + + { + HttpScopedLock lock(mQueueMutex); + + while (mQueue.empty()) + { + if (! wait || mQueueStopped) + return NULL; + mQueueCV.wait(lock); + } + + result = mQueue.front(); + mQueue.erase(mQueue.begin()); + } + + // Caller also acquires the reference count + return result; +} + + +void HttpRequestQueue::fetchAll(bool wait, OpContainer & ops) +{ + // Not valid putting something back on the queue... + llassert_always(ops.empty()); + + { + HttpScopedLock lock(mQueueMutex); + + while (mQueue.empty()) + { + if (! wait || mQueueStopped) + return; + mQueueCV.wait(lock); + } + + mQueue.swap(ops); + } + + // Caller also acquires the reference counts on each op. + return; +} + + +void HttpRequestQueue::wakeAll() +{ + mQueueCV.notify_all(); +} + + +void HttpRequestQueue::stopQueue() +{ + { + HttpScopedLock lock(mQueueMutex); + + mQueueStopped = true; + wakeAll(); + } +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httprequestqueue.h b/indra/llcorehttp/_httprequestqueue.h new file mode 100644 index 0000000000..c9c52b7233 --- /dev/null +++ b/indra/llcorehttp/_httprequestqueue.h @@ -0,0 +1,141 @@ +/** + * @file _httprequestqueue.h + * @brief Internal declaration for the operation request queue + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_REQUEST_QUEUE_H_ +#define _LLCORE_HTTP_REQUEST_QUEUE_H_ + + +#include <vector> + +#include "httpcommon.h" +#include "_refcounted.h" +#include "_mutex.h" + + +namespace LLCore +{ + + +class HttpOperation; + + +/// Thread-safe queue of HttpOperation objects. Just +/// a simple queue that handles the transfer of operation +/// requests from all HttpRequest instances into the +/// singleton HttpService instance. + +class HttpRequestQueue : public LLCoreInt::RefCounted +{ +protected: + /// Caller acquires a Refcount on construction + HttpRequestQueue(); + +protected: + virtual ~HttpRequestQueue(); // Use release() + +private: + HttpRequestQueue(const HttpRequestQueue &); // Not defined + void operator=(const HttpRequestQueue &); // Not defined + +public: + static void init(); + static void term(); + + /// Threading: callable by any thread once inited. + inline static HttpRequestQueue * instanceOf() + { + return sInstance; + } + +public: + typedef std::vector<HttpOperation *> OpContainer; + + /// Insert an object at the back of the request queue. + /// + /// Caller must provide one refcount to the queue which takes + /// possession of the count on success. + /// + /// @return Standard status. On failure, caller + /// must dispose of the operation with + /// an explicit release() call. + /// + /// Threading: callable by any thread. + HttpStatus addOp(HttpOperation * op); + + /// Return the operation on the front of the queue. If + /// the queue is empty and @wait is false, call returns + /// immediately and a NULL pointer is returned. If true, + /// caller will sleep until explicitly woken. Wakeups + /// can be spurious and callers must expect NULL pointers + /// even if waiting is indicated. + /// + /// Caller acquires reference count any returned operation + /// + /// Threading: callable by any thread. + HttpOperation * fetchOp(bool wait); + + /// Return all queued requests to caller. The @ops argument + /// should be empty when called and will be swap()'d with + /// current contents. Handling of the @wait argument is + /// identical to @fetchOp. + /// + /// Caller acquires reference count on each returned operation + /// + /// Threading: callable by any thread. + void fetchAll(bool wait, OpContainer & ops); + + /// Wake any sleeping threads. Normal queuing operations + /// won't require this but it may be necessary for termination + /// requests. + /// + /// Threading: callable by any thread. + void wakeAll(); + + /// Disallow further request queuing. Callers to @addOp will + /// get a failure status (LLCORE, HE_SHUTTING_DOWN). Callers + /// to @fetchAll or @fetchOp will get requests that are on the + /// queue but the calls will no longer wait. Instead they'll + /// return immediately. Also wakes up all sleepers to send + /// them on their way. + /// + /// Threading: callable by any thread. + void stopQueue(); + +protected: + static HttpRequestQueue * sInstance; + +protected: + OpContainer mQueue; + LLCoreInt::HttpMutex mQueueMutex; + LLCoreInt::HttpConditionVariable mQueueCV; + bool mQueueStopped; + +}; // end class HttpRequestQueue + +} // end namespace LLCore + + +#endif // _LLCORE_HTTP_REQUEST_QUEUE_H_ diff --git a/indra/llcorehttp/_httpretryqueue.h b/indra/llcorehttp/_httpretryqueue.h new file mode 100644 index 0000000000..745adec09d --- /dev/null +++ b/indra/llcorehttp/_httpretryqueue.h @@ -0,0 +1,94 @@ +/** + * @file _httpretryqueue.h + * @brief Internal declaration for the operation retry queue + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_RETRY_QUEUE_H_ +#define _LLCORE_HTTP_RETRY_QUEUE_H_ + + +#include <queue> + +#include "_httpoprequest.h" + + +namespace LLCore +{ + +/// HttpRetryQueue provides a simple priority queue for HttpOpRequest objects. +/// +/// This uses the priority_queue adaptor class to provide the queue +/// as well as the ordering scheme while allowing us access to the +/// raw container if we follow a few simple rules. One of the more +/// important of those rules is that any iterator becomes invalid +/// on element erasure. So pay attention. +/// +/// Threading: not thread-safe. Expected to be used entirely by +/// a single thread, typically a worker thread of some sort. + +struct HttpOpRetryCompare +{ + bool operator()(const HttpOpRequest * lhs, const HttpOpRequest * rhs) + { + return lhs->mPolicyRetryAt < rhs->mPolicyRetryAt; + } +}; + + +typedef std::priority_queue<HttpOpRequest *, + std::deque<HttpOpRequest *>, + LLCore::HttpOpRetryCompare> HttpRetryQueueBase; + +class HttpRetryQueue : public HttpRetryQueueBase +{ +public: + HttpRetryQueue() + : HttpRetryQueueBase() + {} + + ~HttpRetryQueue() + {} + +protected: + HttpRetryQueue(const HttpRetryQueue &); // Not defined + void operator=(const HttpRetryQueue &); // Not defined + +public: + const container_type & get_container() const + { + return c; + } + + container_type & get_container() + { + return c; + } + +}; // end class HttpRetryQueue + + +} // end namespace LLCore + + +#endif // _LLCORE_HTTP_RETRY_QUEUE_H_ diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp new file mode 100644 index 0000000000..0825888d0f --- /dev/null +++ b/indra/llcorehttp/_httpservice.cpp @@ -0,0 +1,348 @@ +/** + * @file _httpservice.cpp + * @brief Internal definitions of the Http service thread + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_httpservice.h" + +#include <boost/bind.hpp> +#include <boost/function.hpp> + +#include "_httpoperation.h" +#include "_httprequestqueue.h" +#include "_httppolicy.h" +#include "_httplibcurl.h" +#include "_thread.h" +#include "_httpinternal.h" + +#include "lltimer.h" +#include "llthread.h" + + +namespace LLCore +{ + +HttpService * HttpService::sInstance(NULL); +volatile HttpService::EState HttpService::sState(NOT_INITIALIZED); + +HttpService::HttpService() + : mRequestQueue(NULL), + mExitRequested(0U), + mThread(NULL), + mPolicy(NULL), + mTransport(NULL) +{ + // Create the default policy class + HttpPolicyClass pol_class; + pol_class.set(HttpRequest::CP_CONNECTION_LIMIT, HTTP_CONNECTION_LIMIT_DEFAULT); + pol_class.set(HttpRequest::CP_PER_HOST_CONNECTION_LIMIT, HTTP_CONNECTION_LIMIT_DEFAULT); + pol_class.set(HttpRequest::CP_ENABLE_PIPELINING, 0L); + mPolicyClasses.push_back(pol_class); +} + + +HttpService::~HttpService() +{ + mExitRequested = 1U; + if (RUNNING == sState) + { + // Trying to kill the service object with a running thread + // is a bit tricky. + if (mRequestQueue) + { + mRequestQueue->stopQueue(); + } + + if (mThread) + { + if (! mThread->timedJoin(250)) + { + // Failed to join, expect problems ahead so do a hard termination. + mThread->cancel(); + + LL_WARNS("CoreHttp") << "Destroying HttpService with running thread. Expect problems." + << LL_ENDL; + } + } + } + + if (mRequestQueue) + { + mRequestQueue->release(); + mRequestQueue = NULL; + } + + delete mTransport; + mTransport = NULL; + + delete mPolicy; + mPolicy = NULL; + + if (mThread) + { + mThread->release(); + mThread = NULL; + } +} + + +void HttpService::init(HttpRequestQueue * queue) +{ + llassert_always(! sInstance); + llassert_always(NOT_INITIALIZED == sState); + sInstance = new HttpService(); + + queue->addRef(); + sInstance->mRequestQueue = queue; + sInstance->mPolicy = new HttpPolicy(sInstance); + sInstance->mTransport = new HttpLibcurl(sInstance); + sState = INITIALIZED; +} + + +void HttpService::term() +{ + if (sInstance) + { + if (RUNNING == sState && sInstance->mThread) + { + // Unclean termination. Thread appears to be running. We'll + // try to give the worker thread a chance to cancel using the + // exit flag... + sInstance->mExitRequested = 1U; + sInstance->mRequestQueue->stopQueue(); + + // And a little sleep + for (int i(0); i < 10 && RUNNING == sState; ++i) + { + ms_sleep(100); + } + } + + delete sInstance; + sInstance = NULL; + } + sState = NOT_INITIALIZED; +} + + +HttpRequest::policy_t HttpService::createPolicyClass() +{ + const HttpRequest::policy_t policy_class(mPolicyClasses.size()); + if (policy_class >= HTTP_POLICY_CLASS_LIMIT) + { + return 0; + } + mPolicyClasses.push_back(HttpPolicyClass()); + return policy_class; +} + + +bool HttpService::isStopped() +{ + // What is really wanted here is something like: + // + // HttpService * service = instanceOf(); + // return STOPPED == sState && (! service || ! service->mThread || ! service->mThread->joinable()); + // + // But boost::thread is not giving me a consistent story on joinability + // of a thread after it returns. Debug and non-debug builds are showing + // different behavior on Linux/Etch so we do a weaker test that may + // not be globally correct (i.e. thread *is* stopping, may not have + // stopped but will very soon): + + return STOPPED == sState; +} + + +/// Threading: callable by consumer thread *once*. +void HttpService::startThread() +{ + llassert_always(! mThread || STOPPED == sState); + llassert_always(INITIALIZED == sState || STOPPED == sState); + + if (mThread) + { + mThread->release(); + } + + // Push current policy definitions, enable policy & transport components + mPolicy->start(mPolicyGlobal, mPolicyClasses); + mTransport->start(mPolicyClasses.size()); + + mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1)); + sState = RUNNING; +} + + +/// Threading: callable by worker thread. +void HttpService::stopRequested() +{ + mExitRequested = 1U; +} + + +/// Threading: callable by worker thread. +bool HttpService::changePriority(HttpHandle handle, HttpRequest::priority_t priority) +{ + bool found(false); + + // Skip the request queue as we currently don't leave earlier + // requests sitting there. Start with the ready queue... + found = mPolicy->changePriority(handle, priority); + + // If not there, we could try the transport/active queue but priority + // doesn't really have much effect there so we don't waste cycles. + + return found; +} + + +/// Try to find the given request handle on any of the request +/// queues and cancel the operation. +/// +/// @return True if the request was canceled. +/// +/// Threading: callable by worker thread. +bool HttpService::cancel(HttpHandle handle) +{ + bool canceled(false); + + // Request can't be on request queue so skip that. + + // Check the policy component's queues first + canceled = mPolicy->cancel(handle); + + if (! canceled) + { + // If that didn't work, check transport's. + canceled = mTransport->cancel(handle); + } + + return canceled; +} + + +/// Threading: callable by worker thread. +void HttpService::shutdown() +{ + // Disallow future enqueue of requests + mRequestQueue->stopQueue(); + + // Cancel requests already on the request queue + HttpRequestQueue::OpContainer ops; + mRequestQueue->fetchAll(false, ops); + while (! ops.empty()) + { + HttpOperation * op(ops.front()); + ops.erase(ops.begin()); + + op->cancel(); + op->release(); + } + + // Shutdown transport canceling requests, freeing resources + mTransport->shutdown(); + + // And now policy + mPolicy->shutdown(); +} + + +// Working thread loop-forever method. Gives time to +// each of the request queue, policy layer and transport +// layer pieces and then either sleeps for a small time +// or waits for a request to come in. Repeats until +// requested to stop. +void HttpService::threadRun(LLCoreInt::HttpThread * thread) +{ + boost::this_thread::disable_interruption di; + + LLThread::registerThreadID(); + + ELoopSpeed loop(REQUEST_SLEEP); + while (! mExitRequested) + { + loop = processRequestQueue(loop); + + // Process ready queue issuing new requests as needed + ELoopSpeed new_loop = mPolicy->processReadyQueue(); + loop = (std::min)(loop, new_loop); + + // Give libcurl some cycles + new_loop = mTransport->processTransport(); + loop = (std::min)(loop, new_loop); + + // Determine whether to spin, sleep briefly or sleep for next request + if (REQUEST_SLEEP != loop) + { + ms_sleep(HTTP_SERVICE_LOOP_SLEEP_NORMAL_MS); + } + } + + shutdown(); + sState = STOPPED; +} + + +HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop) +{ + HttpRequestQueue::OpContainer ops; + const bool wait_for_req(REQUEST_SLEEP == loop); + + mRequestQueue->fetchAll(wait_for_req, ops); + while (! ops.empty()) + { + HttpOperation * op(ops.front()); + ops.erase(ops.begin()); + + // Process operation + if (! mExitRequested) + { + // Setup for subsequent tracing + long tracing(HTTP_TRACE_OFF); + mPolicy->getGlobalOptions().get(HttpRequest::GP_TRACE, &tracing); + op->mTracing = (std::max)(op->mTracing, int(tracing)); + + if (op->mTracing > HTTP_TRACE_OFF) + { + LL_INFOS("CoreHttp") << "TRACE, FromRequestQueue, Handle: " + << static_cast<HttpHandle>(op) + << LL_ENDL; + } + + // Stage + op->stageFromRequest(this); + } + + // Done with operation + op->release(); + } + + // Queue emptied, allow polling loop to sleep + return REQUEST_SLEEP; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/_httpservice.h b/indra/llcorehttp/_httpservice.h new file mode 100644 index 0000000000..ffe0349d4d --- /dev/null +++ b/indra/llcorehttp/_httpservice.h @@ -0,0 +1,224 @@ +/** + * @file _httpservice.h + * @brief Declarations for internal class providing HTTP service. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_SERVICE_H_ +#define _LLCORE_HTTP_SERVICE_H_ + + +#include <vector> + +#include "linden_common.h" +#include "llapr.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "_httppolicyglobal.h" +#include "_httppolicyclass.h" + + +namespace LLCoreInt +{ + +class HttpThread; + +} + + +namespace LLCore +{ + + +class HttpRequestQueue; +class HttpPolicy; +class HttpLibcurl; + + +/// The HttpService class does the work behind the request queue. It +/// oversees the HTTP workflow carrying out a number of tasks: +/// - Pulling requests from the global request queue +/// - Executing 'immediate' requests directly +/// - Prioritizing and re-queuing on internal queues the slower requests +/// - Providing cpu cycles to the libcurl plumbing +/// - Overseeing retry operations +/// +/// Note that the service object doesn't have a pointer to any +/// reply queue. These are kept by HttpRequest and HttpOperation +/// only. +/// +/// Service, Policy and Transport +/// +/// HttpService could have been a monolithic class combining a request +/// queue servicer, request policy manager and network transport. +/// Instead, to prevent monolithic growth and allow for easier +/// replacement, it was developed as three separate classes: HttpService, +/// HttpPolicy and HttpLibcurl (transport). These always exist in a +/// 1:1:1 relationship with HttpService managing instances of the other +/// two. So, these classes do not use reference counting to refer +/// to one another, their lifecycles are always managed together. + +class HttpService +{ +protected: + HttpService(); + virtual ~HttpService(); + +private: + HttpService(const HttpService &); // Not defined + void operator=(const HttpService &); // Not defined + +public: + enum EState + { + NOT_INITIALIZED = -1, + INITIALIZED, ///< init() has been called + RUNNING, ///< thread created and running + STOPPED ///< thread has committed to exiting + }; + + // Ordered enumeration of idling strategies available to + // threadRun's loop. Ordered so that std::min on values + // produces the most conservative result of multiple + // requests. + enum ELoopSpeed + { + NORMAL, ///< continuous polling of request, ready, active queues + REQUEST_SLEEP ///< can sleep indefinitely waiting for request queue write + }; + + static void init(HttpRequestQueue *); + static void term(); + + /// Threading: callable by any thread once inited. + inline static HttpService * instanceOf() + { + return sInstance; + } + + /// Return the state of the worker thread. Note that the + /// transition from RUNNING to STOPPED is performed by the + /// worker thread itself. This has two weaknesses: + /// - race where the thread hasn't really stopped but will + /// - data ordering between threads where a non-worker thread + /// may see a stale RUNNING status. + /// + /// This transition is generally of interest only to unit tests + /// and these weaknesses shouldn't be any real burden. + /// + /// Threading: callable by any thread with above exceptions. + static EState getState() + { + return sState; + } + + /// Threading: callable by any thread but uses @see getState() and + /// acquires its weaknesses. + static bool isStopped(); + + /// Threading: callable by consumer thread *once*. + void startThread(); + + /// Threading: callable by worker thread. + void stopRequested(); + + /// Threading: callable by worker thread. + void shutdown(); + + /// Try to find the given request handle on any of the request + /// queues and reset the priority (and queue position) of the + /// request if found. + /// + /// @return True if the request was found somewhere. + /// + /// Threading: callable by worker thread. + bool changePriority(HttpHandle handle, HttpRequest::priority_t priority); + + /// Try to find the given request handle on any of the request + /// queues and cancel the operation. + /// + /// @return True if the request was found and canceled. + /// + /// Threading: callable by worker thread. + bool cancel(HttpHandle handle); + + /// Threading: callable by worker thread. + HttpPolicy & getPolicy() + { + return *mPolicy; + } + + /// Threading: callable by worker thread. + HttpLibcurl & getTransport() + { + return *mTransport; + } + + /// Threading: callable by worker thread. + HttpRequestQueue & getRequestQueue() + { + return *mRequestQueue; + } + + /// Threading: callable by consumer thread. + HttpPolicyGlobal & getGlobalOptions() + { + return mPolicyGlobal; + } + + /// Threading: callable by consumer thread. + HttpRequest::policy_t createPolicyClass(); + + /// Threading: callable by consumer thread. + HttpPolicyClass & getClassOptions(HttpRequest::policy_t policy_class) + { + llassert(policy_class >= 0 && policy_class < mPolicyClasses.size()); + return mPolicyClasses[policy_class]; + } + +protected: + void threadRun(LLCoreInt::HttpThread * thread); + + ELoopSpeed processRequestQueue(ELoopSpeed loop); + +protected: + static HttpService * sInstance; + + // === shared data === + static volatile EState sState; + HttpRequestQueue * mRequestQueue; // Refcounted + LLAtomicU32 mExitRequested; + LLCoreInt::HttpThread * mThread; + + // === consumer-thread-only data === + HttpPolicyGlobal mPolicyGlobal; + std::vector<HttpPolicyClass> mPolicyClasses; + + // === working-thread-only data === + HttpPolicy * mPolicy; // Simple pointer, has ownership + HttpLibcurl * mTransport; // Simple pointer, has ownership +}; // end class HttpService + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_SERVICE_H_ diff --git a/indra/llcorehttp/_mutex.h b/indra/llcorehttp/_mutex.h new file mode 100644 index 0000000000..4be4d016d4 --- /dev/null +++ b/indra/llcorehttp/_mutex.h @@ -0,0 +1,55 @@ +/** + * @file _mutex.hpp + * @brief mutex type abstraction + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLCOREINT_MUTEX_H_ +#define LLCOREINT_MUTEX_H_ + + +#include <boost/thread.hpp> + + +namespace LLCoreInt +{ + +// MUTEX TYPES + +// unique mutex type +typedef boost::mutex HttpMutex; + +// CONDITION VARIABLES + +// standard condition variable +typedef boost::condition_variable HttpConditionVariable; + +// LOCKS AND FENCES + +// scoped unique lock +typedef boost::unique_lock<HttpMutex> HttpScopedLock; + +} + +#endif // LLCOREINT_MUTEX_H + diff --git a/indra/llcorehttp/_refcounted.cpp b/indra/llcorehttp/_refcounted.cpp new file mode 100644 index 0000000000..e7d0b72741 --- /dev/null +++ b/indra/llcorehttp/_refcounted.cpp @@ -0,0 +1,45 @@ +/** + * @file _refcounted.cpp + * @brief Atomic, thread-safe ref counting and destruction mixin class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "_refcounted.h" + + +namespace LLCoreInt +{ + +#if ! LL_WINDOWS + +const S32 RefCounted::NOT_REF_COUNTED; + +#endif // ! LL_WINDOWS + +RefCounted::~RefCounted() +{} + + +} // end namespace LLCoreInt + + diff --git a/indra/llcorehttp/_refcounted.h b/indra/llcorehttp/_refcounted.h new file mode 100644 index 0000000000..a96c65fb6b --- /dev/null +++ b/indra/llcorehttp/_refcounted.h @@ -0,0 +1,125 @@ +/** + * @file _refcounted.h + * @brief Atomic, thread-safe ref counting and destruction mixin class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLCOREINT__REFCOUNTED_H_ +#define LLCOREINT__REFCOUNTED_H_ + + +#include "linden_common.h" + +#include <boost/thread.hpp> + +#include "llapr.h" + + +namespace LLCoreInt +{ + + +class RefCounted +{ +private: + RefCounted(); // Not defined - may not be default constructed + void operator=(const RefCounted &); // Not defined + +public: + explicit RefCounted(bool const implicit) + : mRefCount(implicit) + {} + + // ref-count interface + void addRef() const; + void release() const; + bool isLastRef() const; + S32 getRefCount() const; + void noRef() const; + + static const S32 NOT_REF_COUNTED = -1; + +protected: + virtual ~RefCounted(); + virtual void destroySelf(); + +private: + mutable LLAtomicS32 mRefCount; + +}; // end class RefCounted + + +inline void RefCounted::addRef() const +{ + S32 count(mRefCount++); + llassert_always(count >= 0); +} + + +inline void RefCounted::release() const +{ + S32 count(mRefCount); + llassert_always(count != NOT_REF_COUNTED); + llassert_always(count > 0); + count = mRefCount--; + + // clean ourselves up if that was the last reference + if (0 == count) + { + const_cast<RefCounted *>(this)->destroySelf(); + } +} + + +inline bool RefCounted::isLastRef() const +{ + const S32 count(mRefCount); + llassert_always(count != NOT_REF_COUNTED); + llassert_always(count >= 1); + return (1 == count); +} + + +inline S32 RefCounted::getRefCount() const +{ + const S32 result(mRefCount); + return result; +} + + +inline void RefCounted::noRef() const +{ + llassert_always(mRefCount <= 1); + mRefCount = NOT_REF_COUNTED; +} + + +inline void RefCounted::destroySelf() +{ + delete this; +} + +} // end namespace LLCoreInt + +#endif // LLCOREINT__REFCOUNTED_H_ + diff --git a/indra/llcorehttp/_thread.h b/indra/llcorehttp/_thread.h new file mode 100644 index 0000000000..e058d660e5 --- /dev/null +++ b/indra/llcorehttp/_thread.h @@ -0,0 +1,123 @@ +/** + * @file _thread.h + * @brief thread type abstraction + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLCOREINT_THREAD_H_ +#define LLCOREINT_THREAD_H_ + +#include "linden_common.h" + +#include <boost/thread.hpp> +#include <boost/function.hpp> +#include <boost/date_time/posix_time/posix_time_types.hpp> + +#include "_refcounted.h" + +namespace LLCoreInt +{ + +class HttpThread : public RefCounted +{ +private: + HttpThread(); // Not defined + void operator=(const HttpThread &); // Not defined + + void at_exit() + { + // the thread function has exited so we need to release our reference + // to ourself so that we will be automagically cleaned up. + release(); + } + + void run() + { // THREAD CONTEXT + + // Take out additional reference for the at_exit handler + addRef(); + boost::this_thread::at_thread_exit(boost::bind(&HttpThread::at_exit, this)); + + // run the thread function + mThreadFunc(this); + + } // THREAD CONTEXT + +protected: + virtual ~HttpThread() + { + delete mThread; + } + +public: + /// Constructs a thread object for concurrent execution but does + /// not start running. Caller receives on refcount on the thread + /// instance. If the thread is started, another will be taken + /// out for the exit handler. + explicit HttpThread(boost::function<void (HttpThread *)> threadFunc) + : RefCounted(true), // implicit reference + mThreadFunc(threadFunc) + { + // this creates a boost thread that will call HttpThread::run on this instance + // and pass it the threadfunc callable... + boost::function<void()> f = boost::bind(&HttpThread::run, this); + + mThread = new boost::thread(f); + } + + inline void join() + { + mThread->join(); + } + + inline bool timedJoin(S32 millis) + { + return mThread->timed_join(boost::posix_time::milliseconds(millis)); + } + + inline bool joinable() const + { + return mThread->joinable(); + } + + // A very hostile method to force a thread to quit + inline void cancel() + { + boost::thread::native_handle_type thread(mThread->native_handle()); +#if LL_WINDOWS + TerminateThread(thread, 0); +#else + pthread_cancel(thread); +#endif + } + +private: + boost::function<void(HttpThread *)> mThreadFunc; + boost::thread * mThread; +}; // end class HttpThread + +} // end namespace LLCoreInt + +#endif // LLCOREINT_THREAD_H_ + + diff --git a/indra/llcorehttp/bufferarray.cpp b/indra/llcorehttp/bufferarray.cpp new file mode 100644 index 0000000000..8eaaeed710 --- /dev/null +++ b/indra/llcorehttp/bufferarray.cpp @@ -0,0 +1,352 @@ +/** + * @file bufferarray.cpp + * @brief Implements the BufferArray scatter/gather buffer + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "bufferarray.h" + + +// BufferArray is a list of chunks, each a BufferArray::Block, of contiguous +// data presented as a single array. Chunks are at least BufferArray::BLOCK_ALLOC_SIZE +// in length and can be larger. Any chunk may be partially filled or even +// empty. +// +// The BufferArray itself is sharable as a RefCounted entity. As shared +// reads don't work with the concept of a current position/seek value, +// none is kept with the object. Instead, the read and write operations +// all take position arguments. Single write/shared read isn't supported +// directly and any such attempts have to be serialized outside of this +// implementation. + +namespace LLCore +{ + + +// ================================== +// BufferArray::Block Declaration +// ================================== + +class BufferArray::Block +{ +public: + ~Block(); + + void operator delete(void *); + void operator delete(void *, size_t len); + +protected: + Block(size_t len); + + Block(const Block &); // Not defined + void operator=(const Block &); // Not defined + + // Allocate the block with the additional space for the + // buffered data at the end of the object. + void * operator new(size_t len, size_t addl_len); + +public: + // Only public entry to get a block. + static Block * alloc(size_t len); + +public: + size_t mUsed; + size_t mAlloced; + + // *NOTE: Must be last member of the object. We'll + // overallocate as requested via operator new and index + // into the array at will. + char mData[1]; +}; + + +// ================================== +// BufferArray Definitions +// ================================== + + +#if ! LL_WINDOWS +const size_t BufferArray::BLOCK_ALLOC_SIZE; +#endif // ! LL_WINDOWS + +BufferArray::BufferArray() + : LLCoreInt::RefCounted(true), + mLen(0) +{} + + +BufferArray::~BufferArray() +{ + for (container_t::iterator it(mBlocks.begin()); + it != mBlocks.end(); + ++it) + { + delete *it; + *it = NULL; + } + mBlocks.clear(); +} + + +size_t BufferArray::append(const void * src, size_t len) +{ + const size_t ret(len); + const char * c_src(static_cast<const char *>(src)); + + // First, try to copy into the last block + if (len && ! mBlocks.empty()) + { + Block & last(*mBlocks.back()); + if (last.mUsed < last.mAlloced) + { + // Some will fit... + const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed))); + + memcpy(&last.mData[last.mUsed], c_src, copy_len); + last.mUsed += copy_len; + llassert_always(last.mUsed <= last.mAlloced); + mLen += copy_len; + c_src += copy_len; + len -= copy_len; + } + } + + // Then get new blocks as needed + while (len) + { + const size_t copy_len((std::min)(len, BLOCK_ALLOC_SIZE)); + + if (mBlocks.size() >= mBlocks.capacity()) + { + mBlocks.reserve(mBlocks.size() + 5); + } + Block * block = Block::alloc(BLOCK_ALLOC_SIZE); + memcpy(block->mData, c_src, copy_len); + block->mUsed = copy_len; + llassert_always(block->mUsed <= block->mAlloced); + mBlocks.push_back(block); + mLen += copy_len; + c_src += copy_len; + len -= copy_len; + } + return ret; +} + + +void * BufferArray::appendBufferAlloc(size_t len) +{ + // If someone asks for zero-length, we give them a valid pointer. + if (mBlocks.size() >= mBlocks.capacity()) + { + mBlocks.reserve(mBlocks.size() + 5); + } + Block * block = Block::alloc((std::max)(BLOCK_ALLOC_SIZE, len)); + block->mUsed = len; + mBlocks.push_back(block); + mLen += len; + return block->mData; +} + + +size_t BufferArray::read(size_t pos, void * dst, size_t len) +{ + char * c_dst(static_cast<char *>(dst)); + + if (pos >= mLen) + return 0; + size_t len_limit(mLen - pos); + len = (std::min)(len, len_limit); + if (0 == len) + return 0; + + size_t result(0), offset(0); + const int block_limit(mBlocks.size()); + int block_start(findBlock(pos, &offset)); + if (block_start < 0) + return 0; + + do + { + Block & block(*mBlocks[block_start]); + size_t block_limit(block.mUsed - offset); + size_t block_len((std::min)(block_limit, len)); + + memcpy(c_dst, &block.mData[offset], block_len); + result += block_len; + len -= block_len; + c_dst += block_len; + offset = 0; + ++block_start; + } + while (len && block_start < block_limit); + + return result; +} + + +size_t BufferArray::write(size_t pos, const void * src, size_t len) +{ + const char * c_src(static_cast<const char *>(src)); + + if (pos > mLen || 0 == len) + return 0; + + size_t result(0), offset(0); + const int block_limit(mBlocks.size()); + int block_start(findBlock(pos, &offset)); + + if (block_start >= 0) + { + // Some or all of the write will be on top of + // existing data. + do + { + Block & block(*mBlocks[block_start]); + size_t block_limit(block.mUsed - offset); + size_t block_len((std::min)(block_limit, len)); + + memcpy(&block.mData[offset], c_src, block_len); + result += block_len; + c_src += block_len; + len -= block_len; + offset = 0; + ++block_start; + } + while (len && block_start < block_limit); + } + + // Something left, see if it will fit in the free + // space of the last block. + if (len && ! mBlocks.empty()) + { + Block & last(*mBlocks.back()); + if (last.mUsed < last.mAlloced) + { + // Some will fit... + const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed))); + + memcpy(&last.mData[last.mUsed], c_src, copy_len); + last.mUsed += copy_len; + result += copy_len; + llassert_always(last.mUsed <= last.mAlloced); + mLen += copy_len; + c_src += copy_len; + len -= copy_len; + } + } + + if (len) + { + // Some or all of the remaining write data will + // be an append. + result += append(c_src, len); + } + + return result; +} + + +int BufferArray::findBlock(size_t pos, size_t * ret_offset) +{ + *ret_offset = 0; + if (pos >= mLen) + return -1; // Doesn't exist + + const int block_limit(mBlocks.size()); + for (int i(0); i < block_limit; ++i) + { + if (pos < mBlocks[i]->mUsed) + { + *ret_offset = pos; + return i; + } + pos -= mBlocks[i]->mUsed; + } + + // Shouldn't get here but... + return -1; +} + + +bool BufferArray::getBlockStartEnd(int block, const char ** start, const char ** end) +{ + if (block < 0 || block >= mBlocks.size()) + { + return false; + } + + const Block & b(*mBlocks[block]); + *start = &b.mData[0]; + *end = &b.mData[b.mUsed]; + return true; +} + + +// ================================== +// BufferArray::Block Definitions +// ================================== + + +BufferArray::Block::Block(size_t len) + : mUsed(0), + mAlloced(len) +{ + memset(mData, 0, len); +} + + +BufferArray::Block::~Block() +{ + mUsed = 0; + mAlloced = 0; +} + + +void * BufferArray::Block::operator new(size_t len, size_t addl_len) +{ + void * mem = new char[len + addl_len + sizeof(void *)]; + return mem; +} + + +void BufferArray::Block::operator delete(void * mem) +{ + char * cmem = static_cast<char *>(mem); + delete [] cmem; +} + + +void BufferArray::Block::operator delete(void * mem, size_t) +{ + operator delete(mem); +} + + +BufferArray::Block * BufferArray::Block::alloc(size_t len) +{ + Block * block = new (len) Block(len); + return block; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/bufferarray.h b/indra/llcorehttp/bufferarray.h new file mode 100644 index 0000000000..1094a435b4 --- /dev/null +++ b/indra/llcorehttp/bufferarray.h @@ -0,0 +1,137 @@ +/** + * @file bufferarray.h + * @brief Public-facing declaration for the BufferArray scatter/gather class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_BUFFER_ARRAY_H_ +#define _LLCORE_BUFFER_ARRAY_H_ + + +#include <cstdlib> +#include <vector> + +#include "_refcounted.h" + + +namespace LLCore +{ + +class BufferArrayStreamBuf; + +/// A very simple scatter/gather type map for bulk data. The motivation +/// for this class is the writedata callback used by libcurl. Response +/// bodies are delivered to the caller in a sequence of sequential write +/// operations and this class captures them without having to reallocate +/// and move data. +/// +/// The interface looks a little like a unix file descriptor but only +/// just. There is a notion of a current position, starting from 0, +/// which is used as the position in the data when performing read and +/// write operations. The position also moves after various operations: +/// - seek(...) +/// - read(...) +/// - write(...) +/// - append(...) +/// - appendBufferAlloc(...) +/// The object also keeps a total length value which is updated after +/// write and append operations and beyond which the current position +/// cannot be set. +/// +/// Threading: not thread-safe +/// +/// Allocation: Refcounted, heap only. Caller of the constructor +/// is given a single refcount. +/// +class BufferArray : public LLCoreInt::RefCounted +{ +public: + // BufferArrayStreamBuf has intimate knowledge of this + // implementation to implement a buffer-free adapter. + // Changes here will likely need to be reflected there. + friend class BufferArrayStreamBuf; + + BufferArray(); + +protected: + virtual ~BufferArray(); // Use release() + +private: + BufferArray(const BufferArray &); // Not defined + void operator=(const BufferArray &); // Not defined + +public: + // Internal magic number, may be used by unit tests. + static const size_t BLOCK_ALLOC_SIZE = 65540; + + /// Appends the indicated data to the BufferArray + /// modifying current position and total size. New + /// position is one beyond the final byte of the buffer. + /// + /// @return Count of bytes copied to BufferArray + size_t append(const void * src, size_t len); + + /// Similar to @see append(), this call guarantees a + /// contiguous block of memory of requested size placed + /// at the current end of the BufferArray. On return, + /// the data in the memory is considered valid whether + /// the caller writes to it or not. + /// + /// @return Pointer to contiguous region at end + /// of BufferArray of 'len' size. + void * appendBufferAlloc(size_t len); + + /// Current count of bytes in BufferArray instance. + size_t size() const + { + return mLen; + } + + /// Copies data from the given position in the instance + /// to the caller's buffer. Will return a short count of + /// bytes copied if the 'len' extends beyond the data. + size_t read(size_t pos, void * dst, size_t len); + + /// Copies data from the caller's buffer to the instance + /// at the current position. May overwrite existing data, + /// append data when current position is equal to the + /// size of the instance or do a mix of both. + size_t write(size_t pos, const void * src, size_t len); + +protected: + int findBlock(size_t pos, size_t * ret_offset); + + bool getBlockStartEnd(int block, const char ** start, const char ** end); + +protected: + class Block; + typedef std::vector<Block *> container_t; + + container_t mBlocks; + size_t mLen; +}; // end class BufferArray + + +} // end namespace LLCore + +#endif // _LLCORE_BUFFER_ARRAY_H_ diff --git a/indra/llcorehttp/bufferstream.cpp b/indra/llcorehttp/bufferstream.cpp new file mode 100644 index 0000000000..6553900eef --- /dev/null +++ b/indra/llcorehttp/bufferstream.cpp @@ -0,0 +1,285 @@ +/** + * @file bufferstream.cpp + * @brief Implements the BufferStream adapter class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "bufferstream.h" + +#include "bufferarray.h" + + +namespace LLCore +{ + +BufferArrayStreamBuf::BufferArrayStreamBuf(BufferArray * array) + : mBufferArray(array), + mReadCurPos(0), + mReadCurBlock(-1), + mReadBegin(NULL), + mReadCur(NULL), + mReadEnd(NULL), + mWriteCurPos(0) +{ + if (array) + { + array->addRef(); + mWriteCurPos = array->mLen; + } +} + + +BufferArrayStreamBuf::~BufferArrayStreamBuf() +{ + if (mBufferArray) + { + mBufferArray->release(); + mBufferArray = NULL; + } +} + + +BufferArrayStreamBuf::int_type BufferArrayStreamBuf::underflow() +{ + if (! mBufferArray) + { + return traits_type::eof(); + } + + if (mReadCur == mReadEnd) + { + // Find the next block with actual data or leave + // mCurBlock/mCur/mEnd unchanged if we're at the end + // of any block chain. + const char * new_begin(NULL), * new_end(NULL); + int new_cur_block(mReadCurBlock + 1); + + while (mBufferArray->getBlockStartEnd(new_cur_block, &new_begin, &new_end)) + { + if (new_begin != new_end) + { + break; + } + ++new_cur_block; + } + if (new_begin == new_end) + { + return traits_type::eof(); + } + + mReadCurBlock = new_cur_block; + mReadBegin = mReadCur = new_begin; + mReadEnd = new_end; + } + + return traits_type::to_int_type(*mReadCur); +} + + +BufferArrayStreamBuf::int_type BufferArrayStreamBuf::uflow() +{ + const int_type ret(underflow()); + + if (traits_type::eof() != ret) + { + ++mReadCur; + ++mReadCurPos; + } + return ret; +} + + +BufferArrayStreamBuf::int_type BufferArrayStreamBuf::pbackfail(int_type ch) +{ + if (! mBufferArray) + { + return traits_type::eof(); + } + + if (mReadCur == mReadBegin) + { + // Find the previous block with actual data or leave + // mCurBlock/mBegin/mCur/mEnd unchanged if we're at the + // beginning of any block chain. + const char * new_begin(NULL), * new_end(NULL); + int new_cur_block(mReadCurBlock - 1); + + while (mBufferArray->getBlockStartEnd(new_cur_block, &new_begin, &new_end)) + { + if (new_begin != new_end) + { + break; + } + --new_cur_block; + } + if (new_begin == new_end) + { + return traits_type::eof(); + } + + mReadCurBlock = new_cur_block; + mReadBegin = new_begin; + mReadEnd = mReadCur = new_end; + } + + if (traits_type::eof() != ch && mReadCur[-1] != ch) + { + return traits_type::eof(); + } + --mReadCurPos; + return traits_type::to_int_type(*--mReadCur); +} + + +std::streamsize BufferArrayStreamBuf::showmanyc() +{ + if (! mBufferArray) + { + return -1; + } + return mBufferArray->mLen - mReadCurPos; +} + + +BufferArrayStreamBuf::int_type BufferArrayStreamBuf::overflow(int c) +{ + if (! mBufferArray || mWriteCurPos > mBufferArray->mLen) + { + return traits_type::eof(); + } + const size_t wrote(mBufferArray->write(mWriteCurPos, &c, 1)); + mWriteCurPos += wrote; + return wrote ? c : traits_type::eof(); +} + + +std::streamsize BufferArrayStreamBuf::xsputn(const char * src, std::streamsize count) +{ + if (! mBufferArray || mWriteCurPos > mBufferArray->mLen) + { + return 0; + } + const size_t wrote(mBufferArray->write(mWriteCurPos, src, count)); + mWriteCurPos += wrote; + return wrote; +} + + +std::streampos BufferArrayStreamBuf::seekoff(std::streamoff off, + std::ios_base::seekdir way, + std::ios_base::openmode which) +{ + std::streampos ret(-1); + + if (! mBufferArray) + { + return ret; + } + + if (std::ios_base::in == which) + { + size_t pos(0); + + switch (way) + { + case std::ios_base::beg: + pos = off; + break; + + case std::ios_base::cur: + pos = mReadCurPos += off; + break; + + case std::ios_base::end: + pos = mBufferArray->mLen - off; + break; + + default: + return ret; + } + + if (pos >= mBufferArray->size()) + { + pos = (std::max)(size_t(0), mBufferArray->size() - 1); + } + size_t ba_offset(0); + int block(mBufferArray->findBlock(pos, &ba_offset)); + if (block < 0) + return ret; + const char * start(NULL), * end(NULL); + if (! mBufferArray->getBlockStartEnd(block, &start, &end)) + return ret; + mReadCurBlock = block; + mReadBegin = start; + mReadCur = start + ba_offset; + mReadEnd = end; + ret = mReadCurPos = pos; + } + else if (std::ios_base::out == which) + { + size_t pos(0); + + switch (way) + { + case std::ios_base::beg: + pos = off; + break; + + case std::ios_base::cur: + pos = mWriteCurPos += off; + break; + + case std::ios_base::end: + pos = mBufferArray->mLen - off; + break; + + default: + return ret; + } + + if (pos < 0) + return ret; + if (pos > mBufferArray->size()) + { + pos = mBufferArray->size(); + } + ret = mWriteCurPos = pos; + } + + return ret; +} + + +BufferArrayStream::BufferArrayStream(BufferArray * ba) + : std::iostream(&mStreamBuf), + mStreamBuf(ba) +{} + + +BufferArrayStream::~BufferArrayStream() +{} + + +} // end namespace LLCore + + diff --git a/indra/llcorehttp/bufferstream.h b/indra/llcorehttp/bufferstream.h new file mode 100644 index 0000000000..9327a798aa --- /dev/null +++ b/indra/llcorehttp/bufferstream.h @@ -0,0 +1,153 @@ +/** + * @file bufferstream.h + * @brief Public-facing declaration for the BufferStream adapter class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_BUFFER_STREAM_H_ +#define _LLCORE_BUFFER_STREAM_H_ + + +#include <sstream> +#include <cstdlib> + +#include "bufferarray.h" + + +/// @file bufferstream.h +/// +/// std::streambuf and std::iostream adapters for BufferArray +/// objects. +/// +/// BufferArrayStreamBuf inherits std::streambuf and implements +/// an unbuffered interface for streambuf. This may or may not +/// be the most time efficient implementation and it is a little +/// challenging. +/// +/// BufferArrayStream inherits std::iostream and will be the +/// adapter object most callers will be interested in (though +/// it uses BufferArrayStreamBuf internally). Instances allow +/// for the usual streaming operators ('<<', '>>') and serialization +/// methods. +/// +/// Example of LLSD serialization to a BufferArray: +/// +/// BufferArray * ba = new BufferArray; +/// BufferArrayStream bas(ba); +/// LLSDSerialize::toXML(llsd, bas); +/// operationOnBufferArray(ba); +/// ba->release(); +/// ba = NULL; +/// // operationOnBufferArray and bas are each holding +/// // references to the ba instance at this point. +/// + +namespace LLCore +{ + + +// ===================================================== +// BufferArrayStreamBuf +// ===================================================== + +/// Adapter class to put a std::streambuf interface on a BufferArray +/// +/// Application developers will rarely be interested in anything +/// other than the constructor and even that will rarely be used +/// except indirectly via the @BufferArrayStream class. The +/// choice of interfaces implemented yields a bufferless adapter +/// that doesn't used either the input or output pointer triplets +/// of the more common buffered implementations. This may or may +/// not be faster and that question could stand to be looked at +/// sometime. +/// + +class BufferArrayStreamBuf : public std::streambuf +{ +public: + /// Constructor increments the reference count on the + /// BufferArray argument and calls release() on destruction. + BufferArrayStreamBuf(BufferArray * array); + virtual ~BufferArrayStreamBuf(); + +private: + BufferArrayStreamBuf(const BufferArrayStreamBuf &); // Not defined + void operator=(const BufferArrayStreamBuf &); // Not defined + +public: + // Input interfaces from std::streambuf + int_type underflow(); + int_type uflow(); + int_type pbackfail(int_type ch); + std::streamsize showmanyc(); + + // Output interfaces from std::streambuf + int_type overflow(int c); + std::streamsize xsputn(const char * src, std::streamsize count); + + // Common/misc interfaces from std::streambuf + std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which); + +protected: + BufferArray * mBufferArray; // Ref counted + size_t mReadCurPos; + int mReadCurBlock; + const char * mReadBegin; + const char * mReadCur; + const char * mReadEnd; + size_t mWriteCurPos; + +}; // end class BufferArrayStreamBuf + + +// ===================================================== +// BufferArrayStream +// ===================================================== + +/// Adapter class that supplies streaming operators to BufferArray +/// +/// Provides a streaming adapter to an existing BufferArray +/// instance so that the convenient '<<' and '>>' conversions +/// can be applied to a BufferArray. Very convenient for LLSD +/// serialization and parsing as well. + +class BufferArrayStream : public std::iostream +{ +public: + /// Constructor increments the reference count on the + /// BufferArray argument and calls release() on destruction. + BufferArrayStream(BufferArray * ba); + ~BufferArrayStream(); + +protected: + BufferArrayStream(const BufferArrayStream &); + void operator=(const BufferArrayStream &); + +protected: + BufferArrayStreamBuf mStreamBuf; +}; // end class BufferArrayStream + + +} // end namespace LLCore + +#endif // _LLCORE_BUFFER_STREAM_H_ diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp new file mode 100644 index 0000000000..998dc9240b --- /dev/null +++ b/indra/llcorehttp/examples/http_texture_load.cpp @@ -0,0 +1,943 @@ +/** + * @file http_texture_load.cpp + * @brief Texture download example for core-http library + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include <iostream> +#include <cstdio> +#include <cstdlib> +#include <set> +#include <map> +#if !defined(WIN32) +#include <pthread.h> +#endif + +#include "linden_common.h" + +#include "httpcommon.h" +#include "httprequest.h" +#include "httphandler.h" +#include "httpresponse.h" +#include "httpheaders.h" +#include "bufferarray.h" +#include "_mutex.h" + +#include <curl/curl.h> +#include <openssl/crypto.h> + +#include "lltimer.h" + + +void init_curl(); +void term_curl(); +unsigned long ssl_thread_id_callback(void); +void ssl_locking_callback(int mode, int type, const char * file, int line); +void usage(std::ostream & out); + +// Default command line settings +static int concurrency_limit(40); +static char url_format[1024] = "http://example.com/some/path?texture_id=%s.texture"; + +#if defined(WIN32) + +#define strncpy(_a, _b, _c) strncpy_s(_a, _b, _c) +#define strtok_r(_a, _b, _c) strtok_s(_a, _b, _c) + +int getopt(int argc, char * const argv[], const char *optstring); +char *optarg(NULL); +int optind(1); + +#endif + + +// Mostly just a container for the texture IDs and fetch +// parameters.... +class WorkingSet : public LLCore::HttpHandler +{ +public: + WorkingSet(); + ~WorkingSet(); + + bool reload(LLCore::HttpRequest *); + + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + + void loadTextureUuids(FILE * in); + +public: + struct Spec + { + std::string mUuid; + int mOffset; + int mLength; + }; + typedef std::set<LLCore::HttpHandle> handle_set_t; + typedef std::vector<Spec> texture_list_t; + +public: + bool mVerbose; + bool mRandomRange; + int mMaxConcurrency; + handle_set_t mHandles; + int mRemaining; + int mLimit; + int mAt; + std::string mUrl; + texture_list_t mTextures; + int mErrorsApi; + int mErrorsHttp; + int mErrorsHttp404; + int mErrorsHttp416; + int mErrorsHttp500; + int mErrorsHttp503; + int mSuccesses; + long mByteCount; + LLCore::HttpHeaders * mHeaders; +}; + + +// Gather process information while we run. Process +// size, cpu consumed, wallclock time. + +class Metrics +{ +public: + class MetricsImpl; + +public: + Metrics(); + ~Metrics(); + + void init(); + void sample(); + void term(); + +protected: + MetricsImpl * mImpl; + +public: + U64 mMaxVSZ; + U64 mMinVSZ; + U64 mStartWallTime; + U64 mEndWallTime; + U64 mStartUTime; + U64 mEndUTime; + U64 mStartSTime; + U64 mEndSTime; +}; + + +// +// +// +int main(int argc, char** argv) +{ + bool do_random(false); + bool do_verbose(false); + + int option(-1); + while (-1 != (option = getopt(argc, argv, "u:c:h?Rv"))) + { + switch (option) + { + case 'u': + strncpy(url_format, optarg, sizeof(url_format)); + url_format[sizeof(url_format) - 1] = '\0'; + break; + + case 'c': + { + unsigned long value; + char * end; + + value = strtoul(optarg, &end, 10); + if (value < 1 || value > 100 || *end != '\0') + { + usage(std::cerr); + return 1; + } + concurrency_limit = value; + } + break; + + case 'R': + do_random = true; + break; + + case 'v': + do_verbose = true; + break; + + case 'h': + case '?': + usage(std::cout); + return 0; + } + } + + if ((optind + 1) != argc) + { + usage(std::cerr); + return 1; + } + + FILE * uuids(fopen(argv[optind], "r")); + if (! uuids) + { + const char * errstr(strerror(errno)); + + std::cerr << "Couldn't open UUID file '" << argv[optind] << "'. Reason: " + << errstr << std::endl; + return 1; + } + + // Initialization + init_curl(); + LLCore::HttpRequest::createService(); + LLCore::HttpRequest::startThread(); + + // Get service point + LLCore::HttpRequest * hr = new LLCore::HttpRequest(); + + // Get a handler/working set + WorkingSet ws; + + // Fill the working set with work + ws.mUrl = url_format; + ws.loadTextureUuids(uuids); + ws.mRandomRange = do_random; + ws.mVerbose = do_verbose; + ws.mMaxConcurrency = concurrency_limit; + + if (! ws.mTextures.size()) + { + std::cerr << "No UUIDs found in file '" << argv[optind] << "'." << std::endl; + return 1; + } + + // Setup metrics + Metrics metrics; + metrics.init(); + + // Run it + int passes(0); + while (! ws.reload(hr)) + { + hr->update(5000000); + ms_sleep(2); + if (0 == (++passes % 200)) + { + metrics.sample(); + } + } + metrics.sample(); + metrics.term(); + + // Report + std::cout << "HTTP errors: " << ws.mErrorsHttp << " API errors: " << ws.mErrorsApi + << " Successes: " << ws.mSuccesses << " Byte count: " << ws.mByteCount + << std::endl; + std::cout << "HTTP 404 errors: " << ws.mErrorsHttp404 << " HTTP 416 errors: " << ws.mErrorsHttp416 + << " HTTP 500 errors: " << ws.mErrorsHttp500 << " HTTP 503 errors: " << ws.mErrorsHttp503 + << std::endl; + std::cout << "User CPU: " << (metrics.mEndUTime - metrics.mStartUTime) + << " uS System CPU: " << (metrics.mEndSTime - metrics.mStartSTime) + << " uS Wall Time: " << (metrics.mEndWallTime - metrics.mStartWallTime) + << " uS Maximum VSZ: " << metrics.mMaxVSZ + << " Bytes Minimum VSZ: " << metrics.mMinVSZ << " Bytes" + << std::endl; + + // Clean up + hr->requestStopThread(NULL); + ms_sleep(1000); + delete hr; + LLCore::HttpRequest::destroyService(); + term_curl(); + + return 0; +} + + +void usage(std::ostream & out) +{ + out << "\n" + "usage:\thttp_texture_load [options] uuid_file\n" + "\n" + "This is a standalone program to drive the New Platform HTTP Library.\n" + "The program is supplied with a file of texture UUIDs, one per line\n" + "These are fetched sequentially using a pool of concurrent connection\n" + "until all are fetched. The default URL format is only useful from\n" + "within Linden Lab but this can be overriden with a printf-style\n" + "URL formatting string on the command line.\n" + "\n" + "Options:\n" + "\n" + " -u <url_format> printf-style format string for URL generation\n" + " Default: " << url_format << "\n" + " -R Issue GETs with random Range: headers\n" + " -c <limit> Maximum request concurrency. Range: [1..100]\n" + " Default: " << concurrency_limit << "\n" + " -v Verbose mode. Issue some chatter while running\n" + " -h print this help\n" + "\n" + << std::endl; +} + + +WorkingSet::WorkingSet() + : LLCore::HttpHandler(), + mVerbose(false), + mRandomRange(false), + mRemaining(200), + mLimit(200), + mAt(0), + mErrorsApi(0), + mErrorsHttp(0), + mErrorsHttp404(0), + mErrorsHttp416(0), + mErrorsHttp500(0), + mErrorsHttp503(0), + mSuccesses(0), + mByteCount(0L) +{ + mTextures.reserve(30000); + + mHeaders = new LLCore::HttpHeaders; + mHeaders->mHeaders.push_back("Accept: image/x-j2c"); +} + + +WorkingSet::~WorkingSet() +{ + if (mHeaders) + { + mHeaders->release(); + mHeaders = NULL; + } +} + + +bool WorkingSet::reload(LLCore::HttpRequest * hr) +{ + int to_do((std::min)(mRemaining, mMaxConcurrency - int(mHandles.size()))); + + for (int i(0); i < to_do; ++i) + { + char buffer[1024]; +#if defined(WIN32) + _snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, mUrl.c_str(), mTextures[mAt].mUuid.c_str()); +#else + snprintf(buffer, sizeof(buffer), mUrl.c_str(), mTextures[mAt].mUuid.c_str()); +#endif + int offset(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mTextures[mAt].mOffset); + int length(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mTextures[mAt].mLength); + + LLCore::HttpHandle handle; + if (offset || length) + { + handle = hr->requestGetByteRange(0, 0, buffer, offset, length, NULL, mHeaders, this); + } + else + { + handle = hr->requestGet(0, 0, buffer, NULL, mHeaders, this); + } + if (! handle) + { + // Fatal. Couldn't queue up something. + std::cerr << "Failed to queue work to HTTP Service. Reason: " + << hr->getStatus().toString() << std::endl; + exit(1); + } + else + { + mHandles.insert(handle); + } + mAt++; + mRemaining--; + + if (mVerbose) + { + static int count(0); + ++count; + if (0 == (count %5)) + std::cout << "Queued " << count << std::endl; + } + } + + // Are we done? + return (! mRemaining) && mHandles.empty(); +} + + +void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + handle_set_t::iterator it(mHandles.find(handle)); + if (mHandles.end() == it) + { + // Wha? + std::cerr << "Failed to find handle in request list. Fatal." << std::endl; + exit(1); + } + else + { + LLCore::HttpStatus status(response->getStatus()); + if (status) + { + // More success + LLCore::BufferArray * data(response->getBody()); + mByteCount += data->size(); + ++mSuccesses; + } + else + { + // Something in this library or libcurl + if (status.isHttpStatus()) + { + static const LLCore::HttpStatus hs404(404); + static const LLCore::HttpStatus hs416(416); + static const LLCore::HttpStatus hs500(500); + static const LLCore::HttpStatus hs503(503); + + ++mErrorsHttp; + if (hs404 == status) + { + ++mErrorsHttp404; + } + else if (hs416 == status) + { + ++mErrorsHttp416; + } + else if (hs500 == status) + { + ++mErrorsHttp500; + } + else if (hs503 == status) + { + ++mErrorsHttp503; + } + } + else + { + ++mErrorsApi; + } + } + mHandles.erase(it); + } + + if (mVerbose) + { + static int count(0); + ++count; + if (0 == (count %5)) + std::cout << "Handled " << count << std::endl; + } +} + + +void WorkingSet::loadTextureUuids(FILE * in) +{ + char buffer[1024]; + + while (fgets(buffer, sizeof(buffer), in)) + { + WorkingSet::Spec texture; + char * state(NULL); + char * token = strtok_r(buffer, " \t\n,", &state); + if (token && 36 == strlen(token)) + { + // Close enough for this function + texture.mUuid = token; + texture.mOffset = 0; + texture.mLength = 0; + token = strtok_r(buffer, " \t\n,", &state); + if (token) + { + int offset(atoi(token)); + token = strtok_r(buffer, " \t\n,", &state); + if (token) + { + int length(atoi(token)); + texture.mOffset = offset; + texture.mLength = length; + } + } + mTextures.push_back(texture); + } + } + mRemaining = mLimit = mTextures.size(); +} + + +int ssl_mutex_count(0); +LLCoreInt::HttpMutex ** ssl_mutex_list = NULL; + +void init_curl() +{ + curl_global_init(CURL_GLOBAL_ALL); + + ssl_mutex_count = CRYPTO_num_locks(); + if (ssl_mutex_count > 0) + { + ssl_mutex_list = new LLCoreInt::HttpMutex * [ssl_mutex_count]; + + for (int i(0); i < ssl_mutex_count; ++i) + { + ssl_mutex_list[i] = new LLCoreInt::HttpMutex; + } + + CRYPTO_set_locking_callback(ssl_locking_callback); + CRYPTO_set_id_callback(ssl_thread_id_callback); + } +} + + +void term_curl() +{ + CRYPTO_set_locking_callback(NULL); + for (int i(0); i < ssl_mutex_count; ++i) + { + delete ssl_mutex_list[i]; + } + delete [] ssl_mutex_list; +} + + +unsigned long ssl_thread_id_callback(void) +{ +#if defined(WIN32) + return (unsigned long) GetCurrentThread(); +#else + return (unsigned long) pthread_self(); +#endif +} + + +void ssl_locking_callback(int mode, int type, const char * /* file */, int /* line */) +{ + if (type >= 0 && type < ssl_mutex_count) + { + if (mode & CRYPTO_LOCK) + { + ssl_mutex_list[type]->lock(); + } + else + { + ssl_mutex_list[type]->unlock(); + } + } +} + + +#if defined(WIN32) + +// Very much a subset of posix functionality. Don't push +// it too hard... +int getopt(int argc, char * const argv[], const char *optstring) +{ + static int pos(0); + while (optind < argc) + { + if (pos == 0) + { + if (argv[optind][0] != '-') + return -1; + pos = 1; + } + if (! argv[optind][pos]) + { + ++optind; + pos = 0; + continue; + } + const char * thing(strchr(optstring, argv[optind][pos])); + if (! thing) + { + ++optind; + return -1; + } + if (thing[1] == ':') + { + optarg = argv[++optind]; + ++optind; + pos = 0; + } + else + { + optarg = NULL; + ++pos; + } + return *thing; + } + return -1; +} + +#endif + + + +#if LL_WINDOWS + +#define PSAPI_VERSION 1 +#include "windows.h" +#include "psapi.h" + +class Metrics::MetricsImpl +{ +public: + MetricsImpl() + {} + + ~MetricsImpl() + {} + + void init(Metrics * metrics) + { + HANDLE self(GetCurrentProcess()); // Does not have to be closed + FILETIME ft_dummy, ft_system, ft_user; + GetProcessTimes(self, &ft_dummy, &ft_dummy, &ft_system, &ft_user); + ULARGE_INTEGER uli; + uli.u.LowPart = ft_system.dwLowDateTime; + uli.u.HighPart = ft_system.dwHighDateTime; + metrics->mStartSTime = uli.QuadPart / U64L(10); // Convert to uS + uli.u.LowPart = ft_user.dwLowDateTime; + uli.u.HighPart = ft_user.dwHighDateTime; + metrics->mStartUTime = uli.QuadPart / U64L(10); + metrics->mStartWallTime = totalTime(); + } + + void sample(Metrics * metrics) + { + PROCESS_MEMORY_COUNTERS_EX counters; + + GetProcessMemoryInfo(GetCurrentProcess(), + (PROCESS_MEMORY_COUNTERS *) &counters, + sizeof(counters)); + // Okay, PrivateUsage isn't truly VSZ but it will be + // a good tracker for leaks and fragmentation. Work on + // a better estimator later... + SIZE_T vsz(counters.PrivateUsage); + metrics->mMaxVSZ = (std::max)(metrics->mMaxVSZ, U64(vsz)); + metrics->mMinVSZ = (std::min)(metrics->mMinVSZ, U64(vsz)); + } + + void term(Metrics * metrics) + { + HANDLE self(GetCurrentProcess()); // Does not have to be closed + FILETIME ft_dummy, ft_system, ft_user; + GetProcessTimes(self, &ft_dummy, &ft_dummy, &ft_system, &ft_user); + ULARGE_INTEGER uli; + uli.u.LowPart = ft_system.dwLowDateTime; + uli.u.HighPart = ft_system.dwHighDateTime; + metrics->mEndSTime = uli.QuadPart / U64L(10); + uli.u.LowPart = ft_user.dwLowDateTime; + uli.u.HighPart = ft_user.dwHighDateTime; + metrics->mEndUTime = uli.QuadPart / U64L(10); + metrics->mEndWallTime = totalTime(); + } + +protected: +}; + +#elif LL_DARWIN + +#include <sys/resource.h> +#include <mach/mach.h> + +class Metrics::MetricsImpl +{ +public: + MetricsImpl() + {} + + ~MetricsImpl() + {} + + void init(Metrics * metrics) + { + U64 utime, stime; + + if (getTimes(&utime, &stime)) + { + metrics->mStartSTime = stime; + metrics->mStartUTime = utime; + } + metrics->mStartWallTime = totalTime(); + sample(metrics); + } + + void sample(Metrics * metrics) + { + U64 vsz; + + if (getVM(&vsz)) + { + metrics->mMaxVSZ = (std::max)(metrics->mMaxVSZ, vsz); + metrics->mMinVSZ = (std::min)(metrics->mMinVSZ, vsz); + } + } + + void term(Metrics * metrics) + { + U64 utime, stime; + + if (getTimes(&utime, &stime)) + { + metrics->mEndSTime = stime; + metrics->mEndUTime = utime; + } + metrics->mEndWallTime = totalTime(); + } + +protected: + bool getVM(U64 * vsz) + { + task_basic_info task_info_block; + mach_msg_type_number_t task_info_count(TASK_BASIC_INFO_COUNT); + + if (KERN_SUCCESS != task_info(mach_task_self(), + TASK_BASIC_INFO, + (task_info_t) &task_info_block, + &task_info_count)) + { + return false; + } + * vsz = task_info_block.virtual_size; + return true; + } + + bool getTimes(U64 * utime, U64 * stime) + { + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage)) + { + return false; + } + * utime = U64(usage.ru_utime.tv_sec) * U64L(1000000) + usage.ru_utime.tv_usec; + * stime = U64(usage.ru_stime.tv_sec) * U64L(1000000) + usage.ru_stime.tv_usec; + return true; + } + +}; + +#else + +class Metrics::MetricsImpl +{ +public: + MetricsImpl() + : mProcFS(NULL), + mUsecsPerTick(U64L(0)) + {} + + + ~MetricsImpl() + { + if (mProcFS) + { + fclose(mProcFS); + mProcFS = NULL; + } + } + + void init(Metrics * metrics) + { + if (! mProcFS) + { + mProcFS = fopen("/proc/self/stat", "r"); + if (! mProcFS) + { + const int errnum(errno); + LL_ERRS("Main") << "Error opening proc fs: " << strerror(errnum) << LL_ENDL; + } + } + + long ticks_per_sec(sysconf(_SC_CLK_TCK)); + mUsecsPerTick = U64L(1000000) / ticks_per_sec; + U64 usecs_per_sec(mUsecsPerTick * ticks_per_sec); + if (900000 > usecs_per_sec || 1100000 < usecs_per_sec) + { + LL_ERRS("Main") << "Resolution problems using uSecs for ticks" << LL_ENDL; + } + + U64 utime, stime; + if (scanProcFS(&utime, &stime, NULL)) + { + metrics->mStartSTime = stime; + metrics->mStartUTime = utime; + } + metrics->mStartWallTime = totalTime(); + + sample(metrics); + } + + + void sample(Metrics * metrics) + { + U64 vsz; + if (scanProcFS(NULL, NULL, &vsz)) + { + metrics->mMaxVSZ = (std::max)(metrics->mMaxVSZ, vsz); + metrics->mMinVSZ = (std::min)(metrics->mMinVSZ, vsz); + } + } + + + void term(Metrics * metrics) + { + U64 utime, stime; + if (scanProcFS(&utime, &stime, NULL)) + { + metrics->mEndSTime = stime; + metrics->mEndUTime = utime; + } + metrics->mEndWallTime = totalTime(); + + sample(metrics); + + if (mProcFS) + { + fclose(mProcFS); + mProcFS = NULL; + } + } + +protected: + bool scanProcFS(U64 * utime, U64 * stime, U64 * vsz) + { + if (mProcFS) + { + int i_dummy; + unsigned int ui_dummy; + unsigned long ul_dummy, user_ticks, sys_ticks, vsize; + long l_dummy, rss; + unsigned long long ull_dummy; + char c_dummy; + + char buffer[256]; + + static const char * format("%d %*s %c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %llu %lu %ld"); + + fseek(mProcFS, 0L, SEEK_SET); + size_t len = fread(buffer, 1, sizeof(buffer) - 1, mProcFS); + if (! len) + { + return false; + } + buffer[len] = '\0'; + if (23 == sscanf(buffer, format, + &i_dummy, // pid + // &s_dummy, // command name + &c_dummy, // state + &i_dummy, // ppid + &i_dummy, // pgrp + &i_dummy, // session + &i_dummy, // terminal + &i_dummy, // terminal group id + &ui_dummy, // flags + &ul_dummy, // minor faults + &ul_dummy, // minor faults in children + &ul_dummy, // major faults + &ul_dummy, // major faults in children + &user_ticks, + &sys_ticks, + &l_dummy, // cutime + &l_dummy, // cstime + &l_dummy, // process priority + &l_dummy, // nice value + &l_dummy, // thread count + &l_dummy, // time to SIGALRM + &ull_dummy, // start time + &vsize, + &rss)) + { + // Looks like we understand the line + if (utime) + { + *utime = user_ticks * mUsecsPerTick; + } + + if (stime) + { + *stime = sys_ticks * mUsecsPerTick; + } + + if (vsz) + { + *vsz = vsize; + } + return true; + } + } + return false; + } + +protected: + FILE * mProcFS; + U64 mUsecsPerTick; + +}; + + +#endif // LL_WINDOWS + +Metrics::Metrics() + : mMaxVSZ(U64(0)), + mMinVSZ(U64L(0xffffffffffffffff)), + mStartWallTime(U64(0)), + mEndWallTime(U64(0)), + mStartUTime(U64(0)), + mEndUTime(U64(0)), + mStartSTime(U64(0)), + mEndSTime(U64(0)) +{ + mImpl = new MetricsImpl(); +} + + +Metrics::~Metrics() +{ + delete mImpl; + mImpl = NULL; +} + + +void Metrics::init() +{ + mImpl->init(this); +} + + +void Metrics::sample() +{ + mImpl->sample(this); +} + + +void Metrics::term() +{ + mImpl->term(this); +} + + diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp new file mode 100644 index 0000000000..f2fcbf77a3 --- /dev/null +++ b/indra/llcorehttp/httpcommon.cpp @@ -0,0 +1,179 @@ +/** + * @file httpcommon.cpp + * @brief + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "httpcommon.h" + +#include <curl/curl.h> +#include <string> +#include <sstream> + + +namespace LLCore +{ + +HttpStatus::type_enum_t EXT_CURL_EASY; +HttpStatus::type_enum_t EXT_CURL_MULTI; +HttpStatus::type_enum_t LLCORE; + +HttpStatus::operator unsigned long() const +{ + static const int shift(sizeof(unsigned long) * 4); + + unsigned long result(((unsigned long) mType) << shift | (unsigned long) (int) mStatus); + return result; +} + + +std::string HttpStatus::toHex() const +{ + std::ostringstream result; + result.width(8); + result.fill('0'); + result << std::hex << operator unsigned long(); + return result.str(); +} + + +std::string HttpStatus::toString() const +{ + static const char * llcore_errors[] = + { + "", + "HTTP error reply status", + "Services shutting down", + "Operation canceled", + "Invalid Content-Range header encountered", + "Request handle not found", + "Invalid datatype for argument or option", + "Option has not been explicitly set", + "Option is not dynamic and must be set early", + "Invalid HTTP status code received from server" + }; + static const int llcore_errors_count(sizeof(llcore_errors) / sizeof(llcore_errors[0])); + + static const struct + { + type_enum_t mCode; + const char * mText; + } + http_errors[] = + { + // Keep sorted by mCode, we binary search this list. + { 100, "Continue" }, + { 101, "Switching Protocols" }, + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non-Authoritative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, + { 302, "Found" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 305, "Use Proxy" }, + { 307, "Temporary Redirect" }, + { 400, "Bad Request" }, + { 401, "Unauthorized" }, + { 402, "Payment Required" }, + { 403, "Forbidden" }, + { 404, "Not Found" }, + { 405, "Method Not Allowed" }, + { 406, "Not Acceptable" }, + { 407, "Proxy Authentication Required" }, + { 408, "Request Time-out" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Request Entity Too Large" }, + { 414, "Request-URI Too Large" }, + { 415, "Unsupported Media Type" }, + { 416, "Requested range not satisfiable" }, + { 417, "Expectation Failed" }, + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Unavailable" }, + { 504, "Gateway Time-out" }, + { 505, "HTTP Version not supported" } + }; + static const int http_errors_count(sizeof(http_errors) / sizeof(http_errors[0])); + + if (*this) + { + return std::string(""); + } + switch (mType) + { + case EXT_CURL_EASY: + return std::string(curl_easy_strerror(CURLcode(mStatus))); + + case EXT_CURL_MULTI: + return std::string(curl_multi_strerror(CURLMcode(mStatus))); + + case LLCORE: + if (mStatus >= 0 && mStatus < llcore_errors_count) + { + return std::string(llcore_errors[mStatus]); + } + break; + + default: + if (isHttpStatus()) + { + // Binary search for the error code and string + int bottom(0), top(http_errors_count); + while (true) + { + int at((bottom + top) / 2); + if (mType == http_errors[at].mCode) + { + return std::string(http_errors[at].mText); + } + if (at == bottom) + { + break; + } + else if (mType < http_errors[at].mCode) + { + top = at; + } + else + { + bottom = at; + } + } + } + break; + } + return std::string("Unknown error"); +} + +} // end namespace LLCore + diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h new file mode 100644 index 0000000000..c0d4ec5aad --- /dev/null +++ b/indra/llcorehttp/httpcommon.h @@ -0,0 +1,311 @@ +/** + * @file httpcommon.h + * @brief Public-facing declarations and definitions of common types + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_COMMON_H_ +#define _LLCORE_HTTP_COMMON_H_ + +/// @package LLCore::HTTP +/// +/// This library implements a high-level, Indra-code-free client interface to +/// HTTP services based on actual patterns found in the viewer and simulator. +/// Interfaces are similar to those supplied by the legacy classes +/// LLCurlRequest and LLHTTPClient. To that is added a policy scheme that +/// allows an application to specify connection behaviors: limits on +/// connections, HTTP keepalive, HTTP pipelining, retry-on-error limits, etc. +/// +/// Features of the library include: +/// - Single, private working thread where all transport and processing occurs. +/// - Support for multiple consumers running in multiple threads. +/// - Scatter/gather (a.k.a. buffer array) model for bulk data movement. +/// - Reference counting used for many object instance lifetimes. +/// - Minimal data sharing across threads for correctness and low latency. +/// +/// The public interface is declared in a few key header files: +/// - "llcorehttp/bufferarray.h" +/// - "llcorehttp/httpcommon.h" +/// - "llcorehttp/httphandler.h" +/// - "llcorehttp/httpheaders.h" +/// - "llcorehttp/httpoptions.h" +/// - "llcorehttp/httprequest.h" +/// - "llcorehttp/httpresponse.h" +/// +/// The library is still under early development and particular users +/// may need access to internal implementation details that are found +/// in the _*.h header files. But this is a crutch to be avoided if at +/// all possible and probably indicates some interface work is neeeded. +/// +/// Using the library is fairly easy. Global setup needs a few +/// steps: +/// +/// - libcurl initialization including thread-safely callbacks for SSL: +/// . curl_global_init(...) +/// . CRYPTO_set_locking_callback(...) +/// . CRYPTO_set_id_callback(...) +/// - HttpRequest::createService() called to instantiate singletons +/// and support objects. +/// +/// An HTTP consumer in an application, and an application may have many +/// consumers, does a few things: +/// +/// - Instantiate and retain an object based on HttpRequest. This +/// object becomes the portal into runtime services for the consumer. +/// - Derive or mixin the HttpHandler class if you want notification +/// when requests succeed or fail. This object's onCompleted() +/// method is invoked and an instance can be shared across +/// requests. +/// +/// Issuing a request is straightforward: +/// - Construct a suitable URL. +/// - Configure HTTP options for the request. (optional) +/// - Build a list of additional headers. (optional) +/// - Invoke one of the requestXXXX() methods (requestGetByteRange, +/// requestPost, etc.) on the HttpRequest instance supplying the +/// above along with a policy class, a priority and an optional +/// pointer to an HttpHandler instance. Work is then queued to +/// the worker thread and occurs asynchronously. +/// - Periodically invoke the update() method on the HttpRequest +/// instance which performs completion notification to HttpHandler +/// objects. +/// - Do completion processing in your onCompletion() method. +/// +/// Code fragments: +/// Rather than a poorly-maintained example in comments, look in the +/// example subdirectory which is a minimal yet functional tool to do +/// GET request performance testing. With four calls: +/// +/// init_curl(); +/// LLCore::HttpRequest::createService(); +/// LLCore::HttpRequest::startThread(); +/// LLCore::HttpRequest * hr = new LLCore::HttpRequest(); +/// +/// the program is basically ready to issue requests. +/// + + +#include "linden_common.h" // Modifies curl/curl.h interfaces + +#include <string> + + +namespace LLCore +{ + + +/// All queued requests are represented by an HttpHandle value. +/// The invalid value is returned when a request failed to queue. +/// The actual status for these failures is then fetched with +/// HttpRequest::getStatus(). +/// +/// The handle is valid only for the life of a request. On +/// return from any HttpHandler notification, the handle immediately +/// becomes invalid and may be recycled for other queued requests. + +typedef void * HttpHandle; +#define LLCORE_HTTP_HANDLE_INVALID (NULL) + +/// For internal scheduling and metrics, we use a microsecond +/// timebase compatible with the environment. +typedef U64 HttpTime; + +/// Error codes defined by the library itself as distinct from +/// libcurl (or any other transport provider). +enum HttpError +{ + // Successful value compatible with the libcurl codes. + HE_SUCCESS = 0, + + // Intended for HTTP reply codes 100-999, indicates that + // the reply should be considered an error by the application. + HE_REPLY_ERROR = 1, + + // Service is shutting down and requested operation will + // not be queued or performed. + HE_SHUTTING_DOWN = 2, + + // Operation was canceled by request. + HE_OP_CANCELED = 3, + + // Invalid content range header received. + HE_INV_CONTENT_RANGE_HDR = 4, + + // Request handle not found + HE_HANDLE_NOT_FOUND = 5, + + // Invalid datatype for option/setting + HE_INVALID_ARG = 6, + + // Option hasn't been explicitly set + HE_OPT_NOT_SET = 7, + + // Option not dynamic, must be set during init phase + HE_OPT_NOT_DYNAMIC = 8, + + // Invalid HTTP status code returned by server + HE_INVALID_HTTP_STATUS = 9 + +}; // end enum HttpError + + +/// HttpStatus encapsulates errors from libcurl (easy, multi), HTTP +/// reply status codes and internal errors as well. The encapsulation +/// isn't expected to completely isolate the caller from libcurl but +/// basic operational tests (success or failure) are provided. +/// +/// Non-HTTP status are encoded as (type, status) with type being +/// one of: EXT_CURL_EASY, EXT_CURL_MULTI or LLCORE and status +/// being the success/error code from that domain. HTTP status +/// is encoded as (status, error_flag). Status should be in the +/// range [100, 999] and error_flag is either HE_SUCCESS or +/// HE_REPLY_ERROR to indicate whether this should be treated as +/// a successful status or an error. The application is responsible +/// for making that determination and a range like [200, 299] isn't +/// automatically assumed to be definitive. +/// +/// Examples: +/// +/// 1. Construct a default, successful status code: +/// HttpStatus(); +/// +/// 2. Construct a successful, HTTP 200 status code: +/// HttpStatus(200); +/// +/// 3. Construct a failed, HTTP 404 not-found status code: +/// HttpStatus(404); +/// +/// 4. Construct a failed libcurl couldn't connect status code: +/// HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); +/// +/// 5. Construct an HTTP 301 status code to be treated as success: +/// HttpStatus(301, HE_SUCCESS); +/// + +struct HttpStatus +{ + typedef unsigned short type_enum_t; + + HttpStatus() + : mType(LLCORE), + mStatus(HE_SUCCESS) + {} + + HttpStatus(type_enum_t type, short status) + : mType(type), + mStatus(status) + {} + + HttpStatus(int http_status) + : mType(http_status), + mStatus(http_status >= 200 && http_status <= 299 + ? HE_SUCCESS + : HE_REPLY_ERROR) + { + llassert(http_status >= 100 && http_status <= 999); + } + + HttpStatus(const HttpStatus & rhs) + : mType(rhs.mType), + mStatus(rhs.mStatus) + {} + + HttpStatus & operator=(const HttpStatus & rhs) + { + // Don't care if lhs & rhs are the same object + + mType = rhs.mType; + mStatus = rhs.mStatus; + return *this; + } + + static const type_enum_t EXT_CURL_EASY = 0; + static const type_enum_t EXT_CURL_MULTI = 1; + static const type_enum_t LLCORE = 2; + + type_enum_t mType; + short mStatus; + + /// Test for successful status in the code regardless + /// of error source (internal, libcurl). + /// + /// @return 'true' when status is successful. + /// + operator bool() const + { + return 0 == mStatus; + } + + /// Inverse of previous operator. + /// + /// @return 'true' on any error condition + bool operator !() const + { + return 0 != mStatus; + } + + /// Equality and inequality tests to bypass bool conversion + /// which will do the wrong thing in conditional expressions. + bool operator==(const HttpStatus & rhs) const + { + return mType == rhs.mType && mStatus == rhs.mStatus; + } + + bool operator!=(const HttpStatus & rhs) const + { + return ! operator==(rhs); + } + + /// Convert to single numeric representation. Mainly + /// for logging or other informal purposes. Also + /// creates an ambiguous second path to integer conversion + /// which tends to find programming errors such as formatting + /// the status to a stream (operator<<). + operator unsigned long() const; + unsigned long toULong() const + { + return operator unsigned long(); + } + + /// And to convert to a hex string. + std::string toHex() const; + + /// Convert status to a string representation. For + /// success, returns an empty string. For failure + /// statuses, a string as appropriate for the source of + /// the error code (libcurl easy, libcurl multi, or + /// LLCore itself). + std::string toString() const; + + /// Returns true if the status value represents an + /// HTTP response status (100 - 999). + bool isHttpStatus() const + { + return mType >= type_enum_t(100) && mType <= type_enum_t(999); + } + +}; // end struct HttpStatus + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_COMMON_H_ diff --git a/indra/llcorehttp/httphandler.h b/indra/llcorehttp/httphandler.h new file mode 100644 index 0000000000..9171e4e7b9 --- /dev/null +++ b/indra/llcorehttp/httphandler.h @@ -0,0 +1,88 @@ +/** + * @file httphandler.h + * @brief Public-facing declarations for the HttpHandler class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_HANDLER_H_ +#define _LLCORE_HTTP_HANDLER_H_ + + +#include "httpcommon.h" + + +namespace LLCore +{ + +class HttpResponse; + + +/// HttpHandler defines an interface used by the library to +/// notify library callers of significant events, currently +/// request completion. Callers must derive or mixin this class +/// then provide an implementation of the @see onCompleted +/// method to receive such notifications. An instance may +/// be shared by any number of requests and across instances +/// of HttpRequest running in the same thread. +/// +/// Threading: HttpHandler itself is pure interface and is +/// tread-compatible. Most derivations, however, will have +/// different constraints. +/// +/// Allocation: Not refcounted, may be stack allocated though +/// that is rarely a good idea. Queued requests and replies keep +/// a naked pointer to the handler and this can result in a +/// dangling pointer if lifetimes aren't managed correctly. + +class HttpHandler +{ +public: + virtual ~HttpHandler() + {} + + /// Method invoked during calls to @see update(). Each invocation + /// represents the completion of some requested operation. Caller + /// can identify the request from the handle and interrogate the + /// response argument for success/failure, data and other information. + /// + /// @param handle Identifier of the request generating + /// the notification. + /// @param response Supplies detailed information about + /// the request including status codes + /// (both programming and HTTP), HTTP body + /// data and encodings, headers, etc. + /// The response object is refcounted and + /// the called code may retain the object + /// by invoking @see addRef() on it. The + /// library itself drops all references to + /// to object on return and never touches + /// it again. + /// + virtual void onCompleted(HttpHandle handle, HttpResponse * response) = 0; + +}; // end class HttpHandler + + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_HANDLER_H_ diff --git a/indra/llcorehttp/httpheaders.cpp b/indra/llcorehttp/httpheaders.cpp new file mode 100644 index 0000000000..2832696271 --- /dev/null +++ b/indra/llcorehttp/httpheaders.cpp @@ -0,0 +1,44 @@ +/** + * @file httpheaders.cpp + * @brief Implementation of the HTTPHeaders class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "httpheaders.h" + + +namespace LLCore +{ + + +HttpHeaders::HttpHeaders() + : RefCounted(true) +{} + + +HttpHeaders::~HttpHeaders() +{} + + +} // end namespace LLCore + diff --git a/indra/llcorehttp/httpheaders.h b/indra/llcorehttp/httpheaders.h new file mode 100644 index 0000000000..3449daa3a1 --- /dev/null +++ b/indra/llcorehttp/httpheaders.h @@ -0,0 +1,87 @@ +/** + * @file httpheaders.h + * @brief Public-facing declarations for the HttpHeaders class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_HEADERS_H_ +#define _LLCORE_HTTP_HEADERS_H_ + + +#include <string> + +#include "_refcounted.h" + + +namespace LLCore +{ + +/// +/// Maintains an ordered list of name/value pairs representing +/// HTTP header lines. This is used both to provide additional +/// headers when making HTTP requests and in responses when the +/// caller has asked that headers be returned (not the default +/// option). +/// +/// @note +/// This is a minimally-functional placeholder at the moment +/// to fill out the class hierarchy. The final class will be +/// something else, probably more pair-oriented. It's also +/// an area where shared values are desirable so refcounting is +/// already specced and a copy-on-write scheme imagined. +/// Expect changes here. +/// +/// Threading: Not intrinsically thread-safe. It *is* expected +/// that callers will build these objects and then share them +/// via reference counting with the worker thread. The implication +/// is that once an HttpHeader instance is handed to a request, +/// the object must be treated as read-only. +/// +/// Allocation: Refcounted, heap only. Caller of the +/// constructor is given a refcount. +/// + +class HttpHeaders : public LLCoreInt::RefCounted +{ +public: + /// @post In addition to the instance, caller has a refcount + /// to the instance. A call to @see release() will destroy + /// the instance. + HttpHeaders(); + +protected: + virtual ~HttpHeaders(); // Use release() + + HttpHeaders(const HttpHeaders &); // Not defined + void operator=(const HttpHeaders &); // Not defined + +public: + typedef std::vector<std::string> container_t; + container_t mHeaders; + +}; // end class HttpHeaders + +} // end namespace LLCore + + +#endif // _LLCORE_HTTP_HEADERS_H_ diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp new file mode 100644 index 0000000000..1699d19f8d --- /dev/null +++ b/indra/llcorehttp/httpoptions.cpp @@ -0,0 +1,73 @@ +/** + * @file httpoptions.cpp + * @brief Implementation of the HTTPOptions class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "httpoptions.h" + +#include "_httpinternal.h" + + +namespace LLCore +{ + + +HttpOptions::HttpOptions() + : RefCounted(true), + mWantHeaders(false), + mTracing(HTTP_TRACE_OFF), + mTimeout(HTTP_REQUEST_TIMEOUT_DEFAULT), + mRetries(HTTP_RETRY_COUNT_DEFAULT) +{} + + +HttpOptions::~HttpOptions() +{} + + +void HttpOptions::setWantHeaders(bool wanted) +{ + mWantHeaders = wanted; +} + + +void HttpOptions::setTrace(long level) +{ + mTracing = int(level); +} + + +void HttpOptions::setTimeout(unsigned int timeout) +{ + mTimeout = timeout; +} + + +void HttpOptions::setRetries(unsigned int retries) +{ + mRetries = retries; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h new file mode 100644 index 0000000000..97e46a8cd3 --- /dev/null +++ b/indra/llcorehttp/httpoptions.h @@ -0,0 +1,106 @@ +/** + * @file httpoptions.h + * @brief Public-facing declarations for the HTTPOptions class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_OPTIONS_H_ +#define _LLCORE_HTTP_OPTIONS_H_ + + +#include "httpcommon.h" + +#include "_refcounted.h" + + +namespace LLCore +{ + + +/// Really a struct in spirit, it provides options that +/// modify HTTP requests. +/// +/// Sharing instances across requests. It's intended that +/// these be shared across requests: caller can create one +/// of these, set it up as needed and then reference it +/// repeatedly in HTTP operations. But see the Threading +/// note about references. +/// +/// Threading: While this class does nothing to ensure thread +/// safety, it *is* intended to be shared between the application +/// thread and the worker thread. This means that once an instance +/// is delivered to the library in request operations, the +/// option data must not be written until all such requests +/// complete and relinquish their references. +/// +/// Allocation: Refcounted, heap only. Caller of the constructor +/// is given a refcount. +/// +class HttpOptions : public LLCoreInt::RefCounted +{ +public: + HttpOptions(); + +protected: + virtual ~HttpOptions(); // Use release() + + HttpOptions(const HttpOptions &); // Not defined + void operator=(const HttpOptions &); // Not defined + +public: + void setWantHeaders(bool wanted); + bool getWantHeaders() const + { + return mWantHeaders; + } + + void setTrace(int long); + int getTrace() const + { + return mTracing; + } + + void setTimeout(unsigned int timeout); + unsigned int getTimeout() const + { + return mTimeout; + } + + void setRetries(unsigned int retries); + unsigned int getRetries() const + { + return mRetries; + } + +protected: + bool mWantHeaders; + int mTracing; + unsigned int mTimeout; + unsigned int mRetries; + +}; // end class HttpOptions + + +} // end namespace HttpOptions + +#endif // _LLCORE_HTTP_OPTIONS_H_ diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp new file mode 100644 index 0000000000..9b739a8825 --- /dev/null +++ b/indra/llcorehttp/httprequest.cpp @@ -0,0 +1,504 @@ +/** + * @file httprequest.cpp + * @brief Implementation of the HTTPRequest class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "httprequest.h" + +#include "_httprequestqueue.h" +#include "_httpreplyqueue.h" +#include "_httpservice.h" +#include "_httppolicy.h" +#include "_httpoperation.h" +#include "_httpoprequest.h" +#include "_httpopsetpriority.h" +#include "_httpopcancel.h" +#include "_httpopsetget.h" + +#include "lltimer.h" + + +namespace +{ + +bool has_inited(false); + +} + +namespace LLCore +{ + +// ==================================== +// HttpRequest Implementation +// ==================================== + + +HttpRequest::policy_t HttpRequest::sNextPolicyID(1); + + +HttpRequest::HttpRequest() + : //HttpHandler(), + mReplyQueue(NULL), + mRequestQueue(NULL) +{ + mRequestQueue = HttpRequestQueue::instanceOf(); + mRequestQueue->addRef(); + + mReplyQueue = new HttpReplyQueue(); +} + + +HttpRequest::~HttpRequest() +{ + if (mRequestQueue) + { + mRequestQueue->release(); + mRequestQueue = NULL; + } + + if (mReplyQueue) + { + mReplyQueue->release(); + mReplyQueue = NULL; + } +} + + +// ==================================== +// Policy Methods +// ==================================== + + +HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value) +{ + if (HttpService::RUNNING == HttpService::instanceOf()->getState()) + { + return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + } + return HttpService::instanceOf()->getGlobalOptions().set(opt, value); +} + + +HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value) +{ + if (HttpService::RUNNING == HttpService::instanceOf()->getState()) + { + return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + } + return HttpService::instanceOf()->getGlobalOptions().set(opt, value); +} + + +HttpRequest::policy_t HttpRequest::createPolicyClass() +{ + if (HttpService::RUNNING == HttpService::instanceOf()->getState()) + { + return 0; + } + return HttpService::instanceOf()->createPolicyClass(); +} + + +HttpStatus HttpRequest::setPolicyClassOption(policy_t policy_id, + EClassPolicy opt, + long value) +{ + if (HttpService::RUNNING == HttpService::instanceOf()->getState()) + { + return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + } + return HttpService::instanceOf()->getClassOptions(policy_id).set(opt, value); +} + + +// ==================================== +// Request Methods +// ==================================== + + +HttpStatus HttpRequest::getStatus() const +{ + return mLastReqStatus; +} + + +HttpHandle HttpRequest::requestGet(policy_t policy_id, + priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpRequest * op = new HttpOpRequest(); + if (! (status = op->setupGet(policy_id, priority, url, options, headers))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id, + priority_t priority, + const std::string & url, + size_t offset, + size_t len, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpRequest * op = new HttpOpRequest(); + if (! (status = op->setupGetByteRange(policy_id, priority, url, offset, len, options, headers))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +HttpHandle HttpRequest::requestPost(policy_t policy_id, + priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpRequest * op = new HttpOpRequest(); + if (! (status = op->setupPost(policy_id, priority, url, body, options, headers))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +HttpHandle HttpRequest::requestPut(policy_t policy_id, + priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpRequest * op = new HttpOpRequest(); + if (! (status = op->setupPut(policy_id, priority, url, body, options, headers))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +HttpHandle HttpRequest::requestNoOp(HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpNull * op = new HttpOpNull(); + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +HttpStatus HttpRequest::update(long usecs) +{ + HttpOperation * op(NULL); + + if (usecs) + { + const HttpTime limit(totalTime() + HttpTime(usecs)); + while (limit >= totalTime() && (op = mReplyQueue->fetchOp())) + { + // Process operation + op->visitNotifier(this); + + // We're done with the operation + op->release(); + } + } + else + { + // Same as above, just no time limit + HttpReplyQueue::OpContainer replies; + mReplyQueue->fetchAll(replies); + if (! replies.empty()) + { + for (HttpReplyQueue::OpContainer::iterator iter(replies.begin()); + replies.end() != iter; + ++iter) + { + // Swap op pointer for NULL; + op = *iter; *iter = NULL; + + // Process operation + op->visitNotifier(this); + + // We're done with the operation + op->release(); + } + } + } + + return HttpStatus(); +} + + + + +// ==================================== +// Request Management Methods +// ==================================== + +HttpHandle HttpRequest::requestCancel(HttpHandle request, HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle ret_handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpCancel * op = new HttpOpCancel(request); + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return ret_handle; + } + + mLastReqStatus = status; + ret_handle = static_cast<HttpHandle>(op); + + return ret_handle; +} + + +HttpHandle HttpRequest::requestSetPriority(HttpHandle request, priority_t priority, + HttpHandler * handler) +{ + HttpStatus status; + HttpHandle ret_handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSetPriority * op = new HttpOpSetPriority(request, priority); + op->setReplyPath(mReplyQueue, handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return ret_handle; + } + + mLastReqStatus = status; + ret_handle = static_cast<HttpHandle>(op); + + return ret_handle; +} + + +// ==================================== +// Utility Methods +// ==================================== + +HttpStatus HttpRequest::createService() +{ + HttpStatus status; + + if (! has_inited) + { + HttpRequestQueue::init(); + HttpRequestQueue * rq = HttpRequestQueue::instanceOf(); + HttpService::init(rq); + has_inited = true; + } + + return status; +} + + +HttpStatus HttpRequest::destroyService() +{ + HttpStatus status; + + if (has_inited) + { + HttpService::term(); + HttpRequestQueue::term(); + has_inited = false; + } + + return status; +} + + +HttpStatus HttpRequest::startThread() +{ + HttpStatus status; + + HttpService::instanceOf()->startThread(); + + return status; +} + + +HttpHandle HttpRequest::requestStopThread(HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpStop * op = new HttpOpStop(); + op->setReplyPath(mReplyQueue, user_handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +HttpHandle HttpRequest::requestSpin(int mode) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSpin * op = new HttpOpSpin(mode); + op->setReplyPath(mReplyQueue, NULL); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + +// ==================================== +// Dynamic Policy Methods +// ==================================== + +HttpHandle HttpRequest::requestSetHttpProxy(const std::string & proxy, HttpHandler * handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSetGet * op = new HttpOpSetGet(); + op->setupSet(GP_HTTP_PROXY, proxy); + op->setReplyPath(mReplyQueue, handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast<HttpHandle>(op); + + return handle; +} + + +} // end namespace LLCore + diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h new file mode 100644 index 0000000000..ab2f302d34 --- /dev/null +++ b/indra/llcorehttp/httprequest.h @@ -0,0 +1,535 @@ +/** + * @file httprequest.h + * @brief Public-facing declarations for HttpRequest class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_REQUEST_H_ +#define _LLCORE_HTTP_REQUEST_H_ + + +#include "httpcommon.h" +#include "httphandler.h" + + +namespace LLCore +{ + +class HttpRequestQueue; +class HttpReplyQueue; +class HttpService; +class HttpOptions; +class HttpHeaders; +class HttpOperation; +class BufferArray; + +/// HttpRequest supplies the entry into the HTTP transport +/// services in the LLCore libraries. Services provided include: +/// +/// - Some, but not all, global initialization of libcurl. +/// - Starting asynchronous, threaded HTTP requests. +/// - Definition of policy classes affect request handling. +/// - Utilities to control request options and headers +/// +/// Requests +/// +/// The class supports the current HTTP request operations: +/// +/// - requestGetByteRange: GET with Range header for a single range of bytes +/// +/// Policy Classes +/// +/// <TBD> +/// +/// Usage +/// +/// <TBD> +/// +/// Threading: An instance may only be used by one application/ +/// consumer thread. But a thread may have as many instances of +/// this as it likes. +/// +/// Allocation: Not refcounted, may be stack allocated though that +/// hasn't been tested. Queued requests can still run and any +/// queued replies will keep refcounts to the reply queue leading +/// to memory leaks. +/// +/// @pre Before using this class (static or instances), some global +/// initialization is required. See @see httpcommon.h for more information. +/// +/// @nosubgrouping +/// + +class HttpRequest +{ +public: + HttpRequest(); + virtual ~HttpRequest(); + +private: + HttpRequest(const HttpRequest &); // Disallowed + void operator=(const HttpRequest &); // Disallowed + +public: + typedef unsigned int policy_t; + typedef unsigned int priority_t; + +public: + /// @name PolicyMethods + /// @{ + + /// Represents a default, catch-all policy class that guarantees + /// eventual service for any HTTP request. + static const int DEFAULT_POLICY_ID = 0; + + enum EGlobalPolicy + { + /// Maximum number of connections the library will use to + /// perform operations. This is somewhat soft as the underlying + /// transport will cache some connections (up to 5). + + /// A long value setting the maximum number of connections + /// allowed over all policy classes. Note that this will be + /// a somewhat soft value. There may be an additional five + /// connections per policy class depending upon runtime + /// behavior. + GP_CONNECTION_LIMIT, + + /// String containing a system-appropriate directory name + /// where SSL certs are stored. + GP_CA_PATH, + + /// String giving a full path to a file containing SSL certs. + GP_CA_FILE, + + /// String of host/port to use as simple HTTP proxy. This is + /// going to change in the future into something more elaborate + /// that may support richer schemes. + GP_HTTP_PROXY, + + /// Long value that if non-zero enables the use of the + /// traditional LLProxy code for http/socks5 support. If + /// enabled, has priority over GP_HTTP_PROXY. + GP_LLPROXY, + + /// Long value setting the logging trace level for the + /// library. Possible values are: + /// 0 - No tracing (default) + /// 1 - Basic tracing of request start, stop and major events. + /// 2 - Connection, header and payload size information from + /// HTTP transactions. + /// 3 - Partial logging of payload itself. + /// + /// These values are also used in the trace modes for + /// individual requests in HttpOptions. Also be aware that + /// tracing tends to impact performance of the viewer. + GP_TRACE + }; + + /// Set a parameter on a global policy option. Calls + /// made after the start of the servicing thread are + /// not honored and return an error status. + /// + /// @param opt Enum of option to be set. + /// @param value Desired value of option. + /// @return Standard status code. + static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, long value); + static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value); + + /// Create a new policy class into which requests can be made. + /// + /// @return If positive, the policy_id used to reference + /// the class in other methods. If 0, an error + /// occurred and @see getStatus() may provide more + /// detail on the reason. + static policy_t createPolicyClass(); + + enum EClassPolicy + { + /// Limits the number of connections used for the class. + CP_CONNECTION_LIMIT, + + /// Limits the number of connections used for a single + /// literal address/port pair within the class. + CP_PER_HOST_CONNECTION_LIMIT, + + /// Suitable requests are allowed to pipeline on their + /// connections when they ask for it. + CP_ENABLE_PIPELINING + }; + + /// Set a parameter on a class-based policy option. Calls + /// made after the start of the servicing thread are + /// not honored and return an error status. + /// + /// @param policy_id ID of class as returned by @see createPolicyClass(). + /// @param opt Enum of option to be set. + /// @param value Desired value of option. + /// @return Standard status code. + static HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value); + + /// @} + + /// @name RequestMethods + /// + /// @{ + + /// Some calls expect to succeed as the normal part of operation and so + /// return a useful value rather than a status. When they do fail, the + /// status is saved and can be fetched with this method. + /// + /// @return Status of the failing method invocation. If the + /// preceding call succeeded or other HttpStatus + /// returning calls immediately preceded this method, + /// the returned value may not be reliable. + /// + HttpStatus getStatus() const; + + /// Queue a full HTTP GET request to be issued for entire entity. + /// The request is queued and serviced by the working thread and + /// notification of completion delivered to the optional HttpHandler + /// argument during @see update() calls. + /// + /// With a valid handle returned, it can be used to reference the + /// request in other requests (like cancellation) and will be an + /// argument when any HttpHandler object is invoked. + /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-alive: 300 + /// - Host: <stuff> + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-control: + /// - Range: + /// - Transfer-Encoding: + /// - Referer: + /// + /// @param policy_id Default or user-defined policy class under + /// which this request is to be serviced. + /// @param priority Standard priority scheme inherited from + /// Indra code base (U32-type scheme). + /// @param url URL with any encoded query parameters to + /// be accessed. + /// @param options Optional instance of an HttpOptions object + /// to provide additional controls over the request + /// function for this request only. Any such + /// object then becomes shared-read across threads + /// and no code should modify the HttpOptions + /// instance. + /// @param headers Optional instance of an HttpHeaders object + /// to provide additional and/or overridden + /// headers for the request. As with options, + /// the instance becomes shared-read across threads + /// and no code should modify the HttpHeaders + /// instance. + /// @param handler Optional pointer to an HttpHandler instance + /// whose onCompleted() method will be invoked + /// during calls to update(). This is a non- + /// reference-counted object which would be a + /// problem for shutdown and other edge cases but + /// the pointer is only dereferenced during + /// calls to update(). + /// + /// @return The handle of the request if successfully + /// queued or LLCORE_HTTP_HANDLE_INVALID if the + /// request could not be queued. In the latter + /// case, @see getStatus() will return more info. + /// + HttpHandle requestGet(policy_t policy_id, + priority_t priority, + const std::string & url, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * handler); + + + /// Queue a full HTTP GET request to be issued with a 'Range' header. + /// The request is queued and serviced by the working thread and + /// notification of completion delivered to the optional HttpHandler + /// argument during @see update() calls. + /// + /// With a valid handle returned, it can be used to reference the + /// request in other requests (like cancellation) and will be an + /// argument when any HttpHandler object is invoked. + /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-alive: 300 + /// - Host: <stuff> + /// - Range: <stuff> (will be omitted if offset == 0 and len == 0) + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-control: + /// - Transfer-Encoding: + /// - Referer: + /// + /// @param policy_id @see requestGet() + /// @param priority " + /// @param url " + /// @param offset Offset of first byte into resource to be returned. + /// @param len Count of bytes to be returned + /// @param options @see requestGet() + /// @param headers " + /// @param handler " + /// @return " + /// + HttpHandle requestGetByteRange(policy_t policy_id, + priority_t priority, + const std::string & url, + size_t offset, + size_t len, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * handler); + + + /// Queue a full HTTP POST. Query arguments and body may + /// be provided. Caller is responsible for escaping and + /// encoding and communicating the content types. + /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-Alive: 300 + /// - Host: <stuff> + /// - Content-Length: <digits> + /// - Content-Type: application/x-www-form-urlencoded + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-Control: + /// - Transfer-Encoding: ... chunked ... + /// - Referer: + /// - Content-Encoding: + /// - Expect: + /// + /// @param policy_id @see requestGet() + /// @param priority " + /// @param url " + /// @param body Byte stream to be sent as the body. No + /// further encoding or escaping will be done + /// to the content. + /// @param options @see requestGet()K(optional) + /// @param headers " + /// @param handler " + /// @return " + /// + HttpHandle requestPost(policy_t policy_id, + priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * handler); + + + /// Queue a full HTTP PUT. Query arguments and body may + /// be provided. Caller is responsible for escaping and + /// encoding and communicating the content types. + /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-Alive: 300 + /// - Host: <stuff> + /// - Content-Length: <digits> + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-Control: + /// - Transfer-Encoding: ... chunked ... + /// - Referer: + /// - Content-Encoding: + /// - Expect: + /// - Content-Type: + /// + /// @param policy_id @see requestGet() + /// @param priority " + /// @param url " + /// @param body Byte stream to be sent as the body. No + /// further encoding or escaping will be done + /// to the content. + /// @param options @see requestGet()K(optional) + /// @param headers " + /// @param handler " + /// @return " + /// + HttpHandle requestPut(policy_t policy_id, + priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * handler); + + + /// Queue a NoOp request. + /// The request is queued and serviced by the working thread which + /// immediately processes it and returns the request to the reply + /// queue. + /// + /// @param handler @see requestGet() + /// @return " + /// + HttpHandle requestNoOp(HttpHandler * handler); + + /// While all the heavy work is done by the worker thread, notifications + /// must be performed in the context of the application thread. These + /// are done synchronously during calls to this method which gives the + /// library control so notification can be performed. Application handlers + /// are expected to return 'quickly' and do any significant processing + /// outside of the notification callback to onCompleted(). + /// + /// @param usecs Maximum number of wallclock microseconds to + /// spend in the call. As hinted at above, this + /// is partly a function of application code so it's + /// a soft limit. A '0' value will run without + /// time limit until everything queued has been + /// delivered. + /// + /// @return Standard status code. + HttpStatus update(long usecs); + + /// @} + + /// @name RequestMgmtMethods + /// + /// @{ + + HttpHandle requestCancel(HttpHandle request, HttpHandler *); + + /// Request that a previously-issued request be reprioritized. + /// The status of whether the change itself succeeded arrives + /// via notification. + /// + /// @param request Handle of previously-issued request to + /// be changed. + /// @param priority New priority value. + /// @param handler @see requestGet() + /// @return " + /// + HttpHandle requestSetPriority(HttpHandle request, priority_t priority, HttpHandler * handler); + + /// @} + + /// @name UtilityMethods + /// + /// @{ + + /// Initialization method that needs to be called before queueing any + /// requests. Doesn't start the worker thread and may be called befoer + /// or after policy setup. + static HttpStatus createService(); + + /// Mostly clean shutdown of services prior to exit. Caller is expected + /// to have stopped a running worker thread before calling this. + static HttpStatus destroyService(); + + /// Called once after @see createService() to start the worker thread. + /// Stopping the thread is achieved by requesting it via @see requestStopThread(). + /// May be called before or after requests are issued. + static HttpStatus startThread(); + + /// Queues a request to the worker thread to have it stop processing + /// and exit (without exiting the program). When the operation is + /// picked up by the worker thread, it immediately processes it and + /// begins detaching from refcounted resources like request and + /// reply queues and then returns to the host OS. It *does* queue a + /// reply to give the calling application thread a notification that + /// the operation has been performed. + /// + /// @param handler (optional) + /// @return The handle of the request if successfully + /// queued or LLCORE_HTTP_HANDLE_INVALID if the + /// request could not be queued. In the latter + /// case, @see getStatus() will return more info. + /// As the request cannot be cancelled, the handle + /// is generally not useful. + /// + HttpHandle requestStopThread(HttpHandler * handler); + + /// Queue a Spin request. + /// DEBUG/TESTING ONLY. This puts the worker into a CPU spin for + /// test purposes. + /// + /// @param mode 0 for hard spin, 1 for soft spin + /// @return Standard handle return cases. + /// + HttpHandle requestSpin(int mode); + + /// @} + + /// @name DynamicPolicyMethods + /// + /// @{ + + /// Request that a running transport pick up a new proxy setting. + /// An empty string will indicate no proxy is to be used. + HttpHandle requestSetHttpProxy(const std::string & proxy, HttpHandler * handler); + + /// @} + +protected: + void generateNotification(HttpOperation * op); + +private: + /// @name InstanceData + /// + /// @{ + HttpStatus mLastReqStatus; + HttpReplyQueue * mReplyQueue; + HttpRequestQueue * mRequestQueue; + + /// @} + + // ==================================== + /// @name GlobalState + /// + /// @{ + /// + /// Must be established before any threading is allowed to + /// start. + /// + static policy_t sNextPolicyID; + + /// @} + // End Global State + // ==================================== + +}; // end class HttpRequest + + +} // end namespace LLCore + + + +#endif // _LLCORE_HTTP_REQUEST_H_ diff --git a/indra/llcorehttp/httpresponse.cpp b/indra/llcorehttp/httpresponse.cpp new file mode 100644 index 0000000000..a552e48a1b --- /dev/null +++ b/indra/llcorehttp/httpresponse.cpp @@ -0,0 +1,91 @@ +/** + * @file httpresponse.cpp + * @brief + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "httpresponse.h" +#include "bufferarray.h" +#include "httpheaders.h" + + +namespace LLCore +{ + + +HttpResponse::HttpResponse() + : LLCoreInt::RefCounted(true), + mReplyOffset(0U), + mReplyLength(0U), + mReplyFullLength(0U), + mBufferArray(NULL), + mHeaders(NULL) +{} + + +HttpResponse::~HttpResponse() +{ + setBody(NULL); + setHeaders(NULL); +} + + +void HttpResponse::setBody(BufferArray * ba) +{ + if (mBufferArray == ba) + return; + + if (mBufferArray) + { + mBufferArray->release(); + } + + if (ba) + { + ba->addRef(); + } + + mBufferArray = ba; +} + + +void HttpResponse::setHeaders(HttpHeaders * headers) +{ + if (mHeaders == headers) + return; + + if (mHeaders) + { + mHeaders->release(); + } + + if (headers) + { + headers->addRef(); + } + + mHeaders = headers; +} + + +} // end namespace LLCore diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h new file mode 100644 index 0000000000..4a481db6ac --- /dev/null +++ b/indra/llcorehttp/httpresponse.h @@ -0,0 +1,161 @@ +/** + * @file httpresponse.h + * @brief Public-facing declarations for the HttpResponse class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LLCORE_HTTP_RESPONSE_H_ +#define _LLCORE_HTTP_RESPONSE_H_ + + +#include <string> + +#include "httpcommon.h" + +#include "_refcounted.h" + + +namespace LLCore +{ + +class BufferArray; +class HttpHeaders; + +/// HttpResponse is instantiated by the library and handed to +/// the caller during callbacks to the handler. It supplies +/// all the status, header and HTTP body data the caller is +/// interested in. Methods provide simple getters to return +/// individual pieces of the response. +/// +/// Typical usage will have the caller interrogate the object +/// and return from the handler callback. Instances are refcounted +/// and callers can bump the count and retain the object as needed. +/// +/// Threading: Not intrinsically thread-safe. +/// +/// Allocation: Refcounted, heap only. Caller of the constructor +/// is given a refcount. +/// +class HttpResponse : public LLCoreInt::RefCounted +{ +public: + HttpResponse(); + +protected: + virtual ~HttpResponse(); // Use release() + + HttpResponse(const HttpResponse &); // Not defined + void operator=(const HttpResponse &); // Not defined + +public: + /// Returns the final status of the requested operation. + /// + HttpStatus getStatus() const + { + return mStatus; + } + + void setStatus(const HttpStatus & status) + { + mStatus = status; + } + + /// Simple getter for the response body returned as a scatter/gather + /// buffer. If the operation doesn't produce data (such as the Null + /// or StopThread operations), this may be NULL. + /// + /// Caller can hold onto the response by incrementing the reference + /// count of the returned object. + BufferArray * getBody() const + { + return mBufferArray; + } + + /// Set the response data in the instance. Will drop the reference + /// count to any existing data and increment the count of that passed + /// in. It is legal to set the data to NULL. + void setBody(BufferArray * ba); + + /// And a getter for the headers. And as with @see getResponse(), + /// if headers aren't available because the operation doesn't produce + /// any or delivery of headers wasn't requested in the options, this + /// will be NULL. + /// + /// Caller can hold onto the headers by incrementing the reference + /// count of the returned object. + HttpHeaders * getHeaders() const + { + return mHeaders; + } + + /// Behaves like @see setResponse() but for header data. + void setHeaders(HttpHeaders * headers); + + /// If a 'Range:' header was used, these methods are involved + /// in setting and returning data about the actual response. + /// If both @offset and @length are returned as 0, we probably + /// didn't get a Content-Range header in the response. This + /// occurs with various Capabilities-based services and the + /// caller is going to have to make assumptions on receipt of + /// a 206 status. The @full value may also be zero in cases of + /// parsing problems or a wild-carded length response. + void getRange(unsigned int * offset, unsigned int * length, unsigned int * full) const + { + *offset = mReplyOffset; + *length = mReplyLength; + *full = mReplyFullLength; + } + + void setRange(unsigned int offset, unsigned int length, unsigned int full_length) + { + mReplyOffset = offset; + mReplyLength = length; + mReplyFullLength = full_length; + } + + /// + const std::string & getContentType() const + { + return mContentType; + } + + void setContentType(const std::string & con_type) + { + mContentType = con_type; + } + +protected: + // Response data here + HttpStatus mStatus; + unsigned int mReplyOffset; + unsigned int mReplyLength; + unsigned int mReplyFullLength; + BufferArray * mBufferArray; + HttpHeaders * mHeaders; + std::string mContentType; +}; + + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_RESPONSE_H_ diff --git a/indra/llcorehttp/tests/llcorehttp_test.cpp b/indra/llcorehttp/tests/llcorehttp_test.cpp new file mode 100644 index 0000000000..e863ddd13f --- /dev/null +++ b/indra/llcorehttp/tests/llcorehttp_test.cpp @@ -0,0 +1,175 @@ +/** + * @file llcorehttp_test + * @brief Main test runner + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llcorehttp_test.h" + +#include <iostream> +#include <sstream> + +// These are not the right way in viewer for some reason: +// #include <tut/tut.hpp> +// #include <tut/tut_reporter.hpp> +// This works: +#include "../test/lltut.h" + +// Pull in each of the test sets +#include "test_bufferarray.hpp" +#include "test_bufferstream.hpp" +#include "test_httpstatus.hpp" +#include "test_refcounted.hpp" +#include "test_httpoperation.hpp" +#include "test_httprequest.hpp" +#include "test_httpheaders.hpp" +#include "test_httprequestqueue.hpp" + +#include "llproxy.h" + +unsigned long ssl_thread_id_callback(void); +void ssl_locking_callback(int mode, int type, const char * file, int line); + +#if 0 // lltut provides main and runner + +namespace tut +{ + test_runner_singleton runner; +} + +int main() +{ + curl_global_init(CURL_GLOBAL_ALL); + + // *FIXME: Need threaded/SSL curl setup here. + + tut::reporter reporter; + + tut::runner.get().set_callback(&reporter); + tut::runner.get().run_tests(); + return !reporter.all_ok(); + + curl_global_cleanup(); +} + +#endif // 0 + +int ssl_mutex_count(0); +LLCoreInt::HttpMutex ** ssl_mutex_list = NULL; + +void init_curl() +{ + curl_global_init(CURL_GLOBAL_ALL); + + ssl_mutex_count = CRYPTO_num_locks(); + if (ssl_mutex_count > 0) + { + ssl_mutex_list = new LLCoreInt::HttpMutex * [ssl_mutex_count]; + + for (int i(0); i < ssl_mutex_count; ++i) + { + ssl_mutex_list[i] = new LLCoreInt::HttpMutex; + } + + CRYPTO_set_locking_callback(ssl_locking_callback); + CRYPTO_set_id_callback(ssl_thread_id_callback); + } + + LLProxy::getInstance(); +} + + +void term_curl() +{ + LLProxy::cleanupClass(); + + CRYPTO_set_locking_callback(NULL); + for (int i(0); i < ssl_mutex_count; ++i) + { + delete ssl_mutex_list[i]; + } + delete [] ssl_mutex_list; +} + + +unsigned long ssl_thread_id_callback(void) +{ +#if defined(WIN32) + return (unsigned long) GetCurrentThread(); +#else + return (unsigned long) pthread_self(); +#endif +} + + +void ssl_locking_callback(int mode, int type, const char * /* file */, int /* line */) +{ + if (type >= 0 && type < ssl_mutex_count) + { + if (mode & CRYPTO_LOCK) + { + ssl_mutex_list[type]->lock(); + } + else + { + ssl_mutex_list[type]->unlock(); + } + } +} + + +std::string get_base_url() +{ + const char * env(getenv("LL_TEST_PORT")); + + if (! env) + { + std::cerr << "LL_TEST_PORT environment variable missing." << std::endl; + std::cerr << "Test expects to run in test_llcorehttp_peer.py script." << std::endl; + tut::ensure("LL_TEST_PORT set in environment", NULL != env); + } + + int port(atoi(env)); + std::ostringstream out; + out << "http://localhost:" << port << "/"; + return out.str(); +} + + +void stop_thread(LLCore::HttpRequest * req) +{ + if (req) + { + req->requestStopThread(NULL); + + int count = 0; + int limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + req->update(1000); + usleep(100000); + } + } +} + + diff --git a/indra/llcorehttp/tests/llcorehttp_test.h b/indra/llcorehttp/tests/llcorehttp_test.h new file mode 100644 index 0000000000..a9567435ce --- /dev/null +++ b/indra/llcorehttp/tests/llcorehttp_test.h @@ -0,0 +1,64 @@ +/** + * @file llcorehttp_test.h + * @brief Main test runner + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#ifndef _LLCOREHTTP_TEST_H_ +#define _LLCOREHTTP_TEST_H_ + +#include "linden_common.h" // Modifies curl interfaces + +#include <curl/curl.h> +#include <openssl/crypto.h> +#include <string> + +#include "httprequest.h" + +// Initialization and cleanup for libcurl. Mainly provides +// a mutex callback for SSL and a thread ID hash for libcurl. +// If you don't use these (or equivalent) and do use libcurl, +// you'll see stalls and other anomalies when performing curl +// operations. +extern void init_curl(); +extern void term_curl(); +extern std::string get_base_url(); +extern void stop_thread(LLCore::HttpRequest * req); + +class ScopedCurlInit +{ +public: + ScopedCurlInit() + { + init_curl(); + } + + ~ScopedCurlInit() + { + term_curl(); + } +}; + + +#endif // _LLCOREHTTP_TEST_H_ diff --git a/indra/llcorehttp/tests/test_allocator.cpp b/indra/llcorehttp/tests/test_allocator.cpp new file mode 100644 index 0000000000..ea12dc58eb --- /dev/null +++ b/indra/llcorehttp/tests/test_allocator.cpp @@ -0,0 +1,184 @@ +/** + * @file test_allocator.cpp + * @brief quick and dirty allocator for tracking memory allocations + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "test_allocator.h" + +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 +#include <libkern/OSAtomic.h> +#elif defined(_MSC_VER) +#include <Windows.h> +#elif (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ ) > 40100 +// atomic extensions are built into GCC on posix platforms +#endif + +#include <cassert> +#include <cstdlib> +#include <cstring> +#include <vector> +#include <iostream> +#include <new> + +#include <boost/thread.hpp> + + +#if defined(WIN32) +#define THROW_BAD_ALLOC() _THROW1(std::bad_alloc) +#define THROW_NOTHING() _THROW0() +#else +#define THROW_BAD_ALLOC() throw(std::bad_alloc) +#define THROW_NOTHING() throw() +#endif + + +struct BlockHeader +{ + struct Block * next; + std::size_t size; + bool in_use; +}; + +struct Block +{ + BlockHeader hdr; + unsigned char data[1]; +}; + +#define TRACE_MSG(val) std::cout << __FUNCTION__ << "(" << val << ") [" << __FILE__ << ":" << __LINE__ << "]" << std::endl; + +static unsigned char MemBuf[ 4096 * 1024 ]; +Block * pNext = static_cast<Block *>(static_cast<void *>(MemBuf)); +volatile std::size_t MemTotal = 0; + +// cross-platform compare and swap operation +static bool CAS(void * volatile * ptr, void * expected, void * new_value) +{ +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 + return OSAtomicCompareAndSwapPtr( expected, new_value, ptr ); +#elif defined(_MSC_VER) + return expected == InterlockedCompareExchangePointer( ptr, new_value, expected ); +#elif (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ ) > 40100 + return __sync_bool_compare_and_swap( ptr, expected, new_value ); +#endif +} + +static void * GetMem(std::size_t size) +{ + // TRACE_MSG(size); + volatile Block * pBlock = NULL; + volatile Block * pNewNext = NULL; + + // do a lock-free update of the global next pointer + do + { + pBlock = pNext; + pNewNext = (volatile Block *)(pBlock->data + size); + + } while(! CAS((void * volatile *) &pNext, (void *) pBlock, (void *) pNewNext)); + + // if we get here, we safely carved out a block of memory in the + // memory pool... + + // initialize our block + pBlock->hdr.next = (Block *)(pBlock->data + size); + pBlock->hdr.size = size; + pBlock->hdr.in_use = true; + memset((void *) pBlock->data, 0, pBlock->hdr.size); + + // do a lock-free update of the global memory total + volatile size_t total = 0; + volatile size_t new_total = 0; + do + { + total = MemTotal; + new_total = total + size; + + } while (! CAS((void * volatile *) &MemTotal, (void *) total, (void *) new_total)); + + return (void *) pBlock->data; +} + + +static void FreeMem(void * p) +{ + // get the pointer to the block record + Block * pBlock = (Block *)((unsigned char *) p - sizeof(BlockHeader)); + + // TRACE_MSG(pBlock->hdr.size); + bool * cur_in_use = &(pBlock->hdr.in_use); + volatile bool in_use = false; + bool new_in_use = false; + do + { + in_use = pBlock->hdr.in_use; + } while (! CAS((void * volatile *) cur_in_use, (void *) in_use, (void *) new_in_use)); + + // do a lock-free update of the global memory total + volatile size_t total = 0; + volatile size_t new_total = 0; + do + { + total = MemTotal; + new_total = total - pBlock->hdr.size; + } while (! CAS((void * volatile *)&MemTotal, (void *) total, (void *) new_total)); +} + + +std::size_t GetMemTotal() +{ + return MemTotal; +} + + +void * operator new(std::size_t size) THROW_BAD_ALLOC() +{ + return GetMem( size ); +} + + +void * operator new[](std::size_t size) THROW_BAD_ALLOC() +{ + return GetMem( size ); +} + + +void operator delete(void * p) THROW_NOTHING() +{ + if (p) + { + FreeMem( p ); + } +} + + +void operator delete[](void * p) THROW_NOTHING() +{ + if (p) + { + FreeMem( p ); + } +} + + diff --git a/indra/llcorehttp/tests/test_allocator.h b/indra/llcorehttp/tests/test_allocator.h new file mode 100644 index 0000000000..3572bbc5c5 --- /dev/null +++ b/indra/llcorehttp/tests/test_allocator.h @@ -0,0 +1,47 @@ +/** + * @file test_allocator.h + * @brief quick and dirty allocator for tracking memory allocations + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef TEST_ALLOCATOR_H +#define TEST_ALLOCATOR_H + +#include <cstdlib> +#include <new> + +size_t GetMemTotal(); +#if defined(WIN32) +void * operator new(std::size_t size) _THROW1(std::bad_alloc); +void * operator new[](std::size_t size) _THROW1(std::bad_alloc); +void operator delete(void * p) _THROW0(); +void operator delete[](void * p) _THROW0(); +#else +void * operator new(std::size_t size) throw (std::bad_alloc); +void * operator new[](std::size_t size) throw (std::bad_alloc); +void operator delete(void * p) throw (); +void operator delete[](void * p) throw (); +#endif + +#endif // TEST_ALLOCATOR_H + diff --git a/indra/llcorehttp/tests/test_bufferarray.hpp b/indra/llcorehttp/tests/test_bufferarray.hpp new file mode 100644 index 0000000000..8a2a64d970 --- /dev/null +++ b/indra/llcorehttp/tests/test_bufferarray.hpp @@ -0,0 +1,432 @@ +/** + * @file test_bufferarray.hpp + * @brief unit tests for the LLCore::BufferArray class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCORE_BUFFER_ARRAY_H_ +#define TEST_LLCORE_BUFFER_ARRAY_H_ + +#include "bufferarray.h" + +#include <iostream> + +#include "test_allocator.h" + + +using namespace LLCore; + + + +namespace tut +{ + +struct BufferArrayTestData +{ + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; +}; + +typedef test_group<BufferArrayTestData> BufferArrayTestGroupType; +typedef BufferArrayTestGroupType::object BufferArrayTestObjectType; +BufferArrayTestGroupType BufferArrayTestGroup("BufferArray Tests"); + +template <> template <> +void BufferArrayTestObjectType::test<1>() +{ + set_test_name("BufferArray construction"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + ensure("One ref on construction of BufferArray", ba->getRefCount() == 1); + ensure("Memory being used", mMemTotal < GetMemTotal()); + ensure("Nothing in BA", 0 == ba->size()); + + // Try to read + char buffer[20]; + size_t read_len(ba->read(0, buffer, sizeof(buffer))); + ensure("Read returns empty", 0 == read_len); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void BufferArrayTestObjectType::test<2>() +{ + set_test_name("BufferArray single write"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + char buffer[256]; + + size_t len = ba->write(0, str1, strlen(str1)); + ensure("Wrote length correct", strlen(str1) == len); + ensure("Recorded size correct", strlen(str1) == ba->size()); + + // read some data back + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(2, buffer, 2); + ensure("Read length correct", 2 == len); + ensure("Read content correct", 'c' == buffer[0] && 'd' == buffer[1]); + ensure("Read didn't overwrite", 'X' == buffer[2]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + + +template <> template <> +void BufferArrayTestObjectType::test<3>() +{ + set_test_name("BufferArray multiple writes"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + size_t str1_len(strlen(str1)); + char buffer[256]; + + size_t len = ba->write(0, str1, str1_len); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", str1_len == ba->size()); + + // again... + len = ba->write(str1_len, str1, strlen(str1)); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", (2 * str1_len) == ba->size()); + + // read some data back + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(8, buffer, 4); + ensure("Read length correct", 4 == len); + ensure("Read content correct", 'i' == buffer[0] && 'j' == buffer[1]); + ensure("Read content correct", 'a' == buffer[2] && 'b' == buffer[3]); + ensure("Read didn't overwrite", 'X' == buffer[4]); + + // Read whole thing + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(0, buffer, sizeof(buffer)); + ensure("Read length correct", (2 * str1_len) == len); + ensure("Read content correct (3)", 0 == strncmp(buffer, str1, str1_len)); + ensure("Read content correct (4)", 0 == strncmp(&buffer[str1_len], str1, str1_len)); + ensure("Read didn't overwrite (5)", 'X' == buffer[2 * str1_len]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void BufferArrayTestObjectType::test<4>() +{ + set_test_name("BufferArray overwriting"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + size_t str1_len(strlen(str1)); + char str2[] = "ABCDEFGHIJ"; + char buffer[256]; + + size_t len = ba->write(0, str1, str1_len); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", str1_len == ba->size()); + + // again... + len = ba->write(str1_len, str1, strlen(str1)); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", (2 * str1_len) == ba->size()); + + // reposition and overwrite + len = ba->write(8, str2, 4); + ensure("Overwrite length correct", 4 == len); + + // Leave position and read verifying content (stale really from seek() days) + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(12, buffer, 4); + ensure("Read length correct", 4 == len); + ensure("Read content correct", 'c' == buffer[0] && 'd' == buffer[1]); + ensure("Read content correct.2", 'e' == buffer[2] && 'f' == buffer[3]); + ensure("Read didn't overwrite", 'X' == buffer[4]); + + // reposition and check + len = ba->read(6, buffer, 8); + ensure("Read length correct.2", 8 == len); + ensure("Read content correct.3", 'g' == buffer[0] && 'h' == buffer[1]); + ensure("Read content correct.4", 'A' == buffer[2] && 'B' == buffer[3]); + ensure("Read content correct.5", 'C' == buffer[4] && 'D' == buffer[5]); + ensure("Read content correct.6", 'c' == buffer[6] && 'd' == buffer[7]); + ensure("Read didn't overwrite.7", 'X' == buffer[8]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void BufferArrayTestObjectType::test<5>() +{ + set_test_name("BufferArray multiple writes - sequential reads"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + size_t str1_len(strlen(str1)); + char buffer[256]; + + size_t len = ba->write(0, str1, str1_len); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", str1_len == ba->size()); + + // again... + len = ba->write(str1_len, str1, str1_len); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", (2 * str1_len) == ba->size()); + + // read some data back + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(8, buffer, 4); + ensure("Read length correct", 4 == len); + ensure("Read content correct", 'i' == buffer[0] && 'j' == buffer[1]); + ensure("Read content correct.2", 'a' == buffer[2] && 'b' == buffer[3]); + ensure("Read didn't overwrite", 'X' == buffer[4]); + + // Read some more without repositioning + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(12, buffer, sizeof(buffer)); + ensure("Read length correct", (str1_len - 2) == len); + ensure("Read content correct.3", 0 == strncmp(buffer, str1+2, str1_len-2)); + ensure("Read didn't overwrite.2", 'X' == buffer[str1_len-1]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void BufferArrayTestObjectType::test<6>() +{ + set_test_name("BufferArray overwrite spanning blocks and appending"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + size_t str1_len(strlen(str1)); + char str2[] = "ABCDEFGHIJKLMNOPQRST"; + size_t str2_len(strlen(str2)); + char buffer[256]; + + size_t len = ba->write(0, str1, str1_len); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", str1_len == ba->size()); + + // again... + len = ba->write(str1_len, str1, strlen(str1)); + ensure("Wrote length correct", str1_len == len); + ensure("Recorded size correct", (2 * str1_len) == ba->size()); + + // reposition and overwrite + len = ba->write(8, str2, str2_len); + ensure("Overwrite length correct", str2_len == len); + + // Leave position and read verifying content + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(8 + str2_len, buffer, 0); + ensure("Read length correct", 0 == len); + ensure("Read didn't overwrite", 'X' == buffer[0]); + + // reposition and check + len = ba->read(0, buffer, sizeof(buffer)); + ensure("Read length correct.2", (str1_len + str2_len - 2) == len); + ensure("Read content correct", 0 == strncmp(buffer, str1, str1_len-2)); + ensure("Read content correct.2", 0 == strncmp(buffer+str1_len-2, str2, str2_len)); + ensure("Read didn't overwrite.2", 'X' == buffer[str1_len + str2_len - 2]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure("All memory released", mMemTotal == GetMemTotal()); +} + +template <> template <> +void BufferArrayTestObjectType::test<7>() +{ + set_test_name("BufferArray overwrite spanning blocks and sequential writes"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + size_t str1_len(strlen(str1)); + char str2[] = "ABCDEFGHIJKLMNOPQRST"; + size_t str2_len(strlen(str2)); + char buffer[256]; + + // 2x str1 + size_t len = ba->write(0, str1, str1_len); + len = ba->write(str1_len, str1, str1_len); + + // reposition and overwrite + len = ba->write(6, str2, 2); + ensure("Overwrite length correct", 2 == len); + + len = ba->write(8, str2, 2); + ensure("Overwrite length correct.2", 2 == len); + + len = ba->write(10, str2, 2); + ensure("Overwrite length correct.3", 2 == len); + + // append some data + len = ba->append(str2, str2_len); + ensure("Append length correct", str2_len == len); + + // append some more + void * out_buf(ba->appendBufferAlloc(str1_len)); + memcpy(out_buf, str1, str1_len); + + // And some final writes + len = ba->write(3 * str1_len + str2_len, str2, 2); + ensure("Write length correct.2", 2 == len); + + // Check contents + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(0, buffer, sizeof(buffer)); + ensure("Final buffer length correct", (3 * str1_len + str2_len + 2) == len); + ensure("Read content correct", 0 == strncmp(buffer, str1, 6)); + ensure("Read content correct.2", 0 == strncmp(buffer + 6, str2, 2)); + ensure("Read content correct.3", 0 == strncmp(buffer + 8, str2, 2)); + ensure("Read content correct.4", 0 == strncmp(buffer + 10, str2, 2)); + ensure("Read content correct.5", 0 == strncmp(buffer + str1_len + 2, str1 + 2, str1_len - 2)); + ensure("Read content correct.6", 0 == strncmp(buffer + str1_len + str1_len, str2, str2_len)); + ensure("Read content correct.7", 0 == strncmp(buffer + str1_len + str1_len + str2_len, str1, str1_len)); + ensure("Read content correct.8", 0 == strncmp(buffer + str1_len + str1_len + str2_len + str1_len, str2, 2)); + ensure("Read didn't overwrite", 'X' == buffer[str1_len + str1_len + str2_len + str1_len + 2]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure("All memory released", mMemTotal == GetMemTotal()); +} + +template <> template <> +void BufferArrayTestObjectType::test<8>() +{ + set_test_name("BufferArray zero-length appendBufferAlloc"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArray * ba = new BufferArray(); + + // write some data to the buffer + char str1[] = "abcdefghij"; + size_t str1_len(strlen(str1)); + char str2[] = "ABCDEFGHIJKLMNOPQRST"; + size_t str2_len(strlen(str2)); + char buffer[256]; + + // 2x str1 + size_t len = ba->write(0, str1, str1_len); + len = ba->write(str1_len, str1, str1_len); + + // zero-length allocate (we allow this with a valid pointer returned) + void * out_buf(ba->appendBufferAlloc(0)); + ensure("Buffer from zero-length appendBufferAlloc non-NULL", NULL != out_buf); + + // Do it again + void * out_buf2(ba->appendBufferAlloc(0)); + ensure("Buffer from zero-length appendBufferAlloc non-NULL.2", NULL != out_buf2); + ensure("Two zero-length appendBufferAlloc buffers distinct", out_buf != out_buf2); + + // And some final writes + len = ba->write(2 * str1_len, str2, str2_len); + + // Check contents + memset(buffer, 'X', sizeof(buffer)); + len = ba->read(0, buffer, sizeof(buffer)); + ensure("Final buffer length correct", (2 * str1_len + str2_len) == len); + ensure("Read content correct.1", 0 == strncmp(buffer, str1, str1_len)); + ensure("Read content correct.2", 0 == strncmp(buffer + str1_len, str1, str1_len)); + ensure("Read content correct.3", 0 == strncmp(buffer + str1_len + str1_len, str2, str2_len)); + ensure("Read didn't overwrite", 'X' == buffer[str1_len + str1_len + str2_len]); + + // release the implicit reference, causing the object to be released + ba->release(); + + // make sure we didn't leak any memory + ensure("All memory released", mMemTotal == GetMemTotal()); +} + +} // end namespace tut + + +#endif // TEST_LLCORE_BUFFER_ARRAY_H_ diff --git a/indra/llcorehttp/tests/test_bufferstream.hpp b/indra/llcorehttp/tests/test_bufferstream.hpp new file mode 100644 index 0000000000..831c901b9d --- /dev/null +++ b/indra/llcorehttp/tests/test_bufferstream.hpp @@ -0,0 +1,304 @@ +/** + * @file test_bufferstream.hpp + * @brief unit tests for the LLCore::BufferArrayStreamBuf/BufferArrayStream classes + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCORE_BUFFER_STREAM_H_ +#define TEST_LLCORE_BUFFER_STREAM_H_ + +#include "bufferstream.h" + +#include <iostream> + +#include "test_allocator.h" +#include "llsd.h" +#include "llsdserialize.h" + + +using namespace LLCore; + + +namespace tut +{ + +struct BufferStreamTestData +{ + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; +}; + +typedef test_group<BufferStreamTestData> BufferStreamTestGroupType; +typedef BufferStreamTestGroupType::object BufferStreamTestObjectType; +BufferStreamTestGroupType BufferStreamTestGroup("BufferStream Tests"); +typedef BufferArrayStreamBuf::traits_type tst_traits_t; + + +template <> template <> +void BufferStreamTestObjectType::test<1>() +{ + set_test_name("BufferArrayStreamBuf construction with NULL BufferArray"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(NULL); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // Not much will work with a NULL + ensure("underflow() on NULL fails", tst_traits_t::eof() == bsb->underflow()); + ensure("uflow() on NULL fails", tst_traits_t::eof() == bsb->uflow()); + ensure("pbackfail() on NULL fails", tst_traits_t::eof() == bsb->pbackfail('c')); + ensure("showmanyc() on NULL fails", bsb->showmanyc() == -1); + ensure("overflow() on NULL fails", tst_traits_t::eof() == bsb->overflow('c')); + ensure("xsputn() on NULL fails", bsb->xsputn("blah", 4) == 0); + ensure("seekoff() on NULL fails", bsb->seekoff(0, std::ios_base::beg, std::ios_base::in) == std::streampos(-1)); + + // release the implicit reference, causing the object to be released + delete bsb; + bsb = NULL; + + // make sure we didn't leak any memory + ensure("Allocated memory returned", mMemTotal == GetMemTotal()); +} + + +template <> template <> +void BufferStreamTestObjectType::test<2>() +{ + set_test_name("BufferArrayStream construction with NULL BufferArray"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + BufferArrayStream * bas = new BufferArrayStream(NULL); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // Not much will work with a NULL here + ensure("eof() is false on NULL", ! bas->eof()); + ensure("fail() is false on NULL", ! bas->fail()); + ensure("good() on NULL", bas->good()); + + // release the implicit reference, causing the object to be released + delete bas; + bas = NULL; + + // make sure we didn't leak any memory + ensure("Allocated memory returned", mMemTotal == GetMemTotal()); +} + + +template <> template <> +void BufferStreamTestObjectType::test<3>() +{ + set_test_name("BufferArrayStreamBuf construction with empty BufferArray"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted BufferArray with implicit reference + BufferArray * ba = new BufferArray; + BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(ba); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // I can release my ref on the BA + ba->release(); + ba = NULL; + + // release the implicit reference, causing the object to be released + delete bsb; + bsb = NULL; + + // make sure we didn't leak any memory + ensure("Allocated memory returned", mMemTotal == GetMemTotal()); +} + + +template <> template <> +void BufferStreamTestObjectType::test<4>() +{ + set_test_name("BufferArrayStream construction with empty BufferArray"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted BufferArray with implicit reference + BufferArray * ba = new BufferArray; + + { + // create a new ref counted object with an implicit reference + BufferArrayStream bas(ba); + ensure("Memory being used", mMemTotal < GetMemTotal()); + } + + // release the implicit reference, causing the object to be released + ba->release(); + ba = NULL; + + // make sure we didn't leak any memory + ensure("Allocated memory returned", mMemTotal == GetMemTotal()); +} + + +template <> template <> +void BufferStreamTestObjectType::test<5>() +{ + set_test_name("BufferArrayStreamBuf construction with real BufferArray"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted BufferArray with implicit reference + BufferArray * ba = new BufferArray; + const char * content("This is a string. A fragment."); + const size_t c_len(strlen(content)); + ba->append(content, c_len); + + // Creat an adapter for the BufferArray + BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(ba); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // I can release my ref on the BA + ba->release(); + ba = NULL; + + // Various static state + ensure("underflow() returns 'T'", bsb->underflow() == 'T'); + ensure("underflow() returns 'T' again", bsb->underflow() == 'T'); + ensure("uflow() returns 'T'", bsb->uflow() == 'T'); + ensure("uflow() returns 'h'", bsb->uflow() == 'h'); + ensure("pbackfail('i') fails", tst_traits_t::eof() == bsb->pbackfail('i')); + ensure("pbackfail('T') fails", tst_traits_t::eof() == bsb->pbackfail('T')); + ensure("pbackfail('h') succeeds", bsb->pbackfail('h') == 'h'); + ensure("showmanyc() is everything but the 'T'", bsb->showmanyc() == (c_len - 1)); + ensure("overflow() appends", bsb->overflow('c') == 'c'); + ensure("showmanyc() reflects append", bsb->showmanyc() == (c_len - 1 + 1)); + ensure("xsputn() appends some more", bsb->xsputn("bla!", 4) == 4); + ensure("showmanyc() reflects 2nd append", bsb->showmanyc() == (c_len - 1 + 5)); + ensure("seekoff() succeeds", bsb->seekoff(0, std::ios_base::beg, std::ios_base::in) == std::streampos(0)); + ensure("seekoff() succeeds 2", bsb->seekoff(4, std::ios_base::cur, std::ios_base::in) == std::streampos(4)); + ensure("showmanyc() picks up seekoff", bsb->showmanyc() == (c_len + 5 - 4)); + ensure("seekoff() succeeds 3", bsb->seekoff(0, std::ios_base::end, std::ios_base::in) == std::streampos(c_len + 4)); + ensure("pbackfail('!') succeeds", tst_traits_t::eof() == bsb->pbackfail('!')); + + // release the implicit reference, causing the object to be released + delete bsb; + bsb = NULL; + + // make sure we didn't leak any memory + ensure("Allocated memory returned", mMemTotal == GetMemTotal()); +} + + +template <> template <> +void BufferStreamTestObjectType::test<6>() +{ + set_test_name("BufferArrayStream construction with real BufferArray"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted BufferArray with implicit reference + BufferArray * ba = new BufferArray; + //const char * content("This is a string. A fragment."); + //const size_t c_len(strlen(content)); + //ba->append(content, strlen(content)); + + { + // Creat an adapter for the BufferArray + BufferArrayStream bas(ba); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // Basic operations + bas << "Hello" << 27 << "."; + ensure("BA length 8", ba->size() == 8); + + std::string str; + bas >> str; + ensure("reads correctly", str == "Hello27."); + } + + // release the implicit reference, causing the object to be released + ba->release(); + ba = NULL; + + // make sure we didn't leak any memory + // ensure("Allocated memory returned", mMemTotal == GetMemTotal()); + // static U64 mem = GetMemTotal(); +} + + +template <> template <> +void BufferStreamTestObjectType::test<7>() +{ + set_test_name("BufferArrayStream with LLSD serialization"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted BufferArray with implicit reference + BufferArray * ba = new BufferArray; + + { + // Creat an adapter for the BufferArray + BufferArrayStream bas(ba); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // LLSD + LLSD llsd = LLSD::emptyMap(); + + llsd["int"] = LLSD::Integer(3); + llsd["float"] = LLSD::Real(923289.28992); + llsd["string"] = LLSD::String("aksjdl;ajsdgfjgfal;sdgjakl;sdfjkl;ajsdfkl;ajsdfkl;jaskl;dfj"); + + LLSD llsd_map = LLSD::emptyMap(); + llsd_map["int"] = LLSD::Integer(-2889); + llsd_map["float"] = LLSD::Real(2.37829e32); + llsd_map["string"] = LLSD::String("OHIGODHSPDGHOSDHGOPSHDGP"); + + llsd["map"] = llsd_map; + + // Serialize it + LLSDSerialize::toXML(llsd, bas); + + std::string str; + bas >> str; + // std::cout << "SERIALIZED LLSD: " << str << std::endl; + ensure("Extracted string has reasonable length", str.size() > 60); + } + + // release the implicit reference, causing the object to be released + ba->release(); + ba = NULL; + + // make sure we didn't leak any memory + // ensure("Allocated memory returned", mMemTotal == GetMemTotal()); +} + + +} // end namespace tut + + +#endif // TEST_LLCORE_BUFFER_STREAM_H_ diff --git a/indra/llcorehttp/tests/test_httpheaders.hpp b/indra/llcorehttp/tests/test_httpheaders.hpp new file mode 100644 index 0000000000..ce0d19b058 --- /dev/null +++ b/indra/llcorehttp/tests/test_httpheaders.hpp @@ -0,0 +1,108 @@ +/** + * @file test_httpheaders.hpp + * @brief unit tests for the LLCore::HttpHeaders class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCORE_HTTP_HEADERS_H_ +#define TEST_LLCORE_HTTP_HEADERS_H_ + +#include "httpheaders.h" + +#include <iostream> + +#include "test_allocator.h" + + +using namespace LLCoreInt; + + + +namespace tut +{ + +struct HttpHeadersTestData +{ + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; +}; + +typedef test_group<HttpHeadersTestData> HttpHeadersTestGroupType; +typedef HttpHeadersTestGroupType::object HttpHeadersTestObjectType; +HttpHeadersTestGroupType HttpHeadersTestGroup("HttpHeaders Tests"); + +template <> template <> +void HttpHeadersTestObjectType::test<1>() +{ + set_test_name("HttpHeaders construction"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpHeaders * headers = new HttpHeaders(); + ensure("One ref on construction of HttpHeaders", headers->getRefCount() == 1); + ensure("Memory being used", mMemTotal < GetMemTotal()); + ensure("Nothing in headers", 0 == headers->mHeaders.size()); + + // release the implicit reference, causing the object to be released + headers->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void HttpHeadersTestObjectType::test<2>() +{ + set_test_name("HttpHeaders construction"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpHeaders * headers = new HttpHeaders(); + + { + // Append a few strings + std::string str1("Pragma:"); + headers->mHeaders.push_back(str1); + std::string str2("Accept: application/json"); + headers->mHeaders.push_back(str2); + + ensure("Headers retained", 2 == headers->mHeaders.size()); + ensure("First is first", headers->mHeaders[0] == str1); + ensure("Second is second", headers->mHeaders[1] == str2); + } + + // release the implicit reference, causing the object to be released + headers->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +} // end namespace tut + + +#endif // TEST_LLCORE_HTTP_HEADERS_H_ diff --git a/indra/llcorehttp/tests/test_httpoperation.hpp b/indra/llcorehttp/tests/test_httpoperation.hpp new file mode 100644 index 0000000000..17b1a96878 --- /dev/null +++ b/indra/llcorehttp/tests/test_httpoperation.hpp @@ -0,0 +1,125 @@ +/** + * @file test_httpoperation.hpp + * @brief unit tests for the LLCore::HttpOperation-derived classes + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCORE_HTTP_OPERATION_H_ +#define TEST_LLCORE_HTTP_OPERATION_H_ + +#include "_httpoperation.h" +#include "httphandler.h" + +#include <iostream> + +#include "test_allocator.h" + + +using namespace LLCoreInt; + + +namespace +{ + +class TestHandler : public LLCore::HttpHandler +{ +public: + virtual void onCompleted(HttpHandle, HttpResponse *) + { + std::cout << "TestHandler::onCompleted() invoked" << std::endl; + } + +}; + + +} // end namespace anonymous + + +namespace tut +{ + struct HttpOperationTestData + { + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; + }; + + typedef test_group<HttpOperationTestData> HttpOperationTestGroupType; + typedef HttpOperationTestGroupType::object HttpOperationTestObjectType; + HttpOperationTestGroupType HttpOperationTestGroup("HttpOperation Tests"); + + template <> template <> + void HttpOperationTestObjectType::test<1>() + { + set_test_name("HttpOpNull construction"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpOpNull * op = new HttpOpNull(); + ensure(op->getRefCount() == 1); + ensure(mMemTotal < GetMemTotal()); + + // release the implicit reference, causing the object to be released + op->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); + } + + template <> template <> + void HttpOperationTestObjectType::test<2>() + { + set_test_name("HttpOpNull construction with handlers"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // Get some handlers + TestHandler * h1 = new TestHandler(); + + // create a new ref counted object with an implicit reference + HttpOpNull * op = new HttpOpNull(); + + // Add the handlers + op->setReplyPath(NULL, h1); + + // Check ref count + ensure(op->getRefCount() == 1); + + // release the reference, releasing the operation but + // not the handlers. + op->release(); + op = NULL; + ensure(mMemTotal != GetMemTotal()); + + // release the handlers + delete h1; + h1 = NULL; + + ensure(mMemTotal == GetMemTotal()); + } + +} + +#endif // TEST_LLCORE_HTTP_OPERATION_H_ diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp new file mode 100644 index 0000000000..e5488cf941 --- /dev/null +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -0,0 +1,2670 @@ +/** + * @file test_httprequest.hpp + * @brief unit tests for the LLCore::HttpRequest class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCORE_HTTP_REQUEST_H_ +#define TEST_LLCORE_HTTP_REQUEST_H_ + +#include "httprequest.h" +#include "bufferarray.h" +#include "httphandler.h" +#include "httpheaders.h" +#include "httpresponse.h" +#include "httpoptions.h" +#include "_httpservice.h" +#include "_httprequestqueue.h" + +#include <curl/curl.h> +#include <boost/regex.hpp> +#include <sstream> + +#include "test_allocator.h" +#include "llcorehttp_test.h" + + +using namespace LLCoreInt; + + +namespace +{ + +#if defined(WIN32) + +void usleep(unsigned long usec); + +#endif + +} + +namespace tut +{ + +struct HttpRequestTestData +{ + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; + int mHandlerCalls; + HttpStatus mStatus; +}; + +class TestHandler2 : public LLCore::HttpHandler +{ +public: + TestHandler2(HttpRequestTestData * state, + const std::string & name) + : mState(state), + mName(name), + mExpectHandle(LLCORE_HTTP_HANDLE_INVALID) + {} + + virtual void onCompleted(HttpHandle handle, HttpResponse * response) + { + if (LLCORE_HTTP_HANDLE_INVALID != mExpectHandle) + { + ensure("Expected handle received in handler", mExpectHandle == handle); + } + ensure("Handler got a response", NULL != response); + if (response && mState) + { + const HttpStatus actual_status(response->getStatus()); + std::ostringstream test; + test << "Expected HttpStatus received in response. Wanted: " + << mState->mStatus.toHex() << " Received: " << actual_status.toHex(); + ensure(test.str().c_str(), actual_status == mState->mStatus); + } + if (mState) + { + mState->mHandlerCalls++; + } + if (! mHeadersRequired.empty() || ! mHeadersDisallowed.empty()) + { + ensure("Response required with header check", response != NULL); + HttpHeaders * header(response->getHeaders()); // Will not hold onto this + ensure("Some quantity of headers returned", header != NULL); + + if (! mHeadersRequired.empty()) + { + for (int i(0); i < mHeadersRequired.size(); ++i) + { + bool found = false; + for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); + header->mHeaders.end() != iter; + ++iter) + { + if (boost::regex_match(*iter, mHeadersRequired[i])) + { + found = true; + break; + } + } + std::ostringstream str; + str << "Required header # " << i << " found in response"; + ensure(str.str(), found); + } + } + + if (! mHeadersDisallowed.empty()) + { + for (int i(0); i < mHeadersDisallowed.size(); ++i) + { + for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); + header->mHeaders.end() != iter; + ++iter) + { + if (boost::regex_match(*iter, mHeadersDisallowed[i])) + { + std::ostringstream str; + str << "Disallowed header # " << i << " not found in response"; + ensure(str.str(), false); + } + } + } + } + } + + if (! mCheckContentType.empty()) + { + ensure("Response required with content type check", response != NULL); + std::string con_type(response->getContentType()); + ensure("Content-Type as expected (" + mCheckContentType + ")", + mCheckContentType == con_type); + } + + // std::cout << "TestHandler2::onCompleted() invoked" << std::endl; + } + + HttpRequestTestData * mState; + std::string mName; + HttpHandle mExpectHandle; + std::string mCheckContentType; + std::vector<boost::regex> mHeadersRequired; + std::vector<boost::regex> mHeadersDisallowed; +}; + +typedef test_group<HttpRequestTestData> HttpRequestTestGroupType; +typedef HttpRequestTestGroupType::object HttpRequestTestObjectType; +HttpRequestTestGroupType HttpRequestTestGroup("HttpRequest Tests"); + +template <> template <> +void HttpRequestTestObjectType::test<1>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest construction"); + + HttpRequest * req = NULL; + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + try + { + // Get singletons created + HttpRequest::createService(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // release the request object + delete req; + req = NULL; + + HttpRequest::destroyService(); + + // make sure we didn't leak any memory + ensure("Memory returned", mMemTotal == GetMemTotal()); + } + catch (...) + { + delete req; + HttpRequest::destroyService(); + throw; + } +} + +template <> template <> +void HttpRequestTestObjectType::test<2>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest and Null Op queued"); + + HttpRequest * req = NULL; + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + try + { + // Get singletons created + HttpRequest::createService(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // Issue a NoOp + HttpHandle handle = req->requestNoOp(NULL); + ensure("Request issued", handle != LLCORE_HTTP_HANDLE_INVALID); + + // release the request object + delete req; + req = NULL; + + // We're still holding onto the operation which is + // sitting, unserviced, on the request queue so... + ensure("Memory being used 2", mMemTotal < GetMemTotal()); + + // Request queue should have two references: global singleton & service object + ensure("Two references to request queue", 2 == HttpRequestQueue::instanceOf()->getRefCount()); + + // Okay, tear it down + HttpRequest::destroyService(); + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory returned", mMemTotal == GetMemTotal()); + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<3>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest NoOp + Stop execution"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a NoOp + HttpHandle handle = req->requestNoOp(&handler); + ensure("Valid handle returned for first request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(20); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 100; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + +template <> template <> +void HttpRequestTestObjectType::test<4>() +{ + ScopedCurlInit ready; + + set_test_name("2 HttpRequest instances, one thread"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + TestHandler2 handler1(this, "handler1"); + TestHandler2 handler2(this, "handler2"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req1 = NULL; + HttpRequest * req2 = NULL; + + try + { + + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req1 = new HttpRequest(); + req2 = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue some NoOps + HttpHandle handle = req1->requestNoOp(&handler1); + ensure("Valid handle returned for first request", handle != LLCORE_HTTP_HANDLE_INVALID); + handler1.mExpectHandle = handle; + + handle = req2->requestNoOp(&handler2); + ensure("Valid handle returned for first request", handle != LLCORE_HTTP_HANDLE_INVALID); + handler2.mExpectHandle = handle; + + // Run the notification pump. + int count(0); + int limit(20); + while (count++ < limit && mHandlerCalls < 2) + { + req1->update(1000000); + req2->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 2); + + // Okay, request a shutdown of the servicing thread + handle = req2->requestStopThread(&handler2); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + handler2.mExpectHandle = handle; + + // Run the notification pump again + count = 0; + limit = 100; + while (count++ < limit && mHandlerCalls < 3) + { + req1->update(1000000); + req2->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 3); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req1; + req1 = NULL; + delete req2; + req2 = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 3 == mHandlerCalls); + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); + } + catch (...) + { + stop_thread(req1); + delete req1; + delete req2; + HttpRequest::destroyService(); + throw; + } +} + +template <> template <> +void HttpRequestTestObjectType::test<5>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest Spin (soft) + NoOp + hard termination"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a Spin + HttpHandle handle = req->requestSpin(1); + ensure("Valid handle returned for spin request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Issue a NoOp + handle = req->requestNoOp(&handler); + ensure("Valid handle returned for no-op request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("NoOp notification received", mHandlerCalls == 1); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + // Check memory usage + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); + // This memory test should work but could give problems as it + // relies on the worker thread picking up a friendly request + // to shutdown. Doing so, it drops references to things and + // we should go back to where we started. If it gives you + // problems, look into the code before commenting things out. + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<6>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest Spin + NoOp + hard termination"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a Spin + HttpHandle handle = req->requestSpin(0); // Hard spin + ensure("Valid handle returned for spin request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Issue a NoOp + handle = req->requestNoOp(&handler); + ensure("Valid handle returned for no-op request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("No notifications received", mHandlerCalls == 0); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + // Check memory usage + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + // ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); + // This memory test won't work because we're killing the thread + // hard with the hard spinner. There's no opportunity to join + // nicely so many things leak or get destroyed unilaterally. + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<7>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest GET to dead port + Stop execution"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * opts = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + opts = new HttpOptions(); + opts->setRetries(1); // Don't try for too long - default retries take about 18S + + // Issue a GET that can't connect + mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + "http://127.0.0.1:2/nothing/here", + 0, + 0, + opts, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(50); // With one retry, should fail quickish + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 100; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options + opts->release(); + opts = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if 0 // defined(WIN32) + // Can't do this on any platform anymore, the LL logging system holds + // on to memory and produces what looks like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<8>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET to real service"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<9>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with Range: header to real service"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<10>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest PUT to real service"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + BufferArray * body = new BufferArray; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + static const char * body_text("Now is the time for all good men..."); + body->append(body_text, strlen(body_text)); + mStatus = HttpStatus(200); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + body, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // Lose the request body + body->release(); + body = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if 0 // defined(WIN32) + // Can't do this on any platform anymore, the LL logging system holds + // on to memory and produces what looks like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + if (body) + { + body->release(); + } + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + +template <> template <> +void HttpRequestTestObjectType::test<11>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest POST to real service"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + BufferArray * body = new BufferArray; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + static const char * body_text("Now is the time for all good men..."); + body->append(body_text, strlen(body_text)); + mStatus = HttpStatus(200); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + body, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // Lose the request body + body->release(); + body = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + if (body) + { + body->release(); + } + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + +template <> template <> +void HttpRequestTestObjectType::test<12>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with some tracing"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Enable tracing + HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if 0 // defined(WIN32) + // Can't do this on any platform anymore, the LL logging system holds + // on to memory and produces what looks like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<13>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with returned headers"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + handler.mHeadersRequired.reserve(20); // Avoid memory leak test failure + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * opts = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Enable tracing + HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + opts = new HttpOptions(); + opts->setWantHeaders(true); + + // Issue a GET that succeeds + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("\\W*X-LL-Special:.*", boost::regex::icase)); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + opts, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // release options + opts->release(); + opts = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +template <> template <> +void HttpRequestTestObjectType::test<14>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest GET timeout"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + std::string url_base(get_base_url() + "/sleep/"); // path to a 30-second sleep + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * opts = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + opts = new HttpOptions(); + opts->setRetries(0); // Don't retry + opts->setTimeout(2); + + // Issue a GET that sleeps + mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + opts, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(50); // With one retry, should fail quickish + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 100; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options + opts->release(); + opts = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + +// Test retrieval of Content-Type/Content-Encoding headers +template <> template <> +void HttpRequestTestObjectType::test<15>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with Content-Type"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // Load and clear the string setting to preload std::string object + // for memory return tests. + handler.mCheckContentType = "application/llsd+xml"; + handler.mCheckContentType.clear(); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + handler.mCheckContentType = "application/llsd+xml"; + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mCheckContentType.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on GET requests +template <> template <> +void HttpRequestTestObjectType::test<16>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest GET"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + options, + NULL, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Do a texture-style fetch + headers = new HttpHeaders; + headers->mHeaders.push_back("Accept: image/x-j2c"); + + mStatus = HttpStatus(200); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*image/x-j2c", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + 0, + 47, + options, + headers, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 2); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 3) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 3); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on POST requests +template <> template <> +void HttpRequestTestObjectType::test<17>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest POST"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // And a buffer array + const char * msg("It was the best of times, it was the worst of times."); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default POST + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + NULL, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on PUT requests +template <> template <> +void HttpRequestTestObjectType::test<18>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest PUT"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // And a buffer array + const char * msg("It was the best of times, it was the worst of times."); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default PUT + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:.*", boost::regex::icase)); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + NULL, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on GET requests with overrides +template <> template <> +void HttpRequestTestObjectType::test<19>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest GET with header overrides"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // headers + headers = new HttpHeaders; + headers->mHeaders.push_back("Keep-Alive: 120"); + headers->mHeaders.push_back("Accept-encoding: deflate"); + headers->mHeaders.push_back("Accept: text/plain"); + + // Issue a GET with modified headers + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/plain", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*deflate", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + options, + headers, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on POST requests with overrides +template <> template <> +void HttpRequestTestObjectType::test<20>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest POST with header overrides"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // headers + headers = new HttpHeaders(); + headers->mHeaders.push_back("keep-Alive: 120"); + headers->mHeaders.push_back("Accept: text/html"); + headers->mHeaders.push_back("content-type: application/llsd+xml"); + headers->mHeaders.push_back("cache-control: no-store"); + + // And a buffer array + const char * msg("<xml><llsd><string>It was the best of times, it was the worst of times.</string></llsd></xml>"); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default POST + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/html", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("\\s*X-Reflect-cache-control:\\s*no-store", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + headers, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on PUT requests with overrides +template <> template <> +void HttpRequestTestObjectType::test<21>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest PUT with header overrides"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // headers + headers = new HttpHeaders; + headers->mHeaders.push_back("content-type: text/plain"); + headers->mHeaders.push_back("content-type: text/html"); + headers->mHeaders.push_back("content-type: application/llsd+xml"); + + // And a buffer array + const char * msg("<xml><llsd><string>It was the best of times, it was the worst of times.</string></llsd></xml>"); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default PUT + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/plain", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/html", boost::regex::icase)); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + headers, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +} // end namespace tut + +namespace +{ + +#if defined(WIN32) + +void usleep(unsigned long usec) +{ + Sleep((DWORD) (usec / 1000UL)); +} + +#endif + +} + +#endif // TEST_LLCORE_HTTP_REQUEST_H_ diff --git a/indra/llcorehttp/tests/test_httprequestqueue.hpp b/indra/llcorehttp/tests/test_httprequestqueue.hpp new file mode 100644 index 0000000000..1de2d8f9ab --- /dev/null +++ b/indra/llcorehttp/tests/test_httprequestqueue.hpp @@ -0,0 +1,186 @@ +/** + * @file test_httprequestqueue.hpp + * @brief unit tests for the LLCore::HttpRequestQueue class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCORE_HTTP_REQUESTQUEUE_H_ +#define TEST_LLCORE_HTTP_REQUESTQUEUE_H_ + +#include "_httprequestqueue.h" + +#include <iostream> + +#include "test_allocator.h" +#include "_httpoperation.h" + + +using namespace LLCoreInt; + + + +namespace tut +{ + +struct HttpRequestqueueTestData +{ + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; +}; + +typedef test_group<HttpRequestqueueTestData> HttpRequestqueueTestGroupType; +typedef HttpRequestqueueTestGroupType::object HttpRequestqueueTestObjectType; +HttpRequestqueueTestGroupType HttpRequestqueueTestGroup("HttpRequestqueue Tests"); + +template <> template <> +void HttpRequestqueueTestObjectType::test<1>() +{ + set_test_name("HttpRequestQueue construction"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpRequestQueue::init(); + + ensure("One ref on construction of HttpRequestQueue", HttpRequestQueue::instanceOf()->getRefCount() == 1); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // release the implicit reference, causing the object to be released + HttpRequestQueue::term(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void HttpRequestqueueTestObjectType::test<2>() +{ + set_test_name("HttpRequestQueue refcount works"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpRequestQueue::init(); + + HttpRequestQueue * rq = HttpRequestQueue::instanceOf(); + rq->addRef(); + + // release the singleton, hold on to the object + HttpRequestQueue::term(); + + ensure("One ref after term() called", rq->getRefCount() == 1); + ensure("Memory being used", mMemTotal < GetMemTotal()); + + // Drop ref + rq->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void HttpRequestqueueTestObjectType::test<3>() +{ + set_test_name("HttpRequestQueue addOp/fetchOp work"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpRequestQueue::init(); + + HttpRequestQueue * rq = HttpRequestQueue::instanceOf(); + + HttpOperation * op = new HttpOpNull(); + + rq->addOp(op); // transfer my refcount + + op = rq->fetchOp(true); // Potentially hangs the test on failure + ensure("One goes in, one comes out", NULL != op); + op->release(); + + op = rq->fetchOp(false); + ensure("Better not be two of them", NULL == op); + + // release the singleton, hold on to the object + HttpRequestQueue::term(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void HttpRequestqueueTestObjectType::test<4>() +{ + set_test_name("HttpRequestQueue addOp/fetchAll work"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpRequestQueue::init(); + + HttpRequestQueue * rq = HttpRequestQueue::instanceOf(); + + HttpOperation * op = new HttpOpNull(); + rq->addOp(op); // transfer my refcount + + op = new HttpOpNull(); + rq->addOp(op); // transfer my refcount + + op = new HttpOpNull(); + rq->addOp(op); // transfer my refcount + + { + HttpRequestQueue::OpContainer ops; + rq->fetchAll(true, ops); // Potentially hangs the test on failure + ensure("Three go in, three come out", 3 == ops.size()); + + op = rq->fetchOp(false); + ensure("Better not be any more of them", NULL == op); + + // release the singleton, hold on to the object + HttpRequestQueue::term(); + + // We're still holding onto the ops. + ensure(mMemTotal < GetMemTotal()); + + // Release them + while (! ops.empty()) + { + HttpOperation * op = ops.front(); + ops.erase(ops.begin()); + op->release(); + } + } + + // Should be clean + ensure("All memory returned", mMemTotal == GetMemTotal()); +} + +} // end namespace tut + + +#endif // TEST_LLCORE_HTTP_REQUESTQUEUE_H_ diff --git a/indra/llcorehttp/tests/test_httpstatus.hpp b/indra/llcorehttp/tests/test_httpstatus.hpp new file mode 100644 index 0000000000..f7b542d3b5 --- /dev/null +++ b/indra/llcorehttp/tests/test_httpstatus.hpp @@ -0,0 +1,265 @@ +/** + * @file test_llrefcounted + * @brief unit tests for HttpStatus struct + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef TEST_HTTP_STATUS_H_ +#define TEST_HTTP_STATUS_H_ + +#include "httpcommon.h" + +#include <curl/curl.h> +#include <curl/multi.h> + +using namespace LLCore; + +namespace tut +{ + +struct HttpStatusTestData +{ + HttpStatusTestData() + {} +}; + +typedef test_group<HttpStatusTestData> HttpStatusTestGroupType; +typedef HttpStatusTestGroupType::object HttpStatusTestObjectType; + +HttpStatusTestGroupType HttpStatusTestGroup("HttpStatus Tests"); + +template <> template <> +void HttpStatusTestObjectType::test<1>() +{ + set_test_name("HttpStatus construction"); + + // auto allocation fine for this + HttpStatus status; + status.mType = HttpStatus::EXT_CURL_EASY; + status.mStatus = 0; + + ensure(bool(status)); + ensure(false == !(status)); + + status.mType = HttpStatus::EXT_CURL_MULTI; + status.mStatus = 0; + + ensure(bool(status)); + ensure(false == !(status)); + + status.mType = HttpStatus::LLCORE; + status.mStatus = HE_SUCCESS; + + ensure(bool(status)); + ensure(false == !(status)); + + status.mType = HttpStatus::EXT_CURL_MULTI; + status.mStatus = -1; + + ensure(false == bool(status)); + ensure(!(status)); + + status.mType = HttpStatus::EXT_CURL_EASY; + status.mStatus = CURLE_BAD_DOWNLOAD_RESUME; + + ensure(false == bool(status)); + ensure(!(status)); +} + + +template <> template <> +void HttpStatusTestObjectType::test<2>() +{ + set_test_name("HttpStatus memory structure"); + + // Require that an HttpStatus object can be trivially + // returned as a function return value in registers. + // One should fit in an int on all platforms. + + ensure(sizeof(HttpStatus) <= sizeof(int)); +} + + +template <> template <> +void HttpStatusTestObjectType::test<3>() +{ + set_test_name("HttpStatus valid error string conversion"); + + HttpStatus status; + status.mType = HttpStatus::EXT_CURL_EASY; + status.mStatus = 0; + std::string msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(msg.empty()); + + status.mType = HttpStatus::EXT_CURL_EASY; + status.mStatus = CURLE_BAD_FUNCTION_ARGUMENT; + msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(! msg.empty()); + + status.mType = HttpStatus::EXT_CURL_MULTI; + status.mStatus = CURLM_OUT_OF_MEMORY; + msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(! msg.empty()); + + status.mType = HttpStatus::LLCORE; + status.mStatus = HE_SHUTTING_DOWN; + msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(! msg.empty()); +} + + +template <> template <> +void HttpStatusTestObjectType::test<4>() +{ + set_test_name("HttpStatus invalid error string conversion"); + + HttpStatus status; + status.mType = HttpStatus::EXT_CURL_EASY; + status.mStatus = 32726; + std::string msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(! msg.empty()); + + status.mType = HttpStatus::EXT_CURL_MULTI; + status.mStatus = -470; + msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(! msg.empty()); + + status.mType = HttpStatus::LLCORE; + status.mStatus = 923; + msg = status.toString(); + // std::cout << "Result: " << msg << std::endl; + ensure(! msg.empty()); +} + +template <> template <> +void HttpStatusTestObjectType::test<5>() +{ + set_test_name("HttpStatus equality/inequality testing"); + + // Make certain equality/inequality tests do not pass + // through the bool conversion. Distinct successful + // and error statuses should compare unequal. + + HttpStatus status1(HttpStatus::LLCORE, HE_SUCCESS); + HttpStatus status2(HttpStatus::EXT_CURL_EASY, HE_SUCCESS); + ensure(status1 != status2); + + status1.mType = HttpStatus::LLCORE; + status1.mStatus = HE_REPLY_ERROR; + status2.mType = HttpStatus::LLCORE; + status2.mStatus= HE_SHUTTING_DOWN; + ensure(status1 != status2); +} + +template <> template <> +void HttpStatusTestObjectType::test<6>() +{ + set_test_name("HttpStatus basic HTTP status encoding"); + + HttpStatus status; + status.mType = 200; + status.mStatus = HE_SUCCESS; + std::string msg = status.toString(); + ensure(msg.empty()); + ensure(bool(status)); + + // Normally a success but application says error + status.mStatus = HE_REPLY_ERROR; + msg = status.toString(); + ensure(! msg.empty()); + ensure(! bool(status)); + ensure(status.toULong() > 1UL); // Biggish number, not a bool-to-ulong + + // Same statuses with distinct success/fail are distinct + status.mType = 200; + status.mStatus = HE_SUCCESS; + HttpStatus status2(200, HE_REPLY_ERROR); + ensure(status != status2); + + // Normally an error but application says okay + status.mType = 406; + status.mStatus = HE_SUCCESS; + msg = status.toString(); + ensure(msg.empty()); + ensure(bool(status)); + + // Different statuses but both successful are distinct + status.mType = 200; + status.mStatus = HE_SUCCESS; + status2.mType = 201; + status2.mStatus = HE_SUCCESS; + ensure(status != status2); + + // Different statuses but both failed are distinct + status.mType = 200; + status.mStatus = HE_REPLY_ERROR; + status2.mType = 201; + status2.mStatus = HE_REPLY_ERROR; + ensure(status != status2); +} + +template <> template <> +void HttpStatusTestObjectType::test<7>() +{ + set_test_name("HttpStatus HTTP error text strings"); + + HttpStatus status(100, HE_REPLY_ERROR); + std::string msg(status.toString()); + ensure(! msg.empty()); // Should be something + ensure(msg == "Continue"); + + status.mStatus = HE_SUCCESS; + msg = status.toString(); + ensure(msg.empty()); // Success is empty + + status.mType = 199; + status.mStatus = HE_REPLY_ERROR; + msg = status.toString(); + ensure(msg == "Unknown error"); + + status.mType = 505; // Last defined string + status.mStatus = HE_REPLY_ERROR; + msg = status.toString(); + ensure(msg == "HTTP Version not supported"); + + status.mType = 506; // One beyond + status.mStatus = HE_REPLY_ERROR; + msg = status.toString(); + ensure(msg == "Unknown error"); + + status.mType = 999; // Last HTTP status + status.mStatus = HE_REPLY_ERROR; + msg = status.toString(); + ensure(msg == "Unknown error"); +} + +} // end namespace tut + +#endif // TEST_HTTP_STATUS_H + diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py new file mode 100644 index 0000000000..75a3c39ef2 --- /dev/null +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +"""\ +@file test_llsdmessage_peer.py +@author Nat Goodspeed +@date 2008-10-09 +@brief This script asynchronously runs the executable (with args) specified on + the command line, returning its result code. While that executable is + running, we provide dummy local services for use by C++ tests. + +$LicenseInfo:firstyear=2008&license=viewerlgpl$ +Second Life Viewer Source Code +Copyright (C) 2012, Linden Research, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +version 2.1 of the License only. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +$/LicenseInfo$ +""" + +import os +import sys +import time +import select +import getopt +from threading import Thread +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from SocketServer import ThreadingMixIn + +mydir = os.path.dirname(__file__) # expected to be .../indra/llcorehttp/tests/ +sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python")) +from indra.util.fastest_elementtree import parse as xml_parse +from indra.base import llsd +from testrunner import freeport, run, debug, VERBOSE + +class TestHTTPRequestHandler(BaseHTTPRequestHandler): + """This subclass of BaseHTTPRequestHandler is to receive and echo + LLSD-flavored messages sent by the C++ LLHTTPClient. + """ + def read(self): + # The following logic is adapted from the library module + # SimpleXMLRPCServer.py. + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + try: + size_remaining = int(self.headers["content-length"]) + except (KeyError, ValueError): + return "" + max_chunk_size = 10*1024*1024 + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + chunk = self.rfile.read(chunk_size) + L.append(chunk) + size_remaining -= len(chunk) + return ''.join(L) + # end of swiped read() logic + + def read_xml(self): + # This approach reads the entire POST data into memory first + return llsd.parse(self.read()) +## # This approach attempts to stream in the LLSD XML from self.rfile, +## # assuming that the underlying XML parser reads its input file +## # incrementally. Unfortunately I haven't been able to make it work. +## tree = xml_parse(self.rfile) +## debug("Finished raw parse") +## debug("parsed XML tree %s", tree) +## debug("parsed root node %s", tree.getroot()) +## debug("root node tag %s", tree.getroot().tag) +## return llsd.to_python(tree.getroot()) + + def do_HEAD(self): + self.do_GET(withdata=False) + + def do_GET(self, withdata=True): + # Of course, don't attempt to read data. + self.answer(dict(reply="success", status=200, + reason="Your GET operation worked")) + + def do_POST(self): + # Read the provided POST data. + # self.answer(self.read()) + self.answer(dict(reply="success", status=200, + reason=self.read())) + + def do_PUT(self): + # Read the provided PUT data. + # self.answer(self.read()) + self.answer(dict(reply="success", status=200, + reason=self.read())) + + def answer(self, data, withdata=True): + debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path) + if "/sleep/" in self.path: + time.sleep(30) + + if "fail" not in self.path: + data = data.copy() # we're going to modify + # Ensure there's a "reply" key in data, even if there wasn't before + data["reply"] = data.get("reply", llsd.LLSD("success")) + response = llsd.format_xml(data) + debug("success: %s", response) + self.send_response(200) + if "/reflect/" in self.path: + self.reflect_headers() + self.send_header("Content-type", "application/llsd+xml") + self.send_header("Content-Length", str(len(response))) + self.send_header("X-LL-Special", "Mememememe"); + self.end_headers() + if withdata: + self.wfile.write(response) + else: # fail requested + status = data.get("status", 500) + # self.responses maps an int status to a (short, long) pair of + # strings. We want the longer string. That's why we pass a string + # pair to get(): the [1] will select the second string, whether it + # came from self.responses or from our default pair. + reason = data.get("reason", + self.responses.get(status, + ("fail requested", + "Your request specified failure status %s " + "without providing a reason" % status))[1]) + debug("fail requested: %s: %r", status, reason) + self.send_error(status, reason) + if "/reflect/" in self.path: + self.reflect_headers() + self.end_headers() + + def reflect_headers(self): + for name in self.headers.keys(): + # print "Header: %s: %s" % (name, self.headers[name]) + self.send_header("X-Reflect-" + name, self.headers[name]) + + if not VERBOSE: + # When VERBOSE is set, skip both these overrides because they exist to + # suppress output. + + def log_request(self, code, size=None): + # For present purposes, we don't want the request splattered onto + # stderr, as it would upset devs watching the test run + pass + + def log_error(self, format, *args): + # Suppress error output as well + pass + +class Server(ThreadingMixIn, HTTPServer): + # This pernicious flag is on by default in HTTPServer. But proper + # operation of freeport() absolutely depends on it being off. + allow_reuse_address = False + +if __name__ == "__main__": + do_valgrind = False + path_search = False + options, args = getopt.getopt(sys.argv[1:], "V", ["valgrind"]) + for option, value in options: + if option == "-V" or option == "--valgrind": + do_valgrind = True + + # Instantiate a Server(TestHTTPRequestHandler) on the first free port + # in the specified port range. Doing this inline is better than in a + # daemon thread: if it blows up here, we'll get a traceback. If it blew up + # in some other thread, the traceback would get eaten and we'd run the + # subject test program anyway. + httpd, port = freeport(xrange(8000, 8020), + lambda port: Server(('127.0.0.1', port), TestHTTPRequestHandler)) + + # Pass the selected port number to the subject test program via the + # environment. We don't want to impose requirements on the test program's + # command-line parsing -- and anyway, for C++ integration tests, that's + # performed in TUT code rather than our own. + os.environ["LL_TEST_PORT"] = str(port) + debug("$LL_TEST_PORT = %s", port) + if do_valgrind: + args = ["valgrind", "--log-file=./valgrind.log"] + args + path_search = True + sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), use_path=path_search, *args)) diff --git a/indra/llcorehttp/tests/test_refcounted.hpp b/indra/llcorehttp/tests/test_refcounted.hpp new file mode 100644 index 0000000000..cb4b50287a --- /dev/null +++ b/indra/llcorehttp/tests/test_refcounted.hpp @@ -0,0 +1,156 @@ +/** + * @file test_refcounted.hpp + * @brief unit tests for the LLCoreInt::RefCounted class + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef TEST_LLCOREINT_REF_COUNTED_H_ +#define TEST_LLCOREINT_REF_COUNTED_H_ + +#include "_refcounted.h" + +#include "test_allocator.h" + +using namespace LLCoreInt; + +namespace tut +{ + struct RefCountedTestData + { + // the test objects inherit from this so the member functions and variables + // can be referenced directly inside of the test functions. + size_t mMemTotal; + }; + + typedef test_group<RefCountedTestData> RefCountedTestGroupType; + typedef RefCountedTestGroupType::object RefCountedTestObjectType; + RefCountedTestGroupType RefCountedTestGroup("RefCounted Tests"); + + template <> template <> + void RefCountedTestObjectType::test<1>() + { + set_test_name("RefCounted construction with implicit count"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + RefCounted * rc = new RefCounted(true); + ensure(rc->getRefCount() == 1); + + // release the implicit reference, causing the object to be released + rc->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); + } + + template <> template <> + void RefCountedTestObjectType::test<2>() + { + set_test_name("RefCounted construction without implicit count"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + RefCounted * rc = new RefCounted(false); + ensure(rc->getRefCount() == 0); + + // add a reference + rc->addRef(); + ensure(rc->getRefCount() == 1); + + // release the implicit reference, causing the object to be released + rc->release(); + + ensure(mMemTotal == GetMemTotal()); + } + + template <> template <> + void RefCountedTestObjectType::test<3>() + { + set_test_name("RefCounted addRef and release"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + RefCounted * rc = new RefCounted(false); + + for (int i = 0; i < 1024; ++i) + { + rc->addRef(); + } + + ensure(rc->getRefCount() == 1024); + + for (int i = 0; i < 1024; ++i) + { + rc->release(); + } + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); + } + + template <> template <> + void RefCountedTestObjectType::test<4>() + { + set_test_name("RefCounted isLastRef check"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + RefCounted * rc = new RefCounted(true); + + // with only one reference, isLastRef should be true + ensure(rc->isLastRef()); + + // release it to clean up memory + rc->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); + } + + template <> template <> + void RefCountedTestObjectType::test<5>() + { + set_test_name("RefCounted noRef check"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + RefCounted * rc = new RefCounted(false); + + // set the noRef + rc->noRef(); + + // with only one reference, isLastRef should be true + ensure(rc->getRefCount() == RefCounted::NOT_REF_COUNTED); + + // allow this memory leak, but check that we're leaking a known amount + ensure(mMemTotal == (GetMemTotal() - sizeof(RefCounted))); + } +} + +#endif // TEST_LLCOREINT_REF_COUNTED_H_ diff --git a/indra/llcorehttp/tests/testrunner.py b/indra/llcorehttp/tests/testrunner.py new file mode 100644 index 0000000000..9a2de71142 --- /dev/null +++ b/indra/llcorehttp/tests/testrunner.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +"""\ +@file testrunner.py +@author Nat Goodspeed +@date 2009-03-20 +@brief Utilities for writing wrapper scripts for ADD_COMM_BUILD_TEST unit tests + +$LicenseInfo:firstyear=2009&license=viewerlgpl$ +Second Life Viewer Source Code +Copyright (C) 2010, Linden Research, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +version 2.1 of the License only. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +$/LicenseInfo$ +""" + +from __future__ import with_statement + +import os +import sys +import re +import errno +import socket + +VERBOSE = os.environ.get("INTEGRATION_TEST_VERBOSE", "0") # default to quiet +# Support usage such as INTEGRATION_TEST_VERBOSE=off -- distressing to user if +# that construct actually turns on verbosity... +VERBOSE = not re.match(r"(0|off|false|quiet)$", VERBOSE, re.IGNORECASE) + +if VERBOSE: + def debug(fmt, *args): + print fmt % args + sys.stdout.flush() +else: + debug = lambda *args: None + +def freeport(portlist, expr): + """ + Find a free server port to use. Specifically, evaluate 'expr' (a + callable(port)) until it stops raising EADDRINUSE exception. + + Pass: + + portlist: an iterable (e.g. xrange()) of ports to try. If you exhaust the + range, freeport() lets the socket.error exception propagate. If you want + unbounded, you could pass itertools.count(baseport), though of course in + practice the ceiling is 2^16-1 anyway. But it seems prudent to constrain + the range much more sharply: if we're iterating an absurd number of times, + probably something else is wrong. + + expr: a callable accepting a port number, specifically one of the items + from portlist. If calling that callable raises socket.error with + EADDRINUSE, freeport() retrieves the next item from portlist and retries. + + Returns: (expr(port), port) + + port: the value from portlist for which expr(port) succeeded + + Raises: + + Any exception raised by expr(port) other than EADDRINUSE. + + socket.error if, for every item from portlist, expr(port) raises + socket.error. The exception you see is the one from the last item in + portlist. + + StopIteration if portlist is completely empty. + + Example: + + class Server(HTTPServer): + # If you use BaseHTTPServer.HTTPServer, turning off this flag is + # essential for proper operation of freeport()! + allow_reuse_address = False + # ... + server, port = freeport(xrange(8000, 8010), + lambda port: Server(("localhost", port), + MyRequestHandler)) + # pass 'port' to client code + # call server.serve_forever() + """ + try: + # If portlist is completely empty, let StopIteration propagate: that's an + # error because we can't return meaningful values. We have no 'port', + # therefore no 'expr(port)'. + portiter = iter(portlist) + port = portiter.next() + + while True: + try: + # If this value of port works, return as promised. + value = expr(port) + + except socket.error, err: + # Anything other than 'Address already in use', propagate + if err.args[0] != errno.EADDRINUSE: + raise + + # Here we want the next port from portiter. But on StopIteration, + # we want to raise the original exception rather than + # StopIteration. So save the original exc_info(). + type, value, tb = sys.exc_info() + try: + try: + port = portiter.next() + except StopIteration: + raise type, value, tb + finally: + # Clean up local traceback, see docs for sys.exc_info() + del tb + + else: + debug("freeport() returning %s on port %s", value, port) + return value, port + + # Recap of the control flow above: + # If expr(port) doesn't raise, return as promised. + # If expr(port) raises anything but EADDRINUSE, propagate that + # exception. + # If portiter.next() raises StopIteration -- that is, if the port + # value we just passed to expr(port) was the last available -- reraise + # the EADDRINUSE exception. + # If we've actually arrived at this point, portiter.next() delivered a + # new port value. Loop back to pass that to expr(port). + + except Exception, err: + debug("*** freeport() raising %s: %s", err.__class__.__name__, err) + raise + +def run(*args, **kwds): + """All positional arguments collectively form a command line, executed as + a synchronous child process. + In addition, pass server=new_thread_instance as an explicit keyword (to + differentiate it from an additional command-line argument). + new_thread_instance should be an instantiated but not yet started Thread + subclass instance, e.g.: + run("python", "-c", 'print "Hello, world!"', server=TestHTTPServer(name="httpd")) + """ + # If there's no server= keyword arg, don't start a server thread: simply + # run a child process. + try: + thread = kwds.pop("server") + except KeyError: + pass + else: + # Start server thread. Note that this and all other comm server + # threads should be daemon threads: we'll let them run "forever," + # confident that the whole process will terminate when the main thread + # terminates, which will be when the child process terminates. + thread.setDaemon(True) + thread.start() + # choice of os.spawnv(): + # - [v vs. l] pass a list of args vs. individual arguments, + # - [no p] don't use the PATH because we specifically want to invoke the + # executable passed as our first arg, + # - [no e] child should inherit this process's environment. + debug("Running %s...", " ".join(args)) + if kwds.get("use_path", False): + rc = os.spawnvp(os.P_WAIT, args[0], args) + else: + rc = os.spawnv(os.P_WAIT, args[0], args) + debug("%s returned %s", args[0], rc) + return rc + +# **************************************************************************** +# test code -- manual at this point, see SWAT-564 +# **************************************************************************** +def test_freeport(): + # ------------------------------- Helpers -------------------------------- + from contextlib import contextmanager + # helper Context Manager for expecting an exception + # with exc(SomeError): + # raise SomeError() + # raises AssertionError otherwise. + @contextmanager + def exc(exception_class, *args): + try: + yield + except exception_class, err: + for i, expected_arg in enumerate(args): + assert expected_arg == err.args[i], \ + "Raised %s, but args[%s] is %r instead of %r" % \ + (err.__class__.__name__, i, err.args[i], expected_arg) + print "Caught expected exception %s(%s)" % \ + (err.__class__.__name__, ', '.join(repr(arg) for arg in err.args)) + else: + assert False, "Failed to raise " + exception_class.__class__.__name__ + + # helper to raise specified exception + def raiser(exception): + raise exception + + # the usual + def assert_equals(a, b): + assert a == b, "%r != %r" % (a, b) + + # ------------------------ Sanity check the above ------------------------ + class SomeError(Exception): pass + # Without extra args, accept any err.args value + with exc(SomeError): + raiser(SomeError("abc")) + # With extra args, accept only the specified value + with exc(SomeError, "abc"): + raiser(SomeError("abc")) + with exc(AssertionError): + with exc(SomeError, "abc"): + raiser(SomeError("def")) + with exc(AssertionError): + with exc(socket.error, errno.EADDRINUSE): + raiser(socket.error(errno.ECONNREFUSED, 'Connection refused')) + + # ----------- freeport() without engaging socket functionality ----------- + # If portlist is empty, freeport() raises StopIteration. + with exc(StopIteration): + freeport([], None) + + assert_equals(freeport([17], str), ("17", 17)) + + # This is the magic exception that should prompt us to retry + inuse = socket.error(errno.EADDRINUSE, 'Address already in use') + # Get the iterator to our ports list so we can check later if we've used all + ports = iter(xrange(5)) + with exc(socket.error, errno.EADDRINUSE): + freeport(ports, lambda port: raiser(inuse)) + # did we entirely exhaust 'ports'? + with exc(StopIteration): + ports.next() + + ports = iter(xrange(2)) + # Any exception but EADDRINUSE should quit immediately + with exc(SomeError): + freeport(ports, lambda port: raiser(SomeError())) + assert_equals(ports.next(), 1) + + # ----------- freeport() with platform-dependent socket stuff ------------ + # This is what we should've had unit tests to begin with (see CHOP-661). + def newbind(port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(('127.0.0.1', port)) + return sock + + bound0, port0 = freeport(xrange(7777, 7780), newbind) + assert_equals(port0, 7777) + bound1, port1 = freeport(xrange(7777, 7780), newbind) + assert_equals(port1, 7778) + bound2, port2 = freeport(xrange(7777, 7780), newbind) + assert_equals(port2, 7779) + with exc(socket.error, errno.EADDRINUSE): + bound3, port3 = freeport(xrange(7777, 7780), newbind) + +if __name__ == "__main__": + test_freeport() diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index 0d01dd0e3e..8ffa8e4271 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -294,6 +294,8 @@ LLCurl::Easy* LLCurl::Easy::getEasy() // multi handles cache if they are added to one. CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); check_curl_code(result); + result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + check_curl_code(result); ++gCurlEasyCount; return easy; @@ -482,7 +484,8 @@ void LLCurl::Easy::prepRequest(const std::string& url, //setopt(CURLOPT_VERBOSE, 1); // useful for debugging setopt(CURLOPT_NOSIGNAL, 1); - + setopt(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + // Set the CURL options for either Socks or HTTP proxy LLProxy::getInstance()->applyProxySettings(this); diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp index 612d765969..d6ed08055e 100644 --- a/indra/llmessage/llhttpassetstorage.cpp +++ b/indra/llmessage/llhttpassetstorage.cpp @@ -749,7 +749,7 @@ LLAssetRequest* LLHTTPAssetStorage::findNextRequest(LLAssetStorage::request_list request_list_t::iterator pending_iter = pending.begin(); request_list_t::iterator pending_end = pending.end(); // Loop over all pending requests until we miss finding it in the running list. - for (; pending_iter != pending.end(); ++pending_iter) + for (; pending_iter != pending_end; ++pending_iter) { LLAssetRequest* req = *pending_iter; // Look for this pending request in the running list. diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index 964495a3ab..d70e764769 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -424,6 +424,10 @@ const U32 FENCE_WAIT_TIME_NANOSECONDS = 1000; //1 ms class LLGLFence { public: + virtual ~LLGLFence() + { + } + virtual void placeFence() = 0; virtual bool isCompleted() = 0; virtual void wait() = 0; diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp index cc5c232380..846311a8d0 100644 --- a/indra/llrender/llrendertarget.cpp +++ b/indra/llrender/llrendertarget.cpp @@ -163,10 +163,19 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt) } U32 offset = mTex.size(); - if (offset >= 4 || - (offset > 0 && (mFBO == 0 || !gGLManager.mHasDrawBuffers))) + + if( offset >= 4 ) { - llerrs << "Too many color attachments!" << llendl; + llwarns << "Too many color attachments" << llendl; + llassert( offset < 4 ); + return false; + } + if( offset > 0 && (mFBO == 0 || !gGLManager.mHasDrawBuffers) ) + { + llwarns << "FBO not used or no drawbuffers available; mFBO=" << (U32)mFBO << " gGLManager.mHasDrawBuffers=" << (U32)gGLManager.mHasDrawBuffers << llendl; + llassert( mFBO != 0 ); + llassert( gGLManager.mHasDrawBuffers ); + return false; } U32 tex; diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index eadef93c89..2fe0aa0b72 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -555,8 +555,21 @@ void LLVertexBuffer::drawArrays(U32 mode, const std::vector<LLVector3>& pos, con gGL.syncMatrices(); U32 count = pos.size(); - llassert_always(norm.size() >= pos.size()); - llassert_always(count > 0); + + llassert(norm.size() >= pos.size()); + llassert(count > 0); + + if( count == 0 ) + { + llwarns << "Called drawArrays with 0 vertices" << llendl; + return; + } + + if( norm.size() < pos.size() ) + { + llwarns << "Called drawArrays with #" << norm.size() << " normals and #" << pos.size() << " vertices" << llendl; + return; + } unbind(); diff --git a/indra/llui/llcontainerview.cpp b/indra/llui/llcontainerview.cpp index e08ccb0b78..e08ccb0b78 100755..100644 --- a/indra/llui/llcontainerview.cpp +++ b/indra/llui/llcontainerview.cpp diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index eef697d4ac..4742f6166a 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -18,6 +18,7 @@ include(JsonCpp) include(LLAudio) include(LLCharacter) include(LLCommon) +include(LLCoreHttp) include(LLImage) include(LLImageJ2COJ) include(LLInventory) @@ -57,6 +58,7 @@ include_directories( ${LLAUDIO_INCLUDE_DIRS} ${LLCHARACTER_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} + ${LLCOREHTTP_INCLUDE_DIRS} ${LLPHYSICS_INCLUDE_DIRS} ${FMOD_INCLUDE_DIR} ${LLIMAGE_INCLUDE_DIRS} @@ -97,6 +99,7 @@ set(viewer_SOURCE_FILES llagentwearables.cpp llagentwearablesfetch.cpp llanimstatelabels.cpp + llappcorehttp.cpp llappearancemgr.cpp llappviewer.cpp llappviewerlistener.cpp @@ -673,6 +676,7 @@ set(viewer_HEADER_FILES llagentwearables.h llagentwearablesfetch.h llanimstatelabels.h + llappcorehttp.h llappearance.h llappearancemgr.h llappviewer.h @@ -1814,6 +1818,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLXML_LIBRARIES} ${LSCRIPT_LIBRARIES} ${LLMATH_LIBRARIES} + ${LLCOREHTTP_LIBRARIES} ${LLCOMMON_LIBRARIES} ${NDOF_LIBRARY} ${HUNSPELL_LIBRARY} diff --git a/indra/newview/app_settings/logcontrol.xml b/indra/newview/app_settings/logcontrol.xml index 64122bbb6c..64122bbb6c 100755..100644 --- a/indra/newview/app_settings/logcontrol.xml +++ b/indra/newview/app_settings/logcontrol.xml diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 5e42fc29f7..190dd5e2d5 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10789,6 +10789,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>TextureFetchConcurrency</key> + <map> + <key>Comment</key> + <string>Maximum number of HTTP connections used for texture fetches</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>U32</string> + <key>Value</key> + <integer>0</integer> + </map> <key>TextureFetchDebuggerEnabled</key> <map> <key>Comment</key> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 447836910d..11fa50b51a 100755 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -4045,6 +4045,12 @@ void LLAgent::teleportViaLocation(const LLVector3d& pos_global) void LLAgent::doTeleportViaLocation(const LLVector3d& pos_global) { LLViewerRegion* regionp = getRegion(); + + if (!regionp) + { + return; + } + U64 handle = to_region_handle(pos_global); LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); if(regionp && info) diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index e441f21f90..e441f21f90 100755..100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index 5932be21c6..5932be21c6 100755..100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp new file mode 100644 index 0000000000..0d7d41304d --- /dev/null +++ b/indra/newview/llappcorehttp.cpp @@ -0,0 +1,192 @@ +/** + * @file llappcorehttp.cpp + * @brief + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llappcorehttp.h" + +#include "llviewercontrol.h" + + +const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0); + +LLAppCoreHttp::LLAppCoreHttp() + : mRequest(NULL), + mStopHandle(LLCORE_HTTP_HANDLE_INVALID), + mStopRequested(0.0), + mStopped(false), + mPolicyDefault(-1) +{} + + +LLAppCoreHttp::~LLAppCoreHttp() +{ + delete mRequest; + mRequest = NULL; +} + + +void LLAppCoreHttp::init() +{ + LLCore::HttpStatus status = LLCore::HttpRequest::createService(); + if (! status) + { + LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " + << status.toString() + << LL_ENDL; + } + + // Point to our certs or SSH/https: will fail on connect + status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, + gDirUtilp->getCAFile()); + if (! status) + { + LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " + << status.toString() + << LL_ENDL; + } + + // Establish HTTP Proxy. "LLProxy" is a special string which directs + // the code to use LLProxy::applyProxySettings() to establish any + // HTTP or SOCKS proxy for http operations. + status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); + if (! status) + { + LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " + << status.toString() + << LL_ENDL; + } + + // Tracing levels for library & libcurl (note that 2 & 3 are beyond spammy): + // 0 - None + // 1 - Basic start, stop simple transitions + // 2 - libcurl CURLOPT_VERBOSE mode with brief lines + // 3 - with partial data content + static const std::string http_trace("QAModeHttpTrace"); + if (gSavedSettings.controlExists(http_trace)) + { + long trace_level(0L); + trace_level = long(gSavedSettings.getU32(http_trace)); + status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, trace_level); + } + + // Setup default policy and constrain if directed to + mPolicyDefault = LLCore::HttpRequest::DEFAULT_POLICY_ID; + static const std::string texture_concur("TextureFetchConcurrency"); + if (gSavedSettings.controlExists(texture_concur)) + { + U32 concur(llmin(gSavedSettings.getU32(texture_concur), U32(12))); + + if (concur > 0) + { + LLCore::HttpStatus status; + status = LLCore::HttpRequest::setPolicyClassOption(mPolicyDefault, + LLCore::HttpRequest::CP_CONNECTION_LIMIT, + concur); + if (! status) + { + LL_WARNS("Init") << "Unable to set texture fetch concurrency. Reason: " + << status.toString() + << LL_ENDL; + } + else + { + LL_INFOS("Init") << "Application settings overriding default texture fetch concurrency. New value: " + << concur + << LL_ENDL; + } + } + } + + // Kick the thread + status = LLCore::HttpRequest::startThread(); + if (! status) + { + LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " + << status.toString() + << LL_ENDL; + } + + mRequest = new LLCore::HttpRequest; +} + + +void LLAppCoreHttp::requestStop() +{ + llassert_always(mRequest); + + mStopHandle = mRequest->requestStopThread(this); + if (LLCORE_HTTP_HANDLE_INVALID != mStopHandle) + { + mStopRequested = LLTimer::getTotalSeconds(); + } +} + + +void LLAppCoreHttp::cleanup() +{ + if (LLCORE_HTTP_HANDLE_INVALID == mStopHandle) + { + // Should have been started already... + requestStop(); + } + + if (LLCORE_HTTP_HANDLE_INVALID == mStopHandle) + { + LL_WARNS("Cleanup") << "Attempting to cleanup HTTP services without thread shutdown" + << LL_ENDL; + } + else + { + while (! mStopped && LLTimer::getTotalSeconds() < (mStopRequested + MAX_THREAD_WAIT_TIME)) + { + mRequest->update(200000); + ms_sleep(50); + } + if (! mStopped) + { + LL_WARNS("Cleanup") << "Attempting to cleanup HTTP services with thread shutdown incomplete" + << LL_ENDL; + } + } + + delete mRequest; + mRequest = NULL; + + LLCore::HttpStatus status = LLCore::HttpRequest::destroyService(); + if (! status) + { + LL_WARNS("Cleanup") << "Failed to shutdown HTTP services, continuing. Reason: " + << status.toString() + << LL_ENDL; + } +} + + +void LLAppCoreHttp::onCompleted(LLCore::HttpHandle, LLCore::HttpResponse *) +{ + mStopped = true; +} diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h new file mode 100644 index 0000000000..241d73ad52 --- /dev/null +++ b/indra/newview/llappcorehttp.h @@ -0,0 +1,86 @@ +/** + * @file llappcorehttp.h + * @brief Singleton initialization/shutdown class for llcorehttp library + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef _LL_APP_COREHTTP_H_ +#define _LL_APP_COREHTTP_H_ + + +#include "httprequest.h" +#include "httphandler.h" +#include "httpresponse.h" + + +// This class manages the lifecyle of the core http library. +// Slightly different style than traditional code but reflects +// the use of handler classes and light-weight interface +// object instances of the new libraries. To be used +// as a singleton and static construction is fine. +class LLAppCoreHttp : public LLCore::HttpHandler +{ +public: + LLAppCoreHttp(); + ~LLAppCoreHttp(); + + // Initialize the LLCore::HTTP library creating service classes + // and starting the servicing thread. Caller is expected to do + // other initializations (SSL mutex, thread hash function) appropriate + // for the application. + void init(); + + // Request that the servicing thread stop servicing requests, + // release resource references and stop. Request is asynchronous + // and @see cleanup() will perform a limited wait loop for this + // request to stop the thread. + void requestStop(); + + // Terminate LLCore::HTTP library services. Caller is expected + // to have made a best-effort to shutdown the servicing thread + // by issuing a requestThreadStop() and waiting for completion + // notification that the stop has completed. + void cleanup(); + + // Notification when the stop request is complete. + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + + // Retrieve the policy class for default operations. + int getPolicyDefault() const + { + return mPolicyDefault; + } + +private: + static const F64 MAX_THREAD_WAIT_TIME; + +private: + LLCore::HttpRequest * mRequest; + LLCore::HttpHandle mStopHandle; + F64 mStopRequested; + bool mStopped; + int mPolicyDefault; +}; + + +#endif // _LL_APP_COREHTTP_H_ diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 6d67e098a6..06a9892c7e 100755..100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -52,7 +52,8 @@ std::string self_av_string() { - return gAgentAvatarp->avString(); + // On logout gAgentAvatarp can already be invalid + return isAgentAvatarValid() ? gAgentAvatarp->avString() : ""; } // RAII thingy to guarantee that a variable gets reset when the Setter diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 0a9d6229ef..39df2d08c3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2012, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -219,6 +219,7 @@ #include "llmachineid.h" #include "llmainlooprepeater.h" + // *FIX: These extern globals should be cleaned up. // The globals either represent state/config/resource-storage of either // this app, or another 'component' of the viewer. App globals should be @@ -734,6 +735,10 @@ bool LLAppViewer::init() LLViewerStatsRecorder::initClass(); #endif + // Initialize the non-LLCurl libcurl library. Should be called + // before consumers (LLTextureFetch). + mAppCoreHttp.init(); + // *NOTE:Mani - LLCurl::initClass is not thread safe. // Called before threads are created. LLCurl::initClass(gSavedSettings.getF32("CurlRequestTimeOut"), @@ -1884,6 +1889,7 @@ bool LLAppViewer::cleanup() // Delete workers first // shotdown all worker threads before deleting them in case of co-dependencies + mAppCoreHttp.requestStop(); sTextureFetch->shutdown(); sTextureCache->shutdown(); sImageDecodeThread->shutdown(); @@ -1899,6 +1905,9 @@ bool LLAppViewer::cleanup() LLCurl::cleanupClass(); LL_CHECK_MEMORY + // Non-LLCurl libcurl library + mAppCoreHttp.cleanup(); + LLFilePickerThread::cleanupClass(); //MUST happen AFTER LLCurl::cleanupClass diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index ae3c795d1e..0d9c420ff8 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -31,6 +31,7 @@ #include "llcontrol.h" #include "llsys.h" // for LLOSInfo #include "lltimer.h" +#include "llappcorehttp.h" class LLCommandLineParser; class LLFrameTimer; @@ -173,6 +174,9 @@ public: // Metrics policy helper statics. static void metricsUpdateRegion(U64 region_handle); static void metricsSend(bool enable_reporting); + + // llcorehttp init/shutdown/config information. + LLAppCoreHttp & getAppCoreHttp() { return mAppCoreHttp; } protected: virtual bool initWindow(); // Initialize the viewer's window. @@ -271,6 +275,9 @@ private: boost::scoped_ptr<LLUpdaterService> mUpdater; + // llcorehttp library init/shutdown helper + LLAppCoreHttp mAppCoreHttp; + //--------------------------------------------- //*NOTE: Mani - legacy updater stuff // Still useable? diff --git a/indra/newview/llattachmentsmgr.cpp b/indra/newview/llattachmentsmgr.cpp index c9543988a6..ea0b8f00a4 100644 --- a/indra/newview/llattachmentsmgr.cpp +++ b/indra/newview/llattachmentsmgr.cpp @@ -62,6 +62,12 @@ void LLAttachmentsMgr::onIdle(void *) void LLAttachmentsMgr::onIdle() { + // Make sure we got a region before trying anything else + if( !gAgent.getRegion() ) + { + return; + } + S32 obj_count = mPendingAttachments.size(); if (obj_count == 0) { diff --git a/indra/newview/lldrawpoolwlsky.cpp b/indra/newview/lldrawpoolwlsky.cpp index caf15fe1cb..b5faff7968 100644 --- a/indra/newview/lldrawpoolwlsky.cpp +++ b/indra/newview/lldrawpoolwlsky.cpp @@ -192,14 +192,18 @@ void LLDrawPoolWLSky::renderStars(void) const bool error; LLColor4 star_alpha(LLColor4::black); star_alpha.mV[3] = LLWLParamManager::getInstance()->mCurParams.getFloat("star_brightness", error) / 2.f; - llassert_always(!error); + + // If start_brightness is not set, exit + if( error ) + { + llwarns << "star_brightness missing in mCurParams" << llendl; + return; + } gGL.getTexUnit(0)->bind(gSky.mVOSkyp->getBloomTex()); gGL.pushMatrix(); gGL.rotatef(gFrameTimeSeconds*0.01f, 0.f, 0.f, 1.f); - // gl_FragColor.rgb = gl_Color.rgb; - // gl_FragColor.a = gl_Color.a * star_alpha.a; if (LLGLSLShader::sNoFixedFunction) { gCustomAlphaProgram.bind(); diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index 188f943f13..605cb81c10 100755 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -1062,7 +1062,11 @@ bool LLFace::canRenderAsMask() } const LLTextureEntry* te = getTextureEntry(); - + if( !te || !getViewerObject() || !getTexture() ) + { + return false; + } + if ((te->getColor().mV[3] == 1.0f) && // can't treat as mask if we have face alpha (te->getGlow() == 0.f) && // glowing masks are hard to implement - don't mask getTexture()->getIsAlphaMask()) // texture actually qualifies for masking (lazily recalculated but expensive) diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index fa0ad20fdb..62848586cd 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -422,13 +422,14 @@ void LLFloaterBvhPreview::resetMotion() LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); BOOL paused = avatarp->areAnimationsPaused(); - // *TODO: Fix awful casting hack - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - // Set emotion - std::string emote = getChild<LLUICtrl>("emote_combo")->getValue().asString(); - motionp->setEmote(mIDList[emote]); - + LLKeyframeMotion* motionp = dynamic_cast<LLKeyframeMotion*>(avatarp->findMotion(mMotionID)); + if( motionp ) + { + // Set emotion + std::string emote = getChild<LLUICtrl>("emote_combo")->getValue().asString(); + motionp->setEmote(mIDList[emote]); + } + LLUUID base_id = mIDList[getChild<LLUICtrl>("preview_base_anim")->getValue().asString()]; avatarp->deactivateAllMotions(); avatarp->startMotion(mMotionID, 0.0f); @@ -438,8 +439,12 @@ void LLFloaterBvhPreview::resetMotion() // Set pose std::string handpose = getChild<LLUICtrl>("hand_pose_combo")->getValue().asString(); avatarp->startMotion( ANIM_AGENT_HAND_MOTION, 0.0f ); - motionp->setHandPose(LLHandMotion::getHandPose(handpose)); + if( motionp ) + { + motionp->setHandPose(LLHandMotion::getHandPose(handpose)); + } + if (paused) { mPauseRequest = avatarp->requestPause(); diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index fce0b7c9c9..14a228df1c 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -2577,14 +2577,23 @@ void LLRightClickInventoryFetchDescendentsObserver::execute(bool clear_observer) LLInventoryModel::item_array_t* item_array; gInventory.getDirectDescendentsOf(*current_folder, cat_array, item_array); - S32 item_count = item_array->count(); - S32 cat_count = cat_array->count(); - + S32 item_count(0); + if( item_array ) + { + item_count = item_array->count(); + } + + S32 cat_count(0); + if( cat_array ) + { + cat_count = cat_array->count(); + } + // Move to next if current folder empty if ((item_count == 0) && (cat_count == 0)) - { + { continue; - } + } uuid_vec_t ids; LLRightClickInventoryFetchObserver* outfit = NULL; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 6e23d7c701..6e23d7c701 100755..100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index bc7f522848..ba0a590910 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1097,11 +1097,13 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat mMeshHeader[mesh_id] = header; } + + LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. + //check for pending requests pending_lod_map::iterator iter = mPendingLOD.find(mesh_params); if (iter != mPendingLOD.end()) { - LLMutexLock lock(mMutex); for (U32 i = 0; i < iter->second.size(); ++i) { LODRequest req(mesh_params, iter->second[i]); @@ -1796,7 +1798,12 @@ void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { - + // thread could have already be destroyed during logout + if( !gMeshRepo.mThread ) + { + return; + } + S32 data_size = buffer->countAfter(channels.in(), NULL); if (status < 200 || status > 400) @@ -1851,6 +1858,12 @@ void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { + // thread could have already be destroyed during logout + if( !gMeshRepo.mThread ) + { + return; + } + S32 data_size = buffer->countAfter(channels.in(), NULL); if (status < 200 || status > 400) @@ -1905,6 +1918,11 @@ void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& r const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { + if( !gMeshRepo.mThread ) + { + return; + } + S32 data_size = buffer->countAfter(channels.in(), NULL); if (status < 200 || status > 400) @@ -1959,6 +1977,12 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { + // thread could have already be destroyed during logout + if( !gMeshRepo.mThread ) + { + return; + } + S32 data_size = buffer->countAfter(channels.in(), NULL); if (status < 200 || status > 400) @@ -2013,6 +2037,12 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { + // thread could have already be destroyed during logout + if( !gMeshRepo.mThread ) + { + return; + } + if (status < 200 || status > 400) { //llwarns diff --git a/indra/newview/llnotificationmanager.cpp b/indra/newview/llnotificationmanager.cpp index f792f53ac5..3d8150eed3 100644 --- a/indra/newview/llnotificationmanager.cpp +++ b/indra/newview/llnotificationmanager.cpp @@ -97,6 +97,13 @@ bool LLNotificationManager::onNotification(const LLSD& notify) { LLSysHandler* handle = NULL; + // Don't bother if we're going down. + // Otherwise we might crash when trying to use handlers that are already dead. + if( LLApp::isExiting() ) + { + return false; + } + if (LLNotifications::destroyed()) return false; diff --git a/indra/newview/llpaneleditwearable.cpp b/indra/newview/llpaneleditwearable.cpp index d58d6d536c..d42056ef9d 100644 --- a/indra/newview/llpaneleditwearable.cpp +++ b/indra/newview/llpaneleditwearable.cpp @@ -835,11 +835,11 @@ BOOL LLPanelEditWearable::isDirty() const BOOL isDirty = FALSE; if (mWearablePtr) { - if (mWearablePtr->isDirty() || - mWearableItem->getName().compare(mNameEditor->getText()) != 0) - { - isDirty = TRUE; - } + if (mWearablePtr->isDirty() || + ( mWearableItem && mNameEditor && mWearableItem->getName().compare(mNameEditor->getText()) != 0 )) + { + isDirty = TRUE; + } } return isDirty; } diff --git a/indra/newview/llpreview.cpp b/indra/newview/llpreview.cpp index 18626e3273..04934b13f1 100644 --- a/indra/newview/llpreview.cpp +++ b/indra/newview/llpreview.cpp @@ -464,7 +464,9 @@ LLMultiPreview::LLMultiPreview() void LLMultiPreview::onOpen(const LLSD& key) { - LLPreview* frontmost_preview = (LLPreview*)mTabContainer->getCurrentPanel(); + // Floater could be something else than LLPreview, eg LLFloaterProfile. + LLPreview* frontmost_preview = dynamic_cast<LLPreview*>(mTabContainer->getCurrentPanel()); + if (frontmost_preview && frontmost_preview->getAssetStatus() == LLPreview::PREVIEW_ASSET_UNLOADED) { frontmost_preview->loadAsset(); @@ -477,8 +479,13 @@ void LLMultiPreview::handleReshape(const LLRect& new_rect, bool by_user) { if(new_rect.getWidth() != getRect().getWidth() || new_rect.getHeight() != getRect().getHeight()) { - LLPreview* frontmost_preview = (LLPreview*)mTabContainer->getCurrentPanel(); - if (frontmost_preview) frontmost_preview->userResized(); + // Floater could be something else than LLPreview, eg LLFloaterProfile. + LLPreview* frontmost_preview = dynamic_cast<LLPreview*>(mTabContainer->getCurrentPanel()); + + if (frontmost_preview) + { + frontmost_preview->userResized(); + } } LLFloater::handleReshape(new_rect, by_user); } @@ -486,7 +493,9 @@ void LLMultiPreview::handleReshape(const LLRect& new_rect, bool by_user) void LLMultiPreview::tabOpen(LLFloater* opened_floater, bool from_click) { - LLPreview* opened_preview = (LLPreview*)opened_floater; + // Floater could be something else than LLPreview, eg LLFloaterProfile. + LLPreview* opened_preview = dynamic_cast<LLPreview*>(opened_floater); + if (opened_preview && opened_preview->getAssetStatus() == LLPreview::PREVIEW_ASSET_UNLOADED) { opened_preview->loadAsset(); diff --git a/indra/newview/llscreenchannel.cpp b/indra/newview/llscreenchannel.cpp index d340b304ca..d2280ea089 100644 --- a/indra/newview/llscreenchannel.cpp +++ b/indra/newview/llscreenchannel.cpp @@ -571,9 +571,13 @@ void LLScreenChannel::showToastsBottom() LLDockableFloater* floater = dynamic_cast<LLDockableFloater*>(LLDockableFloater::getInstanceHandle().get()); - for(it = mToastList.rbegin(); it != mToastList.rend(); ++it) + // Use a local variable instead of mToastList. + // mToastList can be modified during recursive calls and then all iteratos will be invalidated. + std::vector<ToastElem> vToastList( mToastList ); + + for(it = vToastList.rbegin(); it != vToastList.rend(); ++it) { - if(it != mToastList.rbegin()) + if(it != vToastList.rbegin()) { LLToast* toast = (it-1)->getToast(); if (!toast) @@ -601,7 +605,7 @@ void LLScreenChannel::showToastsBottom() if(floater && floater->overlapsScreenChannel()) { - if(it == mToastList.rbegin()) + if(it == vToastList.rbegin()) { // move first toast above docked floater S32 shift = floater->getRect().getHeight(); @@ -624,7 +628,7 @@ void LLScreenChannel::showToastsBottom() if(!stop_showing_toasts) { - if( it != mToastList.rend()-1) + if( it != vToastList.rend()-1) { S32 toast_top = toast->getRect().mTop + gSavedSettings.getS32("ToastGap"); stop_showing_toasts = toast_top > getRect().mTop; @@ -632,7 +636,8 @@ void LLScreenChannel::showToastsBottom() } // at least one toast should be visible - if(it == mToastList.rbegin()) + + if(it == vToastList.rbegin()) { stop_showing_toasts = false; } @@ -655,10 +660,11 @@ void LLScreenChannel::showToastsBottom() } // Dismiss toasts we don't have space for (STORM-391). - if(it != mToastList.rend()) + if(it != vToastList.rend()) { mHiddenToastsNum = 0; - for(; it != mToastList.rend(); it++) + + for(; it != vToastList.rend(); it++) { LLToast* toast = it->getToast(); if (toast) @@ -714,9 +720,13 @@ void LLScreenChannel::showToastsTop() LLDockableFloater* floater = dynamic_cast<LLDockableFloater*>(LLDockableFloater::getInstanceHandle().get()); - for(it = mToastList.rbegin(); it != mToastList.rend(); ++it) + // Use a local variable instead of mToastList. + // mToastList can be modified during recursive calls and then all iteratos will be invalidated. + std::vector<ToastElem> vToastList( mToastList ); + + for(it = vToastList.rbegin(); it != vToastList.rend(); ++it) { - if(it != mToastList.rbegin()) + if(it != vToastList.rbegin()) { LLToast* toast = (it-1)->getToast(); if (!toast) @@ -744,7 +754,7 @@ void LLScreenChannel::showToastsTop() if(floater && floater->overlapsScreenChannel()) { - if(it == mToastList.rbegin()) + if(it == vToastList.rbegin()) { // move first toast above docked floater S32 shift = -floater->getRect().getHeight(); @@ -767,7 +777,7 @@ void LLScreenChannel::showToastsTop() if(!stop_showing_toasts) { - if( it != mToastList.rend()-1) + if( it != vToastList.rend()-1) { S32 toast_bottom = toast->getRect().mBottom - gSavedSettings.getS32("ToastGap"); stop_showing_toasts = toast_bottom < channel_rect.mBottom; @@ -775,7 +785,7 @@ void LLScreenChannel::showToastsTop() } // at least one toast should be visible - if(it == mToastList.rbegin()) + if(it == vToastList.rbegin()) { stop_showing_toasts = false; } @@ -799,10 +809,12 @@ void LLScreenChannel::showToastsTop() // Dismiss toasts we don't have space for (STORM-391). std::vector<LLToast*> toasts_to_hide; - if(it != mToastList.rend()) + + if(it != vToastList.rend()) { mHiddenToastsNum = 0; - for(; it != mToastList.rend(); it++) + + for(; it != vToastList.rend(); it++) { LLToast* toast = it->getToast(); if (toast) diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index adffb2c706..a2854dd6d8 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2012, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -28,12 +28,12 @@ #include <iostream> #include <map> +#include <algorithm> #include "llstl.h" #include "lltexturefetch.h" -#include "llcurl.h" #include "lldir.h" #include "llhttpclient.h" #include "llhttpstatuscodes.h" @@ -54,28 +54,214 @@ #include "llworld.h" #include "llsdutil.h" #include "llstartup.h" -#include "llviewerstats.h" +#include "llsdserialize.h" + +#include "httprequest.h" +#include "httphandler.h" +#include "httpresponse.h" +#include "bufferarray.h" +#include "bufferstream.h" bool LLTextureFetchDebugger::sDebuggerEnabled = false ; LLStat LLTextureFetch::sCacheHitRate("texture_cache_hits", 128); LLStat LLTextureFetch::sCacheReadLatency("texture_cache_read_latency", 128); + +////////////////////////////////////////////////////////////////////////////// +// +// Introduction +// +// This is an attempt to document what's going on in here after-the-fact. +// It's a sincere attempt to be accurate but there will be mistakes. +// +// +// Purpose +// +// What is this module trying to do? It accepts requests to load textures +// at a given priority and discard level and notifies the caller when done +// (successfully or not). Additional constraints are: +// +// * Support a local texture cache. Don't hit network when possible +// to avoid it. +// * Use UDP or HTTP as directed or as fallback. HTTP is tried when +// not disabled and a URL is available. UDP when a URL isn't +// available or HTTP attempts fail. +// * Asynchronous (using threads). Main thread is not to be blocked or +// burdened. +// * High concurrency. Many requests need to be in-flight and at various +// stages of completion. +// * Tolerate frequent re-prioritizations of requests. Priority is +// a reflection of a camera's viewpoint and as that viewpoint changes, +// objects and textures become more and less relevant and that is +// expressed at this level by priority changes and request cancelations. +// +// The caller interfaces that fall out of the above and shape the +// implementation are: +// * createRequest - Load j2c image via UDP or HTTP at given discard level and priority +// * deleteRequest - Request removal of prior request +// * getRequestFinished - Test if request is finished returning data to caller +// * updateRequestPriority - Change priority of existing request +// * getFetchState - Retrieve progress on existing request +// +// Everything else in here is mostly plumbing, metrics and debug. +// +// +// The Work Queue +// +// The two central classes are LLTextureFetch and LLTextureFetchWorker. +// LLTextureFetch combines threading with a priority queue of work +// requests. The priority queue is sorted by a U32 priority derived +// from the F32 priority in the APIs. The *only* work request that +// receives service time by this thread is the highest priority +// request. All others wait until it is complete or a dynamic priority +// change has re-ordered work. +// +// LLTextureFetchWorker implements the work request and is 1:1 with +// texture fetch requests. Embedded in each is a state machine that +// walks it through the cache, HTTP, UDP, image decode and retry +// steps of texture acquisition. +// +// +// Threads +// +// Several threads are actively invoking code in this module. They +// include: +// +// 1. Tmain Main thread of execution +// 2. Ttf LLTextureFetch's worker thread provided by LLQueuedThread +// 3. Tcurl LLCurl's worker thread (should disappear over time) +// 4. Ttc LLTextureCache's worker thread +// 5. Tid Image decoder's worker thread +// 6. Thl HTTP library's worker thread +// +// +// Mutexes/Condition Variables +// +// 1. Mt Mutex defined for LLThread's condition variable (base class of +// LLTextureFetch) +// 2. Ct Condition variable for LLThread and used by lock/unlockData(). +// 3. Mwtd Special LLWorkerThread mutex used for request deletion +// operations (base class of LLTextureFetch) +// 4. Mfq LLTextureFetch's mutex covering request and command queue +// data. +// 5. Mfnq LLTextureFetch's mutex covering udp and http request +// queue data. +// 6. Mwc Mutex covering LLWorkerClass's members (base class of +// LLTextureFetchWorker). One per request. +// 7. Mw LLTextureFetchWorker's mutex. One per request. +// +// +// Lock Ordering Rules +// +// Not an exhaustive list but shows the order of lock acquisition +// needed to prevent deadlocks. 'A < B' means acquire 'A' before +// acquiring 'B'. +// +// 1. Mw < Mfnq +// (there are many more...) +// +// +// Method and Member Definitions +// +// With the above, we'll try to document what threads can call what +// methods (using T* for any), what locks must be held on entry and +// are taken out during execution and what data is covered by which +// lock (if any). This latter category will be especially prone to +// error so be skeptical. +// +// A line like: "// Locks: M<xxx>" indicates a method that must +// be invoked by a caller holding the 'M<xxx>' lock. Similarly, +// "// Threads: T<xxx>" means that a caller should be running in +// the indicated thread. +// +// For data members, a trailing comment like "// M<xxx>" means that +// the data member is covered by the specified lock. Absence of a +// comment can mean the member is unlocked or that I didn't bother +// to do the archaeology. In the case of LLTextureFetchWorker, +// most data members added by the leaf class are actually covered +// by the Mw lock. You may also see "// T<xxx>" which means that +// the member's usage is restricted to one thread (except for +// perhaps construction and destruction) and so explicit locking +// isn't used. +// +// In code, a trailing comment like "// [-+]M<xxx>" indicates a +// lock acquision or release point. +// +// +// Worker Lifecycle +// +// The threading and responder model makes it very likely that +// other components are holding on to a pointer to a worker request. +// So, uncoordinated deletions of requests is a guarantee of memory +// corruption in a short time. So destroying a request involves +// invocations's of LLQueuedThread/LLWorkerThread's abort/stop +// logic that removes workers and puts them ona delete queue for +// 2-phase destruction. That second phase is deferrable by calls +// to deleteOK() which only allow final destruction (via dtor) +// once deleteOK has determined that the request is in a safe +// state. +// +// +// Worker State Machine +// +// (ASCII art needed) +// +// +// Priority Scheme +// +// [PRIORITY_LOW, PRIORITY_NORMAL) - for WAIT_HTTP_RESOURCE state +// and other wait states +// [PRIORITY_HIGH, PRIORITY_URGENT) - External event delivered, +// rapidly transitioning through states, +// no waiting allowed +// +// By itself, the above work queue model would fail the concurrency +// and liveness requirements of the interface. A high priority +// request could find itself on the head and stalled for external +// reasons (see VWR-28996). So a few additional constraints are +// required to keep things running: +// * Anything that can make forward progress must be kept at a +// higher priority than anything that can't. +// * On completion of external events, the associated request +// needs to be elevated beyond the normal range to handle +// any data delivery and release any external resource. +// +// This effort is made to keep higher-priority entities moving +// forward in their state machines at every possible step of +// processing. It's not entirely proven that this produces the +// experiencial benefits promised. +// + + ////////////////////////////////////////////////////////////////////////////// -class LLTextureFetchWorker : public LLWorkerClass + +// Tuning/Parameterization Constants + +static const S32 HTTP_REQUESTS_IN_QUEUE_HIGH_WATER = 40; // Maximum requests to have active in HTTP +static const S32 HTTP_REQUESTS_IN_QUEUE_LOW_WATER = 20; // Active level at which to refill + + +////////////////////////////////////////////////////////////////////////////// + +class LLTextureFetchWorker : public LLWorkerClass, public LLCore::HttpHandler + { friend class LLTextureFetch; - friend class HTTPGetResponder; friend class LLTextureFetchDebugger; private: class CacheReadResponder : public LLTextureCache::ReadResponder { public: + + // Threads: Ttf CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image) : mFetcher(fetcher), mID(id) { setImage(image); } + + // Threads: Ttc virtual void completed(bool success) { LLTextureFetchWorker* worker = mFetcher->getWorker(mID); @@ -92,10 +278,14 @@ private: class CacheWriteResponder : public LLTextureCache::WriteResponder { public: + + // Threads: Ttf CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id) : mFetcher(fetcher), mID(id) { } + + // Threads: Ttc virtual void completed(bool success) { LLTextureFetchWorker* worker = mFetcher->getWorker(mID); @@ -112,10 +302,14 @@ private: class DecodeResponder : public LLImageDecodeThread::Responder { public: + + // Threads: Ttf DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker) : mFetcher(fetcher), mID(id), mWorker(worker) { } + + // Threads: Tid virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) { LLTextureFetchWorker* worker = mFetcher->getWorker(mID); @@ -148,22 +342,35 @@ private: }; public: + + // Threads: Ttf /*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest() + + // Threads: Ttf /*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) - /*virtual*/ bool deleteOK(); // called from update() (WORK THREAD) - + + // Threads: Tmain + /*virtual*/ bool deleteOK(); // called from update() + ~LLTextureFetchWorker(); - // void relese() { --mActiveCount; } - S32 callbackHttpGet(const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer, - bool partial, bool success); + // Threads: Ttf + // Locks: Mw + S32 callbackHttpGet(LLCore::HttpResponse * response, + bool partial, bool success); + + // Threads: Ttc void callbackCacheRead(bool success, LLImageFormatted* image, S32 imagesize, BOOL islocal); + + // Threads: Ttc void callbackCacheWrite(bool success); + + // Threads: Tid void callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux); - void setGetStatus(U32 status, const std::string& reason) + // Threads: T* + void setGetStatus(LLCore::HttpStatus status, const std::string& reason) { LLMutexLock lock(&mWorkMutex); @@ -175,35 +382,93 @@ public: bool getCanUseHTTP() const { return mCanUseHTTP; } LLTextureFetch & getFetcher() { return *mFetcher; } + + // Inherited from LLCore::HttpHandler + // Threads: Ttf + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); protected: LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, S32 discard, S32 size); private: + + // Threads: Tmain /*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD) + + // Threads: Tmain /*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD) + // Locks: Mw void resetFormattedData(); + // Locks: Mw void setImagePriority(F32 priority); + + // Locks: Mw (ctor invokes without lock) void setDesiredDiscard(S32 discard, S32 size); + + // Threads: T* + // Locks: Mw bool insertPacket(S32 index, U8* data, S32 size); + + // Locks: Mw void clearPackets(); + + // Locks: Mw void setupPacketData(); + + // Locks: Mw (ctor invokes without lock) U32 calcWorkPriority(); + + // Locks: Mw void removeFromCache(); + + // Threads: Ttf + // Locks: Mw bool processSimulatorPackets(); + + // Threads: Ttf bool writeToCacheComplete(); - void removeFromHTTPQueue(); + // Threads: Ttf + void recordTextureStart(bool is_http); + + // Threads: Ttf + void recordTextureDone(bool is_http); void lockWorkMutex() { mWorkMutex.lock(); } void unlockWorkMutex() { mWorkMutex.unlock(); } + // Threads: Ttf + // Locks: Mw + bool acquireHttpSemaphore() + { + llassert(! mHttpHasResource); + if (mFetcher->mHttpSemaphore <= 0) + { + return false; + } + mHttpHasResource = true; + mFetcher->mHttpSemaphore--; + return true; + } + + // Threads: Ttf + // Locks: Mw + void releaseHttpSemaphore() + { + llassert(mHttpHasResource); + mHttpHasResource = false; + mFetcher->mHttpSemaphore++; + } + private: enum e_state // mState { + // *NOTE: Do not change the order/value of state variables, some code + // depends upon specific ordering/adjacency. + // NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack) INVALID = 0, INIT, @@ -211,8 +476,10 @@ private: CACHE_POST, LOAD_FROM_NETWORK, LOAD_FROM_SIMULATOR, - SEND_HTTP_REQ, - WAIT_HTTP_REQ, + WAIT_HTTP_RESOURCE, // Waiting for HTTP resources + WAIT_HTTP_RESOURCE2, // Waiting for HTTP resources + SEND_HTTP_REQ, // Commit to sending as HTTP + WAIT_HTTP_REQ, // Request sent, wait for completion DECODE_IMAGE, DECODE_IMAGE_UPDATE, WRITE_TO_CACHE, @@ -256,9 +523,8 @@ private: F32 mCacheReadTime; LLTextureCache::handle_t mCacheReadHandle; LLTextureCache::handle_t mCacheWriteHandle; - U8* mBuffer; - S32 mBufferSize; S32 mRequestedSize; + S32 mRequestedOffset; S32 mDesiredSize; S32 mFileSize; S32 mCachedSize; @@ -273,12 +539,9 @@ private: BOOL mInCache; bool mCanUseHTTP ; bool mCanUseNET ; //can get from asset server. - S32 mHTTPFailCount; S32 mRetryAttempt; S32 mActiveCount; - U32 mGetStatus; - U32 mHTTPHandle; - F32 mDelay; + LLCore::HttpStatus mGetStatus; std::string mGetReason; // Work Data @@ -298,105 +561,19 @@ private: U8 mImageCodec; LLViewerAssetStats::duration_t mMetricsStartTime; -}; - -////////////////////////////////////////////////////////////////////////////// - -class HTTPGetResponder : public LLCurl::Responder -{ - LOG_CLASS(HTTPGetResponder); -public: - HTTPGetResponder(LLTextureFetch* fetcher, const LLUUID& id, U64 startTime, S32 requestedSize, U32 offset, bool redir) - : mFetcher(fetcher), mID(id), mStartTime(startTime), mRequestedSize(requestedSize), mOffset(offset), mFollowRedir(redir) - { - } - ~HTTPGetResponder() - { - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) - { - static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog"); - static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator"); - static LLCachedControl<bool> log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic") ; - if (log_to_viewer_log || log_to_sim) - { - mFetcher->mTextureInfo.setRequestStartTime(mID, mStartTime); - U64 timeNow = LLTimer::getTotalTime(); - mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP); - mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize); - mFetcher->mTextureInfo.setRequestOffset(mID, mOffset); - mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, timeNow); - } - - S32 data_size = 0; - lldebugs << "HTTP COMPLETE: " << mID << llendl; - LLTextureFetchWorker* worker = mFetcher->getWorker(mID); - if (worker) - { - bool success = false; - bool partial = false; - if (HTTP_OK <= status && status < HTTP_MULTIPLE_CHOICES) - { - success = true; - if (HTTP_PARTIAL_CONTENT == status) // partial information - { - partial = true; - } - } - - if (!success) - { - worker->setGetStatus(status, reason); -// llwarns << "CURL GET FAILED, status:" << status << " reason:" << reason << llendl; - } - - data_size = worker->callbackHttpGet(channels, buffer, partial, success); - - if(log_texture_traffic && data_size > 0) - { - LLViewerTexture* tex = LLViewerTextureManager::findTexture(mID) ; - if(tex) - { - gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size ; - } - } - - if (worker->mMetricsStartTime) - { - LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, - true, - LLImageBase::TYPE_AVATAR_BAKE == worker->mType, - LLViewerAssetStatsFF::get_timestamp() - worker->mMetricsStartTime); - worker->mMetricsStartTime = 0; - } - LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, - true, - LLImageBase::TYPE_AVATAR_BAKE == worker->mType); - } - else - { - llwarns << "Worker not found: " << mID << llendl; - } - - mFetcher->getCurlRequest().completeRequest(data_size); - } - - virtual bool followRedir() - { - return mFollowRedir; - } - -private: - LLTextureFetch* mFetcher; - LLUUID mID; - U64 mStartTime; - S32 mRequestedSize; - U32 mOffset; - bool mFollowRedir; + LLCore::HttpHandle mHttpHandle; // Handle of any active request + LLCore::BufferArray * mHttpBufferArray; // Refcounted pointer to response data + int mHttpPolicyClass; + bool mHttpActive; // Active request to http library + unsigned int mHttpReplySize; // Actual received data size + unsigned int mHttpReplyOffset; // Actual received data offset + bool mHttpHasResource; // Counts against Fetcher's mHttpSemaphore + + // State history + U32 mCacheReadCount; + U32 mCacheWriteCount; + U32 mResourceWaitCount; // Requests entering WAIT_HTTP_RESOURCE2 }; ////////////////////////////////////////////////////////////////////////////// @@ -632,13 +809,15 @@ const char* LLTextureFetchWorker::sStateDescs[] = { "CACHE_POST", "LOAD_FROM_NETWORK", "LOAD_FROM_SIMULATOR", + "WAIT_HTTP_RESOURCE", + "WAIT_HTTP_RESOURCE2", "SEND_HTTP_REQ", "WAIT_HTTP_REQ", "DECODE_IMAGE", "DECODE_IMAGE_UPDATE", "WRITE_TO_CACHE", "WAIT_ON_WRITE", - "DONE", + "DONE" }; // static @@ -654,6 +833,7 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, S32 discard, // Desired discard S32 size) // Desired size : LLWorkerClass(fetcher, "TextureFetch"), + LLCore::HttpHandler(), mState(INIT), mWriteToCacheState(NOT_WRITE), mFetcher(fetcher), @@ -671,9 +851,8 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, mCacheReadTime(0.f), mCacheReadHandle(LLTextureCache::nullHandle()), mCacheWriteHandle(LLTextureCache::nullHandle()), - mBuffer(NULL), - mBufferSize(0), mRequestedSize(0), + mRequestedOffset(0), mDesiredSize(TEXTURE_CACHE_ENTRY_SIZE), mFileSize(0), mCachedSize(0), @@ -687,18 +866,24 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, mInLocalCache(FALSE), mInCache(FALSE), mCanUseHTTP(true), - mHTTPFailCount(0), mRetryAttempt(0), mActiveCount(0), - mGetStatus(0), mWorkMutex(NULL), mFirstPacket(0), mLastPacket(-1), mTotalPackets(0), mImageCodec(IMG_CODEC_INVALID), mMetricsStartTime(0), - mHTTPHandle(0), - mDelay(-1.f) + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), + mHttpBufferArray(NULL), + mHttpPolicyClass(mFetcher->mHttpPolicyClass), + mHttpActive(false), + mHttpReplySize(0U), + mHttpReplyOffset(0U), + mHttpHasResource(false), + mCacheReadCount(0U), + mCacheWriteCount(0U), + mResourceWaitCount(0U) { mCanUseNET = mUrl.empty() ; @@ -720,7 +905,20 @@ LLTextureFetchWorker::~LLTextureFetchWorker() // << " Requested=" << mRequestedDiscard // << " Desired=" << mDesiredDiscard << llendl; llassert_always(!haveWork()); - lockWorkMutex(); + + lockWorkMutex(); // +Mw (should be useless) + if (mHttpHasResource) + { + // Last-chance catchall to recover the resource. Using an + // atomic datatype solely because this can be running in + // another thread. + releaseHttpSemaphore(); + } + if (mHttpActive) + { + // Issue a cancel on a live request... + mFetcher->getHttpRequest().requestCancel(mHttpHandle, NULL); + } if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache) { mFetcher->mTextureCache->readComplete(mCacheReadHandle, true); @@ -731,22 +929,18 @@ LLTextureFetchWorker::~LLTextureFetchWorker() } mFormattedImage = NULL; clearPackets(); - unlockWorkMutex(); - - removeFromHTTPQueue(); -} - -void LLTextureFetchWorker::removeFromHTTPQueue() -{ - if(mHTTPHandle > 0) + if (mHttpBufferArray) { - llassert_always(mState == WAIT_HTTP_REQ); - - mFetcher->getCurlRequest().removeRequest(mHTTPHandle); - mHTTPHandle = 0; + mHttpBufferArray->release(); + mHttpBufferArray = NULL; } + unlockWorkMutex(); // -Mw + mFetcher->removeFromHTTPQueue(mID, 0); + mFetcher->removeHttpWaiter(mID); + mFetcher->updateStateStats(mCacheReadCount, mCacheWriteCount, mResourceWaitCount); } +// Locks: Mw void LLTextureFetchWorker::clearPackets() { for_each(mPackets.begin(), mPackets.end(), DeletePointer()); @@ -756,6 +950,7 @@ void LLTextureFetchWorker::clearPackets() mFirstPacket = 0; } +// Locks: Mw void LLTextureFetchWorker::setupPacketData() { S32 data_size = 0; @@ -788,6 +983,7 @@ void LLTextureFetchWorker::setupPacketData() } } +// Locks: Mw (ctor invokes without lock) U32 LLTextureFetchWorker::calcWorkPriority() { //llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerFetchedTexture::maxDecodePriority()); @@ -797,7 +993,7 @@ U32 LLTextureFetchWorker::calcWorkPriority() return mWorkPriority; } -// mWorkMutex is locked +// Locks: Mw (ctor invokes without lock) void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size) { bool prioritize = false; @@ -833,6 +1029,7 @@ void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size) } } +// Locks: Mw void LLTextureFetchWorker::setImagePriority(F32 priority) { // llassert_always(priority >= 0 && priority <= LLViewerTexture::maxDecodePriority()); @@ -842,35 +1039,41 @@ void LLTextureFetchWorker::setImagePriority(F32 priority) mImagePriority = priority; calcWorkPriority(); U32 work_priority = mWorkPriority | (getPriority() & LLWorkerThread::PRIORITY_HIGHBITS); - mFetcher->getCurlRequest().updatePriority(mHTTPHandle, mWorkPriority); setPriority(work_priority); } } +// Locks: Mw void LLTextureFetchWorker::resetFormattedData() { - FREE_MEM(LLImageBase::getPrivatePool(), mBuffer); - mBuffer = NULL; - mBufferSize = 0; + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } if (mFormattedImage.notNull()) { mFormattedImage->deleteData(); } + mHttpReplySize = 0; + mHttpReplyOffset = 0; mHaveAllData = FALSE; } -// Called from MAIN thread +// Threads: Tmain void LLTextureFetchWorker::startWork(S32 param) { llassert(mFormattedImage.isNull()); } -#include "llviewertexturelist.h" // debug - -// Called from LLWorkerThread::processRequest() +// Threads: Ttf bool LLTextureFetchWorker::doWork(S32 param) { - LLMutexLock lock(&mWorkMutex); + static const LLCore::HttpStatus http_not_found(HTTP_NOT_FOUND); // 404 + static const LLCore::HttpStatus http_service_unavail(HTTP_SERVICE_UNAVAILABLE); // 503 + static const LLCore::HttpStatus http_not_sat(HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); // 416; + + LLMutexLock lock(&mWorkMutex); // +Mw if ((mFetcher->isQuitting() || getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) { @@ -914,22 +1117,26 @@ bool LLTextureFetchWorker::doWork(S32 param) mLoadedDiscard = -1; mDecodedDiscard = -1; mRequestedSize = 0; + mRequestedOffset = 0; mFileSize = 0; mCachedSize = 0; mLoaded = FALSE; mSentRequest = UNSENT; mDecoded = FALSE; mWritten = FALSE; - FREE_MEM(LLImageBase::getPrivatePool(), mBuffer); - mBuffer = NULL; - mBufferSize = 0; + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } + mHttpReplySize = 0; + mHttpReplyOffset = 0; mHaveAllData = FALSE; clearPackets(); // TODO: Shouldn't be necessary mCacheReadHandle = LLTextureCache::nullHandle(); mCacheWriteHandle = LLTextureCache::nullHandle(); mState = LOAD_FROM_TEXTURE_CACHE; mInCache = FALSE; - mDelay = -1.f; mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE LL_DEBUGS("Texture") << mID << ": Priority: " << llformat("%8.0f",mImagePriority) << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL; @@ -945,7 +1152,7 @@ bool LLTextureFetchWorker::doWork(S32 param) S32 size = mDesiredSize - offset; if (size <= 0) { - mState = CACHE_POST; //have enough data, will fall to decode + mState = CACHE_POST; return false; } mFileSize = 0; @@ -956,6 +1163,7 @@ bool LLTextureFetchWorker::doWork(S32 param) setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it // read file from local disk + ++mCacheReadCount; std::string filename = mUrl.substr(7, std::string::npos); CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, cache_priority, @@ -966,6 +1174,7 @@ bool LLTextureFetchWorker::doWork(S32 param) { setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it + ++mCacheReadCount; CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority, offset, size, responder); @@ -979,7 +1188,7 @@ bool LLTextureFetchWorker::doWork(S32 param) llwarns << "Unknown URL Type: " << mUrl << llendl; } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); - mState = SEND_HTTP_REQ; + mState = WAIT_HTTP_RESOURCE; } else { @@ -1082,7 +1291,7 @@ bool LLTextureFetchWorker::doWork(S32 param) } if (mCanUseHTTP && !mUrl.empty()) { - mState = SEND_HTTP_REQ; + mState = WAIT_HTTP_RESOURCE; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); if(mWriteToCacheState != NOT_WRITE) { @@ -1099,13 +1308,7 @@ bool LLTextureFetchWorker::doWork(S32 param) mRequestedDiscard = mDesiredDiscard; mSentRequest = QUEUED; mFetcher->addToNetworkQueue(this); - if (! mMetricsStartTime) - { - mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); - } - LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, - false, - LLImageBase::TYPE_AVATAR_BAKE == mType); + recordTextureStart(false); setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return false; @@ -1116,12 +1319,7 @@ bool LLTextureFetchWorker::doWork(S32 param) //llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end()); // Make certain this is in the network queue //mFetcher->addToNetworkQueue(this); - //if (! mMetricsStartTime) - //{ - // mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); - //} - //LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, false, - // LLImageBase::TYPE_AVATAR_BAKE == mType); + //recordTextureStart(false); //setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return false; } @@ -1146,193 +1344,201 @@ bool LLTextureFetchWorker::doWork(S32 param) setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); mState = DECODE_IMAGE; mWriteToCacheState = SHOULD_WRITE; - - if (mMetricsStartTime) - { - LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, - false, - LLImageBase::TYPE_AVATAR_BAKE == mType, - LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime); - mMetricsStartTime = 0; - } - LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, - false, - LLImageBase::TYPE_AVATAR_BAKE == mType); + recordTextureDone(false); } else { mFetcher->addToNetworkQueue(this); // failsafe - if (! mMetricsStartTime) - { - mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); - } - LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, - false, - LLImageBase::TYPE_AVATAR_BAKE == mType); setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + recordTextureStart(false); + } + return false; + } + + if (mState == WAIT_HTTP_RESOURCE) + { + // NOTE: + // control the number of the http requests issued for: + // 1, not openning too many file descriptors at the same time; + // 2, control the traffic of http so udp gets bandwidth. + // + // If it looks like we're busy, keep this request here. + // Otherwise, advance into the HTTP states. + if (mFetcher->getHttpWaitersCount() || ! acquireHttpSemaphore()) + { + mState = WAIT_HTTP_RESOURCE2; + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + mFetcher->addHttpWaiter(this->mID); + ++mResourceWaitCount; + return false; } + + mState = SEND_HTTP_REQ; + // *NOTE: You must invoke releaseHttpSemaphore() if you transition + // to a state other than SEND_HTTP_REQ or WAIT_HTTP_REQ or abort + // the request. + } + + if (mState == WAIT_HTTP_RESOURCE2) + { + // Just idle it if we make it to the head... return false; } if (mState == SEND_HTTP_REQ) { - if(mCanUseHTTP) + if (! mCanUseHTTP) { - mFetcher->removeFromNetworkQueue(this, false); + releaseHttpSemaphore(); + return true; // abort + } + + mFetcher->removeFromNetworkQueue(this, false); - S32 cur_size = 0; - if (mFormattedImage.notNull()) + S32 cur_size = 0; + if (mFormattedImage.notNull()) + { + cur_size = mFormattedImage->getDataSize(); // amount of data we already have + if (mFormattedImage->getDiscardLevel() == 0) { - cur_size = mFormattedImage->getDataSize(); // amount of data we already have - if (mFormattedImage->getDiscardLevel() == 0) + if (cur_size > 0) { - if(cur_size > 0) - { - // We already have all the data, just decode it - mLoadedDiscard = mFormattedImage->getDiscardLevel(); - setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); - mState = DECODE_IMAGE; - return false; - } - else - { - return true ; //abort. - } - } - } - mRequestedSize = mDesiredSize; - mRequestedDiscard = mDesiredDiscard; - mRequestedSize -= cur_size; - S32 offset = cur_size; - mBufferSize = cur_size; // This will get modified by callbackHttpGet() - - bool res = false; - if (!mUrl.empty()) - { - mRequestedTimer.reset(); - - mLoaded = FALSE; - mGetStatus = 0; - mGetReason.clear(); - LL_DEBUGS("Texture") << "HTTP GET: " << mID << " Offset: " << offset - << " Bytes: " << mRequestedSize - << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth - << LL_ENDL; - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); - mState = WAIT_HTTP_REQ; - - if (! mMetricsStartTime) - { - mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + // We already have all the data, just decode it + mLoadedDiscard = mFormattedImage->getDiscardLevel(); + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + mState = DECODE_IMAGE; + releaseHttpSemaphore(); + return false; } - LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, - true, - LLImageBase::TYPE_AVATAR_BAKE == mType); - - // Will call callbackHttpGet when curl request completes - std::vector<std::string> headers; - headers.push_back("Accept: image/x-j2c"); - // If we try to fetch the whole file, we set the size to 0 so that we generate the correct curl range request - // Note: it looks a bit hacky but we need to limit this (size==0) to mean "whole file" to HTTP only as it messes up UDP fetching - if ((offset+mRequestedSize) == MAX_IMAGE_DATA_SIZE) + else { - mRequestedSize = 0; + releaseHttpSemaphore(); + return true; // abort. } - mHTTPHandle = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize, mWorkPriority, - new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true), mDelay); - mDelay = -1.f; //reset - res = true; } - if (!res) - { - llwarns << "HTTP GET request failed for " << mID << llendl; - resetFormattedData(); - ++mHTTPFailCount; - return true; // failed - } - // fall through } - else //can not use http fetch. + mRequestedSize = mDesiredSize; + mRequestedDiscard = mDesiredDiscard; + mRequestedSize -= cur_size; + mRequestedOffset = cur_size; + if (mRequestedOffset) { - return true ; //abort + // Texture fetching often issues 'speculative' loads that + // start beyond the end of the actual asset. Some cache/web + // systems, e.g. Varnish, will respond to this not with a + // 416 but with a 200 and the entire asset in the response + // body. By ensuring that we always have a partially + // satisfiable Range request, we avoid that hit to the network. + // We just have to deal with the overlapping data which is made + // somewhat harder by the fact that grid services don't necessarily + // return the Content-Range header on 206 responses. *Sigh* + mRequestedOffset -= 1; + mRequestedSize += 1; + } + + mHttpHandle = LLCORE_HTTP_HANDLE_INVALID; + if (!mUrl.empty()) + { + mRequestedTimer.reset(); + mLoaded = FALSE; + mGetStatus = LLCore::HttpStatus(); + mGetReason.clear(); + LL_DEBUGS("Texture") << "HTTP GET: " << mID << " Offset: " << mRequestedOffset + << " Bytes: " << mRequestedSize + << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth + << LL_ENDL; + + // Will call callbackHttpGet when curl request completes + mHttpHandle = mFetcher->mHttpRequest->requestGetByteRange(mHttpPolicyClass, + mWorkPriority, + mUrl, + mRequestedOffset, + mRequestedSize, + mFetcher->mHttpOptions, + mFetcher->mHttpHeaders, + this); + } + if (LLCORE_HTTP_HANDLE_INVALID == mHttpHandle) + { + llwarns << "HTTP GET request failed for " << mID << llendl; + resetFormattedData(); + releaseHttpSemaphore(); + return true; // failed } + + mHttpActive = true; + mFetcher->addToHTTPQueue(mID); + recordTextureStart(true); + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + mState = WAIT_HTTP_REQ; + + // fall through } if (mState == WAIT_HTTP_REQ) { + // *NOTE: As stated above, all transitions out of this state should + // call releaseHttpSemaphore(). if (mLoaded) { S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; if (mRequestedSize < 0) { - S32 max_attempts; - if (mGetStatus == HTTP_NOT_FOUND) + if (http_not_found == mGetStatus) { if(mWriteToCacheState == NOT_WRITE) //map tiles { mState = DONE; + releaseHttpSemaphore(); return true; // failed, means no map tile on the empty region. } - mHTTPFailCount = max_attempts = 1; // Don't retry llwarns << "Texture missing from server (404): " << mUrl << llendl; - //roll back to try UDP - if(mCanUseNET) + // roll back to try UDP + if (mCanUseNET) { - mState = INIT ; - mCanUseHTTP = false ; + mState = INIT; + mCanUseHTTP = false; mUrl.clear(); setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); - return false ; + releaseHttpSemaphore(); + return false; } } - else if (mGetStatus == HTTP_SERVICE_UNAVAILABLE) + else if (http_service_unavail == mGetStatus) { - // *TODO: Should probably introduce a timer here to delay future HTTP requsts - // for a short time (~1s) to ease server load? Ideally the server would queue - // requests instead of returning 503... we already limit the number pending. - ++mHTTPFailCount; - max_attempts = mHTTPFailCount+1; // Keep retrying LL_INFOS_ONCE("Texture") << "Texture server busy (503): " << mUrl << LL_ENDL; - mDelay = 2.0f; //delay 2 second to re-issue the http request + } + else if (http_not_sat == mGetStatus) + { + // Allowed, we'll accept whatever data we have as complete. + mHaveAllData = TRUE; } else { - const S32 HTTP_MAX_RETRY_COUNT = 3; - max_attempts = HTTP_MAX_RETRY_COUNT + 1; - ++mHTTPFailCount; - mDelay = 2.0f; //delay 2 second to re-issue the http request - llinfos << "HTTP GET failed for: " << mUrl - << " Status: " << mGetStatus << " Reason: '" << mGetReason << "'" - << " Attempt:" << mHTTPFailCount+1 << "/" << max_attempts << llendl; + << " Status: " << mGetStatus.toHex() + << " Reason: '" << mGetReason << "'" + << llendl; } - if (mHTTPFailCount >= max_attempts) - { - mUrl.clear(); - if (cur_size > 0) - { - // Use available data - mLoadedDiscard = mFormattedImage->getDiscardLevel(); - setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); - mState = DECODE_IMAGE; - return false; - } - else - { - resetFormattedData(); - mState = DONE; - return true; // failed - } - } - else + mUrl.clear(); + if (cur_size > 0) { + // Use available data + mLoadedDiscard = mFormattedImage->getDiscardLevel(); setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); - mState = SEND_HTTP_REQ; - return false; // retry + mState = DECODE_IMAGE; + releaseHttpSemaphore(); + return false; } + + // Fail harder + resetFormattedData(); + mState = DONE; + releaseHttpSemaphore(); + return true; // failed } // Clear the url since we're done with the fetch @@ -1342,18 +1548,46 @@ bool LLTextureFetchWorker::doWork(S32 param) { mUrl.clear(); } - - llassert_always(mBufferSize == cur_size + mRequestedSize); - if(!mBufferSize)//no data received. + + if (! mHttpBufferArray || ! mHttpBufferArray->size()) { - FREE_MEM(LLImageBase::getPrivatePool(), mBuffer); - mBuffer = NULL; + // no data received. + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } - //abort. + // abort. mState = DONE; + releaseHttpSemaphore(); return true; } + S32 append_size(mHttpBufferArray->size()); + S32 total_size(cur_size + append_size); + S32 src_offset(0); + llassert_always(append_size == mRequestedSize); + if (mHttpReplyOffset && mHttpReplyOffset != cur_size) + { + // In case of a partial response, our offset may + // not be trivially contiguous with the data we have. + // Get back into alignment. + if (mHttpReplyOffset > cur_size) + { + LL_WARNS("Texture") << "Partial HTTP response produces break in image data for texture " + << mID << ". Aborting load." << LL_ENDL; + mState = DONE; + releaseHttpSemaphore(); + return true; + } + src_offset = cur_size - mHttpReplyOffset; + append_size -= src_offset; + total_size -= src_offset; + mRequestedSize -= src_offset; // Make requested values reflect useful part + mRequestedOffset += src_offset; + } + if (mFormattedImage.isNull()) { // For now, create formatted image based on extension @@ -1365,41 +1599,50 @@ bool LLTextureFetchWorker::doWork(S32 param) } } - if (mHaveAllData && mRequestedDiscard == 0) //the image file is fully loaded. + if (mHaveAllData) //the image file is fully loaded. { - mFileSize = mBufferSize; + mFileSize = total_size; } else //the file size is unknown. { - mFileSize = mBufferSize + 1 ; //flag the file is not fully loaded. + mFileSize = total_size + 1 ; //flag the file is not fully loaded. } - U8* buffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), mBufferSize); + U8 * buffer = (U8 *) ALLOCATE_MEM(LLImageBase::getPrivatePool(), total_size); if (cur_size > 0) { memcpy(buffer, mFormattedImage->getData(), cur_size); } - memcpy(buffer + cur_size, mBuffer, mRequestedSize); // append + mHttpBufferArray->read(src_offset, (char *) buffer + cur_size, append_size); + // NOTE: setData releases current data and owns new data (buffer) - mFormattedImage->setData(buffer, mBufferSize); - // delete temp data - FREE_MEM(LLImageBase::getPrivatePool(), mBuffer); // Note: not 'buffer' (assigned in setData()) - mBuffer = NULL; - mBufferSize = 0; + mFormattedImage->setData(buffer, total_size); + + // Done with buffer array + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + mHttpReplySize = 0; + mHttpReplyOffset = 0; + mLoadedDiscard = mRequestedDiscard; mState = DECODE_IMAGE; - if(mWriteToCacheState != NOT_WRITE) + if (mWriteToCacheState != NOT_WRITE) { mWriteToCacheState = SHOULD_WRITE ; } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + releaseHttpSemaphore(); return false; } else { - // - //No need to timeout, the responder should be triggered automatically. - // + // *HISTORY: There was a texture timeout test here originally that + // would cancel a request that was over 120 seconds old. That's + // probably not a good idea. Particularly rich regions can take + // an enormous amount of time to load textures. We'll revisit the + // various possible timeout components (total request time, connection + // time, I/O time, with and without retries, etc.) in the future. + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return false; } @@ -1408,11 +1651,12 @@ bool LLTextureFetchWorker::doWork(S32 param) if (mState == DECODE_IMAGE) { static LLCachedControl<bool> textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled"); - if(textures_decode_disabled) + + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it + if (textures_decode_disabled) { // for debug use, don't decode mState = DONE; - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return true; } @@ -1420,7 +1664,6 @@ bool LLTextureFetchWorker::doWork(S32 param) { // We aborted, don't decode mState = DONE; - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return true; } @@ -1430,7 +1673,6 @@ bool LLTextureFetchWorker::doWork(S32 param) //abort, don't decode mState = DONE; - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return true; } if (mLoadedDiscard < 0) @@ -1439,10 +1681,9 @@ bool LLTextureFetchWorker::doWork(S32 param) //abort, don't decode mState = DONE; - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return true; } - setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it + mRawImage = NULL; mAuxImage = NULL; llassert_always(mFormattedImage.notNull()); @@ -1528,6 +1769,7 @@ bool LLTextureFetchWorker::doWork(S32 param) U32 cache_priority = mWorkPriority; mWritten = FALSE; mState = WAIT_ON_WRITE; + ++mCacheWriteCount; CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID); mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority, mFormattedImage->getData(), datasize, @@ -1572,9 +1814,84 @@ bool LLTextureFetchWorker::doWork(S32 param) } return false; -} +} // -Mw + +// Threads: Ttf +// virtual +void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + static LLCachedControl<bool> log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog"); + static LLCachedControl<bool> log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator"); + static LLCachedControl<bool> log_texture_traffic(gSavedSettings, "LogTextureNetworkTraffic") ; -// Called from MAIN thread + LLMutexLock lock(&mWorkMutex); // +Mw + + mHttpActive = false; + + if (log_to_viewer_log || log_to_sim) + { + U64 timeNow = LLTimer::getTotalTime(); + mFetcher->mTextureInfo.setRequestStartTime(mID, mMetricsStartTime); + mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP); + mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize); + mFetcher->mTextureInfo.setRequestOffset(mID, mRequestedOffset); + mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, timeNow); + } + + bool success = true; + bool partial = false; + LLCore::HttpStatus status(response->getStatus()); + + lldebugs << "HTTP COMPLETE: " << mID + << " status: " << status.toHex() + << " '" << status.toString() << "'" + << llendl; +// unsigned int offset(0), length(0), full_length(0); +// response->getRange(&offset, &length, &full_length); +// llwarns << "HTTP COMPLETE: " << mID << " handle: " << handle +// << " status: " << status.toULong() << " '" << status.toString() << "'" +// << " req offset: " << mRequestedOffset << " req length: " << mRequestedSize +// << " offset: " << offset << " length: " << length +// << llendl; + + if (! status) + { + success = false; + std::string reason(status.toString()); + setGetStatus(status, reason); + llwarns << "CURL GET FAILED, status: " << status.toHex() + << " reason: " << reason << llendl; + } + else + { + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + partial = (par_status == status); + } + + S32 data_size = callbackHttpGet(response, partial, success); + + if (log_texture_traffic && data_size > 0) + { + LLViewerTexture* tex = LLViewerTextureManager::findTexture(mID); + if (tex) + { + gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size ; + } + } + + mFetcher->removeFromHTTPQueue(mID, data_size); + + recordTextureDone(true); +} // -Mw + + +// Threads: Tmain void LLTextureFetchWorker::endWork(S32 param, bool aborted) { if (mDecodeHandle != 0) @@ -1587,6 +1904,8 @@ void LLTextureFetchWorker::endWork(S32 param, bool aborted) ////////////////////////////////////////////////////////////////////////////// +// Threads: Ttf + // virtual void LLTextureFetchWorker::finishWork(S32 param, bool completed) { @@ -1603,10 +1922,37 @@ void LLTextureFetchWorker::finishWork(S32 param, bool completed) } } +// LLQueuedThread's update() method is asking if it's okay to +// delete this worker. You'll notice we're not locking in here +// which is a slight concern. Caller is expected to have made +// this request 'quiet' by whatever means... +// +// Threads: Tmain + // virtual bool LLTextureFetchWorker::deleteOK() { bool delete_ok = true; + + if (mHttpActive) + { + // HTTP library has a pointer to this worker + // and will dereference it to do notification. + delete_ok = false; + } + + if (WAIT_HTTP_RESOURCE2 == mState) + { + if (mFetcher->isHttpWaiter(mID)) + { + // Don't delete the worker out from under the releaseHttpWaiters() + // method. Keep the pointers valid, clean up after that method + // has recognized the cancelation and removed the UUID from the + // waiter list. + delete_ok = false; + } + } + // Allow any pending reads or writes to complete if (mCacheReadHandle != LLTextureCache::nullHandle()) { @@ -1641,6 +1987,7 @@ bool LLTextureFetchWorker::deleteOK() return delete_ok; } +// Threads: Ttf void LLTextureFetchWorker::removeFromCache() { if (!mInLocalCache) @@ -1652,6 +1999,8 @@ void LLTextureFetchWorker::removeFromCache() ////////////////////////////////////////////////////////////////////////////// +// Threads: Ttf +// Locks: Mw bool LLTextureFetchWorker::processSimulatorPackets() { if (mFormattedImage.isNull() || mRequestedSize < 0) @@ -1712,14 +2061,13 @@ bool LLTextureFetchWorker::processSimulatorPackets() ////////////////////////////////////////////////////////////////////////////// -S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer, - bool partial, bool success) +// Threads: Ttf +// Locks: Mw +S32 LLTextureFetchWorker::callbackHttpGet(LLCore::HttpResponse * response, + bool partial, bool success) { S32 data_size = 0 ; - LLMutexLock lock(&mWorkMutex); - mHTTPHandle = 0; if (mState != WAIT_HTTP_REQ) { llwarns << "callbackHttpGet for unrequested fetch worker: " << mID @@ -1734,27 +2082,68 @@ S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels, if (success) { // get length of stream: - data_size = buffer->countAfter(channels.in(), NULL); - + LLCore::BufferArray * body(response->getBody()); + data_size = body ? body->size() : 0; + LL_DEBUGS("Texture") << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL; if (data_size > 0) { // *TODO: set the formatted image data here directly to avoid the copy - mBuffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), data_size); - buffer->readAfter(channels.in(), NULL, mBuffer, data_size); - mBufferSize += data_size; - if (mRequestedSize == 0) + + // Hold on to body for later copy + llassert_always(NULL == mHttpBufferArray); + body->addRef(); + mHttpBufferArray = body; + + if (partial) + { + unsigned int offset(0), length(0), full_length(0); + response->getRange(&offset, &length, &full_length); + if (! offset && ! length) + { + // This is the case where we receive a 206 status but + // there wasn't a useful Content-Range header in the response. + // This could be because it was badly formatted but is more + // likely due to capabilities services which scrub headers + // from responses. Assume we got what we asked for... + mHttpReplySize = data_size; + mHttpReplyOffset = mRequestedOffset; + } + else + { + mHttpReplySize = length; + mHttpReplyOffset = offset; + } + } + + if (! partial) + { + // Response indicates this is the entire asset regardless + // of our asking for a byte range. Mark it so and drop + // any partial data we might have so that the current + // response body becomes the entire dataset. + if (data_size <= mRequestedOffset) + { + LL_WARNS("Texture") << "Fetched entire texture " << mID + << " when it was expected to be marked complete. mImageSize: " + << mFileSize << " datasize: " << mFormattedImage->getDataSize() + << LL_ENDL; + } + mHaveAllData = TRUE; + llassert_always(mDecodeHandle == 0); + mFormattedImage = NULL; // discard any previous data we had + } + else if (data_size < mRequestedSize) { mHaveAllData = TRUE; } else if (data_size > mRequestedSize) { - // *TODO: This shouldn't be happening any more + // *TODO: This shouldn't be happening any more (REALLY don't expect this anymore) llwarns << "data_size = " << data_size << " > requested: " << mRequestedSize << llendl; mHaveAllData = TRUE; llassert_always(mDecodeHandle == 0); mFormattedImage = NULL; // discard any previous data we had - mBufferSize = data_size; } } else @@ -1778,10 +2167,11 @@ S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels, ////////////////////////////////////////////////////////////////////////////// +// Threads: Ttc void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image, S32 imagesize, BOOL islocal) { - LLMutexLock lock(&mWorkMutex); + LLMutexLock lock(&mWorkMutex); // +Mw if (mState != LOAD_FROM_TEXTURE_CACHE) { // llwarns << "Read callback for " << mID << " with state = " << mState << llendl; @@ -1801,11 +2191,12 @@ void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* ima } mLoaded = TRUE; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); -} +} // -Mw +// Threads: Ttc void LLTextureFetchWorker::callbackCacheWrite(bool success) { - LLMutexLock lock(&mWorkMutex); + LLMutexLock lock(&mWorkMutex); // +Mw if (mState != WAIT_ON_WRITE) { // llwarns << "Write callback for " << mID << " with state = " << mState << llendl; @@ -1813,13 +2204,14 @@ void LLTextureFetchWorker::callbackCacheWrite(bool success) } mWritten = TRUE; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); -} +} // -Mw ////////////////////////////////////////////////////////////////////////////// +// Threads: Tid void LLTextureFetchWorker::callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux) { - LLMutexLock lock(&mWorkMutex); + LLMutexLock lock(&mWorkMutex); // +Mw if (mDecodeHandle == 0) { return; // aborted, ignore @@ -1852,10 +2244,11 @@ void LLTextureFetchWorker::callbackDecoded(bool success, LLImageRaw* raw, LLImag // llinfos << mID << " : DECODE COMPLETE " << llendl; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); mCacheReadTime = mCacheReadTimer.getElapsedTimeF32(); -} +} // -Mw ////////////////////////////////////////////////////////////////////////////// +// Threads: Ttf bool LLTextureFetchWorker::writeToCacheComplete() { // Complete write to cache @@ -1878,6 +2271,36 @@ bool LLTextureFetchWorker::writeToCacheComplete() } +// Threads: Ttf +void LLTextureFetchWorker::recordTextureStart(bool is_http) +{ + if (! mMetricsStartTime) + { + mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, + is_http, + LLImageBase::TYPE_AVATAR_BAKE == mType); +} + + +// Threads: Ttf +void LLTextureFetchWorker::recordTextureDone(bool is_http) +{ + if (mMetricsStartTime) + { + LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, + is_http, + LLImageBase::TYPE_AVATAR_BAKE == mType, + LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime); + mMetricsStartTime = 0; + } + LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, + is_http, + LLImageBase::TYPE_AVATAR_BAKE == mType); +} + + ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // public @@ -1893,14 +2316,23 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mTextureCache(cache), mImageDecodeThread(imagedecodethread), mTextureBandwidth(0), - mCurlGetRequest(NULL), + mHTTPTextureBits(0), + mTotalHTTPRequests(0), mQAMode(qa_mode), + mHttpRequest(NULL), + mHttpOptions(NULL), + mHttpHeaders(NULL), + mHttpMetricsHeaders(NULL), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpSemaphore(HTTP_REQUESTS_IN_QUEUE_HIGH_WATER), + mTotalCacheReadCount(0U), + mTotalCacheWriteCount(0U), + mTotalResourceWaitCount(0U), mFetchDebugger(NULL), mFetchSource(LLTextureFetch::FROM_ALL), mOriginFetchSource(LLTextureFetch::FROM_ALL), mFetcherLocked(FALSE) { - mCurlPOSTRequestCount = 0; mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold")); @@ -1916,11 +2348,19 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image } mOriginFetchSource = mFetchSource; } + + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = new LLCore::HttpOptions; + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHttpMetricsHeaders = new LLCore::HttpHeaders; + mHttpMetricsHeaders->mHeaders.push_back("Content-Type: application/llsd+xml"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyDefault(); } LLTextureFetch::~LLTextureFetch() { - clearDeleteList() ; + clearDeleteList(); while (! mCommands.empty()) { @@ -1928,10 +2368,34 @@ LLTextureFetch::~LLTextureFetch() mCommands.erase(mCommands.begin()); delete req; } + + if (mHttpOptions) + { + mHttpOptions->release(); + mHttpOptions = NULL; + } + + if (mHttpHeaders) + { + mHttpHeaders->release(); + mHttpHeaders = NULL; + } + + if (mHttpMetricsHeaders) + { + mHttpMetricsHeaders->release(); + mHttpMetricsHeaders = NULL; + } + + mHttpWaitResource.clear(); - // ~LLQueuedThread() called here + delete mHttpRequest; + mHttpRequest = NULL; delete mFetchDebugger; + mFetchDebugger = NULL; + + // ~LLQueuedThread() called here } bool LLTextureFetch::createRequest(const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, @@ -1997,7 +2461,7 @@ bool LLTextureFetch::createRequest(const std::string& url, const LLUUID& id, con { return false; // need to wait for previous aborted request to complete } - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw worker->mActiveCount++; worker->mNeedsAux = needs_aux; worker->setImagePriority(priority); @@ -2006,41 +2470,44 @@ bool LLTextureFetch::createRequest(const std::string& url, const LLUUID& id, con if (!worker->haveWork()) { worker->mState = LLTextureFetchWorker::INIT; - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); } else { - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw } } else { worker = new LLTextureFetchWorker(this, url, id, host, priority, desired_discard, desired_size); - lockQueue() ; + lockQueue(); // +Mfq mRequestMap[id] = worker; - unlockQueue() ; + unlockQueue(); // -Mfq - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw worker->mActiveCount++; worker->mNeedsAux = needs_aux; worker->setCanUseHTTP(can_use_http) ; - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw } // llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl; return true; } + +// Threads: T* (but Ttf in practice) + // protected void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker) { - lockQueue() ; + lockQueue(); // +Mfq bool in_request_map = (mRequestMap.find(worker->mID) != mRequestMap.end()) ; - unlockQueue() ; + unlockQueue(); // -Mfq - LLMutexLock lock(&mNetworkQueueMutex); + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq if (in_request_map) { // only add to the queue if in the request map @@ -2052,27 +2519,68 @@ void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker) { iter1->second.erase(worker->mID); } -} +} // -Mfnq +// Threads: T* void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel) { - LLMutexLock lock(&mNetworkQueueMutex); + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq size_t erased = mNetworkQueue.erase(worker->mID); if (cancel && erased > 0) { mCancelQueue[worker->mHost].insert(worker->mID); } -} +} // -Mfnq +// Threads: T* +// +// protected +void LLTextureFetch::addToHTTPQueue(const LLUUID& id) +{ + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq + mHTTPTextureQueue.insert(id); + mTotalHTTPRequests++; +} // -Mfnq + +// Threads: T* +void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32 received_size) +{ + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq + mHTTPTextureQueue.erase(id); + mHTTPTextureBits += received_size * 8; // Approximate - does not include header bits +} // -Mfnq + +// NB: If you change deleteRequest() you should probably make +// parallel changes in removeRequest(). They're functionally +// identical with only argument variations. +// +// Threads: T* void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel) { - lockQueue() ; + lockQueue(); // +Mfq LLTextureFetchWorker* worker = getWorkerAfterLock(id); - unlockQueue() ; + if (worker) + { + size_t erased_1 = mRequestMap.erase(worker->mID); + unlockQueue(); // -Mfq - removeRequest(worker, cancel); + llassert_always(erased_1 > 0) ; + removeFromNetworkQueue(worker, cancel); + llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ; + + worker->scheduleDelete(); + } + else + { + unlockQueue(); // -Mfq + } } +// NB: If you change removeRequest() you should probably make +// parallel changes in deleteRequest(). They're functionally +// identical with only argument variations. +// +// Threads: T* void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel) { if(!worker) @@ -2080,15 +2588,14 @@ void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel) return; } - lockQueue() ; + lockQueue(); // +Mfq size_t erased_1 = mRequestMap.erase(worker->mID); - unlockQueue() ; + unlockQueue(); // -Mfq llassert_always(erased_1 > 0) ; removeFromNetworkQueue(worker, cancel); llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ; - worker->removeFromHTTPQueue(); worker->scheduleDelete(); } @@ -2110,16 +2617,39 @@ void LLTextureFetch::deleteAllRequests() } } +// Threads: T* S32 LLTextureFetch::getNumRequests() { - lockQueue() ; + lockQueue(); // +Mfq S32 size = (S32)mRequestMap.size(); - unlockQueue() ; + unlockQueue(); // -Mfq - return size ; + return size; +} + +// Threads: T* +S32 LLTextureFetch::getNumHTTPRequests() +{ + mNetworkQueueMutex.lock(); // +Mfq + S32 size = (S32)mHTTPTextureQueue.size(); + mNetworkQueueMutex.unlock(); // -Mfq + + return size; +} + +// Threads: T* +U32 LLTextureFetch::getTotalNumHTTPRequests() +{ + mNetworkQueueMutex.lock(); // +Mfq + U32 size = mTotalHTTPRequests; + mNetworkQueueMutex.unlock(); // -Mfq + + return size; } // call lockQueue() first! +// Threads: T* +// Locks: Mfq LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id) { LLTextureFetchWorker* res = NULL; @@ -2131,14 +2661,16 @@ LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id) return res; } +// Threads: T* LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id) { - LLMutexLock lock(&mQueueMutex) ; + LLMutexLock lock(&mQueueMutex); // +Mfq - return getWorkerAfterLock(id) ; -} + return getWorkerAfterLock(id); +} // -Mfq +// Threads: T* bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux) { @@ -2161,7 +2693,7 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, } else if (worker->checkWork()) { - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw discard_level = worker->mDecodedDiscard; raw = worker->mRawImage; aux = worker->mAuxImage; @@ -2172,11 +2704,11 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, } res = true; LL_DEBUGS("Texture") << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL; - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw } else { - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw if ((worker->mDecodedDiscard >= 0) && (worker->mDecodedDiscard < discard_level || discard_level < 0) && (worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE)) @@ -2186,7 +2718,7 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, raw = worker->mRawImage; aux = worker->mAuxImage; } - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw } } else @@ -2196,15 +2728,16 @@ bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, return res; } +// Threads: T* bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority) { bool res = false; LLTextureFetchWorker* worker = getWorker(id); if (worker) { - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw worker->setImagePriority(priority); - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw res = true; } return res; @@ -2219,24 +2752,24 @@ bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority) // in step, at least until this can be refactored and // the redundancy eliminated. // -// May be called from any thread +// Threads: T* //virtual S32 LLTextureFetch::getPending() { S32 res; - lockData(); + lockData(); // +Ct { - LLMutexLock lock(&mQueueMutex); + LLMutexLock lock(&mQueueMutex); // +Mfq res = mRequestQueue.size(); - res += mCurlPOSTRequestCount; res += mCommands.size(); - } - unlockData(); + } // -Mfq + unlockData(); // -Ct return res; } +// Locks: Ct // virtual bool LLTextureFetch::runCondition() { @@ -2251,42 +2784,53 @@ bool LLTextureFetch::runCondition() bool have_no_commands(false); { - LLMutexLock lock(&mQueueMutex); + LLMutexLock lock(&mQueueMutex); // +Mfq have_no_commands = mCommands.empty(); - } - - bool have_no_curl_requests(0 == mCurlPOSTRequestCount); + } // -Mfq return ! (have_no_commands - && have_no_curl_requests && (mRequestQueue.empty() && mIdleThread)); // From base class } ////////////////////////////////////////////////////////////////////////////// -// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs) +// Threads: Ttf void LLTextureFetch::commonUpdate() { + // Release waiters + releaseHttpWaiters(); + // Run a cross-thread command, if any. cmdDoWork(); - // Update Curl on same thread as mCurlGetRequest was constructed - S32 processed = mCurlGetRequest->process(); - if (processed > 0) + // Deliver all completion notifications + LLCore::HttpStatus status = mHttpRequest->update(0); + if (! status) { - lldebugs << "processed: " << processed << " messages." << llendl; + LL_INFOS_ONCE("Texture") << "Problem during HTTP servicing. Reason: " + << status.toString() + << LL_ENDL; } } -// MAIN THREAD +// Threads: Tmain + //virtual S32 LLTextureFetch::update(F32 max_time_ms) { static LLCachedControl<F32> band_width(gSavedSettings,"ThrottleBandwidthKBPS"); - mMaxBandwidth = band_width ; + { + mNetworkQueueMutex.lock(); // +Mfnq + mMaxBandwidth = band_width; + + gTextureList.sTextureBits += mHTTPTextureBits; + mHTTPTextureBits = 0; + + mNetworkQueueMutex.unlock(); // -Mfnq + } S32 res = LLWorkerThread::update(max_time_ms); @@ -2304,19 +2848,9 @@ S32 LLTextureFetch::update(F32 max_time_ms) if (!mThreaded) { commonUpdate(); - - if(mCurlGetRequest) - { - mCurlGetRequest->nextRequests(); - } - } - - if(mCurlGetRequest) - { - gTextureList.sTextureBits += mCurlGetRequest->getTotalReceivedBits(); } - if(mFetchDebugger) + if (mFetchDebugger) { mFetchDebugger->tryToStopDebug(); //check if need to stop debugger. } @@ -2324,7 +2858,9 @@ S32 LLTextureFetch::update(F32 max_time_ms) return res; } -//called in the MAIN thread after the TextureCacheThread shuts down. +// called in the MAIN thread after the TextureCacheThread shuts down. +// +// Threads: Tmain void LLTextureFetch::shutDownTextureCacheThread() { if(mTextureCache) @@ -2334,7 +2870,9 @@ void LLTextureFetch::shutDownTextureCacheThread() } } -//called in the MAIN thread after the ImageDecodeThread shuts down. +// called in the MAIN thread after the ImageDecodeThread shuts down. +// +// Threads: Tmain void LLTextureFetch::shutDownImageDecodeThread() { if(mImageDecodeThread) @@ -2344,37 +2882,27 @@ void LLTextureFetch::shutDownImageDecodeThread() } } -// WORKER THREAD +// Threads: Ttf void LLTextureFetch::startThread() { - // Construct mCurlGetRequest from Worker Thread - mCurlGetRequest = new LLCurlTextureRequest(8); - - if(mFetchDebugger) - { - mFetchDebugger->setCurlGetRequest(mCurlGetRequest); - } } -// WORKER THREAD +// Threads: Ttf void LLTextureFetch::endThread() { - // Destroy mCurlGetRequest from Worker Thread - delete mCurlGetRequest; - mCurlGetRequest = NULL; - if(mFetchDebugger) - { - mFetchDebugger->setCurlGetRequest(NULL); - } + LL_INFOS("Texture") << "CacheReads: " << mTotalCacheReadCount + << ", CacheWrites: " << mTotalCacheWriteCount + << ", ResWaits: " << mTotalResourceWaitCount + << ", TotalHTTPReq: " << getTotalNumHTTPRequests() + << LL_ENDL; } -// WORKER THREAD +// Threads: Ttf void LLTextureFetch::threadedUpdate() { - llassert_always(mCurlGetRequest); - - mCurlGetRequest->nextRequests(); + llassert_always(mHttpRequest); +#if 0 // Limit update frequency const F32 PROCESS_TIME = 0.05f; static LLFrameTimer process_timer; @@ -2383,9 +2911,10 @@ void LLTextureFetch::threadedUpdate() return; } process_timer.reset(); +#endif commonUpdate(); - + #if 0 const F32 INFO_TIME = 1.0f; static LLFrameTimer info_timer; @@ -2399,11 +2928,11 @@ void LLTextureFetch::threadedUpdate() } } #endif - } ////////////////////////////////////////////////////////////////////////////// +// Threads: Tmain void LLTextureFetch::sendRequestListToSimulators() { // All requests @@ -2429,48 +2958,48 @@ void LLTextureFetch::sendRequestListToSimulators() typedef std::map< LLHost, request_list_t > work_request_map_t; work_request_map_t requests; { - LLMutexLock lock2(&mNetworkQueueMutex); - for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); ) - { - queue_t::iterator curiter = iter++; - LLTextureFetchWorker* req = getWorker(*curiter); - if (!req) + LLMutexLock lock2(&mNetworkQueueMutex); // +Mfnq + for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); ) { - mNetworkQueue.erase(curiter); - continue; // paranoia - } - if ((req->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK) && - (req->mState != LLTextureFetchWorker::LOAD_FROM_SIMULATOR)) - { - // We already received our URL, remove from the queue - llwarns << "Worker: " << req->mID << " in mNetworkQueue but in wrong state: " << req->mState << llendl; - mNetworkQueue.erase(curiter); - continue; - } - if (req->mID == mDebugID) - { - mDebugCount++; // for setting breakpoints - } - if (req->mSentRequest == LLTextureFetchWorker::SENT_SIM && - req->mTotalPackets > 0 && - req->mLastPacket >= req->mTotalPackets-1) - { - // We have all the packets... make sure this is high priority + queue_t::iterator curiter = iter++; + LLTextureFetchWorker* req = getWorker(*curiter); + if (!req) + { + mNetworkQueue.erase(curiter); + continue; // paranoia + } + if ((req->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK) && + (req->mState != LLTextureFetchWorker::LOAD_FROM_SIMULATOR)) + { + // We already received our URL, remove from the queue + llwarns << "Worker: " << req->mID << " in mNetworkQueue but in wrong state: " << req->mState << llendl; + mNetworkQueue.erase(curiter); + continue; + } + if (req->mID == mDebugID) + { + mDebugCount++; // for setting breakpoints + } + if (req->mSentRequest == LLTextureFetchWorker::SENT_SIM && + req->mTotalPackets > 0 && + req->mLastPacket >= req->mTotalPackets-1) + { + // We have all the packets... make sure this is high priority // req->setPriority(LLWorkerThread::PRIORITY_HIGH | req->mWorkPriority); - continue; - } - F32 elapsed = req->mRequestedTimer.getElapsedTimeF32(); - { - F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority); - if ((req->mSimRequestedDiscard != req->mDesiredDiscard) || - (delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) || - (elapsed >= SIM_LAZY_FLUSH_TIMEOUT)) + continue; + } + F32 elapsed = req->mRequestedTimer.getElapsedTimeF32(); { - requests[req->mHost].insert(req); + F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority); + if ((req->mSimRequestedDiscard != req->mDesiredDiscard) || + (delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) || + (elapsed >= SIM_LAZY_FLUSH_TIMEOUT)) + { + requests[req->mHost].insert(req); + } } } - } - } + } // -Mfnq for (work_request_map_t::iterator iter1 = requests.begin(); iter1 != requests.end(); ++iter1) @@ -2493,9 +3022,9 @@ void LLTextureFetch::sendRequestListToSimulators() if (req->mSentRequest != LLTextureFetchWorker::SENT_SIM) { // Initialize packet data based on data read from cache - req->lockWorkMutex(); + req->lockWorkMutex(); // +Mw req->setupPacketData(); - req->unlockWorkMutex(); + req->unlockWorkMutex(); // -Mw } if (0 == sim_request_count) { @@ -2524,12 +3053,12 @@ void LLTextureFetch::sendRequestListToSimulators() mTextureInfo.setRequestType(req->mID, LLTextureInfoDetails::REQUEST_TYPE_UDP); } - req->lockWorkMutex(); + req->lockWorkMutex(); // +Mw req->mSentRequest = LLTextureFetchWorker::SENT_SIM; req->mSimRequestedDiscard = req->mDesiredDiscard; req->mRequestedPriority = req->mImagePriority; req->mRequestedTimer.reset(); - req->unlockWorkMutex(); + req->unlockWorkMutex(); // -Mw sim_request_count++; if (sim_request_count >= IMAGES_PER_REQUEST) { @@ -2550,55 +3079,57 @@ void LLTextureFetch::sendRequestListToSimulators() // Send cancelations { - LLMutexLock lock2(&mNetworkQueueMutex); - if (gMessageSystem && !mCancelQueue.empty()) - { - for (cancel_queue_t::iterator iter1 = mCancelQueue.begin(); - iter1 != mCancelQueue.end(); ++iter1) + LLMutexLock lock2(&mNetworkQueueMutex); // +Mfnq + if (gMessageSystem && !mCancelQueue.empty()) { - LLHost host = iter1->first; - if (host == LLHost::invalid) - { - host = gAgent.getRegionHost(); - } - S32 request_count = 0; - for (queue_t::iterator iter2 = iter1->second.begin(); - iter2 != iter1->second.end(); ++iter2) + for (cancel_queue_t::iterator iter1 = mCancelQueue.begin(); + iter1 != mCancelQueue.end(); ++iter1) { - if (0 == request_count) + LLHost host = iter1->first; + if (host == LLHost::invalid) { - gMessageSystem->newMessageFast(_PREHASH_RequestImage); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + host = gAgent.getRegionHost(); } - gMessageSystem->nextBlockFast(_PREHASH_RequestImage); - gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2); - gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1); - gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0); - gMessageSystem->addU32Fast(_PREHASH_Packet, 0); - gMessageSystem->addU8Fast(_PREHASH_Type, 0); + S32 request_count = 0; + for (queue_t::iterator iter2 = iter1->second.begin(); + iter2 != iter1->second.end(); ++iter2) + { + if (0 == request_count) + { + gMessageSystem->newMessageFast(_PREHASH_RequestImage); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + } + gMessageSystem->nextBlockFast(_PREHASH_RequestImage); + gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2); + gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1); + gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0); + gMessageSystem->addU32Fast(_PREHASH_Packet, 0); + gMessageSystem->addU8Fast(_PREHASH_Type, 0); // llinfos << "CANCELING IMAGE REQUEST: " << (*iter2) << llendl; - request_count++; - if (request_count >= IMAGES_PER_REQUEST) + request_count++; + if (request_count >= IMAGES_PER_REQUEST) + { + gMessageSystem->sendSemiReliable(host, NULL, NULL); + request_count = 0; + } + } + if (request_count > 0 && request_count < IMAGES_PER_REQUEST) { gMessageSystem->sendSemiReliable(host, NULL, NULL); - request_count = 0; } } - if (request_count > 0 && request_count < IMAGES_PER_REQUEST) - { - gMessageSystem->sendSemiReliable(host, NULL, NULL); - } + mCancelQueue.clear(); } - mCancelQueue.clear(); - } - } + } // -Mfnq } ////////////////////////////////////////////////////////////////////////////// +// Threads: T* +// Locks: Mw bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size) { mRequestedTimer.reset(); @@ -2631,6 +3162,7 @@ bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size) return true; } +// Threads: T* bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes, U16 data_size, U8* data) { @@ -2665,14 +3197,14 @@ bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 } if (!res) { + mNetworkQueueMutex.lock(); // +Mfnq ++mBadPacketCount; - mNetworkQueueMutex.lock() ; mCancelQueue[host].insert(id); - mNetworkQueueMutex.unlock() ; + mNetworkQueueMutex.unlock(); // -Mfnq return false; } - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw // Copy header data into image object worker->mImageCodec = codec; @@ -2683,10 +3215,12 @@ bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 res = worker->insertPacket(0, data, data_size); worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR; - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw return res; } + +// Threads: T* bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data) { LLTextureFetchWorker* worker = getWorker(id); @@ -2711,14 +3245,14 @@ bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U1 } if (!res) { + mNetworkQueueMutex.lock(); // +Mfnq ++mBadPacketCount; - mNetworkQueueMutex.lock() ; mCancelQueue[host].insert(id); - mNetworkQueueMutex.unlock() ; + mNetworkQueueMutex.unlock(); // -Mfnq return false; } - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw res = worker->insertPacket(packet_num, data, data_size); @@ -2735,7 +3269,7 @@ bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U1 removeFromNetworkQueue(worker, true); // failsafe } - if(packet_num >= (worker->mTotalPackets - 1)) + if (packet_num >= (worker->mTotalPackets - 1)) { static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog"); static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator"); @@ -2747,12 +3281,14 @@ bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U1 mTextureInfo.setRequestCompleteTimeAndLog(id, timeNow); } } - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw return res; } ////////////////////////////////////////////////////////////////////////////// + +// Threads: T* BOOL LLTextureFetch::isFromLocalCache(const LLUUID& id) { BOOL from_cache = FALSE ; @@ -2760,14 +3296,15 @@ BOOL LLTextureFetch::isFromLocalCache(const LLUUID& id) LLTextureFetchWorker* worker = getWorker(id); if (worker) { - worker->lockWorkMutex() ; - from_cache = worker->mInLocalCache ; - worker->unlockWorkMutex() ; + worker->lockWorkMutex(); // +Mw + from_cache = worker->mInLocalCache; + worker->unlockWorkMutex(); // -Mw } return from_cache ; } +// Threads: T* S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p, U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http) { @@ -2781,7 +3318,7 @@ S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& r LLTextureFetchWorker* worker = getWorker(id); if (worker && worker->haveWork()) { - worker->lockWorkMutex(); + worker->lockWorkMutex(); // +Mw state = worker->mState; fetch_dtime = worker->mFetchTimer.getElapsedTimeF32(); request_dtime = worker->mRequestedTimer.getElapsedTimeF32(); @@ -2808,7 +3345,7 @@ S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& r } fetch_priority = worker->getPriority(); can_use_http = worker->getCanUseHTTP() ; - worker->unlockWorkMutex(); + worker->unlockWorkMutex(); // -Mw } data_progress_p = data_progress; requested_priority_p = requested_priority; @@ -2832,12 +3369,219 @@ void LLTextureFetch::dump() << " STATE: " << worker->sStateDescs[worker->mState] << llendl; } + + llinfos << "LLTextureFetch ACTIVE_HTTP:" << llendl; + for (queue_t::const_iterator iter(mHTTPTextureQueue.begin()); + mHTTPTextureQueue.end() != iter; + ++iter) + { + llinfos << " ID: " << (*iter) << llendl; + } + + llinfos << "LLTextureFetch WAIT_HTTP_RESOURCE:" << llendl; + for (wait_http_res_queue_t::const_iterator iter(mHttpWaitResource.begin()); + mHttpWaitResource.end() != iter; + ++iter) + { + llinfos << " ID: " << (*iter) << llendl; + } +} + +////////////////////////////////////////////////////////////////////////////// + +// HTTP Resource Waiting Methods + +// Threads: Ttf +void LLTextureFetch::addHttpWaiter(const LLUUID & tid) +{ + mNetworkQueueMutex.lock(); // +Mfnq + mHttpWaitResource.insert(tid); + mNetworkQueueMutex.unlock(); // -Mfnq +} + +// Threads: Ttf +void LLTextureFetch::removeHttpWaiter(const LLUUID & tid) +{ + mNetworkQueueMutex.lock(); // +Mfnq + wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid)); + if (mHttpWaitResource.end() != iter) + { + mHttpWaitResource.erase(iter); + } + mNetworkQueueMutex.unlock(); // -Mfnq +} + +// Threads: T* +bool LLTextureFetch::isHttpWaiter(const LLUUID & tid) +{ + mNetworkQueueMutex.lock(); // +Mfnq + wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid)); + const bool ret(mHttpWaitResource.end() != iter); + mNetworkQueueMutex.unlock(); // -Mfnq + return ret; +} + +// Release as many requests as permitted from the WAIT_HTTP_RESOURCE2 +// state to the SEND_HTTP_REQ state based on their current priority. +// +// This data structures and code associated with this looks a bit +// indirect and naive but it's done in the name of safety. An +// ordered container may become invalid from time to time due to +// priority changes caused by actions in other threads. State itself +// could also suffer the same fate with canceled operations. Even +// done this way, I'm not fully trusting we're truly safe. This +// module is due for a major refactoring and we'll deal with it then. +// +// Threads: Ttf +// Locks: -Mw (must not hold any worker when called) +void LLTextureFetch::releaseHttpWaiters() +{ + // Use mHttpSemaphore rather than mHTTPTextureQueue.size() + // to avoid a lock. + if (mHttpSemaphore < (HTTP_REQUESTS_IN_QUEUE_HIGH_WATER - HTTP_REQUESTS_IN_QUEUE_LOW_WATER)) + return; + + // Quickly make a copy of all the LLUIDs. Get off the + // mutex as early as possible. + typedef std::vector<LLUUID> uuid_vec_t; + uuid_vec_t tids; + + { + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq + + if (mHttpWaitResource.empty()) + return; + tids.reserve(mHttpWaitResource.size()); + tids.assign(mHttpWaitResource.begin(), mHttpWaitResource.end()); + } // -Mfnq + + // Now lookup the UUUIDs to find valid requests and sort + // them in priority order, highest to lowest. We're going + // to modify priority later as a side-effect of releasing + // these objects. That, in turn, would violate the partial + // ordering assumption of std::set, std::map, etc. so we + // don't use those containers. We use a vector and an explicit + // sort to keep the containers valid later. + typedef std::vector<LLTextureFetchWorker *> worker_list_t; + worker_list_t tids2; + + tids2.reserve(tids.size()); + for (uuid_vec_t::iterator iter(tids.begin()); + tids.end() != iter; + ++iter) + { + LLTextureFetchWorker * worker(getWorker(* iter)); + if (worker) + { + tids2.push_back(worker); + } + else + { + // If worker isn't found, this should be due to a request + // for deletion. We signal our recognition that this + // uuid shouldn't be used for resource waiting anymore by + // erasing it from the resource waiter list. That allows + // deleteOK to do final deletion on the worker. + removeHttpWaiter(* iter); + } + } + tids.clear(); + + // Sort into priority order, if necessary and only as much as needed + if (tids2.size() > mHttpSemaphore) + { + LLTextureFetchWorker::Compare compare; + std::partial_sort(tids2.begin(), tids2.begin() + mHttpSemaphore, tids2.end(), compare); + } + + // Release workers up to the high water mark. Since we aren't + // holding any locks at this point, we can be in competition + // with other callers. Do defensive things like getting + // refreshed counts of requests and checking if someone else + // has moved any worker state around.... + for (worker_list_t::iterator iter2(tids2.begin()); tids2.end() != iter2; ++iter2) + { + LLTextureFetchWorker * worker(* iter2); + + worker->lockWorkMutex(); // +Mw + if (LLTextureFetchWorker::WAIT_HTTP_RESOURCE2 != worker->mState) + { + // Not in expected state, remove it, try the next one + worker->unlockWorkMutex(); // -Mw + LL_WARNS("Texture") << "Resource-waited texture " << worker->mID + << " in unexpected state: " << worker->mState + << ". Removing from wait list." + << LL_ENDL; + removeHttpWaiter(worker->mID); + continue; + } + + if (! worker->acquireHttpSemaphore()) + { + // Out of active slots, quit + worker->unlockWorkMutex(); // -Mw + break; + } + + worker->mState = LLTextureFetchWorker::SEND_HTTP_REQ; + worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority); + worker->unlockWorkMutex(); // -Mw + + removeHttpWaiter(worker->mID); + } +} + +// Threads: T* +void LLTextureFetch::cancelHttpWaiters() +{ + mNetworkQueueMutex.lock(); // +Mfnq + mHttpWaitResource.clear(); + mNetworkQueueMutex.unlock(); // -Mfnq +} + +// Threads: T* +int LLTextureFetch::getHttpWaitersCount() +{ + mNetworkQueueMutex.lock(); // +Mfnq + int ret(mHttpWaitResource.size()); + mNetworkQueueMutex.unlock(); // -Mfnq + return ret; +} + + +// Threads: T* +void LLTextureFetch::updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait) +{ + LLMutexLock lock(&mQueueMutex); // +Mfq + + mTotalCacheReadCount += cache_read; + mTotalCacheWriteCount += cache_write; + mTotalResourceWaitCount += res_wait; +} // -Mfq + + +// Threads: T* +void LLTextureFetch::getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait) +{ + U32 ret1(0U), ret2(0U), ret3(0U); + + { + LLMutexLock lock(&mQueueMutex); // +Mfq + ret1 = mTotalCacheReadCount; + ret2 = mTotalCacheWriteCount; + ret3 = mTotalResourceWaitCount; + } // -Mfq + + *cache_read = ret1; + *cache_write = ret2; + *res_wait = ret3; } ////////////////////////////////////////////////////////////////////////////// // cross-thread command methods +// Threads: T* void LLTextureFetch::commandSetRegion(U64 region_handle) { TFReqSetRegion * req = new TFReqSetRegion(region_handle); @@ -2845,6 +3589,7 @@ void LLTextureFetch::commandSetRegion(U64 region_handle) cmdEnqueue(req); } +// Threads: T* void LLTextureFetch::commandSendMetrics(const std::string & caps_url, const LLUUID & session_id, const LLUUID & agent_id, @@ -2855,6 +3600,7 @@ void LLTextureFetch::commandSendMetrics(const std::string & caps_url, cmdEnqueue(req); } +// Threads: T* void LLTextureFetch::commandDataBreak() { // The pedantically correct way to implement this is to create a command @@ -2865,30 +3611,33 @@ void LLTextureFetch::commandDataBreak() LLTextureFetch::svMetricsDataBreak = true; } +// Threads: T* void LLTextureFetch::cmdEnqueue(TFRequest * req) { - lockQueue(); + lockQueue(); // +Mfq mCommands.push_back(req); - unlockQueue(); + unlockQueue(); // -Mfq unpause(); } +// Threads: T* LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue() { TFRequest * ret = 0; - lockQueue(); + lockQueue(); // +Mfq if (! mCommands.empty()) { ret = mCommands.front(); mCommands.erase(mCommands.begin()); } - unlockQueue(); + unlockQueue(); // -Mfq return ret; } +// Threads: Ttf void LLTextureFetch::cmdDoWork() { if (mDebugPause) @@ -2912,6 +3661,37 @@ void LLTextureFetch::cmdDoWork() namespace { + +// Example of a simple notification handler for metrics +// delivery notification. Earlier versions of the code used +// a Responder that tried harder to detect delivery breaks +// but it really isn't that important. If someone wants to +// revisit that effort, here is a place to start. +class AssetReportHandler : public LLCore::HttpHandler +{ +public: + + // Threads: Ttf + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) + { + LLCore::HttpStatus status(response->getStatus()); + + if (status) + { + LL_WARNS("Texture") << "Successfully delivered asset metrics to grid." + << LL_ENDL; + } + else + { + LL_WARNS("Texture") << "Error delivering asset metrics to grid. Reason: " + << status.toString() << LL_ENDL; + } + } +}; // end class AssetReportHandler + +AssetReportHandler stats_handler; + + /** * Implements the 'Set Region' command. * @@ -2942,75 +3722,8 @@ TFReqSendMetrics::~TFReqSendMetrics() bool TFReqSendMetrics::doWork(LLTextureFetch * fetcher) { - /* - * HTTP POST responder. Doesn't do much but tries to - * detect simple breaks in recording the metrics stream. - * - * The 'volatile' modifiers don't indicate signals, - * mmap'd memory or threads, really. They indicate that - * the referenced data is part of a pseudo-closure for - * this responder rather than being required for correct - * operation. - * - * We don't try very hard with the POST request. We give - * it one shot and that's more-or-less it. With a proper - * refactoring of the LLQueuedThread usage, these POSTs - * could be put in a request object and made more reliable. - */ - class lcl_responder : public LLCurl::Responder - { - public: - lcl_responder(LLTextureFetch * fetcher, - S32 expected_sequence, - volatile const S32 & live_sequence, - volatile bool & reporting_break, - volatile bool & reporting_started) - : LLCurl::Responder(), - mFetcher(fetcher), - mExpectedSequence(expected_sequence), - mLiveSequence(live_sequence), - mReportingBreak(reporting_break), - mReportingStarted(reporting_started) - { - mFetcher->incrCurlPOSTCount(); - } - - ~lcl_responder() - { - LL_CHECK_MEMORY - mFetcher->decrCurlPOSTCount(); - LL_CHECK_MEMORY - } - - // virtual - void error(U32 status_num, const std::string & reason) - { - if (mLiveSequence == mExpectedSequence) - { - mReportingBreak = true; - } - LL_WARNS("Texture") << "Break in metrics stream due to POST failure to metrics collection service. Reason: " - << reason << LL_ENDL; - } - - // virtual - void result(const LLSD & content) - { - if (mLiveSequence == mExpectedSequence) - { - mReportingBreak = false; - mReportingStarted = true; - } - } - - private: - LLTextureFetch * mFetcher; - S32 mExpectedSequence; - volatile const S32 & mLiveSequence; - volatile bool & mReportingBreak; - volatile bool & mReportingStarted; - - }; // class lcl_responder + static const U32 report_priority(1); + static LLCore::HttpHandler * const handler(fetcher->isQAMode() || true ? &stats_handler : NULL); if (! gViewerAssetStatsThread1) return true; @@ -3038,21 +3751,26 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher) // Update sequence number if (S32_MAX == ++report_sequence) report_sequence = 0; - + reporting_started = true; + // Limit the size of the stats report if necessary. merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd); if (! mCapsURL.empty()) { - LLCurlRequest::headers_t headers; - fetcher->getCurlRequest().post(mCapsURL, - headers, - merged_llsd, - new lcl_responder(fetcher, - report_sequence, - report_sequence, - LLTextureFetch::svMetricsDataBreak, - reporting_started)); + LLCore::BufferArray * ba = new LLCore::BufferArray; + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::toXML(merged_llsd, bas); + + fetcher->getHttpRequest().requestPost(fetcher->getPolicyClass(), + report_priority, + mCapsURL, + ba, + NULL, + fetcher->getMetricsHeaders(), + handler); + ba->release(); + LLTextureFetch::svMetricsDataBreak = false; } else { @@ -3110,6 +3828,7 @@ truncate_viewer_metrics(int max_regions, LLSD & metrics) } // end of anonymous namespace + /////////////////////////////////////////////////////////////////////////////////////////// //Start LLTextureFetchDebugger /////////////////////////////////////////////////////////////////////////////////////////// @@ -3163,48 +3882,13 @@ private: S32 mID; }; -class LLDebuggerHTTPResponder : public LLCurl::Responder -{ -public: - LLDebuggerHTTPResponder(LLTextureFetchDebugger* debugger, S32 index) - : mDebugger(debugger), mIndex(index) - { - } - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) - { - bool success = false; - bool partial = false; - if (HTTP_OK <= status && status < HTTP_MULTIPLE_CHOICES) - { - success = true; - if (HTTP_PARTIAL_CONTENT == status) // partial information - { - partial = true; - } - } - if (!success) - { - llinfos << "Fetch Debugger : CURL GET FAILED, index = " << mIndex << ", status:" << status << " reason:" << reason << llendl; - } - mDebugger->callbackHTTP(mIndex, channels, buffer, partial, success); - mDebugger->getCurlGetRequest()->completeRequest(0); - } - virtual bool followRedir() - { - return true; - } -private: - LLTextureFetchDebugger* mDebugger; - S32 mIndex; -}; LLTextureFetchDebugger::LLTextureFetchDebugger(LLTextureFetch* fetcher, LLTextureCache* cache, LLImageDecodeThread* imagedecodethread) : mFetcher(fetcher), mTextureCache(cache), mImageDecodeThread(imagedecodethread), - mCurlGetRequest(NULL) + mHttpHeaders(NULL), + mHttpPolicyClass(fetcher->getPolicyClass()) { init(); } @@ -3214,6 +3898,11 @@ LLTextureFetchDebugger::~LLTextureFetchDebugger() mFetchingHistory.clear(); mStopDebug = TRUE; tryToStopDebug(); + if (mHttpHeaders) + { + mHttpHeaders->release(); + mHttpHeaders = NULL; + } } void LLTextureFetchDebugger::init() @@ -3251,6 +3940,12 @@ void LLTextureFetchDebugger::init() mFreezeHistory = FALSE; mStopDebug = FALSE; mClearHistory = FALSE; + + if (! mHttpHeaders) + { + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + } } void LLTextureFetchDebugger::startWork(e_debug_state state) @@ -3388,7 +4083,7 @@ void LLTextureFetchDebugger::tryToStopDebug() { mTextureCache->readComplete(mFetchingHistory[i].mCacheHandle, true); } - } + } break; case WRITE_CACHE: for(S32 i = 0 ; i < size; i++) @@ -3430,6 +4125,7 @@ void LLTextureFetchDebugger::tryToStopDebug() if(mClearHistory) { mFetchingHistory.clear(); + mHandleToFetchIndex.clear(); init(); mTotalFetchingTime = gDebugTimers[0].getElapsedTimeF32(); //reset } @@ -3608,6 +4304,7 @@ void LLTextureFetchDebugger::debugHTTP() mFetchingHistory[i].mCurlState = FetchEntry::CURL_NOT_DONE; mFetchingHistory[i].mCurlReceivedSize = 0; mFetchingHistory[i].mHTTPFailCount = 0; + mFetchingHistory[i].mFormattedImage = NULL; } mNbCurlRequests = 0; mNbCurlCompleted = 0; @@ -3622,15 +4319,12 @@ S32 LLTextureFetchDebugger::fillCurlQueue() mNbCurlCompleted = mFetchingHistory.size(); return 0; } - S32 size = mFetchingHistory.size(); - - if (mNbCurlRequests == size) //all issued + if (mNbCurlRequests > HTTP_REQUESTS_IN_QUEUE_LOW_WATER) { - return 0; - } + return mNbCurlRequests; + } - S32 counter = 8; - mNbCurlRequests = 0; + S32 size = mFetchingHistory.size(); for (S32 i = 0 ; i < size ; i++) { mNbCurlRequests++; @@ -3645,13 +4339,28 @@ S32 LLTextureFetchDebugger::fillCurlQueue() requestedSize = llmax(0,requestedSize); // We request the whole file if the size was set to an absurdly high value (meaning all file) requestedSize = (requestedSize == 33554432 ? 0 : requestedSize); - std::vector<std::string> headers; - headers.push_back("Accept: image/x-j2c"); - mCurlGetRequest->getByteRange(texture_url, headers, 0, requestedSize, 0x10000, new LLDebuggerHTTPResponder(this, i)); - - mFetchingHistory[i].mCurlState = FetchEntry::CURL_IN_PROGRESS; - counter--; - if(counter < 1) + + LLCore::HttpHandle handle = mFetcher->getHttpRequest().requestGetByteRange(mHttpPolicyClass, + LLWorkerThread::PRIORITY_LOWBITS, + texture_url, + 0, + requestedSize, + NULL, + mHttpHeaders, + this); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + mHandleToFetchIndex[handle] = i; + mFetchingHistory[i].mHttpHandle = handle; + mFetchingHistory[i].mCurlState = FetchEntry::CURL_IN_PROGRESS; + mNbCurlRequests++; + // Hack + if (mNbCurlRequests == HTTP_REQUESTS_IN_QUEUE_HIGH_WATER) // emulate normal pipeline + { + break; + } + } + else { break; } @@ -3881,9 +4590,8 @@ bool LLTextureFetchDebugger::update(F32 max_time) } break; case HTTP_FETCHING: - mCurlGetRequest->process(); - mCurlGetRequest->nextRequests(); - LLCurl::getCurlThread()->update(1); + // Do some notifications... + mFetcher->getHttpRequest().update(10); if (!fillCurlQueue() && mNbCurlCompleted == mFetchingHistory.size()) { mHTTPTime = mTimer.getElapsedTimeF32() ; @@ -3954,6 +4662,28 @@ bool LLTextureFetchDebugger::update(F32 max_time) return mState == IDLE; } +void LLTextureFetchDebugger::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + handle_fetch_map_t::iterator iter(mHandleToFetchIndex.find(handle)); + if (mHandleToFetchIndex.end() == iter) + { + llinfos << "Fetch Debugger : Couldn't find handle " << handle << " in fetch list." << llendl; + return; + } + + S32 fetch_ind(iter->second); + mHandleToFetchIndex.erase(iter); + if (fetch_ind >= mFetchingHistory.size() || mFetchingHistory[fetch_ind].mHttpHandle != handle) + { + llinfos << "Fetch Debugger : Handle and fetch object in disagreement. Punting." << llendl; + } + else + { + callbackHTTP(mFetchingHistory[fetch_ind], response); + mFetchingHistory[fetch_ind].mHttpHandle = LLCORE_HTTP_HANDLE_INVALID; // Not valid after notification + } +} + void LLTextureFetchDebugger::callbackCacheRead(S32 id, bool success, LLImageFormatted* image, S32 imagesize, BOOL islocal) { @@ -3980,50 +4710,60 @@ void LLTextureFetchDebugger::callbackDecoded(S32 id, bool success, LLImageRaw* r } } -void LLTextureFetchDebugger::callbackHTTP(S32 id, const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer, - bool partial, bool success) +void LLTextureFetchDebugger::callbackHTTP(FetchEntry & fetch, LLCore::HttpResponse * response) { - if (success) + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + LLCore::HttpStatus status(response->getStatus()); + mNbCurlRequests--; + if (status) { - mFetchingHistory[id].mCurlState = FetchEntry::CURL_DONE; + const bool partial(par_status == status); + LLCore::BufferArray * ba(response->getBody()); // *Not* holding reference to body + + fetch.mCurlState = FetchEntry::CURL_DONE; mNbCurlCompleted++; - S32 data_size = buffer->countAfter(channels.in(), NULL); - mFetchingHistory[id].mCurlReceivedSize += data_size; - //llinfos << "Fetch Debugger : got results for " << id << ", data_size = " << data_size << ", received = " << mFetchingHistory[id].mCurlReceivedSize << ", requested = " << mFetchingHistory[id].mRequestedSize << ", partial = " << partial << llendl; - if ((mFetchingHistory[id].mCurlReceivedSize >= mFetchingHistory[id].mRequestedSize) || !partial || (mFetchingHistory[id].mRequestedSize == 600)) + S32 data_size = ba ? ba->size() : 0; + fetch.mCurlReceivedSize += data_size; + //llinfos << "Fetch Debugger : got results for " << fetch.mID << ", data_size = " << data_size << ", received = " << fetch.mCurlReceivedSize << ", requested = " << fetch.mRequestedSize << ", partial = " << partial << llendl; + if ((fetch.mCurlReceivedSize >= fetch.mRequestedSize) || !partial || (fetch.mRequestedSize == 600)) { U8* d_buffer = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), data_size); - buffer->readAfter(channels.in(), NULL, d_buffer, data_size); + if (ba) + { + ba->read(0, d_buffer, data_size); + } - mFetchingHistory[id].mFormattedImage = NULL; + llassert_always(fetch.mFormattedImage.isNull()); { // For now, create formatted image based on extension - std::string texture_url = mHTTPUrl + "/?texture_id=" + mFetchingHistory[id].mID.asString().c_str(); + std::string texture_url = mHTTPUrl + "/?texture_id=" + fetch.mID.asString().c_str(); std::string extension = gDirUtilp->getExtension(texture_url); - mFetchingHistory[id].mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension)); - if (mFetchingHistory[id].mFormattedImage.isNull()) + fetch.mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension)); + if (fetch.mFormattedImage.isNull()) { - mFetchingHistory[id].mFormattedImage = new LLImageJ2C; // default + fetch.mFormattedImage = new LLImageJ2C; // default } } - mFetchingHistory[id].mFormattedImage->setData(d_buffer, data_size); + fetch.mFormattedImage->setData(d_buffer, data_size); } } else //failed { - mFetchingHistory[id].mHTTPFailCount++; - if(mFetchingHistory[id].mHTTPFailCount < 5) + llinfos << "Fetch Debugger : CURL GET FAILED, ID = " << fetch.mID + << ", status: " << status.toHex() + << " reason: " << status.toString() << llendl; + fetch.mHTTPFailCount++; + if(fetch.mHTTPFailCount < 5) { // Fetch will have to be redone - mFetchingHistory[id].mCurlState = FetchEntry::CURL_NOT_DONE; - mNbCurlRequests--; + fetch.mCurlState = FetchEntry::CURL_NOT_DONE; } else //skip { - mFetchingHistory[id].mCurlState = FetchEntry::CURL_DONE; + fetch.mCurlState = FetchEntry::CURL_DONE; mNbCurlCompleted++; } } diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index f5072a79f1..3a99432b48 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2012, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,19 +27,24 @@ #ifndef LL_LLTEXTUREFETCH_H #define LL_LLTEXTUREFETCH_H +#include <vector> +#include <map> + #include "lldir.h" #include "llimage.h" #include "lluuid.h" #include "llworkerthread.h" -#include "llcurl.h" #include "lltextureinfo.h" #include "llapr.h" #include "llimageworker.h" -//#include "lltexturecache.h" +#include "llcurl.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" class LLViewerTexture; class LLTextureFetchWorker; -class HTTPGetResponder; class LLImageDecodeThread; class LLHost; class LLViewerAssetStats; @@ -47,10 +52,10 @@ class LLTextureFetchDebugger; class LLTextureCache; // Interface class + class LLTextureFetch : public LLWorkerThread { friend class LLTextureFetchWorker; - friend class HTTPGetResponder; public: LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode); @@ -58,70 +63,201 @@ public: class TFRequest; - /*virtual*/ S32 update(F32 max_time_ms); - void shutDownTextureCacheThread() ; //called in the main thread after the TextureCacheThread shuts down. - void shutDownImageDecodeThread() ; //called in the main thread after the ImageDecodeThread shuts down. + // Threads: Tmain + /*virtual*/ S32 update(F32 max_time_ms); + + // called in the main thread after the TextureCacheThread shuts down. + // Threads: Tmain + void shutDownTextureCacheThread(); + + //called in the main thread after the ImageDecodeThread shuts down. + // Threads: Tmain + void shutDownImageDecodeThread(); + // Threads: T* (but Tmain mostly) bool createRequest(const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, S32 w, S32 h, S32 c, S32 discard, bool needs_aux, bool can_use_http); + + // Requests that a fetch operation be deleted from the queue. + // If @cancel is true, also stops any I/O operations pending. + // Actual delete will be scheduled and performed later. + // + // Note: This *looks* like an override/variant of the + // base class's deleteRequest() but is functionally quite + // different. + // + // Threads: T* void deleteRequest(const LLUUID& id, bool cancel); + void deleteAllRequests(); + + // Threads: T* bool getRequestFinished(const LLUUID& id, S32& discard_level, LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux); + + // Threads: T* bool updateRequestPriority(const LLUUID& id, F32 priority); + // Threads: T* bool receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes, U16 data_size, U8* data); + + // Threads: T* bool receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data); + // Threads: T* (but not safe) void setTextureBandwidth(F32 bandwidth) { mTextureBandwidth = bandwidth; } + + // Threads: T* (but not safe) F32 getTextureBandwidth() { return mTextureBandwidth; } - // Debug + // Threads: T* BOOL isFromLocalCache(const LLUUID& id); + + // @return Magic number giving the internal state of the + // request. We should make these codes public if we're + // going to return them as a status value. + // + // Threads: T* S32 getFetchState(const LLUUID& id, F32& decode_progress_p, F32& requested_priority_p, U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http); + + // Debug utility - generally not safe void dump(); - S32 getNumRequests() ; + + // Threads: T* + S32 getNumRequests(); + + // Threads: T* + S32 getNumHTTPRequests(); + + // Threads: T* + U32 getTotalNumHTTPRequests(); - // Public for access by callbacks + // Threads: T* S32 getPending(); + + // Threads: T* void lockQueue() { mQueueMutex.lock(); } + + // Threads: T* void unlockQueue() { mQueueMutex.unlock(); } + + // Threads: T* LLTextureFetchWorker* getWorker(const LLUUID& id); + + // Threads: T* + // Locks: Mfq LLTextureFetchWorker* getWorkerAfterLock(const LLUUID& id); - LLTextureInfo* getTextureInfo() { return &mTextureInfo; } - // Commands available to other threads to control metrics gathering operations. + + // Threads: T* void commandSetRegion(U64 region_handle); + + // Threads: T* void commandSendMetrics(const std::string & caps_url, const LLUUID & session_id, const LLUUID & agent_id, LLViewerAssetStats * main_stats); + + // Threads: T* void commandDataBreak(); - LLCurlTextureRequest & getCurlRequest() { return *mCurlGetRequest; } + // Threads: T* + LLCore::HttpRequest & getHttpRequest() { return *mHttpRequest; } + + // Threads: T* + LLCore::HttpRequest::policy_t getPolicyClass() const { return mHttpPolicyClass; } + + // Return a pointer to the shared metrics headers definition. + // Does not increment the reference count, caller is required + // to do that to hold a reference for any length of time. + // + // Threads: T* + LLCore::HttpHeaders * getMetricsHeaders() const { return mHttpMetricsHeaders; } bool isQAMode() const { return mQAMode; } - // Curl POST counter maintenance - inline void incrCurlPOSTCount() { mCurlPOSTRequestCount++; } - inline void decrCurlPOSTCount() { mCurlPOSTRequestCount--; } + // ---------------------------------- + // HTTP resource waiting methods + + // Threads: T* + void addHttpWaiter(const LLUUID & tid); + + // Threads: T* + void removeHttpWaiter(const LLUUID & tid); + // Threads: T* + bool isHttpWaiter(const LLUUID & tid); + + // If there are slots, release one or more LLTextureFetchWorker + // requests from resource wait state (WAIT_HTTP_RESOURCE) to + // active (SEND_HTTP_REQ). + // + // Because this will modify state of many workers, you may not + // hold any Mw lock while calling. This makes it a little + // inconvenient to use but that's the rule. + // + // Threads: T* + // Locks: -Mw (must not hold any worker when called) + void releaseHttpWaiters(); + + // Threads: T* + void cancelHttpWaiters(); + + // Threads: T* + int getHttpWaitersCount(); + // ---------------------------------- + // Stats management + + // Add given counts to the global totals for the states/requests + // Threads: T* + void updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait); + + // Return the global counts + // Threads: T* + void getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait); + + // ---------------------------------- + protected: + // Threads: T* (but Ttf in practice) void addToNetworkQueue(LLTextureFetchWorker* worker); + + // Threads: T* void removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel); + + // Threads: T* void addToHTTPQueue(const LLUUID& id); - void removeRequest(LLTextureFetchWorker* worker, bool cancel); + // XXX possible delete + // Threads: T* + void removeFromHTTPQueue(const LLUUID& id, S32 received_size); + + // Identical to @deleteRequest but with different arguments + // (caller already has the worker pointer). + // + // Threads: T* + void removeRequest(LLTextureFetchWorker* worker, bool cancel); + // Overrides from the LLThread tree + // Locks: Ct bool runCondition(); private: + // Threads: Tmain void sendRequestListToSimulators(); + + // Threads: Ttf /*virtual*/ void startThread(void); + + // Threads: Ttf /*virtual*/ void endThread(void); + + // Threads: Ttf /*virtual*/ void threadedUpdate(void); + + // Threads: Ttf void commonUpdate(); // Metrics command helpers @@ -132,6 +268,8 @@ private: * Takes ownership of the TFRequest object. * * Method locks the command queue. + * + * Threads: T* */ void cmdEnqueue(TFRequest *); @@ -142,6 +280,8 @@ private: * Caller acquires ownership of the object and must dispose of it. * * Method locks the command queue. + * + * Threads: T* */ TFRequest * cmdDequeue(); @@ -151,6 +291,8 @@ private: * additional commands. * * Method locks the command queue. + * + * Threads: Ttf */ void cmdDoWork(); @@ -170,38 +312,64 @@ private: LLTextureCache* mTextureCache; LLImageDecodeThread* mImageDecodeThread; - LLCurlTextureRequest* mCurlGetRequest; - + // Map of all requests by UUID typedef std::map<LLUUID,LLTextureFetchWorker*> map_t; - map_t mRequestMap; + map_t mRequestMap; // Mfq // Set of requests that require network data typedef std::set<LLUUID> queue_t; - queue_t mNetworkQueue; - queue_t mHTTPTextureQueue; + queue_t mNetworkQueue; // Mfnq + queue_t mHTTPTextureQueue; // Mfnq typedef std::map<LLHost,std::set<LLUUID> > cancel_queue_t; - cancel_queue_t mCancelQueue; - F32 mTextureBandwidth; - F32 mMaxBandwidth; + cancel_queue_t mCancelQueue; // Mfnq + F32 mTextureBandwidth; // <none> + F32 mMaxBandwidth; // Mfnq LLTextureInfo mTextureInfo; + // XXX possible delete + U32 mHTTPTextureBits; // Mfnq + + // XXX possible delete + //debug use + U32 mTotalHTTPRequests; + // Out-of-band cross-thread command queue. This command queue // is logically tied to LLQueuedThread's list of // QueuedRequest instances and so must be covered by the // same locks. typedef std::vector<TFRequest *> command_queue_t; - command_queue_t mCommands; + command_queue_t mCommands; // Mfq // If true, modifies some behaviors that help with QA tasks. const bool mQAMode; - // Count of POST requests outstanding. We maintain the count - // indirectly in the CURL request responder's ctor and dtor and - // use it when determining whether or not to sleep the thread. Can't - // use the LLCurl module's request counter as it isn't thread compatible. - // *NOTE: Don't mix Atomic and static, apr_initialize must be called first. - LLAtomic32<S32> mCurlPOSTRequestCount; + // Interfaces and objects into the core http library used + // to make our HTTP requests. These replace the various + // LLCurl interfaces used in the past. + LLCore::HttpRequest * mHttpRequest; // Ttf + LLCore::HttpOptions * mHttpOptions; // Ttf + LLCore::HttpHeaders * mHttpHeaders; // Ttf + LLCore::HttpHeaders * mHttpMetricsHeaders; // Ttf + LLCore::HttpRequest::policy_t mHttpPolicyClass; // T* + + // We use a resource semaphore to keep HTTP requests in + // WAIT_HTTP_RESOURCE2 if there aren't sufficient slots in the + // transport. This keeps them near where they can be cheaply + // reprioritized rather than dumping them all across a thread + // where it's more expensive to get at them. Requests in either + // SEND_HTTP_REQ or WAIT_HTTP_REQ charge against the semaphore + // and tracking state transitions is critical to liveness. + LLAtomicS32 mHttpSemaphore; // Ttf + Tmain + + typedef std::set<LLUUID> wait_http_res_queue_t; + wait_http_res_queue_t mHttpWaitResource; // Mfnq + + // Cumulative stats on the states/requests issued by + // textures running through here. + U32 mTotalCacheReadCount; // Mfq + U32 mTotalCacheWriteCount; // Mfq + U32 mTotalResourceWaitCount; // Mfq public: // A probabilistically-correct indicator that the current @@ -237,7 +405,7 @@ public: //debug use class LLViewerFetchedTexture; -class LLTextureFetchDebugger +class LLTextureFetchDebugger : public LLCore::HttpHandler { friend class LLTextureFetch; public: @@ -282,11 +450,13 @@ private: e_curl_state mCurlState; S32 mCurlReceivedSize; S32 mHTTPFailCount; + LLCore::HttpHandle mHttpHandle; FetchEntry() : mDecodedLevel(-1), mFetchedSize(0), - mDecodedSize(0) + mDecodedSize(0), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID) {} FetchEntry(LLUUID& id, S32 r_size, /*S32 f_discard, S32 c,*/ S32 level, S32 f_size, S32 d_size) : mID(id), @@ -295,10 +465,15 @@ private: mFetchedSize(f_size), mDecodedSize(d_size), mNeedsAux(false), - mHTTPFailCount(0) + mHTTPFailCount(0), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID) {} }; - std::vector<FetchEntry> mFetchingHistory; + typedef std::vector<FetchEntry> fetch_list_t; + fetch_list_t mFetchingHistory; + + typedef std::map<LLCore::HttpHandle, S32> handle_fetch_map_t; + handle_fetch_map_t mHandleToFetchIndex; e_debug_state mState; @@ -319,7 +494,8 @@ private: LLTextureFetch* mFetcher; LLTextureCache* mTextureCache; LLImageDecodeThread* mImageDecodeThread; - LLCurlTextureRequest* mCurlGetRequest; + LLCore::HttpHeaders* mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; S32 mNumFetchedTextures; S32 mNumCacheHits; @@ -359,21 +535,18 @@ public: void clearHistory(); void addHistoryEntry(LLTextureFetchWorker* worker); - void setCurlGetRequest(LLCurlTextureRequest* request) { mCurlGetRequest = request;} - LLCurlTextureRequest* getCurlGetRequest() { return mCurlGetRequest;} + // Inherited from LLCore::HttpHandler + // Threads: Ttf + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); void startWork(e_debug_state state); void setStopDebug() {mStopDebug = TRUE;} void tryToStopDebug(); //stop everything - void callbackCacheRead(S32 id, bool success, LLImageFormatted* image, S32 imagesize, BOOL islocal); void callbackCacheWrite(S32 id, bool success); void callbackDecoded(S32 id, bool success, LLImageRaw* raw, LLImageRaw* aux); - void callbackHTTP(S32 id, const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer, - bool partial, bool success); - + void callbackHTTP(FetchEntry & fetch, LLCore::HttpResponse * response); e_debug_state getState() {return mState;} S32 getNumFetchedTextures() {return mNumFetchedTextures;} diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index c60b4155a0..16c42dbd43 100755 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2012, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -74,7 +74,7 @@ static std::string title_string4(" W x H (Dis) Mem"); static S32 title_x1 = 0; static S32 title_x2 = 460; static S32 title_x3 = title_x2 + 40; -static S32 title_x4 = title_x3 + 50; +static S32 title_x4 = title_x3 + 46; static S32 texture_bar_height = 8; //////////////////////////////////////////////////////////////////////////// @@ -232,6 +232,8 @@ void LLTextureBar::draw() { "DSK", LLColor4::blue }, // CACHE_POST { "NET", LLColor4::green }, // LOAD_FROM_NETWORK { "SIM", LLColor4::green }, // LOAD_FROM_SIMULATOR + { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE + { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE2 { "REQ", LLColor4::yellow },// SEND_HTTP_REQ { "HTP", LLColor4::green }, // WAIT_HTTP_REQ { "DEC", LLColor4::yellow },// DECODE_IMAGE @@ -239,7 +241,7 @@ void LLTextureBar::draw() { "WRT", LLColor4::purple },// WRITE_TO_CACHE { "WRT", LLColor4::orange },// WAIT_ON_WRITE { "END", LLColor4::red }, // DONE -#define LAST_STATE 12 +#define LAST_STATE 14 { "CRE", LLColor4::magenta }, // LAST_STATE+1 { "FUL", LLColor4::green }, // LAST_STATE+2 { "BAD", LLColor4::red }, // LAST_STATE+3 @@ -345,7 +347,7 @@ void LLTextureBar::draw() // draw the image size at the end { - std::string num_str = llformat("%3dx%3d (%d) %7d", mImagep->getWidth(), mImagep->getHeight(), + std::string num_str = llformat("%3dx%3d (%2d) %7d", mImagep->getWidth(), mImagep->getHeight(), mImagep->getDiscardLevel(), mImagep->hasGLTexture() ? mImagep->getTextureMemory() : 0); LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, title_x4, getRect().getHeight(), color, LLFontGL::LEFT, LLFontGL::TOP); @@ -514,14 +516,18 @@ void LLGLTexMemBar::draw() S32 v_offset = 0;//(S32)((texture_bar_height + 2.2f) * mTextureView->mNumTextureBars + 2.0f); F32 total_texture_downloaded = (F32)gTotalTextureBytes / (1024 * 1024); F32 total_object_downloaded = (F32)gTotalObjectBytes / (1024 * 1024); - U32 total_http_requests = LLAppViewer::getTextureFetch()->getCurlRequest().getTotalIssuedRequests() ; + U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests(); //---------------------------------------------------------------------------- LLGLSUIDefault gls_ui; LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); LLColor4 color; - - std::string text = ""; + // Gray background using completely magic numbers + gGL.color4f(0.f, 0.f, 0.f, 0.25f); + // const LLRect & rect(getRect()); + // gl_rect_2d(-4, v_offset, rect.mRight - rect.mLeft + 2, v_offset + line_height*4); + + std::string text = ""; LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6, text_color, LLFontGL::LEFT, LLFontGL::TOP); @@ -531,14 +537,26 @@ void LLGLTexMemBar::draw() bound_mem, max_bound_mem, LLRenderTarget::sBytesAllocated/(1024*1024), - LLImageRaw::sGlobalRawMemory >> 20, discard_bias, - cache_usage, cache_max_usage); + LLImageRaw::sGlobalRawMemory >> 20, + discard_bias, + cache_usage, + cache_max_usage); + //, cache_entries, cache_max_entries + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, text_color, LLFontGL::LEFT, LLFontGL::TOP); - text = llformat("Net Tot Tex: %.1f MB Tot Obj: %.1f MB Tot Htp: %d", - total_texture_downloaded, total_object_downloaded, total_http_requests); - //, cache_entries, cache_max_entries + U32 cache_read(0U), cache_write(0U), res_wait(0U); + LLAppViewer::getTextureFetch()->getStateStats(&cache_read, &cache_write, &res_wait); + + text = llformat("Net Tot Tex: %.1f MB Tot Obj: %.1f MB Tot Htp: %d Cread: %u Cwrite: %u Rwait: %u", + total_texture_downloaded, + total_object_downloaded, + total_http_requests, + cache_read, + cache_write, + res_wait); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, text_color, LLFontGL::LEFT, LLFontGL::TOP); @@ -552,7 +570,7 @@ void LLGLTexMemBar::draw() LLAppViewer::getTextureCache()->getNumReads(), LLAppViewer::getTextureCache()->getNumWrites(), LLLFSThread::sLocal->getPending(), LLImageRaw::sRawImageCount, - LLAppViewer::getTextureFetch()->getCurlRequest().getNumRequests(), + LLAppViewer::getTextureFetch()->getNumHTTPRequests(), LLAppViewer::getImageDecodeThread()->getPending(), gTextureList.mCreateTextureList.size()); diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp index 4c59fd0371..4c59fd0371 100755..100644 --- a/indra/newview/llviewerassetstats.cpp +++ b/indra/newview/llviewerassetstats.cpp diff --git a/indra/newview/llviewerassetstats.h b/indra/newview/llviewerassetstats.h index 8319752230..8319752230 100755..100644 --- a/indra/newview/llviewerassetstats.h +++ b/indra/newview/llviewerassetstats.h diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index b47a41c44c..b47a41c44c 100755..100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp index 5d1aa870a3..5d1aa870a3 100755..100644 --- a/indra/newview/llviewerjointmesh.cpp +++ b/indra/newview/llviewerjointmesh.cpp diff --git a/indra/newview/llviewerjointmesh.h b/indra/newview/llviewerjointmesh.h index dd5dae1dc1..dd5dae1dc1 100755..100644 --- a/indra/newview/llviewerjointmesh.h +++ b/indra/newview/llviewerjointmesh.h diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 6770bf5d14..54fc31cf9f 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -2872,21 +2872,6 @@ void LLViewerObject::updateInventory( { LLMemType mt(LLMemType::MTYPE_OBJECT); - std::list<LLUUID>::iterator begin = mPendingInventoryItemsIDs.begin(); - std::list<LLUUID>::iterator end = mPendingInventoryItemsIDs.end(); - - bool is_fetching = std::find(begin, end, item->getAssetUUID()) != end; - bool is_fetched = getInventoryItemByAsset(item->getAssetUUID()) != NULL; - - if (is_fetched || is_fetching) - { - return; - } - else - { - mPendingInventoryItemsIDs.push_back(item->getAssetUUID()); - } - // This slices the object into what we're concerned about on the // viewer. The simulator will take the permissions and transfer // ownership. diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 554e4d647e..554e4d647e 100755..100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 8eb8717de2..32cf8cc1b3 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -1904,7 +1904,7 @@ void LLViewerFetchedTexture::updateVirtualSize() for(U32 i = 0 ; i < mNumFaces ; i++) { LLFace* facep = mFaceList[i] ; - if(facep->getDrawable()->isRecentlyVisible()) + if( facep && facep->getDrawable() && facep->getDrawable()->isRecentlyVisible()) { addTextureStats(facep->getVirtualSize()) ; setAdditionalDecodePriority(facep->getImportanceToCamera()) ; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 366b6004be..627238b0f5 100755 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -4421,7 +4421,9 @@ U32 LLVOAvatar::renderTransparent(BOOL first_pass) } // Can't test for baked hair being defined, since that won't always be the case (not all viewers send baked hair) // TODO: 1.25 will be able to switch this logic back to calling isTextureVisible(); - if (getImage(TEX_HAIR_BAKED, 0)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha) + + if ( getImage(TEX_HAIR_BAKED, 0) + && getImage(TEX_HAIR_BAKED, 0)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha) { num_indices += mMeshLOD[MESH_ID_HAIR]->render(mAdjustedPixelArea, first_pass, mIsDummy); first_pass = FALSE; @@ -4565,7 +4567,20 @@ void LLVOAvatar::updateTextures() LLWearableType::EType wearable_type = LLVOAvatarDictionary::getTEWearableType((ETextureIndex)texture_index); U32 num_wearables = gAgentWearables.getWearableCount(wearable_type); const LLTextureEntry *te = getTE(texture_index); - const F32 texel_area_ratio = fabs(te->mScaleS * te->mScaleT); + + // getTE can return 0. + // Not sure yet why it does, but of course it crashes when te->mScale? gets used. + // Put safeguard in place so this corner case get better handling and does not result in a crash. + F32 texel_area_ratio = 1.0f; + if( te ) + { + texel_area_ratio = fabs(te->mScaleS * te->mScaleT); + } + else + { + llwarns << "getTE( " << texture_index << " ) returned 0" <<llendl; + } + LLViewerFetchedTexture *imagep = NULL; for (U32 wearable_index = 0; wearable_index < num_wearables; wearable_index++) { @@ -8705,6 +8720,12 @@ BOOL LLVOAvatar::isTextureDefined(LLVOAvatarDefines::ETextureIndex te, U32 index return FALSE; } + if( !getImage( te, index ) ) + { + llwarns << "getImage( " << te << ", " << index << " ) returned 0" << llendl; + return FALSE; + } + return (getImage(te, index)->getID() != IMG_DEFAULT_AVATAR && getImage(te, index)->getID() != IMG_DEFAULT); } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 1adb680962..1adb680962 100755..100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index 7bd0c0bf93..7bd0c0bf93 100755..100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index fb53e5c677..50ba7e47a4 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -1228,6 +1228,13 @@ BOOL LLVOVolume::calcLOD() if (mDrawable->isState(LLDrawable::RIGGED)) { LLVOAvatar* avatar = getAvatar(); + + // Not sure how this can really happen, but alas it does. Better exit here than crashing. + if( !avatar || !avatar->mDrawable ) + { + return FALSE; + } + distance = avatar->mDrawable->mDistanceWRTCamera; radius = avatar->getBinRadius(); } @@ -1336,7 +1343,8 @@ BOOL LLVOVolume::setDrawableParent(LLDrawable* parentp) void LLVOVolume::updateFaceFlags() { - for (S32 i = 0; i < getVolume()->getNumFaces(); i++) + // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() + for (S32 i = 0; i < getVolume()->getNumFaces() && i < mDrawable->getNumFaces(); i++) { LLFace *face = mDrawable->getFace(i); if (face) @@ -1437,7 +1445,10 @@ BOOL LLVOVolume::genBBoxes(BOOL force_global) volume = getVolume(); } - for (S32 i = 0; i < getVolume()->getNumVolumeFaces(); i++) + // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() + for (S32 i = 0; + i < getVolume()->getNumVolumeFaces() && i < mDrawable->getNumFaces() && i < getNumTEs(); + i++) { LLFace *face = mDrawable->getFace(i); if (!face) @@ -1738,6 +1749,11 @@ BOOL LLVOVolume::updateGeometry(LLDrawable *drawable) void LLVOVolume::updateFaceSize(S32 idx) { + if( mDrawable->getNumFaces() <= idx ) + { + return; + } + LLFace* facep = mDrawable->getFace(idx); if (facep) { @@ -2439,7 +2455,12 @@ void LLVOVolume::addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) //add the face to show the media if it is in playing if(mDrawable) { - LLFace* facep = mDrawable->getFace(texture_index) ; + LLFace* facep(NULL); + if( texture_index < mDrawable->getNumFaces() ) + { + facep = mDrawable->getFace(texture_index) ; + } + if(facep) { LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; @@ -3838,6 +3859,7 @@ void LLRiggedVolume::update(const LLMeshSkinInfo* skin, LLVOAvatar* avatar, cons LLVector4a* pos = dst_face.mPositions; + if( pos && weight && dst_face.mExtents ) { LLFastTimer t(FTM_SKIN_RIGGED); diff --git a/indra/newview/llwlhandlers.cpp b/indra/newview/llwlhandlers.cpp index 2425b96678..be3e3ff30e 100644 --- a/indra/newview/llwlhandlers.cpp +++ b/indra/newview/llwlhandlers.cpp @@ -105,10 +105,16 @@ LLEnvironmentRequestResponder::LLEnvironmentRequestResponder() return; } - if (unvalidated_content[0]["regionID"].asUUID() != gAgent.getRegion()->getRegionID()) + LLUUID regionId; + if( gAgent.getRegion() ) + { + regionId = gAgent.getRegion()->getRegionID(); + } + + if (unvalidated_content[0]["regionID"].asUUID() != regionId ) { LL_WARNS("WindlightCaps") << "Not in the region from where this data was received (wanting " - << gAgent.getRegion()->getRegionID() << " but got " << unvalidated_content[0]["regionID"].asUUID() + << regionId << " but got " << unvalidated_content[0]["regionID"].asUUID() << ") - ignoring..." << LL_ENDL; return; } diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index e30f78e021..351dc1731a 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -2011,6 +2011,7 @@ void LLPipeline::grabReferences(LLCullResult& result) void LLPipeline::clearReferences() { sCull = NULL; + mGroupSaveQ1.clear(); } void check_references(LLSpatialGroup* group, LLDrawable* drawable) @@ -2619,6 +2620,7 @@ void LLPipeline::rebuildPriorityGroups() group->clearState(LLSpatialGroup::IN_BUILD_Q1); } + mGroupSaveQ1 = mGroupQ1; mGroupQ1.clear(); mGroupQ1Locked = false; diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index fd2a1e06cd..7a0ca86231 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -668,6 +668,8 @@ protected: LLSpatialGroup::sg_vector_t mGroupQ1; //priority LLSpatialGroup::sg_vector_t mGroupQ2; // non-priority + LLSpatialGroup::sg_vector_t mGroupSaveQ1; // a place to save mGroupQ1 until it is safe to unref + LLSpatialGroup::sg_vector_t mMeshDirtyGroup; //groups that need rebuildMesh called U32 mMeshDirtyQueryObject; diff --git a/indra/newview/skins/default/xui/de/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/de/floater_pathfinding_linksets.xml index f1d561e51b..0d3ba59efb 100644 --- a/indra/newview/skins/default/xui/de/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/de/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [Gruppe] </floater.string> + <floater.string name="linkset_is_scripted"> + Ja + </floater.string> + <floater.string name="linkset_is_not_scripted"> + Nein + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Unbekannt + </floater.string> <floater.string name="linkset_use_walkable"> Begehbar </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Name (Hauptprim)" name="name"/> <scroll_list.columns label="Beschreibung (Hauptprim)" name="description"/> <scroll_list.columns label="Eigentümer" name="owner"/> + <scroll_list.columns label="Geskriptet" name="scripted"/> <scroll_list.columns label="Belastung" name="land_impact"/> <scroll_list.columns label="Abstand" name="dist_from_you"/> <scroll_list.columns label="Linkset-Nutzung" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/de/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/de/floater_texture_fetch_debugger.xml index 0f407cb15c..97b0364832 100644 --- a/indra/newview/skins/default/xui/de/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/de/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, Neuabruf sichtbarer Texturen aus Cache, Zeit: [TIME] s, Abrufmenge: [SIZE2] KB, [PIXEL] MPixel </text> + <text name="total_time_refetch_all_cache_label"> + 16, Neuabruf aller Texturen aus Cache, Zeit: [TIME] s, Abrufmenge: [SIZE2] KB, [PIXEL] MPixel + </text> <text name="total_time_refetch_vis_http_label"> - 16, Neuabruf sichtbarer Texturen von HTTP, Zeit: [TIME] s, Abrufmenge: [SIZE2] KB, [PIXEL] MPixel + 17, Neuabruf sichtbarer Texturen von HTTP, Zeit: [TIME] s, Abrufmenge: [SIZE2] KB, [PIXEL] MPixel + </text> + <text name="total_time_refetch_all_http_label"> + 18, Neuabruf aller Texturen von HTTP, Zeit: [TIME] s, Abrufmenge: [SIZE2] KB, [PIXEL] MPixel + </text> + <spinner label="19, Verhältnis Texel/Pixel:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, Texturquelle: </text> - <spinner label="17, Verhältnis Texel/Pixel:" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Cache + HTTP" name="0"/> + <radio_item label="Nur HTTP" name="1"/> + </radio_group> <button label="Starten" name="start_btn"/> <button label="Zurücksetzen" name="clear_btn"/> <button label="Schließen" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Decodieren" name="decode_btn"/> <button label="GL-Textur" name="gl_btn"/> <button label="Neuabruf sichtbarer Texturen (Cache)" name="refetchviscache_btn"/> + <button label="Neuabruf des gesamten Cache" name="refetchallcache_btn"/> <button label="Neuabruf sichtbarer Texturen (HTTP)" name="refetchvishttp_btn"/> + <button label="Neuabruf des gesamten HTTP" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/de/menu_inventory.xml b/indra/newview/skins/default/xui/de/menu_inventory.xml index 39b3099336..cd2fca313e 100644 --- a/indra/newview/skins/default/xui/de/menu_inventory.xml +++ b/indra/newview/skins/default/xui/de/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="Systemordner löschen" name="Delete System Folder"/> <menu_item_call label="Konferenz-Chat starten" name="Conference Chat Folder"/> <menu_item_call label="Wiedergeben/Abspielen" name="Sound Play"/> + <menu_item_call label="SLurl kopieren" name="url_copy"/> <menu_item_call label="Landmarken-Info" name="About Landmark"/> <menu_item_call label="Inworld abspielen" name="Animation Play"/> <menu_item_call label="Lokal abspielen" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/de/notifications.xml b/indra/newview/skins/default/xui/de/notifications.xml index 33dfcdcc84..4b7a60b4eb 100644 --- a/indra/newview/skins/default/xui/de/notifications.xml +++ b/indra/newview/skins/default/xui/de/notifications.xml @@ -1221,7 +1221,7 @@ in „[THIS_GPU]“ Sie wurden zur nächstgelegenen Region teleportiert. </notification> <notification name="AvatarMovedLast"> - Ihr letzter Standort ist zurzeit nicht verfügbar. + Ihr angeforderter Standort ist zurzeit nicht verfügbar. Sie wurden zur nächstgelegenen Region teleportiert. </notification> <notification name="AvatarMovedHome"> @@ -1240,7 +1240,7 @@ Sie können [SECOND_LIFE] normal verwenden. Andere Benutzer können Sie korrekt Installation von [APP_NAME] vollständig abgeschlossen. Falls Sie [SECOND_LIFE] zum ersten Mal verwenden, müssen Sie zuerst ein Konto erstellen, bevor Sie sich anmelden können. - <usetemplate name="okcancelbuttons" notext="Weiter" yestext="Neues Konto..."/> + <usetemplate name="okcancelbuttons" notext="Weiter" yestext="Konto erstellen..."/> </notification> <notification name="LoginPacketNeverReceived"> Es gibt Probleme mit der Verbindung. Möglicherweise besteht ein Problem mit Ihrer Internetverbindung oder dem [SECOND_LIFE_GRID]. @@ -3246,19 +3246,58 @@ Durch Ausblenden der Schaltfläche „Sprechen“ wird die Sprechfunktion deakti Durch diese Aktion werden alle Menüelemente und Schaltflächen ausgeblendet. Um sie wieder anzuzeigen, klicken Sie erneut auf [SHORTCUT]. <usetemplate ignoretext="Vor Ausblenden der UI bestätigen" name="okcancelignore" notext="Abbrechen" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> - Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht auf „[REQUESTED_TYPE]“ gesetzt werden. Diese Linksets werden stattdessen auf „[RESTRICTED_TYPE]“ gesetzt. + <notification name="PathfindingLinksets_WarnOnPhantom"> + Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet. + +Möchten Sie fortfahren? + <usetemplate ignoretext="Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet." name="okcancelignore" notext="Abbrechen" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> + Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht auf „[REQUESTED_TYPE]“ gesetzt werden. Diese Linksets werden stattdessen auf „[RESTRICTED_TYPE]“ gesetzt. + +Möchten Sie fortfahren? <usetemplate ignoretext="Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht gesetzt werden." name="okcancelignore" notext="Abbrechen" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Einige der ausgewählten Linksets können nicht auf „[REQUESTED_TYPE]“ gesetzt werden, da die Form nicht konvex ist. + +Möchten Sie fortfahren? <usetemplate ignoretext="Einige der ausgewählten Linksets können nicht gesetzt werden, da die Form nicht konvex ist." name="okcancelignore" notext="Abbrechen" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> - Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht auf „[REQUESTED_TYPE]“ gesetzt werden. Diese Linksets werden stattdessen auf „[RESTRICTED_TYPE]“ gesetzt. - Einige der ausgewählten Linksets können nicht auf „[REQUESTED_TYPE]“ gesetzt werden, da die Form nicht konvex ist. Die Nutzungsarten dieser Linksets bleiben unverändert. + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet. + +Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht auf „[REQUESTED_TYPE]“ gesetzt werden. Diese Linksets werden stattdessen auf „[RESTRICTED_TYPE]“ gesetzt. + +Möchten Sie fortfahren? + <usetemplate ignoretext="Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet und andere können aufgrund von Berechtigungseinschränkungen nicht gesetzt werden." name="okcancelignore" notext="Abbrechen" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet. + +Einige der ausgewählten Linksets können nicht auf „[REQUESTED_TYPE]“ gesetzt werden, da die Form nicht konvex ist. + +Möchten Sie fortfahren? + <usetemplate ignoretext="Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet und andere können nicht gesetzt werden, da die Form nicht konvex ist." name="okcancelignore" notext="Abbrechen" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> + Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht auf „[REQUESTED_TYPE]“ gesetzt werden. Diese Linksets werden stattdessen auf „[RESTRICTED_TYPE]“ gesetzt. + +Einige der ausgewählten Linksets können nicht auf „[REQUESTED_TYPE]“ gesetzt werden, da die Form nicht konvex ist. Die Nutzungsarten dieser Linksets bleiben unverändert. + +Möchten Sie fortfahren? <usetemplate ignoretext="Einige der ausgewählten Linksets können nicht gesetzt werden, da die Berechtigungen eingeschränkt sind und die Form nicht konvex ist." name="okcancelignore" notext="Abbrechen" yestext="OK"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet. + +Einige der ausgewählten Linksets können aufgrund von Berechtigungseinschränkungen nicht auf „[REQUESTED_TYPE]“ gesetzt werden. Diese Linksets werden stattdessen auf „[RESTRICTED_TYPE]“ gesetzt. + +Einige der ausgewählten Linksets können nicht auf „[REQUESTED_TYPE]“ gesetzt werden, da die Form nicht konvex ist. Die Nutzungsarten dieser Linksets bleiben unverändert. + +Möchten Sie fortfahren? + <usetemplate ignoretext="Bei einigen ausgewählten Linksets wird die Phantom-Markierung umgeschaltet und andere können nicht gesetzt werden, da die Berechtigungen für das Linkset eingeschränkt sind und die Form nicht konvex ist." name="okcancelignore" notext="Abbrechen" yestext="OK"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> Das ausgewählte Objekt wirkt sich auf das Navmesh aus. Wenn Sie es in einen flexiblen Pfad ändern, wird es aus dem Navmesh entfernt. <usetemplate ignoretext="Das ausgewählte Objekt wirkt sich auf das Navmesh aus. Wenn Sie es in einen flexiblen Pfad ändern, wird es aus dem Navmesh entfernt." name="okcancelignore" notext="Abbrechen" yestext="OK"/> diff --git a/indra/newview/skins/default/xui/de/panel_login.xml b/indra/newview/skins/default/xui/de/panel_login.xml index 2203b6d310..8cc467185c 100644 --- a/indra/newview/skins/default/xui/de/panel_login.xml +++ b/indra/newview/skins/default/xui/de/panel_login.xml @@ -18,7 +18,7 @@ </layout_panel> <layout_panel name="start_location_panel"> <text name="start_location_text"> - Hier anfangen: + Hier starten: </text> <combo_box name="start_location_combo"> <combo_box.item label="Mein letzter Standort" name="MyLastLocation"/> @@ -28,19 +28,19 @@ </layout_panel> <layout_panel name="links_login_panel"> <text name="login_help"> - Sie brauchen Hilfe? + Brauchen Sie Hilfe beim Anmelden? </text> <text name="forgot_password_text"> Benutzernamen oder Kennwort vergessen? </text> <button label="Anmelden" name="connect_btn"/> - <check_box label="Kennwort merken" name="remember_check"/> + <check_box label="Kennwort speichern" name="remember_check"/> </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + IHR KONTO ERSTELLEN </text> - <button name="create_new_account_btn" label="Registrieren"/> + <button label="Jetzt starten" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/de/panel_region_debug.xml b/indra/newview/skins/default/xui/de/panel_region_debug.xml index 4a85bd85fd..a03a0b8b7b 100644 --- a/indra/newview/skins/default/xui/de/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/de/panel_region_debug.xml @@ -36,5 +36,5 @@ <button label="?" name="top_scripts_help"/> <button label="Region neu starten" name="restart_btn" tool_tip="2-Minuten-Countdown und Region neu starten"/> <button label="?" name="restart_help"/> - <button label="Neustart abbrechen" name="cancel_restart_btn" tool_tip="Regionsneustart um eine Stunde verschieben"/> + <button label="Neustart abbrechen" name="cancel_restart_btn" tool_tip="Regionsneustart abbrechen"/> </panel> diff --git a/indra/newview/skins/default/xui/de/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/de/panel_volume_pulldown.xml index e6ab9165d7..3d43200e48 100644 --- a/indra/newview/skins/default/xui/de/panel_volume_pulldown.xml +++ b/indra/newview/skins/default/xui/de/panel_volume_pulldown.xml @@ -1,15 +1,15 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="volumepulldown_floater" width="240"> <button left="217" name="prefs_btn"/> - <slider label="Master" name="System Volume" label_width="80" width="180"/> - <slider label="Schaltflächen" label_width="80" width="180" name="UI Volume"/> - <slider label="Umgebung" label_width="80" width="180" name="Wind Volume"/> - <slider label="Soundeffekte" label_width="80" width="180" name="SFX Volume"/> - <check_box name="gesture_audio_play_btn" tool_tip="Sounds von Gesten abspielen"/> - <slider label="Musikstream" label_width="80" width="180" name="Music Volume"/> - <check_box tool_tip="Musikstream aktivieren" name="enable_music"/> - <slider label="Medien" label_width="80" width="180" name="Media Volume"/> - <check_box tool_tip="Medienstream aktivieren" name="enable_media"/> - <slider label="Voice-Chat" label_width="80" width="180" name="Voice Volume"/> - <check_box tool_tip="Voice-Chat aktivieren" name="enable_voice_check"/> + <slider label="Master" label_width="80" name="System Volume" width="180"/> + <slider label="Schaltflächen" label_width="80" name="UI Volume" width="180"/> + <slider label="Umgebung" label_width="80" name="Wind Volume" width="180"/> + <slider label="Sounds" label_width="80" name="SFX Volume" width="180"/> + <check_box name="gesture_audio_play_btn" tool_tip="Sounds von Gesten aktivieren"/> + <slider label="Musik" label_width="80" name="Music Volume" width="180"/> + <check_box name="enable_music" tool_tip="Streaming-Musik aktivieren"/> + <slider label="Medien" label_width="80" name="Media Volume" width="180"/> + <check_box name="enable_media" tool_tip="Streaming-Medien aktivieren"/> + <slider label="Voice" label_width="80" name="Voice Volume" width="180"/> + <check_box name="enable_voice_check" tool_tip="Voice-Chat aktivieren"/> </panel> diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index 206b631874..aeed9f3796 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -1414,6 +1414,12 @@ Warten Sie kurz und versuchen Sie dann noch einmal, sich anzumelden. <string name="InvFolder favorite"> Meine Favoriten </string> + <string name="InvFolder Favorites"> + Meine Favoriten + </string> + <string name="InvFolder favorites"> + Meine Favoriten + </string> <string name="InvFolder Current Outfit"> Aktuelles Outfit </string> @@ -1429,6 +1435,12 @@ Warten Sie kurz und versuchen Sie dann noch einmal, sich anzumelden. <string name="InvFolder Meshes"> Netze </string> + <string name="InvFolder Received Items"> + Erhaltene Artikel + </string> + <string name="InvFolder Merchant Outbox"> + Händler-Outbox + </string> <string name="InvFolder Friends"> Freunde </string> diff --git a/indra/newview/skins/default/xui/es/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/es/floater_pathfinding_linksets.xml index 266e8138c9..e6f864eef5 100644 --- a/indra/newview/skins/default/xui/es/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/es/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [grupo] </floater.string> + <floater.string name="linkset_is_scripted"> + Sí + </floater.string> + <floater.string name="linkset_is_not_scripted"> + No + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Desconocido + </floater.string> <floater.string name="linkset_use_walkable"> Objeto transitable </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Nombre (prim raíz)" name="name"/> <scroll_list.columns label="Descripción (prim raíz)" name="description"/> <scroll_list.columns label="Propietario" name="owner"/> + <scroll_list.columns label="Con scripts" name="scripted"/> <scroll_list.columns label="Impacto" name="land_impact"/> <scroll_list.columns label="Distancia" name="dist_from_you"/> <scroll_list.columns label="Utilización de linkset" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/es/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/es/floater_texture_fetch_debugger.xml index 29fd2ab2a3..59aaf7f74a 100644 --- a/indra/newview/skins/default/xui/es/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/es/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, Volviendo a obtener visibles de la caché, Tiempo: [TIME] segundos, Obtenidos: [SIZE] KB, [PIXEL] MPíxeles </text> + <text name="total_time_refetch_all_cache_label"> + 16, Volviendo a obtener todas las texturas de caché, Tiempo: [TIME] segundos, Obtenidos: [SIZE] KB, [PIXEL] MPíxeles + </text> <text name="total_time_refetch_vis_http_label"> - 16, Volviendo a obtener visibles de HTTP, Tiempo: [TIME] segundos, Obtenidos: [SIZE] KB, [PIXEL] MPíxeles + 17, Volviendo a obtener visibles de HTTP, Tiempo: [TIME] segundos, Obtenidos: [SIZE] KB, [PIXEL] MPíxeles + </text> + <text name="total_time_refetch_all_http_label"> + 18, Volviendo a obtener todas las texturas de HTTP, Tiempo: [TIME] segundos, Obtenidos: [SIZE] KB, [PIXEL] MPíxeles + </text> + <spinner label="19, Proporción de texeles/píxeles:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, Fuente de texturas: </text> - <spinner label="17, Proporción de texeles/píxeles:" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Caché + HTTP" name="0"/> + <radio_item label="Solo HTTP" name="1"/> + </radio_group> <button label="Iniciar" name="start_btn"/> <button label="Restablecer" name="clear_btn"/> <button label="Cerrar" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Descodificar" name="decode_btn"/> <button label="Textura GL" name="gl_btn"/> <button label="Volver a obtener caché de vis." name="refetchviscache_btn"/> + <button label="Volver a obtener toda la caché" name="refetchallcache_btn"/> <button label="Volver a obtener HTTP de vis." name="refetchvishttp_btn"/> + <button label="Volver a obtener todo el HTTP" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/es/menu_inventory.xml b/indra/newview/skins/default/xui/es/menu_inventory.xml index 4a8f37dee4..803d3f1331 100644 --- a/indra/newview/skins/default/xui/es/menu_inventory.xml +++ b/indra/newview/skins/default/xui/es/menu_inventory.xml @@ -67,6 +67,7 @@ <menu_item_call label="Borrar carpeta del sistema" name="Delete System Folder"/> <menu_item_call label="Empezar multiconferencia" name="Conference Chat Folder"/> <menu_item_call label="Escuchar" name="Sound Play"/> + <menu_item_call label="Copiar la SLurl" name="url_copy"/> <menu_item_call label="Acerca del hito" name="About Landmark"/> <menu_item_call label="Escuchar en el mundo" name="Animation Play"/> <menu_item_call label="Ejecutarla para usted" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/es/notifications.xml b/indra/newview/skins/default/xui/es/notifications.xml index 991a079be5..7dfb27717d 100644 --- a/indra/newview/skins/default/xui/es/notifications.xml +++ b/indra/newview/skins/default/xui/es/notifications.xml @@ -1211,7 +1211,7 @@ a '[THIS_GPU]' Se te ha llevado a una región cercana. </notification> <notification name="AvatarMovedLast"> - En estos momentos no está disponible tu última posición. + En estos momentos no está disponible la posición solicitada. Se te ha llevado a una región cercana. </notification> <notification name="AvatarMovedHome"> @@ -1229,8 +1229,8 @@ Puedes usar [SECOND_LIFE] de forma normal; los demás residentes te verán corre <notification name="FirstRun"> Se ha completado la instalación de [SECOND_LIFE]. -Si es la primera vez que usas [SECOND_LIFE], debes crear una cuenta antes de poder iniciar una sesión. - <usetemplate name="okcancelbuttons" notext="Continuar" yestext="Cuenta nueva..."/> +Si es la primera vez que usas [SECOND_LIFE], debes crear una cuenta para poder iniciar una sesión. + <usetemplate name="okcancelbuttons" notext="Continuar" yestext="Crear cuenta..."/> </notification> <notification name="LoginPacketNeverReceived"> Tenemos problemas de conexión. Puede deberse a un problema de tu conexión a Internet o de [SECOND_LIFE_GRID]. @@ -3231,19 +3231,58 @@ Al ocultar el botón Hablar se desactiva la función de voz. Esta acción ocultará todos los botones y elementos de menú. Para restaurarlos, pulsa otra vez en [SHORTCUT]. <usetemplate ignoretext="Confirmar antes de ocultar la IU" name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> - Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' debido a restricciones de los permisos del linkset. Estos linksets se configurarán como '[RESTRICTED_TYPE]'. + <notification name="PathfindingLinksets_WarnOnPhantom"> + El indicador de inmaterial de algunos linksets seleccionados se conmutará. + +¿Quieres continuar? + <usetemplate ignoretext="El indicador de inmaterial de algunos linksets seleccionados se conmutará." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> + Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' debido a restricciones de los permisos del linkset. Estos linksets se configurarán como '[RESTRICTED_TYPE]'. + +¿Quieres continuar? <usetemplate ignoretext="Algunos de los linksets seleccionados no pueden configurarse debido a restricciones de los permisos del linkset." name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' porque su forma no es convexa. + +¿Quieres continuar? <usetemplate ignoretext="Algunos de los linksets seleccionados no pueden configurarse porque su forma no es convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> - Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' debido a restricciones de los permisos del linkset. Estos linksets se configurarán como '[RESTRICTED_TYPE]'. - Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' porque su forma no es convexa. Los tipos de utilización de estos linksets no cambiarán. + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + El indicador de inmaterial de algunos linksets seleccionados se conmutará. + +Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' debido a restricciones de los permisos del linkset. Estos linksets se configurarán como '[RESTRICTED_TYPE]'. + +¿Quieres continuar? + <usetemplate ignoretext="El indicador de inmaterial de algunos linksets seleccionados se conmutará y otros no se podrán establecer debido a restricciones de los permisos del linkset." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + El indicador de inmaterial de algunos linksets seleccionados se conmutará. + +Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' porque su forma no es convexa. + +¿Quieres continuar? + <usetemplate ignoretext="El indicador de inmaterial de algunos linksets seleccionados se conmutará y otros no se podrán establecer porque la forma no es convexa" name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> + Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' debido a restricciones de los permisos del linkset. Estos linksets se configurarán como '[RESTRICTED_TYPE]'. + +Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' porque su forma no es convexa. Los tipos de utilización de estos linksets no cambiarán. + +¿Quieres continuar? <usetemplate ignoretext="Algunos de los linksets seleccionados no pueden configurarse debido a restricciones de los permisos del linkset y porque su forma no es convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + El indicador de inmaterial de algunos linksets seleccionados se conmutará. + +Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' debido a restricciones de los permisos del linkset. Estos linksets se configurarán como '[RESTRICTED_TYPE]'. + +Algunos de los linksets seleccionados no pueden configurarse como '[REQUESTED_TYPE]' porque su forma no es convexa. Los tipos de utilización de estos linksets no cambiarán. + +¿Quieres continuar? + <usetemplate ignoretext="El indicador de inmaterial de algunos linksets seleccionados se conmutará y otros no se podrán establecer debido a restricciones de los permisos del linkset y porque su forma no es convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> El objeto que has seleccionado afecta al navmesh. Al cambiarlo a una Ruta flexible se eliminará del navmesh. <usetemplate ignoretext="El objeto que has seleccionado afecta al navmesh. Al cambiarlo a una Ruta flexible se eliminará del navmesh." name="okcancelignore" notext="Cancelar" yestext="OK"/> diff --git a/indra/newview/skins/default/xui/es/panel_login.xml b/indra/newview/skins/default/xui/es/panel_login.xml index 683e0a096a..1d7f077fe7 100644 --- a/indra/newview/skins/default/xui/es/panel_login.xml +++ b/indra/newview/skins/default/xui/es/panel_login.xml @@ -23,7 +23,7 @@ <combo_box name="start_location_combo"> <combo_box.item label="Mi última posición" name="MyLastLocation"/> <combo_box.item label="Mi Base" name="MyHome"/> - <combo_box.item label="<Escribe en qué región>" name="Typeregionname"/> + <combo_box.item label="<Escribe el nombre de la región>" name="Typeregionname"/> </combo_box> </layout_panel> <layout_panel name="links_login_panel"> @@ -38,9 +38,9 @@ </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + CREA TU CUENTA </text> - <button name="create_new_account_btn" label="Registrarme"/> + <button label="Iniciar ahora" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/es/panel_region_debug.xml b/indra/newview/skins/default/xui/es/panel_region_debug.xml index f07f3d3951..71bdba1a25 100644 --- a/indra/newview/skins/default/xui/es/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/es/panel_region_debug.xml @@ -36,5 +36,5 @@ <button label="?" left="297" name="top_scripts_help"/> <button label="Reiniciar la región" name="restart_btn" tool_tip="Cuenta atrás de 2 minutos y reiniciar la región"/> <button label="?" name="restart_help"/> - <button label="Cancelar reinicio" name="cancel_restart_btn" tool_tip="Retrasar una hora el reinicio de la región"/> + <button label="Cancelar reinicio" name="cancel_restart_btn" tool_tip="Cancelar el reinicio de región"/> </panel> diff --git a/indra/newview/skins/default/xui/es/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/es/panel_volume_pulldown.xml index 9193da6cde..426783aa8e 100644 --- a/indra/newview/skins/default/xui/es/panel_volume_pulldown.xml +++ b/indra/newview/skins/default/xui/es/panel_volume_pulldown.xml @@ -1,14 +1,14 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="volumepulldown_floater" width="215"> - <slider label="Maestro" name="System Volume" label_width="55" width="155"/> - <slider label="Botones" name="UI Volume" label_width="55" width="155"/> - <slider label="Ambiente" name="Wind Volume" label_width="55" width="155"/> - <slider label="Sonidos" name="SFX Volume" label_width="55" width="155"/> - <check_box name="gesture_audio_play_btn" tool_tip="Activa el sonido de los gestos"/> - <slider label="Música" name="Music Volume" label_width="55" width="155"/> - <check_box tool_tip="Activa el flujo de audio" name="enable_music"/> - <slider label="Medios" name="Media Volume" label_width="55" width="155"/> - <check_box tool_tip="Activa el flujo de medios" name="enable_media"/> - <slider label="Voz" name="Voice Volume" label_width="55" width="155"/> - <check_box tool_tip="Activar chat de voz" name="enable_voice_check"/> + <slider label="General" label_width="55" name="System Volume" width="155"/> + <slider label="Botones" label_width="55" name="UI Volume" width="155"/> + <slider label="Ambiental" label_width="55" name="Wind Volume" width="155"/> + <slider label="Sonidos" label_width="55" name="SFX Volume" width="155"/> + <check_box name="gesture_audio_play_btn" tool_tip="Activar sonidos de los gestos"/> + <slider label="Música" label_width="55" name="Music Volume" width="155"/> + <check_box name="enable_music" tool_tip="Activar música en streaming"/> + <slider label="Media" label_width="55" name="Media Volume" width="155"/> + <check_box name="enable_media" tool_tip="Activar media en streaming"/> + <slider label="Voz" label_width="55" name="Voice Volume" width="155"/> + <check_box name="enable_voice_check" tool_tip="Activar el chat de voz"/> </panel> diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index d4698b8cb3..dcde68a93a 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -1390,6 +1390,12 @@ Intenta iniciar sesión de nuevo en unos instantes. <string name="InvFolder favorite"> Mis Favoritos </string> + <string name="InvFolder Favorites"> + Mis Favoritos + </string> + <string name="InvFolder favorites"> + Mis Favoritos + </string> <string name="InvFolder Current Outfit"> Vestuario actual </string> @@ -1405,6 +1411,12 @@ Intenta iniciar sesión de nuevo en unos instantes. <string name="InvFolder Meshes"> Redes </string> + <string name="InvFolder Received Items"> + Objetos recibidos + </string> + <string name="InvFolder Merchant Outbox"> + Buzón de salida de comerciante + </string> <string name="InvFolder Friends"> Amigos </string> diff --git a/indra/newview/skins/default/xui/fr/floater_about_land.xml b/indra/newview/skins/default/xui/fr/floater_about_land.xml index 26844d9849..25c49b97b5 100644 --- a/indra/newview/skins/default/xui/fr/floater_about_land.xml +++ b/indra/newview/skins/default/xui/fr/floater_about_land.xml @@ -339,7 +339,7 @@ Seules les parcelles de grande taille peuvent apparaître dans la recherche. <check_box label="Groupe" name="check group scripts"/> <check_box label="Sécurisé (pas de dégâts)" name="check safe" tool_tip="Si cette option est cochée, le terrain est sécurisé et il n'y pas de risques de dommages causés par des combats. Si elle est décochée, des dommages causés par les combats peuvent avoir lieu."/> <check_box label="Pas de bousculades" name="PushRestrictCheck" tool_tip="Empêche l'utilisation de scripts causant des bousculades. Cette option est utile pour empêcher les comportements abusifs sur votre terrain."/> - <check_box label="Afficher le lieu dans la recherche (30 L$/semaine)" name="ShowDirectoryCheck" tool_tip="Afficher la parcelle dans les résultats de recherche"/> + <check_box label="Voir le lieu dans la recherche (30 L$/sem.)" name="ShowDirectoryCheck" tool_tip="Afficher la parcelle dans les résultats de recherche"/> <combo_box name="land category with adult"> <combo_box.item label="Toutes catégories" name="item0"/> <combo_box.item label="Appartenant aux Lindens" name="item1"/> diff --git a/indra/newview/skins/default/xui/fr/floater_edit_day_cycle.xml b/indra/newview/skins/default/xui/fr/floater_edit_day_cycle.xml index de1ba220a0..5ec68458e1 100644 --- a/indra/newview/skins/default/xui/fr/floater_edit_day_cycle.xml +++ b/indra/newview/skins/default/xui/fr/floater_edit_day_cycle.xml @@ -22,13 +22,13 @@ Remarque : si vous changez votre préréglage de nom, un nouveau préréglage sera créé et celui existant restera tel quel. </text> <text name="hint_item1"> - - Modifier un réglage de ciel et d'heure : clic sur le repère + - Modifier un réglage de ciel/heure : clic sur le repère </text> <text name="hint_item2"> - - Définir les heures de transition : clic et glissement des repères + - Heures de transition : clic-glissement des repères </text> <text name="hint_item3"> - - Afficher un aperçu du cycle du jour : déplacer le triangle + - Aperçu du cycle du jour : déplacement du triangle </text> <panel name="day_cycle_slider_panel"> <multi_slider initial_value="0" name="WLTimeSlider"/> diff --git a/indra/newview/skins/default/xui/fr/floater_model_preview.xml b/indra/newview/skins/default/xui/fr/floater_model_preview.xml index 0f717cb2cd..bd3dae6599 100644 --- a/indra/newview/skins/default/xui/fr/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/fr/floater_model_preview.xml @@ -201,7 +201,7 @@ Passes : </text> <text name="Detail Scale label"> - Échelle de détail : + Échelle détail : </text> <text name="Retain%_label"> Retenue : @@ -227,7 +227,7 @@ </panel> <panel label="Option de chargement" name="modifiers_panel"> <text name="scale_label"> - Echelle (1 = pas d'échelle) : + Échelle (1 = aucune) : </text> <spinner name="import_scale" value="1.0"/> <text name="dimensions_label"> @@ -238,12 +238,12 @@ </text> <check_box label="Inclure les textures" name="upload_textures"/> <text name="include_label"> - Pour les modèles d'avatar uniquement : + Modèles d'avatar uniquement : </text> - <check_box label="Inclure la pondération de la peau :" name="upload_skin"/> - <check_box label="Inclure la position des articulations :" name="upload_joints"/> + <check_box label="Inclure pondération de la peau :" name="upload_skin"/> + <check_box label="Inclure position des articulations :" name="upload_joints"/> <text name="pelvis_offset_label"> - Décalage Z (élever ou abaisser l'avatar) : + Décalage Z (élever/abaisser l'avatar) : </text> <spinner name="pelvis_offset" value="0.0"/> </panel> @@ -252,7 +252,7 @@ <button label="Calculer les poids et les frais" name="calculate_btn" tool_tip="Calculer les poids et les frais."/> <button label="Annuler" name="cancel_btn"/> <button label="Charger le modèle" name="ok_btn" tool_tip="Charger dans le simulateur"/> - <button label="Effacer les paramètres et réinitialiser le formulaire" name="reset_btn"/> + <button label="Effacer les paramètres / réinitialiser le formulaire" name="reset_btn"/> <text name="upload_fee"> Frais de chargement : [FEE] L$ </text> diff --git a/indra/newview/skins/default/xui/fr/floater_pathfinding_console.xml b/indra/newview/skins/default/xui/fr/floater_pathfinding_console.xml index 6d85f8035d..02d969dc08 100644 --- a/indra/newview/skins/default/xui/fr/floater_pathfinding_console.xml +++ b/indra/newview/skins/default/xui/fr/floater_pathfinding_console.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_pathfinding_console" title="Vue / test de recherche de chemin"> +<floater name="floater_pathfinding_console" title="Vue/Test de recherche de chemin"> <floater.string name="navmesh_viewer_status_library_not_implemented"> Implémentation de la bibliothèque de recherche de chemin introuvable </floater.string> @@ -93,10 +93,10 @@ </panel> <panel label="Chemin test" name="test_panel"> <text name="ctrl_click_label"> - Ctrl-clic : sélection du point de départ + Ctrl-clic : sélection point de départ </text> <text name="shift_click_label"> - Maj-clic : sélection du point d'arrivée + Maj-clic : sélection point d'arrivée </text> <text name="character_width_label"> Largeur du personnage @@ -115,7 +115,7 @@ <combo_box.item label="C" name="path_character_type_c"/> <combo_box.item label="D" name="path_character_type_d"/> </combo_box> - <button label="Effacer le chemin" name="clear_path"/> + <button label="Effacer chemin" name="clear_path"/> </panel> </tab_container> </floater> diff --git a/indra/newview/skins/default/xui/fr/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/fr/floater_pathfinding_linksets.xml index 3abddd54ce..894ec6dd9c 100644 --- a/indra/newview/skins/default/xui/fr/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/fr/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [Groupe] </floater.string> + <floater.string name="linkset_is_scripted"> + Oui + </floater.string> + <floater.string name="linkset_is_not_scripted"> + Non + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Inconnu + </floater.string> <floater.string name="linkset_use_walkable"> Marche possible </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Nom (prim racine)" name="name"/> <scroll_list.columns label="Description (prim racine)" name="description"/> <scroll_list.columns label="Propriétaire" name="owner"/> + <scroll_list.columns label="Scripté" name="scripted"/> <scroll_list.columns label="Impact" name="land_impact"/> <scroll_list.columns label="Distance" name="dist_from_you"/> <scroll_list.columns label="Usage du groupe de liens" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/fr/floater_spellcheck.xml b/indra/newview/skins/default/xui/fr/floater_spellcheck.xml index 077ce25828..635db52ffa 100644 --- a/indra/newview/skins/default/xui/fr/floater_spellcheck.xml +++ b/indra/newview/skins/default/xui/fr/floater_spellcheck.xml @@ -2,7 +2,7 @@ <floater name="spellcheck_floater" title="Paramètres du vérificateur orthographique"> <check_box label="Activer le vérificateur orthographique" name="spellcheck_enable"/> <text name="spellcheck_main"> - Dictionnaire principal : + Diction. principal : </text> <text label="Journaux :" name="spellcheck_additional"> Dictionnaires supplémentaires : diff --git a/indra/newview/skins/default/xui/fr/floater_texture_ctrl.xml b/indra/newview/skins/default/xui/fr/floater_texture_ctrl.xml index 43397c2acd..f86c1a4217 100644 --- a/indra/newview/skins/default/xui/fr/floater_texture_ctrl.xml +++ b/indra/newview/skins/default/xui/fr/floater_texture_ctrl.xml @@ -20,7 +20,7 @@ <button label="Vierge" label_selected="Vierge" name="Blank" width="60"/> <button label="Aucune" label_selected="Aucune" left="68" name="None" width="60"/> <button bottom="-240" label="" label_selected="" name="Pipette"/> - <check_box initial_value="true" label="Aperçu en direct" name="apply_immediate_check"/> + <check_box initial_value="true" label="Aperçu direct" name="apply_immediate_check"/> <text name="preview_disabled" value="Aperçu désactivé"/> <filter_editor label="Filtrer les textures" name="inventory search editor"/> <check_box initial_value="false" label="Afficher les dossiers" name="show_folders_check"/> diff --git a/indra/newview/skins/default/xui/fr/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/fr/floater_texture_fetch_debugger.xml index f0cc95319d..caae15ea17 100644 --- a/indra/newview/skins/default/xui/fr/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/fr/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, nouvelle récupération des données visibles du cache, Durée : [TIME] secondes, Récupéré : [SIZE] Ko, [PIXEL] Mpixels </text> + <text name="total_time_refetch_all_cache_label"> + 16, nouvelle récupération de toutes les textures du cache, Durée : [TIME] secondes, Récupéré : [SIZE] Ko, [PIXEL] Mpixels + </text> <text name="total_time_refetch_vis_http_label"> - 16, nouvelle récupération des données visibles de la requête HTTP, Durée : [TIME] secondes, Récupéré : [SIZE] Ko, [PIXEL] Mpixels + 17, nouvelle récupération des données visibles de la requête HTTP, Durée : [TIME] secondes, Récupéré : [SIZE] Ko, [PIXEL] Mpixels + </text> + <text name="total_time_refetch_all_http_label"> + 18, nouvelle récupération de toutes les textures de la requête HTTP, Durée : [TIME] secondes, Récupéré : [SIZE] Ko, [PIXEL] Mpixels + </text> + <spinner label="19, taux de texels/pixels :" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, source des textures : </text> - <spinner label="17, taux de texels/pixels :" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Cache + HTTP" name="0"/> + <radio_item label="HTTP uniquement" name="1"/> + </radio_group> <button label="Démarrer" name="start_btn"/> <button label="Réinitialiser" name="clear_btn"/> <button label="Fermer" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Décoder" name="decode_btn"/> <button label="Texture GL" name="gl_btn"/> <button label="Récupérer à nouveau les données visibles du cache" name="refetchviscache_btn"/> + <button label="Récupérer cache" name="refetchallcache_btn"/> <button label="Récupérer à nouveau les données visibles de la requête HTTP" name="refetchvishttp_btn"/> + <button label="Récupérer HTTP" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/fr/floater_top_objects.xml b/indra/newview/skins/default/xui/fr/floater_top_objects.xml index aeeb462ac6..eb084d9184 100644 --- a/indra/newview/skins/default/xui/fr/floater_top_objects.xml +++ b/indra/newview/skins/default/xui/fr/floater_top_objects.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="top_objects" title="Objets les plus utilisés"> +<floater name="top_objects" title="Objets les plus consommateurs"> <floater.string name="top_scripts_title"> Scripts les plus consommateurs </floater.string> @@ -45,7 +45,7 @@ <text name="owner_name_text"> Propriétaire : </text> - <button label="Filtre" name="filter_owner_btn"/> + <button label="Filtrer" name="filter_owner_btn"/> <text name="parcel_name_text"> Parcelle : </text> diff --git a/indra/newview/skins/default/xui/fr/menu_inventory.xml b/indra/newview/skins/default/xui/fr/menu_inventory.xml index 59dcff9075..627d3068c3 100644 --- a/indra/newview/skins/default/xui/fr/menu_inventory.xml +++ b/indra/newview/skins/default/xui/fr/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="Supprimer le dossier système" name="Delete System Folder"/> <menu_item_call label="Démarrer le chat conférence" name="Conference Chat Folder"/> <menu_item_call label="Jouer" name="Sound Play"/> + <menu_item_call label="Copier la SLurl" name="url_copy"/> <menu_item_call label="À propos du repère" name="About Landmark"/> <menu_item_call label="Jouer dans Second Life" name="Animation Play"/> <menu_item_call label="Jouer localement" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/fr/menu_viewer.xml b/indra/newview/skins/default/xui/fr/menu_viewer.xml index 3982a6159f..85020afe25 100644 --- a/indra/newview/skins/default/xui/fr/menu_viewer.xml +++ b/indra/newview/skins/default/xui/fr/menu_viewer.xml @@ -8,7 +8,7 @@ <menu_item_call label="Nouvelle fenêtre d'inventaire" name="NewInventoryWindow"/> <menu_item_call label="Endroits..." name="Places"/> <menu_item_call label="Favoris..." name="Picks"/> - <menu_item_call label="Caméra..." name="Camera Controls"/> + <menu_item_call label="Contrôles de la caméra..." name="Camera Controls"/> <menu label="Déplacement" name="Movement"> <menu_item_call label="M'asseoir" name="Sit Down Here"/> <menu_item_check label="Voler" name="Fly"/> diff --git a/indra/newview/skins/default/xui/fr/notifications.xml b/indra/newview/skins/default/xui/fr/notifications.xml index bb23a1063d..30154d1873 100644 --- a/indra/newview/skins/default/xui/fr/notifications.xml +++ b/indra/newview/skins/default/xui/fr/notifications.xml @@ -1202,7 +1202,7 @@ par une carte [THIS_GPU]. Vous avez été téléporté vers une région voisine. </notification> <notification name="AvatarMovedLast"> - Votre dernière destination n'est pas disponible actuellement. + La destination demandée n'est pas disponible actuellement. Vous avez été téléporté vers une région voisine. </notification> <notification name="AvatarMovedHome"> @@ -1221,7 +1221,7 @@ Vous pouvez utiliser [SECOND_LIFE] normalement, les autres résidents vous voien L'installation de [APP_NAME] est terminée. Si vous utilisez [SECOND_LIFE] pour la première fois, vous devez ouvrir un compte avant de pouvoir vous connecter. - <usetemplate name="okcancelbuttons" notext="Continuer" yestext="Nouveau compte..."/> + <usetemplate name="okcancelbuttons" notext="Continuer" yestext="Créer un compte..."/> </notification> <notification name="LoginPacketNeverReceived"> Nous avons des difficultés à vous connecter. Il y a peut-être un problème avec votre connexion Internet ou la [SECOND_LIFE_GRID]. @@ -3233,19 +3233,58 @@ Cliquez sur un point dans le monde et faites glisser votre souris pour faire tou Cette action masquera tous les boutons et articles de menu. Pour les récupérer, cliquez de nouveau sur [SHORTCUT]. <usetemplate ignoretext="Confirmer avant de masquer l'interface" name="okcancelignore" notext="Annuler" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> - Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison des restrictions d'autorisation définies les concernant. Ces groupes de liens seront définis sur [RESTRICTED_TYPE]. + <notification name="PathfindingLinksets_WarnOnPhantom"> + L'indicateur Fantôme de certains groupes de liens sélectionnés basculera. + +Voulez-vous continuer ? + <usetemplate ignoretext="L'indicateur Fantôme de certains groupes de liens basculera." name="okcancelignore" notext="Annuler" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> + Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison des restrictions d'autorisation les concernant. Ces groupes de liens seront définis sur [RESTRICTED_TYPE]. + +Voulez-vous continuer ? <usetemplate ignoretext="Certains groupes de liens sélectionnés ne peuvent pas être définis en raison des restrictions d'autorisation les concernant." name="okcancelignore" notext="Annuler" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison d'une forme non convexe. + +Voulez-vous continuer ? <usetemplate ignoretext="Certains groupes de liens sélectionnés ne peuvent pas être définis en raison d'une forme non convexe." name="okcancelignore" notext="Annuler" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + L'indicateur Fantôme de certains groupes de liens sélectionnés basculera. + +Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison des restrictions d'autorisation les concernant. Ces groupes de liens seront définis sur [RESTRICTED_TYPE]. + +Voulez-vous continuer ? + <usetemplate ignoretext="L'indicateur Fantôme de certains groupes de liens sélectionnés basculera et d'autres ne peuvent pas être définis en raison de restrictions d'autorisation sur les groupes de liens." name="okcancelignore" notext="Annuler" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + L'indicateur Fantôme de certains groupes de liens sélectionnés basculera. + +Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison d'une forme non convexe. + +Voulez-vous continuer ? + <usetemplate ignoretext="L'indicateur Fantôme de certains groupes de liens sélectionnés basculera et d'autres ne peuvent pas être définis en raison d'une forme non convexe." name="okcancelignore" notext="Annuler" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison des restrictions d'autorisation les concernant. Ces groupes de liens seront définis sur [RESTRICTED_TYPE]. - Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison d'une forme non convexe. Les types d'usage de ces groupes de liens ne seront pas modifiés. + +Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison d'une forme non convexe. Les types d'usage de ces groupes de liens ne seront pas modifiés. + +Voulez-vous continuer ? <usetemplate ignoretext="Certains groupes de liens sélectionnés ne peuvent pas être définis en raison des restrictions d'autorisation les concernant et d'une forme non convexe." name="okcancelignore" notext="Annuler" yestext="OK"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + L'indicateur Fantôme de certains groupes de liens sélectionnés basculera. + +Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison des restrictions d'autorisation les concernant. Ces groupes de liens seront définis sur [RESTRICTED_TYPE]. + +Certains groupes de liens sélectionnés ne peuvent pas être définis sur [REQUESTED_TYPE] en raison d'une forme non convexe. Les types d'usage de ces groupes de liens ne seront pas modifiés. + +Voulez-vous continuer ? + <usetemplate ignoretext="L'indicateur Fantôme de certains groupes de liens sélectionnés basculera et d'autres ne peuvent pas être définis en raison de restrictions d'autorisation sur les groupes de liens et d'une forme non convexe." name="okcancelignore" notext="Annuler" yestext="OK"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> L'objet sélectionné affecte le maillage de navigation. Si vous le modifiez en Flexibilité, il sera supprimé de ce maillage. <usetemplate ignoretext="L'objet sélectionné affecte le maillage de navigation. Si vous le modifiez en Flexibilité, il sera supprimé de ce maillage." name="okcancelignore" notext="Annuler" yestext="OK"/> diff --git a/indra/newview/skins/default/xui/fr/panel_block_list_sidetray.xml b/indra/newview/skins/default/xui/fr/panel_block_list_sidetray.xml index f54bed4fae..96add2a74b 100644 --- a/indra/newview/skins/default/xui/fr/panel_block_list_sidetray.xml +++ b/indra/newview/skins/default/xui/fr/panel_block_list_sidetray.xml @@ -5,6 +5,6 @@ </text> <scroll_list name="blocked" tool_tip="Liste des résidents actuellement ignorés"/> <button label="Ignorer une personne" label_selected="Ignorer le résident..." name="Block resident..." tool_tip="Choisir un résident à ignorer"/> - <button label="Ignorer l'objet par nom" label_selected="Ignorer l'objet par nom..." name="Block object by name..." tool_tip="Choisir un objet à ignorer par nom"/> + <button label="Ignorer un objet par son nom" label_selected="Ignorer un objet par son nom..." name="Block object by name..." tool_tip="Choisir un objet à ignorer par nom"/> <button label="Ne plus ignorer" label_selected="Ne plus ignorer" name="Unblock" tool_tip="Enlever le résident ou l'objet de la liste des ignorés"/> </panel> diff --git a/indra/newview/skins/default/xui/fr/panel_login.xml b/indra/newview/skins/default/xui/fr/panel_login.xml index 7843513e00..c8a1fe8751 100644 --- a/indra/newview/skins/default/xui/fr/panel_login.xml +++ b/indra/newview/skins/default/xui/fr/panel_login.xml @@ -23,7 +23,7 @@ <combo_box name="start_location_combo"> <combo_box.item label="Dernier emplacement" name="MyLastLocation"/> <combo_box.item label="Domicile" name="MyHome"/> - <combo_box.item label="<Saisir le nom de la région>" name="Typeregionname"/> + <combo_box.item label="<Nom de la région>" name="Typeregionname"/> </combo_box> </layout_panel> <layout_panel name="links_login_panel"> @@ -38,9 +38,9 @@ </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + CRÉER VOTRE COMPTE </text> - <button name="create_new_account_btn" label="S'inscrire"/> + <button label="Commencer" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/fr/panel_postcard_settings.xml b/indra/newview/skins/default/xui/fr/panel_postcard_settings.xml index 6f4e9c23f9..945a5e0272 100644 --- a/indra/newview/skins/default/xui/fr/panel_postcard_settings.xml +++ b/indra/newview/skins/default/xui/fr/panel_postcard_settings.xml @@ -9,12 +9,12 @@ </combo_box> <layout_stack name="postcard_image_params_ls"> <layout_panel name="postcard_image_size_lp"> - <spinner label="Largeur" name="postcard_snapshot_width"/> - <spinner label="Hauteur" name="postcard_snapshot_height"/> + <spinner label="Larg." name="postcard_snapshot_width"/> + <spinner label="Haut." name="postcard_snapshot_height"/> <check_box label="Conserver les proportions" name="postcard_keep_aspect_check"/> </layout_panel> <layout_panel name="postcard_image_format_quality_lp"> - <slider label="Qualité de l'image" name="image_quality_slider"/> + <slider label="Qualité image" name="image_quality_slider"/> <text name="image_quality_level"> ([QLVL]) </text> diff --git a/indra/newview/skins/default/xui/fr/panel_region_debug.xml b/indra/newview/skins/default/xui/fr/panel_region_debug.xml index b15af0d1f2..98ae250215 100644 --- a/indra/newview/skins/default/xui/fr/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/fr/panel_region_debug.xml @@ -36,5 +36,5 @@ <button label="?" left="337" name="top_scripts_help"/> <button label="Redémarrer la région" name="restart_btn" tool_tip="Redémarrer la région au bout de 2 minutes" width="160"/> <button label="?" left="177" name="restart_help"/> - <button label="Annuler le redémarrage" name="cancel_restart_btn" tool_tip="Retarder le redémarrage de la région d'une heure" width="160"/> + <button label="Annuler le redémarrage" name="cancel_restart_btn" tool_tip="Annuler le redémarrage de la région." width="160"/> </panel> diff --git a/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml index f40bcec908..472c4a5e8f 100644 --- a/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml @@ -4,7 +4,7 @@ Enregistrer dans l'inventaire </text> <text name="hint_lbl"> - L'enregistrement d'une image dans votre inventaire coûte [UPLOAD_COST] L$. Pour enregistrer votre image sous forme de texture, sélectionnez l'un des formats carrés. + L'enregistrement d'une image dans l'inventaire coûte [UPLOAD_COST] L$. Pour enregistrer votre image sous forme de texture, sélectionnez un format carré. </text> <combo_box label="Résolution" name="texture_size_combo"> <combo_box.item label="Fenêtre actuelle" name="CurrentWindow"/> diff --git a/indra/newview/skins/default/xui/fr/panel_snapshot_local.xml b/indra/newview/skins/default/xui/fr/panel_snapshot_local.xml index 48ccacb374..97dc3e7e2b 100644 --- a/indra/newview/skins/default/xui/fr/panel_snapshot_local.xml +++ b/indra/newview/skins/default/xui/fr/panel_snapshot_local.xml @@ -25,7 +25,7 @@ <combo_box.item label="JPEG" name="JPEG"/> <combo_box.item label="BMP (sans perte)" name="BMP"/> </combo_box> - <slider label="Qualité d'image" name="image_quality_slider"/> + <slider label="Qualité image" name="image_quality_slider"/> <text name="image_quality_level"> ([QLVL]) </text> diff --git a/indra/newview/skins/default/xui/fr/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/fr/panel_volume_pulldown.xml new file mode 100644 index 0000000000..e05c93a4ac --- /dev/null +++ b/indra/newview/skins/default/xui/fr/panel_volume_pulldown.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="volumepulldown_floater"> + <slider label="Principal" name="System Volume"/> + <slider label="Boutons" name="UI Volume"/> + <slider label="Ambiant" name="Wind Volume"/> + <slider label="Sons" name="SFX Volume"/> + <check_box name="gesture_audio_play_btn" tool_tip="Activer les sons des gestes."/> + <slider label="Musique" name="Music Volume"/> + <check_box name="enable_music" tool_tip="Activer les flux de musique."/> + <slider label="Médias" name="Media Volume"/> + <check_box name="enable_media" tool_tip="Activer les flux de média."/> + <slider label="Voix" name="Voice Volume"/> + <check_box name="enable_voice_check" tool_tip="Activer le chat vocal."/> +</panel> diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 6dc1439593..27f0e9464b 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -1414,6 +1414,12 @@ Veuillez réessayer de vous connecter dans une minute. <string name="InvFolder favorite"> Mes Favoris </string> + <string name="InvFolder Favorites"> + Mes favoris + </string> + <string name="InvFolder favorites"> + Mes favoris + </string> <string name="InvFolder Current Outfit"> Tenue actuelle </string> @@ -1429,6 +1435,12 @@ Veuillez réessayer de vous connecter dans une minute. <string name="InvFolder Meshes"> Maillages </string> + <string name="InvFolder Received Items"> + Articles reçus + </string> + <string name="InvFolder Merchant Outbox"> + Boîte d'envoi vendeur + </string> <string name="InvFolder Friends"> Amis </string> @@ -4902,7 +4914,7 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles Parler </string> <string name="Command_View_Label"> - Paramètres de la caméra + Caméra </string> <string name="Command_Voice_Label"> Paramètres vocaux diff --git a/indra/newview/skins/default/xui/it/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/it/floater_pathfinding_linksets.xml index 2c6d620b0a..7edac3ff46 100644 --- a/indra/newview/skins/default/xui/it/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/it/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [gruppo] </floater.string> + <floater.string name="linkset_is_scripted"> + Sì + </floater.string> + <floater.string name="linkset_is_not_scripted"> + No + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Sconosciuto + </floater.string> <floater.string name="linkset_use_walkable"> Camminabile </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Nome (prim principale)" name="name"/> <scroll_list.columns label="Descrizione (prim principale)" name="description"/> <scroll_list.columns label="Proprietario" name="owner"/> + <scroll_list.columns label="Scriptato" name="scripted"/> <scroll_list.columns label="Impatto" name="land_impact"/> <scroll_list.columns label="Distanza" name="dist_from_you"/> <scroll_list.columns label="Uso set collegati" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/it/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/it/floater_texture_fetch_debugger.xml index 57e65c3456..49b6453319 100644 --- a/indra/newview/skins/default/xui/it/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/it/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, Nuovo fetching elementi visibili dalla cache, Tempo: [TIME] secondi, fetching: [SIZE] KB, [PIXEL] MPixels </text> + <text name="total_time_refetch_all_cache_label"> + 16, Nuovo fetching di tutte le texture dalla cache, Tempo: [TIME] secondi, fetching: [SIZE] KB, [PIXEL] MPixels + </text> <text name="total_time_refetch_vis_http_label"> - 16, Nuovo fetching elementi visibili da HTTP, Tempo: [TIME] secondi, fetching: [SIZE] KB, [PIXEL] MPixels + 17, Nuovo fetching elementi visibili da HTTP, Tempo: [TIME] secondi, fetching: [SIZE] KB, [PIXEL] MPixels + </text> + <text name="total_time_refetch_all_http_label"> + 18, Nuovo fetching di tutte le texture da HTTP, Tempo: [TIME] secondi, fetching: [SIZE] KB, [PIXEL] MPixels + </text> + <spinner label="19, Rapporto Texel/Pixel:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, Fonte texture: </text> - <spinner label="17, Rapporto Texel/Pixel:" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Cache + HTTP" name="0"/> + <radio_item label="Solo HTTP" name="1"/> + </radio_group> <button label="Attiva" name="start_btn"/> <button label="Reimposta" name="clear_btn"/> <button label="Chiudi" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Decodifica" name="decode_btn"/> <button label="Texture GL" name="gl_btn"/> <button label="Nuovo fetch visibili cache" name="refetchviscache_btn"/> + <button label="Nuovo fetching di tutta la cache" name="refetchallcache_btn"/> <button label="Nuovo fetch visibili HTTP" name="refetchvishttp_btn"/> + <button label="Nuovo fetching di tutto il contenuto HTTP" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/it/menu_inventory.xml b/indra/newview/skins/default/xui/it/menu_inventory.xml index 4bf6be82fd..b31e35771d 100644 --- a/indra/newview/skins/default/xui/it/menu_inventory.xml +++ b/indra/newview/skins/default/xui/it/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="Elimina la cartella di sistema" name="Delete System Folder"/> <menu_item_call label="Inizia la conferenza chat" name="Conference Chat Folder"/> <menu_item_call label="Esegui" name="Sound Play"/> + <menu_item_call label="Copia SLurl" name="url_copy"/> <menu_item_call label="Informazioni sul punto di riferimento" name="About Landmark"/> <menu_item_call label="Riproduci in Second Life" name="Animation Play"/> <menu_item_call label="Esegui localmente" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/it/notifications.xml b/indra/newview/skins/default/xui/it/notifications.xml index 9bdce2e2f6..d5fdde4e7d 100644 --- a/indra/newview/skins/default/xui/it/notifications.xml +++ b/indra/newview/skins/default/xui/it/notifications.xml @@ -1207,7 +1207,7 @@ a '[THIS_GPU]' Sei stato trasferito in una regione vicina. </notification> <notification name="AvatarMovedLast"> - La tua ultima posizione non è al momento disponibile. + La posizione richiesta non è al momento disponibile. Sei stato trasferito in una regione vicina. </notification> <notification name="AvatarMovedHome"> @@ -1226,7 +1226,7 @@ Puoi comunque usare [SECOND_LIFE] normalmente e gli altri residenti ti vedranno L'installazione di [APP_NAME] è terminata. Se questa è la prima volta che usi [SECOND_LIFE], devi creare un account prima che tu possa effettuare l'accesso. - <usetemplate name="okcancelbuttons" notext="Continua" yestext="Nuovo Account..."/> + <usetemplate name="okcancelbuttons" notext="Continua" yestext="Crea account..."/> </notification> <notification name="LoginPacketNeverReceived"> Ci sono problemi di connessione. È possibile che ci siano problemi con la tua connessione Internet oppure sulla [SECOND_LIFE_GRID]. @@ -3238,19 +3238,58 @@ Clicca e trascina dovunque nel mondo per ruotare la visuale Questa azione cancellerà tutte le voci di menu e i pulsanti. Per visualizzarli nuovamente cliccare ancora [SHORTCUT]. <usetemplate ignoretext="Conferma prima di nascondere l'interfaccia" name="okcancelignore" notext="Annulla" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> + <notification name="PathfindingLinksets_WarnOnPhantom"> + L'indicatore oggetto fantasma di alcuni set collegati verrà commutato. + +Vuoi continuare? + <usetemplate ignoretext="L'indicatore oggetto fantasma di alcuni set collegati verrà commutato." name="okcancelignore" notext="Annulla" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> Alcuni set collegati selezionati non possono essere impostati su '[REQUESTED_TYPE]' a causa di limitazioni nelle autorizzazioni per i set collegati. Questi set collegati verranno invece impostati su '[RESTRICTED_TYPE]'. + +Vuoi continuare? <usetemplate ignoretext="Alcuni set collegati selezionati non possono essere impostati a causa di limitazioni nelle autorizzazioni per i set collegati." name="okcancelignore" notext="Annulla" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Alcuni set collegati selezionati non possono essere impostati a '[REQUESTED_TYPE]' perché la forma è non-convessa. + +Vuoi continuare? <usetemplate ignoretext="Alcuni set collegati selezionati non possono essere impostati perché la forma è non-convessa." name="okcancelignore" notext="Annulla" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + L'indicatore oggetto fantasma di alcuni set collegati verrà commutato. + +Alcuni set collegati selezionati non possono essere impostati su '[REQUESTED_TYPE]' a causa di limitazioni nelle autorizzazioni per i set collegati. Questi set collegati verranno invece impostati su '[RESTRICTED_TYPE]'. + +Vuoi continuare? + <usetemplate ignoretext="L'indicatore oggetto fantasma per alcuni set collegati selezionati verrà commutato, mentre quello degli altri non può essere impostato a causa di limitazioni nelle autorizzazioni per i set collegati." name="okcancelignore" notext="Annulla" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + L'indicatore oggetto fantasma di alcuni set collegati verrà commutato. + +Alcuni set collegati selezionati non possono essere impostati a '[REQUESTED_TYPE]' perché la forma è non-convessa. + +Vuoi continuare? + <usetemplate ignoretext="L'indicatore oggetto fantasma per alcuni set collegati selezionati verrà commutato, mentre quello degli altri non può essere impostato perché la forma è non-convessa." name="okcancelignore" notext="Annulla" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> Alcuni set collegati selezionati non possono essere impostati su '[REQUESTED_TYPE]' a causa di limitazioni nelle autorizzazioni per i set collegati. Questi set collegati verranno invece impostati su '[RESTRICTED_TYPE]'. - Alcuni set collegati selezionati non possono essere impostati a '[REQUESTED_TYPE]' perché la forma è non-convessa. Il tipo di utilizzo di questi set collegati non cambierà. + +Alcuni set collegati selezionati non possono essere impostati a '[REQUESTED_TYPE]' perché la forma è non-convessa. Il tipo di utilizzo di questi set collegati non cambierà. + +Vuoi continuare? <usetemplate ignoretext="Alcuni set collegati selezionati non possono essere impostati a causa delle limitazioni nelle autorizzazioni per il set collegato e perché la forma è non-convessa." name="okcancelignore" notext="Annulla" yestext="OK"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + L'indicatore oggetto fantasma di alcuni set collegati verrà commutato. + +Alcuni set collegati selezionati non possono essere impostati su '[REQUESTED_TYPE]' a causa di limitazioni nelle autorizzazioni per i set collegati. Questi set collegati verranno invece impostati su '[RESTRICTED_TYPE]'. + +Alcuni set collegati selezionati non possono essere impostati a '[REQUESTED_TYPE]' perché la forma è non-convessa. Il tipo di utilizzo di questi set collegati non cambierà. + +Vuoi continuare? + <usetemplate ignoretext="L'indicatore oggetto fantasma per alcuni set collegati selezionati verrà commutato, mentre quello degli altri non può essere impostato a causa delle limitazioni nelle autorizzazioni per il set collegato e perché la forma è non-convessa." name="okcancelignore" notext="Annulla" yestext="OK"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> L'oggetto selezionato influenza il navmesh. Se lo si trasforma in un percorso flessibile verrà rimosso dal navmesh. <usetemplate ignoretext="L'oggetto selezionato influenza il navmesh. Se lo si trasforma in un percorso flessibile verrà rimosso dal navmesh." name="okcancelignore" notext="Annulla" yestext="OK"/> diff --git a/indra/newview/skins/default/xui/it/panel_login.xml b/indra/newview/skins/default/xui/it/panel_login.xml index 02a3f8271c..2afde40940 100644 --- a/indra/newview/skins/default/xui/it/panel_login.xml +++ b/indra/newview/skins/default/xui/it/panel_login.xml @@ -38,9 +38,9 @@ </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + CREA IL TUO ACCOUNT </text> - <button name="create_new_account_btn" label="Iscriviti"/> + <button label="Inizia adesso" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/it/panel_region_debug.xml b/indra/newview/skins/default/xui/it/panel_region_debug.xml index 789dade097..aba60d03aa 100644 --- a/indra/newview/skins/default/xui/it/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/it/panel_region_debug.xml @@ -36,5 +36,5 @@ <button label="?" left="297" name="top_scripts_help"/> <button label="Riavvia la regione" name="restart_btn" tool_tip="Dai 2 minuti di tempo massimo e fai riavviare la regione"/> <button label="?" name="restart_help"/> - <button label="Annulla riavvio" name="cancel_restart_btn" tool_tip="Ritarda il riavvio della regione di un'ora"/> + <button label="Annulla riavvio" name="cancel_restart_btn" tool_tip="Annulla riavvio regione"/> </panel> diff --git a/indra/newview/skins/default/xui/it/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/it/panel_volume_pulldown.xml index 1792b09413..bc17fc0c89 100644 --- a/indra/newview/skins/default/xui/it/panel_volume_pulldown.xml +++ b/indra/newview/skins/default/xui/it/panel_volume_pulldown.xml @@ -1,15 +1,15 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="volumepulldown_floater" width="220"> - <button left="197" name="prefs_btn"/> - <slider label="Generale" name="System Volume" label_width="60" width="160"/> - <slider label="Interfaccia" name="UI Volume" label_width="60" width="160"/> - <slider label="Ambiente" name="Wind Volume" label_width="60" width="160"/> - <slider label="Suoni" name="SFX Volume" label_width="60" width="160"/> - <check_box name="gesture_audio_play_btn" tool_tip="Attiva suoni Gesture"/> - <slider label="Musica" name="Music Volume" label_width="60" width="160"/> - <check_box tool_tip="Abilita musica in streaming" name="enable_music"/> - <slider label="Media" name="Media Volume" label_width="60" width="160"/> - <check_box tool_tip="Abilita riproduzione media" name="enable_media"/> - <slider label="Voice" name="Voice Volume" label_width="60" width="160"/> - <check_box tool_tip="Abilita il voice" name="enable_voice_check"/> + <button left="197" name="prefs_btn"/> + <slider label="Principale" label_width="60" name="System Volume" width="160"/> + <slider label="Pulsanti" label_width="60" name="UI Volume" width="160"/> + <slider label="Ambiente" label_width="60" name="Wind Volume" width="160"/> + <slider label="Suoni" label_width="60" name="SFX Volume" width="160"/> + <check_box name="gesture_audio_play_btn" tool_tip="Attiva suoni dai gesti"/> + <slider label="Musica" label_width="60" name="Music Volume" width="160"/> + <check_box name="enable_music" tool_tip="Attiva streaming musica"/> + <slider label="Multimedia" label_width="60" name="Media Volume" width="160"/> + <check_box name="enable_media" tool_tip="Attiva streaming multimediale"/> + <slider label="Voce" label_width="60" name="Voice Volume" width="160"/> + <check_box name="enable_voice_check" tool_tip="Attiva chat vocale"/> </panel> diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index f2afcafbea..2dc91d316a 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -1399,6 +1399,12 @@ Prova ad accedere nuovamente tra un minuto. <string name="InvFolder favorite"> I miei preferiti </string> + <string name="InvFolder Favorites"> + I miei preferiti + </string> + <string name="InvFolder favorites"> + I miei preferiti + </string> <string name="InvFolder Current Outfit"> Abbigliamento attuale </string> @@ -1414,6 +1420,12 @@ Prova ad accedere nuovamente tra un minuto. <string name="InvFolder Meshes"> Reticoli </string> + <string name="InvFolder Received Items"> + Oggetti ricevuti + </string> + <string name="InvFolder Merchant Outbox"> + Casella venditore in uscita + </string> <string name="InvFolder Friends"> Amici </string> diff --git a/indra/newview/skins/default/xui/ja/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/ja/floater_pathfinding_linksets.xml index d05a74fe09..4441d5e738 100644 --- a/indra/newview/skins/default/xui/ja/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/ja/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [group] </floater.string> + <floater.string name="linkset_is_scripted"> + はい + </floater.string> + <floater.string name="linkset_is_not_scripted"> + いいえ + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + 不明 + </floater.string> <floater.string name="linkset_use_walkable"> 歩行可能 </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="名前(ルートプリム)" name="name"/> <scroll_list.columns label="説明(ルートプリム)" name="description"/> <scroll_list.columns label="所有者" name="owner"/> + <scroll_list.columns label="スクリプト" name="scripted"/> <scroll_list.columns label="負荷" name="land_impact"/> <scroll_list.columns label="距離" name="dist_from_you"/> <scroll_list.columns label="リンクセットの用途" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/ja/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/ja/floater_texture_fetch_debugger.xml index 4efdf7d40d..adc35137b5 100644 --- a/indra/newview/skins/default/xui/ja/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/ja/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, キャッシュから表示テクスチャを再取得、時間:[TIME] 秒、取得:[SIZE]KB、[PIXEL]メガピクセル </text> + <text name="total_time_refetch_all_cache_label"> + 16、キャッシュからすべてのテクスチャを再フェッチ中、時間: [TIME] 秒、フェッチ済み: [SIZE]KB、[PIXEL]メガピクセル + </text> <text name="total_time_refetch_vis_http_label"> - 16, HTTP から表示テクスチャを再取得、時間:[TIME] 秒、取得:[SIZE]KB、[PIXEL]メガピクセル + 17、HTTP から可視ファイルを再フェッチ中、時間: [TIME] 秒、フェッチ済み: [SIZE]KB、[PIXEL]メガピクセル + </text> + <text name="total_time_refetch_all_http_label"> + 18、HTTP からすべてのテクスチャを再フェッチ中、時間: [TIME] 秒、フェッチ済み: [SIZE]KB、[PIXEL]メガピクセル + </text> + <spinner label="19、テセル/ピクセル比:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20、テクスチャ ソース: </text> - <spinner label="17, テクセル/ピクセルの比率:" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="キャッシュ + HTTP" name="0"/> + <radio_item label="HTTP のみ" name="1"/> + </radio_group> <button label="開始" name="start_btn"/> <button label="リセット" name="clear_btn"/> <button label="閉じる" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="デコード" name="decode_btn"/> <button label="GL テクスチャ" name="gl_btn"/> <button label="キャッシュ表示テクスチャ再取得" name="refetchviscache_btn"/> + <button label="すべてのキャッシュを再フェッチ" name="refetchallcache_btn"/> <button label="HTTP表示テクスチャ再取得" name="refetchvishttp_btn"/> + <button label="すべての HTTP を再フェッチ" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/ja/menu_inventory.xml b/indra/newview/skins/default/xui/ja/menu_inventory.xml index d1893a0fc8..106b09453a 100644 --- a/indra/newview/skins/default/xui/ja/menu_inventory.xml +++ b/indra/newview/skins/default/xui/ja/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="システムフォルダを削除する" name="Delete System Folder"/> <menu_item_call label="コンファレンスチャットを開始する" name="Conference Chat Folder"/> <menu_item_call label="再生する" name="Sound Play"/> + <menu_item_call label="SLurl をコピー" name="url_copy"/> <menu_item_call label="ランドマークの情報" name="About Landmark"/> <menu_item_call label="インワールドで再生する" name="Animation Play"/> <menu_item_call label="ローカルで再生する" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/ja/notifications.xml b/indra/newview/skins/default/xui/ja/notifications.xml index 9453e9ba3b..d5cff18a24 100644 --- a/indra/newview/skins/default/xui/ja/notifications.xml +++ b/indra/newview/skins/default/xui/ja/notifications.xml @@ -1240,7 +1240,7 @@ https://wiki.secondlife.com/wiki/Adding_Spelling_Dictionaries を参照してく 近くのリージョンに移動しました。 </notification> <notification name="AvatarMovedLast"> - 前回いた場所は現在ご利用いただけません。 + リクエストされた場所は現在ご利用いただけません。 近くのリージョンに移動しました。 </notification> <notification name="AvatarMovedHome"> @@ -1259,7 +1259,7 @@ https://wiki.secondlife.com/wiki/Adding_Spelling_Dictionaries を参照してく [APP_NAME] のインストールが完了しました。 [SECOND_LIFE] を使ったことがない場合は、ログインする前にアカウントの作成を行ってください。 - <usetemplate name="okcancelbuttons" notext="続行" yestext="新規アカウント..."/> + <usetemplate name="okcancelbuttons" notext="続行" yestext="アカウントを作成..."/> </notification> <notification name="LoginPacketNeverReceived"> 接続がなかなかできません。 お使いのインターネット接続か、[SECOND_LIFE_GRID] の問題と考えられます。 @@ -3278,19 +3278,58 @@ M キーを押して変更します。 この操作により、全てのメニュー項目とボタンが非表示になります。再び表示するには [SHORTCUT] をもう一度クリックしてください。 <usetemplate ignoretext="UI を非表示にする前に確認" name="okcancelignore" notext="取り消し" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> + <notification name="PathfindingLinksets_WarnOnPhantom"> + 選択された一部のリンクセットはファントムフラグが切り替えられます。 + +続けますか? + <usetemplate ignoretext="選択された一部のリンクセットのファントムフラグが切り替えられます。" name="okcancelignore" notext="取り消し" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> 一部の選択されたリンクセットは、リンクセットへの権限が制限されているため、'[REQUESTED_TYPE]' に設定できません。これらのリンクセットは代わりに '[RESTRICTED_TYPE]' に設定されます。 + +続けますか? <usetemplate ignoretext="一部の選択されたリンクセットは、リンクセットへの権限が制限されているため設定できません。" name="okcancelignore" notext="取り消し" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> 選択された一部のリンクセットは、形状が凸状でないため、'[REQUESTED_TYPE]' に設定できません。 + +続けますか? <usetemplate ignoretext="選択された一部のリンクセットは、形状が凸状でないため設定できません。" name="okcancelignore" notext="取り消し" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + 選択された一部のリンクセットはファントムフラグが切り替えられます。 + +一部の選択されたリンクセットは、リンクセットへの権限が制限されているため、'[REQUESTED_TYPE]' に設定できません。これらのリンクセットは代わりに '[RESTRICTED_TYPE]' に設定されます。 + +続けますか? + <usetemplate ignoretext="選択された一部のリンクセットのファントムフラグが切り替えられ、他のリンクセットはリンクセットへの権限が制限されているため設定できません。" name="okcancelignore" notext="取り消し" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + 選択された一部のリンクセットはファントムフラグが切り替えられます。 + +選択された一部のリンクセットは、形状が凸状でないため、'[REQUESTED_TYPE]' に設定できません。 + +続けますか? + <usetemplate ignoretext="選択された一部のリンクセットのファントムフラグが切り替えられ、他のリンクセットは形状が凸状でないため設定できません。" name="okcancelignore" notext="取り消し" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> 一部の選択されたリンクセットは、リンクセットへの権限が制限されているため、'[REQUESTED_TYPE]' に設定できません。これらのリンクセットは代わりに '[RESTRICTED_TYPE]' に設定されます。 + 選択された一部のリンクセットは、形状が凸状でないため、'[REQUESTED_TYPE]' に設定できません。これらのリンクセットの用途タイプは変わりません。 + +続けますか? <usetemplate ignoretext="一部の選択されたリンクセットは、リンクセットへの権限が制限されており、また形状が凸状でないため設定できません。" name="okcancelignore" notext="取り消し" yestext="OK"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + 選択された一部のリンクセットはファントムフラグが切り替えられます。 + +一部の選択されたリンクセットは、リンクセットへの権限が制限されているため、'[REQUESTED_TYPE]' に設定できません。これらのリンクセットは代わりに '[RESTRICTED_TYPE]' に設定されます。 + +選択された一部のリンクセットは、形状が凸状でないため、'[REQUESTED_TYPE]' に設定できません。これらのリンクセットの用途タイプは変わりません。 + +続けますか? + <usetemplate ignoretext="選択された一部のリンクセットのファントムフラグが切り替えられ、他のリンクセットはリンクセットへの権限が制限され、また形状が凸状でないため設定できません。" name="okcancelignore" notext="取り消し" yestext="OK"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> 選択されたオブジェクトはナビメッシュに影響を与えます。オブジェクトをフレキシブルパスに変更すると、ナビメッシュから削除されます。 <usetemplate ignoretext="選択されたオブジェクトはナビメッシュに影響を与えます。オブジェクトをフレキシブルパスに変更すると、ナビメッシュから削除されます。" name="okcancelignore" notext="取り消し" yestext="OK"/> diff --git a/indra/newview/skins/default/xui/ja/panel_login.xml b/indra/newview/skins/default/xui/ja/panel_login.xml index 780f7aa1ae..396d9e65b1 100644 --- a/indra/newview/skins/default/xui/ja/panel_login.xml +++ b/indra/newview/skins/default/xui/ja/panel_login.xml @@ -38,9 +38,9 @@ </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + アカウントを作成してください </text> - <button name="create_new_account_btn" label="お申し込み"/> + <button label="今すぐ開始" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/ja/panel_region_debug.xml b/indra/newview/skins/default/xui/ja/panel_region_debug.xml index 7d1d837b73..169da27ce5 100644 --- a/indra/newview/skins/default/xui/ja/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/ja/panel_region_debug.xml @@ -36,5 +36,5 @@ <button label="?" name="top_scripts_help"/> <button label="地域再起動" name="restart_btn" tool_tip="2分間のカウントダウン後、地域を再起動します"/> <button label="?" name="restart_help"/> - <button label="再起動をキャンセル" name="cancel_restart_btn" tool_tip="地域の再起動を1時間遅延します"/> + <button label="再起動をキャンセル" name="cancel_restart_btn" tool_tip="リージョンの再起動をキャンセル"/> </panel> diff --git a/indra/newview/skins/default/xui/ja/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/ja/panel_volume_pulldown.xml new file mode 100644 index 0000000000..967dedf061 --- /dev/null +++ b/indra/newview/skins/default/xui/ja/panel_volume_pulldown.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="volumepulldown_floater"> + <slider label="マスター" name="System Volume"/> + <slider label="ボタン" name="UI Volume"/> + <slider label="環境音" name="Wind Volume"/> + <slider label="サウンド" name="SFX Volume"/> + <check_box name="gesture_audio_play_btn" tool_tip="ジェスチャーの音を有効にする"/> + <slider label="音楽" name="Music Volume"/> + <check_box name="enable_music" tool_tip="ストリーミング音楽を有効にする"/> + <slider label="メディア" name="Media Volume"/> + <check_box name="enable_media" tool_tip="ストリーミングメディアを有効にする"/> + <slider label="ボイス" name="Voice Volume"/> + <check_box name="enable_voice_check" tool_tip="ボイスチャットを有効にする"/> +</panel> diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index 7c3f9489da..ee7c11c3e9 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -1414,6 +1414,12 @@ support@secondlife.com にお問い合わせください。 <string name="InvFolder favorite"> お気に入り </string> + <string name="InvFolder Favorites"> + お気に入り + </string> + <string name="InvFolder favorites"> + お気に入り + </string> <string name="InvFolder Current Outfit"> 着用中のアウトフィット </string> @@ -1429,6 +1435,12 @@ support@secondlife.com にお問い合わせください。 <string name="InvFolder Meshes"> メッシュ </string> + <string name="InvFolder Received Items"> + 受け取った商品 + </string> + <string name="InvFolder Merchant Outbox"> + マーチャントのアウトボックス + </string> <string name="InvFolder Friends"> フレンド </string> diff --git a/indra/newview/skins/default/xui/pt/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/pt/floater_pathfinding_linksets.xml index 8dc14a79a5..e0c60679dd 100644 --- a/indra/newview/skins/default/xui/pt/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/pt/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [grupo] </floater.string> + <floater.string name="linkset_is_scripted"> + Sim + </floater.string> + <floater.string name="linkset_is_not_scripted"> + Não + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Desconhecido + </floater.string> <floater.string name="linkset_use_walkable"> Caminhável </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Nome (prim raiz)" name="name"/> <scroll_list.columns label="Descrição (prim raiz)" name="description"/> <scroll_list.columns label="Proprietário" name="owner"/> + <scroll_list.columns label="Com script" name="scripted"/> <scroll_list.columns label="Impacto" name="land_impact"/> <scroll_list.columns label="Distância" name="dist_from_you"/> <scroll_list.columns label="Uso do linkset" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/pt/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/pt/floater_texture_fetch_debugger.xml index f20d50362e..0e897aea09 100644 --- a/indra/newview/skins/default/xui/pt/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/pt/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, Obtendo novamente visíveis do cache, Tempo: [TIME] segundos, Obtidos: [SIZE]KB, [PIXEL]MPixels </text> + <text name="total_time_refetch_all_cache_label"> + 16, Obtendo novamente todas as texturas do cache, Tempo: [TIME] segundos, Obtidos: [SIZE]KB, [PIXEL]MPixels + </text> <text name="total_time_refetch_vis_http_label"> - 16, Obtendo novamente visíveis do HTTP, Tempo: [TIME] segundos, Obtidos: [SIZE]KB, [PIXEL]MPixels + 17, Obtendo novamente visíveis do HTTP, Tempo: [TIME] segundos, Obtidos: [SIZE]KB, [PIXEL]MPixels + </text> + <text name="total_time_refetch_all_http_label"> + 18, Obtendo novamente todas as texturas do HTTP, Tempo: [TIME] segundos, Obtidos: [SIZE]KB, [PIXEL]MPixels + </text> + <spinner label="19, Proporção de texel/pixel:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, Origem da textura: </text> - <spinner label="17, Proporção de texel/pixel:" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Cache + HTTP" name="0"/> + <radio_item label="Apenas HTTP" name="1"/> + </radio_group> <button label="Iniciar" name="start_btn"/> <button label="Redefinir" name="clear_btn"/> <button label="Fechar" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Decodificar" name="decode_btn"/> <button label="Textura GL" name="gl_btn"/> <button label="Obter novamente cache visível" name="refetchviscache_btn"/> + <button label="Obter todo o cache novamente" name="refetchallcache_btn"/> <button label="Obter novamente HTTP visível" name="refetchvishttp_btn"/> + <button label="Obter todo o HTTP novamente" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/pt/menu_inventory.xml b/indra/newview/skins/default/xui/pt/menu_inventory.xml index 09e1fbf72e..a3a648eb34 100644 --- a/indra/newview/skins/default/xui/pt/menu_inventory.xml +++ b/indra/newview/skins/default/xui/pt/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="Excluir pasta do sistema" name="Delete System Folder"/> <menu_item_call label="Pasta conversa em conferência" name="Conference Chat Folder"/> <menu_item_call label="Executar som" name="Sound Play"/> + <menu_item_call label="Copiar SLurl" name="url_copy"/> <menu_item_call label="Sobre o marco" name="About Landmark"/> <menu_item_call label="Executar animação" name="Animation Play"/> <menu_item_call label="Executar áudio" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/pt/notifications.xml b/indra/newview/skins/default/xui/pt/notifications.xml index 11d987d73a..ff7382bf80 100644 --- a/indra/newview/skins/default/xui/pt/notifications.xml +++ b/indra/newview/skins/default/xui/pt/notifications.xml @@ -1196,7 +1196,7 @@ para '[THIS_GPU]' Você chegou a uma região próxima. </notification> <notification name="AvatarMovedLast"> - Esse destino não está disponível no momento. + O destino solicitado não está disponível no momento. Você chegou a uma região próxima. </notification> <notification name="AvatarMovedHome"> @@ -1212,10 +1212,10 @@ Enquando isso, use o [SECOND_LIFE] normalmente. Seu visual será exibido correta </form> </notification> <notification name="FirstRun"> - A instalação do [APP_NAME] está pronta. + A instalação do [APP_NAME] está pronta. Se você ainda não conhece o [SECOND_LIFE], basta criar uma conta para começar. - <usetemplate name="okcancelbuttons" notext="Continuar" yestext="Nova conta.."/> + <usetemplate name="okcancelbuttons" notext="Continuar" yestext="Criar conta"/> </notification> <notification name="LoginPacketNeverReceived"> Estamos detectando um problema de conexão. Pode haver um problema com a sua conexão à internet ou com o [SECOND_LIFE_GRID]. @@ -3210,18 +3210,57 @@ Se o botão Falar for ocultado, o recurso de voz será desabilitado. Essa ação irá ocultar todos os itens de menu e botões. Para trazê-los de volta, clique em [SHORTCUT] novamente. <usetemplate ignoretext="Confirmar antes de ocultar interface" name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> - Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]' devido às restrições de permissão no linkset. Ao invés disso, estes linksets serão definidos como '[RESTRICTED_TYPE]'. + <notification name="PathfindingLinksets_WarnOnPhantom"> + Alguns linksets selecionados terão suas sinalizações fantasmas alternadas. + +Deseja continuar? + <usetemplate ignoretext="Algumas sinalizações fantasmas dos linksets selecionados serão alternadas." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> + Algumas sinalizações fantasmas dos linksets selecionados não podem ser definidas como '[REQUESTED_TYPE]' devido às restrições de permissão no linkset. Ao invés disso, estes linksets serão definidos como '[RESTRICTED_TYPE]'. + +Deseja continuar? <usetemplate ignoretext="Alguns linksets selecionados não podem ser definidos devido às restrições de permissão no linkset." name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]', pois a forma não é convexa. + +Deseja continuar? <usetemplate ignoretext="Alguns linksets selecionados não podem ser definidos, pois a forma não é convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> - Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]' devido às restrições de permissão no linkset. Ao invés disso, estes linksets serão definidos como '[RESTRICTED_TYPE]'. - Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]', pois a forma não é convexa. Estes tipos de uso de linksets não mudarão. - <usetemplate ignoretext="Alguns linksets selecionados não podem ser definido devido às restrições de permissão no linkset e porque a forma não é convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + Alguns linksets selecionados terão suas sinalizações fantasmas alternadas. + +Algumas sinalizações fantasmas dos linksets selecionados não podem ser definidas como '[REQUESTED_TYPE]' devido às restrições de permissão no linkset. Ao invés disso, estes linksets serão definidos como '[RESTRICTED_TYPE]'. + +Deseja continuar? + <usetemplate ignoretext="Algumas sinalizações fantasmas dos linksets selecionados serão alternadas e outras não poderão ser definidas devido às restrições de permissão no linkset." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + Alguns linksets selecionados terão suas sinalizações fantasmas alternadas. + +Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]', pois a forma não é convexa. + +Deseja continuar? + <usetemplate ignoretext="Algumas sinalizações fantasmas dos linksets selecionados serão alternadas e outros não podem ser definidas, pois a forma não é convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> + Algumas sinalizações fantasmas dos linksets selecionados não podem ser definidas como '[REQUESTED_TYPE]' devido às restrições de permissão no linkset. Ao invés disso, estes linksets serão definidos como '[RESTRICTED_TYPE]'. + +Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]', pois a forma não é convexa. Estes tipos de uso de linksets não mudarão. + +Deseja continuar? + <usetemplate ignoretext="Alguns linksets selecionados não podem ser definidos devido às restrições de permissão no linkset e porque a forma não é convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + Alguns linksets selecionados terão suas sinalizações fantasmas alternadas. + +Algumas sinalizações fantasmas dos linksets selecionados não podem ser definidas como '[REQUESTED_TYPE]' devido às restrições de permissão no linkset. Ao invés disso, estes linksets serão definidos como '[RESTRICTED_TYPE]'. + +Alguns linksets selecionados não podem ser definidos como '[REQUESTED_TYPE]', pois a forma não é convexa. Estes tipos de uso de linksets não mudarão. + +Deseja continuar? + <usetemplate ignoretext="Algumas sinalizações fantasmas dos linksets selecionados serão alternadas e outras não poderão ser definidas, pois a forma não é convexa." name="okcancelignore" notext="Cancelar" yestext="OK"/> </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> O objeto selecionado afeta o navmesh. Alterá-lo para um Caminho Flexível irá removê-lo do navmesh. diff --git a/indra/newview/skins/default/xui/pt/panel_login.xml b/indra/newview/skins/default/xui/pt/panel_login.xml index 25a2c9fda0..d7e9fa76ea 100644 --- a/indra/newview/skins/default/xui/pt/panel_login.xml +++ b/indra/newview/skins/default/xui/pt/panel_login.xml @@ -6,7 +6,7 @@ <layout_stack name="login_widgets"> <layout_panel name="login"> <text name="log_in_text"> - CONECTAR + LOGIN </text> <text name="username_text"> Nome de usuário: @@ -22,24 +22,25 @@ </text> <combo_box name="start_location_combo"> <combo_box.item label="Última posição" name="MyLastLocation"/> - <combo_box.item label="Meu início" name="MyHome"/> + <combo_box.item label="Minha casa" name="MyHome"/> + <combo_box.item label="<Digite o nome da região>" name="Typeregionname"/> </combo_box> </layout_panel> <layout_panel name="links_login_panel"> <text name="login_help"> - Precisa de ajuda ao conectar? + Precisa de ajuda com o login? </text> <text name="forgot_password_text"> Esqueceu seu nome ou senha? </text> - <button label="conectar" name="connect_btn"/> + <button label="Login" name="connect_btn"/> <check_box label="Lembrar senha" name="remember_check"/> </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + CRIE SUA CONTA </text> - <button name="create_new_account_btn" label="Cadastre-se"/> + <button label="Comece agora" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/pt/panel_region_debug.xml b/indra/newview/skins/default/xui/pt/panel_region_debug.xml index 72e348e48c..be15d40d74 100644 --- a/indra/newview/skins/default/xui/pt/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/pt/panel_region_debug.xml @@ -36,5 +36,5 @@ <button label="?" left="297" name="top_scripts_help"/> <button label="Reiniciar a região" name="restart_btn" tool_tip="Após 2 minutos de contagem regressiva, reiniciar a região"/> <button label="?" name="restart_help"/> - <button label="Cancelar reinício" name="cancel_restart_btn" tool_tip="Adiar o reinício da região por uma hora"/> + <button label="Cancelar reinício" name="cancel_restart_btn" tool_tip="Cancelar reinício da região"/> </panel> diff --git a/indra/newview/skins/default/xui/pt/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/pt/panel_volume_pulldown.xml new file mode 100644 index 0000000000..1dfd2a69ca --- /dev/null +++ b/indra/newview/skins/default/xui/pt/panel_volume_pulldown.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="volumepulldown_floater"> + <slider label="Master" name="System Volume"/> + <slider label="Botões" name="UI Volume"/> + <slider label="Ambiente" name="Wind Volume"/> + <slider label="Sons" name="SFX Volume"/> + <check_box name="gesture_audio_play_btn" tool_tip="Ativar sons dos gestos"/> + <slider label="Música" name="Music Volume"/> + <check_box name="enable_music" tool_tip="Ativar streaming de música"/> + <slider label="Mídia" name="Media Volume"/> + <check_box name="enable_media" tool_tip="Ativar mídia em stream"/> + <slider label="Voz" name="Voice Volume"/> + <check_box name="enable_voice_check" tool_tip="Ativar bate-papo de voz"/> +</panel> diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index a7234bdc14..9f611c08e4 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -1354,6 +1354,12 @@ Pessoas com contas gratuitas não poderão acessar o Second Life no momento para <string name="InvFolder favorite"> Meus favoritos </string> + <string name="InvFolder Favorites"> + Meus favoritos + </string> + <string name="InvFolder favorites"> + Meus favoritos + </string> <string name="InvFolder Current Outfit"> Look atual </string> @@ -1369,6 +1375,12 @@ Pessoas com contas gratuitas não poderão acessar o Second Life no momento para <string name="InvFolder Meshes"> Meshes: </string> + <string name="InvFolder Received Items"> + Itens recebidos + </string> + <string name="InvFolder Merchant Outbox"> + Caixa de saída do lojista + </string> <string name="InvFolder Friends"> Amigos </string> diff --git a/indra/newview/skins/default/xui/ru/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/ru/floater_pathfinding_linksets.xml index f1b8e23a2c..db100fa415 100644 --- a/indra/newview/skins/default/xui/ru/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/ru/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [группа] </floater.string> + <floater.string name="linkset_is_scripted"> + Да + </floater.string> + <floater.string name="linkset_is_not_scripted"> + Нет + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Неизвестно + </floater.string> <floater.string name="linkset_use_walkable"> Проходимое место </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Имя (корневой примитив)" name="name"/> <scroll_list.columns label="Описание (корневой примитив)" name="description"/> <scroll_list.columns label="Владелец" name="owner"/> + <scroll_list.columns label="Скриптовые" name="scripted"/> <scroll_list.columns label="Воздействие" name="land_impact"/> <scroll_list.columns label="Расстояние" name="dist_from_you"/> <scroll_list.columns label="Использование набора связей" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/ru/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/ru/floater_texture_fetch_debugger.xml index 034106f24c..628e6c5c87 100644 --- a/indra/newview/skins/default/xui/ru/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/ru/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, Повторное извлечение из кэша, время: [TIME] с, извлечено: [SIZE] КБ, [PIXEL] Мпикселов </text> + <text name="total_time_refetch_all_cache_label"> + 16, Повторное извлечение всех текстур из кэша, время: [TIME] с, извлечено: [SIZE] КБ, [PIXEL] Мпикселов + </text> <text name="total_time_refetch_vis_http_label"> - 16, Повторное извлечение из HTTP, время: [TIME] с, извлечено: [SIZE] КБ, [PIXEL] Мпикселов + 17, Повторное извлечение из HTTP, время: [TIME] с, извлечено: [SIZE] КБ, [PIXEL] Мпикселов + </text> + <text name="total_time_refetch_all_http_label"> + 18, Повторное извлечение всех текстур из HTTP, время: [TIME] с, извлечено: [SIZE] КБ, [PIXEL] Мпикселов + </text> + <spinner label="19, Отношение текселы/пикселы:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, Истояник текстур: </text> - <spinner label="17, Отношение текселы/пикселы" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Кэш + HTTP" name="0"/> + <radio_item label="Только HTTP" name="1"/> + </radio_group> <button label="Пуск" name="start_btn"/> <button label="Сброс" name="clear_btn"/> <button label="Закрыть" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Декодировать" name="decode_btn"/> <button label="Текстура GL" name="gl_btn"/> <button label="Повторно извлечь из кэша" name="refetchviscache_btn"/> + <button label="Повторно извлечь все из кэша" name="refetchallcache_btn"/> <button label="Повторно извлечь из HTTP" name="refetchvishttp_btn"/> + <button label="Повторно извлечь все из HTTP" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/ru/menu_inventory.xml b/indra/newview/skins/default/xui/ru/menu_inventory.xml index 49f7281b4e..37ee19fc1d 100644 --- a/indra/newview/skins/default/xui/ru/menu_inventory.xml +++ b/indra/newview/skins/default/xui/ru/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="Удалить системную папку" name="Delete System Folder"/> <menu_item_call label="Начать конференцию" name="Conference Chat Folder"/> <menu_item_call label="Воспроизвести" name="Sound Play"/> + <menu_item_call label="Копировать URL SL" name="url_copy"/> <menu_item_call label="О закладке" name="About Landmark"/> <menu_item_call label="Проиграть для всех" name="Animation Play"/> <menu_item_call label="Проиграть для себя" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/ru/notifications.xml b/indra/newview/skins/default/xui/ru/notifications.xml index 9dad569ddd..85b7074253 100644 --- a/indra/newview/skins/default/xui/ru/notifications.xml +++ b/indra/newview/skins/default/xui/ru/notifications.xml @@ -1207,7 +1207,7 @@ Вы перемещены в соседний регион. </notification> <notification name="AvatarMovedLast"> - Ваше последнее местоположение сейчас недоступно. + Требуемое вами местоположение сейчас недоступно. Вы перемещены в соседний регион. </notification> <notification name="AvatarMovedHome"> @@ -3233,19 +3233,58 @@ http://secondlife.com/download. Это действие приведет к скрытию всех меню и кнопок. Чтобы вернуть их, щелкните [SHORTCUT] снова. <usetemplate ignoretext="Подтверждать перед скрытием интерфейса" name="okcancelignore" notext="Отмена" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> + <notification name="PathfindingLinksets_WarnOnPhantom"> + Признак фантомности некоторых выбранных наборов связей будет изменен. + +Продолжить? + <usetemplate ignoretext="Признак фантомности некоторых выбранных наборов связей будет изменен." name="okcancelignore" notext="Отмена" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> Для некоторых выбранных наборов связей нельзя задать тип «[REQUESTED_TYPE]» из-за ограничений этих наборов. Эти наборы связей будут иметь тип «[RESTRICTED_TYPE]». + +Продолжить? <usetemplate ignoretext="Нельзя задать некоторые выбранные наборы связей из-за ограничений этих наборов." name="okcancelignore" notext="Отмена" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Для некоторых выбранных наборов связей невозможно задать тип «[REQUESTED_TYPE]», так как фигура не выпуклая. + +Продолжить? <usetemplate ignoretext="Нельзя задать некоторые выбранные наборы связей, так как фигура не выпуклая." name="okcancelignore" notext="Отмена" yestext="OK"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + Признак фантомности некоторых выбранных наборов связей будет изменен. + +Для некоторых выбранных наборов связей нельзя задать тип «[REQUESTED_TYPE]» из-за ограничений этих наборов. Эти наборы связей будут иметь тип «[RESTRICTED_TYPE]». + +Продолжить? + <usetemplate ignoretext="Для некоторых выбранных наборов связей будет изменен признак фантомности, а другие наборы нельзя задать из-за ограничений этих наборов." name="okcancelignore" notext="Отмена" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + Признак фантомности некоторых выбранных наборов связей будет изменен. + +Для некоторых выбранных наборов связей невозможно задать тип «[REQUESTED_TYPE]», так как фигура не выпуклая. + +Продолжить? + <usetemplate ignoretext="Для некоторых выбранных наборов связей будет изменен признак фантомности, а другие наборы нельзя задать, так как фигура не выпуклая." name="okcancelignore" notext="Отмена" yestext="OK"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> Для некоторых выбранных наборов связей нельзя задать тип «[REQUESTED_TYPE]» из-за ограничений этих наборов. Эти наборы связей будут иметь тип «[RESTRICTED_TYPE]». - Для некоторых выбранных наборов связей невозможно задать тип «[REQUESTED_TYPE]», так как фигура не выпуклая. Тип использования этих наборов связей не изменится. + +Для некоторых выбранных наборов связей невозможно задать тип «[REQUESTED_TYPE]», так как фигура не выпуклая. Тип использования этих наборов связей не изменится. + +Продолжить? <usetemplate ignoretext="Нельзя задать некоторые выбранные наборы связей из-за ограничений этих наборов и потому, что фигура не выпуклая." name="okcancelignore" notext="Отмена" yestext="OK"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + Признак фантомности некоторых выбранных наборов связей будет изменен. + +Для некоторых выбранных наборов связей нельзя задать тип «[REQUESTED_TYPE]» из-за ограничений этих наборов. Эти наборы связей будут иметь тип «[RESTRICTED_TYPE]». + +Для некоторых выбранных наборов связей невозможно задать тип «[REQUESTED_TYPE]», так как фигура не выпуклая. Тип использования этих наборов связей не изменится. + +Продолжить? + <usetemplate ignoretext="Для некоторых выбранных наборов связей будет изменен признак фантомности, а другие наборы нельзя задать из-за ограничений этих наборов и потому, что фигура не выпуклая." name="okcancelignore" notext="Отмена" yestext="OK"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> Выбранный объект влияет на навигационную сетку. Если заменить его на гибкий путь, он будет удален из навигационной сетки. <usetemplate ignoretext="Выбранный объект влияет на навигационную сетку. Если заменить его на гибкий путь, он будет удален из навигационной сетки." name="okcancelignore" notext="Отмена" yestext="OK"/> diff --git a/indra/newview/skins/default/xui/ru/panel_login.xml b/indra/newview/skins/default/xui/ru/panel_login.xml index 7ead924c1f..f0877731c6 100644 --- a/indra/newview/skins/default/xui/ru/panel_login.xml +++ b/indra/newview/skins/default/xui/ru/panel_login.xml @@ -38,9 +38,9 @@ </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + СОЗДАЙТЕ СВОЙ АККАУНТ </text> - <button name="create_new_account_btn" label="Регистрация"/> + <button label="Начать" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/ru/panel_region_debug.xml b/indra/newview/skins/default/xui/ru/panel_region_debug.xml index 7bef927507..4be1e781fa 100644 --- a/indra/newview/skins/default/xui/ru/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/ru/panel_region_debug.xml @@ -30,5 +30,5 @@ <button label="Самые активные участники столкновений..." name="top_colliders_btn" tool_tip="Список объектов, для которых столкновения наиболее вероятны"/> <button label="Список лучших скриптов..." name="top_scripts_btn" tool_tip="Объекты, в которых скрипты выполняются дольше всего"/> <button label="Перезагрузить регион" name="restart_btn" tool_tip="Отсчитать 2 минуты и перезагрузить регион"/> - <button label="Отменить перезапуск" name="cancel_restart_btn" tool_tip="Отложить перезагрузку региона на час"/> + <button label="Отменить перезапуск" name="cancel_restart_btn" tool_tip="Отменить перезапуск региона"/> </panel> diff --git a/indra/newview/skins/default/xui/ru/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/ru/panel_volume_pulldown.xml new file mode 100644 index 0000000000..fe044cd083 --- /dev/null +++ b/indra/newview/skins/default/xui/ru/panel_volume_pulldown.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="volumepulldown_floater"> + <slider label="Общая" name="System Volume"/> + <slider label="Кнопки" name="UI Volume"/> + <slider label="Окружающая" name="Wind Volume"/> + <slider label="Звуки" name="SFX Volume"/> + <check_box name="gesture_audio_play_btn" tool_tip="Включить звуки от жестов"/> + <slider label="Музыка" name="Music Volume"/> + <check_box name="enable_music" tool_tip="Включить потоковую музыку"/> + <slider label="Медиа" name="Media Volume"/> + <check_box name="enable_media" tool_tip="Включить потоковое медиа"/> + <slider label="Голос" name="Voice Volume"/> + <check_box name="enable_voice_check" tool_tip="Включить голосовой чат"/> +</panel> diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index 3745aff8fd..6e8ec05e9f 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -1411,6 +1411,12 @@ support@secondlife.com. <string name="InvFolder favorite"> Мое избранное </string> + <string name="InvFolder Favorites"> + Мое избранное + </string> + <string name="InvFolder favorites"> + Мое избранное + </string> <string name="InvFolder Current Outfit"> Текущий костюм </string> @@ -1426,6 +1432,12 @@ support@secondlife.com. <string name="InvFolder Meshes"> Меши </string> + <string name="InvFolder Received Items"> + Полученные вещи + </string> + <string name="InvFolder Merchant Outbox"> + Торговые исходящие + </string> <string name="InvFolder Friends"> Друзья </string> diff --git a/indra/newview/skins/default/xui/tr/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/tr/floater_pathfinding_linksets.xml index 3aee66a860..2e416c9311 100644 --- a/indra/newview/skins/default/xui/tr/floater_pathfinding_linksets.xml +++ b/indra/newview/skins/default/xui/tr/floater_pathfinding_linksets.xml @@ -30,6 +30,9 @@ <floater.string name="linkset_terrain_owner"> -- </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> <floater.string name="linkset_terrain_land_impact"> -- </floater.string> @@ -45,6 +48,15 @@ <floater.string name="linkset_owner_group"> [grup] </floater.string> + <floater.string name="linkset_is_scripted"> + Evet + </floater.string> + <floater.string name="linkset_is_not_scripted"> + Hayır + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + Bilinmiyor + </floater.string> <floater.string name="linkset_use_walkable"> Yürüyebilir </floater.string> @@ -94,6 +106,7 @@ <scroll_list.columns label="Ad (kök prim)" name="name"/> <scroll_list.columns label="Açıklama (kök prim)" name="description"/> <scroll_list.columns label="Sahip" name="owner"/> + <scroll_list.columns label="Komut Dosyalı" name="scripted"/> <scroll_list.columns label="Etki" name="land_impact"/> <scroll_list.columns label="Mesafe" name="dist_from_you"/> <scroll_list.columns label="Bağlantı kümesi kullanımı" name="linkset_use"/> diff --git a/indra/newview/skins/default/xui/tr/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/tr/floater_texture_fetch_debugger.xml index a592479b6c..42426225c7 100644 --- a/indra/newview/skins/default/xui/tr/floater_texture_fetch_debugger.xml +++ b/indra/newview/skins/default/xui/tr/floater_texture_fetch_debugger.xml @@ -45,10 +45,23 @@ <text name="total_time_refetch_vis_cache_label"> 15, Görünür dokuların önbellekten tekrar alınması, Süre: [TIME] saniye, Alınan: [SIZE]KB, [PIXEL]MPiksel </text> + <text name="total_time_refetch_all_cache_label"> + 16, Tüm dokuların önbellekten tekrar alınması, Süre: [TIME] saniye, Alınan: [SIZE]KB, [PIXEL]MPiksel + </text> <text name="total_time_refetch_vis_http_label"> - 16, Görünür dokuların HTTP'den tekrar alınması, Süre: [TIME] saniye, Alınan: [SIZE]KB, [PIXEL]MPiksel + 17, Görünür dokuların HTTP'den tekrar alınması, Süre: [TIME] saniye, Alınan: [SIZE]KB, [PIXEL]MPiksel + </text> + <text name="total_time_refetch_all_http_label"> + 18, Tüm dokuların HTTP'den tekrar alınması, Süre: [TIME] saniye, Alınan: [SIZE]KB, [PIXEL]MPiksel + </text> + <spinner label="19, Teksel/Piksel Oranı:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20, Doku Kaynağı: </text> - <spinner label="17, Teksel/Piksel Oranı:" name="texel_pixel_ratio"/> + <radio_group name="texture_source"> + <radio_item label="Önbellek + HTTP" name="0"/> + <radio_item label="Sadece HTTP" name="1"/> + </radio_group> <button label="Başla" name="start_btn"/> <button label="Sıfırla" name="clear_btn"/> <button label="Kapat" name="close_btn"/> @@ -58,5 +71,7 @@ <button label="Şifre Çöz" name="decode_btn"/> <button label="GL Dokusu" name="gl_btn"/> <button label="Görünür Dokuları Önbellekten Tekrar Al" name="refetchviscache_btn"/> + <button label="Tüm Önbelleği Tekrar Al" name="refetchallcache_btn"/> <button label="Görünür Dokuları HTTP'den Tekrar Al" name="refetchvishttp_btn"/> + <button label="Tüm HTTP'yi Tekrar Al" name="refetchallhttp_btn"/> </floater> diff --git a/indra/newview/skins/default/xui/tr/menu_inventory.xml b/indra/newview/skins/default/xui/tr/menu_inventory.xml index 170cdebd24..51049427af 100644 --- a/indra/newview/skins/default/xui/tr/menu_inventory.xml +++ b/indra/newview/skins/default/xui/tr/menu_inventory.xml @@ -68,6 +68,7 @@ <menu_item_call label="Sistem Klasörünü Sil" name="Delete System Folder"/> <menu_item_call label="Konferans Sohbeti Başlat" name="Conference Chat Folder"/> <menu_item_call label="Oyna" name="Sound Play"/> + <menu_item_call label="SLurl'i Kopyala" name="url_copy"/> <menu_item_call label="Yer İmi Hakkında" name="About Landmark"/> <menu_item_call label="SL Dünyasında Oynat" name="Animation Play"/> <menu_item_call label="Yerel Olarak Oynat" name="Animation Audition"/> diff --git a/indra/newview/skins/default/xui/tr/notifications.xml b/indra/newview/skins/default/xui/tr/notifications.xml index 708e05062f..488702f9ca 100644 --- a/indra/newview/skins/default/xui/tr/notifications.xml +++ b/indra/newview/skins/default/xui/tr/notifications.xml @@ -1207,7 +1207,7 @@ sonraki: '[THIS_GPU]' Yakınındaki başka bir bölgeye taşındınız. </notification> <notification name="AvatarMovedLast"> - Son konumunuz şu anda kullanılamıyor. + Talep ettiğiniz konum şu anda kullanılamıyor. Yakınındaki başka bir bölgeye taşındınız. </notification> <notification name="AvatarMovedHome"> @@ -1223,10 +1223,10 @@ Yeni bir ana konum ayarlamak isteyebilirsiniz. </form> </notification> <notification name="FirstRun"> - [APP_NAME] kurulumu tamamlandı. + [APP_NAME] yüklemesi tamamlandı. [SECOND_LIFE]'ı ilk kez kullanıyorsanız, oturum açmadan önce bir hesap oluşturmalısınız. - <usetemplate name="okcancelbuttons" notext="Devam" yestext="Yeni Hesap..."/> + <usetemplate name="okcancelbuttons" notext="Devam" yestext="Hesap Oluştur..."/> </notification> <notification name="LoginPacketNeverReceived"> Bağlantıda sorun yaşıyoruz. İnternet bağlantınızda ya da [SECOND_LIFE_GRID] uygulamasında bir problem olabilir. @@ -3233,19 +3233,58 @@ Görünümünüzü döndürmek için dünya üzerindeki herhangi bir yeri tıkla Bu eylem tüm menü öğelerini ve düğmelerini gizler. Bunları geri almak için [SHORTCUT] üzerine tekrar tıklayın. <usetemplate ignoretext="KA'ni gizlemeden önce onayla" name="okcancelignore" notext="İptal" yestext="Tamam"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestricted"> + <notification name="PathfindingLinksets_WarnOnPhantom"> + Bazı seçili bağlantı kümelerinin Fantom bayrağı dönüştürülecek. + +Devam etmek istiyor musunuz? + <usetemplate ignoretext="Bazı seçili bağlantı kümelerinin fantom bayrağı dönüştürülecek." name="okcancelignore" notext="İptal" yestext="Tamam"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> Seçilen bazı bağlantı kümeleri, bağlantı kümesi için izin kısıtlamaları nedeniyle '[REQUESTED_TYPE]' olarak ayarlanamıyor. Bunun yerine bu bağlantı kümeleri '[RESTRICTED_TYPE]' olarak ayarlanacak. + +Devam etmek istiyor musunuz? <usetemplate ignoretext="Seçilen bazı bağlantı kümeleri, bağlantı kümesi için izin kısıtlamaları nedeniyle ayarlanamıyor." name="okcancelignore" notext="İptal" yestext="Tamam"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnVolume"> + <notification name="PathfindingLinksets_MismatchOnVolume"> Seçilen bazı bağlantı kümeleri '[REQUESTED_TYPE]' olarak ayarlanamaz, çünkü şekil konveks değil. + +Devam etmek istiyor musunuz? <usetemplate ignoretext="Seçilen bazı bağlantı kümeleri ayarlanamaz, çünkü şekil konveks değil." name="okcancelignore" notext="İptal" yestext="Tamam"/> </notification> - <notification name="PathfindingLinksets_SetLinksetUseMismatchOnRestrictedAndVolume"> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + Bazı seçili bağlantı kümelerinin Fantom bayrağı dönüştürülecek. + +Seçilen bazı bağlantı kümeleri, bağlantı kümesi için izin kısıtlamaları nedeniyle '[REQUESTED_TYPE]' olarak ayarlanamıyor. Bunun yerine bu bağlantı kümeleri '[RESTRICTED_TYPE]' olarak ayarlanacak. + +Devam etmek istiyor musunuz? + <usetemplate ignoretext="Bazı seçili bağlantı kümelerinin fantom bayrağı dönüştürülecek, diğerleri ise bağlantı kümesi için izin kısıtlamaları nedeniyle ayarlanamıyor." name="okcancelignore" notext="İptal" yestext="Tamam"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + Bazı seçili bağlantı kümelerinin Fantom bayrağı dönüştürülecek. + +Seçilen bazı bağlantı kümeleri '[REQUESTED_TYPE]' olarak ayarlanamaz, çünkü şekil konveks değil. + +Devam etmek istiyor musunuz? + <usetemplate ignoretext="Bazı seçili bağlantı kümelerinin fantom bayrağı dönüştürülecek, diğerleri ise şekil konveks olmadığı için ayarlanamıyor" name="okcancelignore" notext="İptal" yestext="Tamam"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> Seçilen bazı bağlantı kümeleri, bağlantı kümesi için izin kısıtlamaları nedeniyle '[REQUESTED_TYPE]' olarak ayarlanamıyor. Bunun yerine bu bağlantı kümeleri '[RESTRICTED_TYPE]' olarak ayarlanacak. - Seçilen bazı bağlantı kümeleri '[REQUESTED_TYPE]' olarak ayarlanamaz, çünkü şekil konveks değil. Bu bağlantı kümelerinin kullanım tipleri değişmez. + +Seçilen bazı bağlantı kümeleri '[REQUESTED_TYPE]' olarak ayarlanamaz, çünkü şekil konveks değil. Bu bağlantı kümelerinin kullanım tipleri değişmez. + +Devam etmek istiyor musunuz? <usetemplate ignoretext="Seçilen bazı bağlantı kümeleri, bağlantı kümesi için izin kısıtlamaları nedeniyle ve şekil konveks olmadığı için ayarlanamıyor." name="okcancelignore" notext="İptal" yestext="Tamam"/> </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + Bazı seçili bağlantı kümelerinin Fantom bayrağı dönüştürülecek. + +Seçilen bazı bağlantı kümeleri, bağlantı kümesi için izin kısıtlamaları nedeniyle '[REQUESTED_TYPE]' olarak ayarlanamıyor. Bunun yerine bu bağlantı kümeleri '[RESTRICTED_TYPE]' olarak ayarlanacak. + +Seçilen bazı bağlantı kümeleri '[REQUESTED_TYPE]' olarak ayarlanamaz, çünkü şekil konveks değil. Bu bağlantı kümelerinin kullanım tipleri değişmez. + +Devam etmek istiyor musunuz? + <usetemplate ignoretext="Bazı seçili bağlantı kümelerinin fantom bayrağı dönüştürülecek, diğerleri ise bağlantı kümesi için izin kısıtlamaları nedeniyle ve şekil konveks olmadığı için ayarlanamıyor." name="okcancelignore" notext="İptal" yestext="Tamam"/> + </notification> <notification name="PathfindingLinksets_ChangeToFlexiblePath"> Seçilen nesne navigasyon örgüsünü etkiliyor. Bunu bir Esnek Yol olarak değiştirirseniz, navigasyon örgüsünden çıkartmış olursunuz. <usetemplate ignoretext="Seçilen nesne navigasyon örgüsünü etkiliyor. Bunu bir Esnek Yol olarak değiştirirseniz, navigasyon örgüsünden çıkartmış olursunuz." name="okcancelignore" notext="İptal" yestext="Tamam"/> diff --git a/indra/newview/skins/default/xui/tr/panel_login.xml b/indra/newview/skins/default/xui/tr/panel_login.xml index 15684ba402..28d316e46b 100644 --- a/indra/newview/skins/default/xui/tr/panel_login.xml +++ b/indra/newview/skins/default/xui/tr/panel_login.xml @@ -38,9 +38,9 @@ </layout_panel> <layout_panel name="links"> <text name="create_account_text"> - CREATE YǾUR ACCǾUNT + HESABINIZI OLUŞTURUN </text> - <button name="create_new_account_btn" label="Kaydolun"/> + <button label="Şimdi başla" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/tr/panel_region_debug.xml b/indra/newview/skins/default/xui/tr/panel_region_debug.xml index 3f101a1824..834ece563f 100644 --- a/indra/newview/skins/default/xui/tr/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/tr/panel_region_debug.xml @@ -30,5 +30,5 @@ <button label="En Çok Çarpışanlar..." name="top_colliders_btn" tool_tip="En çok potansiyel çarpışma yaşayan nesnelerin listesi"/> <button label="En Çok Komut Dsy. Çalştr...." name="top_scripts_btn" tool_tip="Komut dosyalarını çalıştırırken en çok zaman harcayan nesnelerin listesi"/> <button label="Bölgeyi Yeniden Başlat" name="restart_btn" tool_tip="2 dakikalık bir geri sayımdan sonra bölgeyi yeniden başlat"/> - <button label="Yeniden Başlatmayı İptal Et" name="cancel_restart_btn" tool_tip="Bölgenin yeniden başlatılmasını 1 saat ertele"/> + <button label="Yeniden Başlatmayı İptal Et" name="cancel_restart_btn" tool_tip="Bölge yeniden başlatmasını iptal et"/> </panel> diff --git a/indra/newview/skins/default/xui/tr/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/tr/panel_volume_pulldown.xml new file mode 100644 index 0000000000..0c8c7b68b5 --- /dev/null +++ b/indra/newview/skins/default/xui/tr/panel_volume_pulldown.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="volumepulldown_floater"> + <slider label="Ana" name="System Volume"/> + <slider label="Düğmeler" name="UI Volume"/> + <slider label="Ortam" name="Wind Volume"/> + <slider label="Sesler" name="SFX Volume"/> + <check_box name="gesture_audio_play_btn" tool_tip="Mimiklerdeki sesleri etkinleştir"/> + <slider label="Müzik" name="Music Volume"/> + <check_box name="enable_music" tool_tip="Müzik Akışını Etkinleştir"/> + <slider label="Ortam" name="Media Volume"/> + <check_box name="enable_media" tool_tip="Akış Ortamını Etkinleştir"/> + <slider label="Ses" name="Voice Volume"/> + <check_box name="enable_voice_check" tool_tip="Sesli Sohbeti Etkinleştir"/> +</panel> diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index eed65f8c75..a35e1de721 100644 --- a/indra/newview/skins/default/xui/tr/strings.xml +++ b/indra/newview/skins/default/xui/tr/strings.xml @@ -1411,6 +1411,12 @@ Lütfen bir dakika içerisinde tekrar oturum açmayı deneyin. <string name="InvFolder favorite"> Favorilerim </string> + <string name="InvFolder Favorites"> + Sık Kullanılanlarım + </string> + <string name="InvFolder favorites"> + Sık Kullanılanlarım + </string> <string name="InvFolder Current Outfit"> Mevcut Dış Görünüm </string> @@ -1426,6 +1432,12 @@ Lütfen bir dakika içerisinde tekrar oturum açmayı deneyin. <string name="InvFolder Meshes"> Örgüler </string> + <string name="InvFolder Received Items"> + Alınan Öğeler + </string> + <string name="InvFolder Merchant Outbox"> + Satıcı Giden Kutusu + </string> <string name="InvFolder Friends"> Arkadaşlar </string> diff --git a/indra/newview/skins/default/xui/zh/floater_about.xml b/indra/newview/skins/default/xui/zh/floater_about.xml index 48a417ba46..643881e416 100644 --- a/indra/newview/skins/default/xui/zh/floater_about.xml +++ b/indra/newview/skins/default/xui/zh/floater_about.xml @@ -10,7 +10,7 @@ <floater.string name="AboutPosition"> 你的方位是 [POSITION_0,number,1], [POSITION_1,number,1], [POSITION_2,number,1],地區名:[REGION],主機:<nolink>[HOSTNAME]</nolink> ([HOSTIP]) [SERVER_VERSION] -[[SERVER_RELEASE_NOTES_URL] [ReleaseNotes]] +[SERVER_RELEASE_NOTES_URL] </floater.string> <floater.string name="AboutSystem"> CPU:[CPU] @@ -37,51 +37,60 @@ Qt Webkit 版本: [QT_WEBKIT_VERSION] <floater.string name="AboutTraffic"> 封包損失:[PACKETS_LOST,number,0]/[PACKETS_IN,number,0] ([PACKETS_PCT,number,1]%) </floater.string> + <floater.string name="ErrorFetchingServerReleaseNotesURL"> + 擷取伺服器版本說明 URL 時出錯。 + </floater.string> <tab_container name="about_tab"> <panel label="資訊" name="support_panel"> <button label="覆製到剪貼簿" name="copy_btn"/> </panel> <panel label="貸記" name="credits_panel"> - <text_editor name="credits_editor"> - 「第二人生」由……等多人協力開發。 - -感謝下列居民相助,我們得以推出如此完美的版本:……。要感謝的人還很多,族繁不及備載。 - - - - -「任務永續,目標常在,希望長存,夢想萬古不朽。」——愛德華・甘迺迪 + <text name="linden_intro"> + 「第二人生」由以下的 Linden 家族帶給你: + </text> + <text name="contrib_intro"> + 這些人士做了開放源碼的貢獻: + </text> + <text_editor name="contrib_names"> + 執行時期被取代的假名稱 + </text_editor> + <text name="trans_intro"> + 以下人士提供翻譯: + </text> + <text_editor name="trans_names"> + 執行時期被取代的假名稱 </text_editor> </panel> <panel label="許可" name="licenses_panel"> <text_editor name="credits_editor"> - 3Dconnexion SDK Copyright (C) 1992-2007 3Dconnexion -APR Copyright (C) 2000-2004 The Apache Software Foundation -Collada DOM Copyright 2005 Sony Computer Entertainment Inc. -cURL Copyright (C) 1996-2002, Daniel Stenberg, (daniel@haxx.se) -DBus/dbus-glib Copyright (C) 2002, 2003 CodeFactory AB / Copyright (C) 2003, 2004 Red Hat, Inc. -expat Copyright (C) 1998, 1999, 2000 Thai Open Source Software Center Ltd. -FreeType Copyright (C) 1996-2002, The FreeType Project (www.freetype.org). -GL Copyright (C) 1999-2004 Brian Paul. -GLOD Copyright (C) 2003-04 Jonathan Cohen, Nat Duca, Chris Niski, Johns Hopkins University and David Luebke, Brenden Schubert, University of Virginia. -google-perftools Copyright (c) 2005, Google Inc. -Havok.com(TM) Copyright (C) 1999-2001, Telekinesys Research Limited. -jpeg2000 Copyright (C) 2001, David Taubman, The University of New South Wales (UNSW) -jpeglib Copyright (C) 1991-1998, Thomas G. Lane. -ogg/vorbis Copyright (C) 2001, Xiphophorus -OpenSSL Copyright (C) 1998-2002 The OpenSSL Project. -PCRE Copyright (c) 1997-2008 University of Cambridge -SDL Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga -SSLeay Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) -xmlrpc-epi Copyright (C) 2000 Epinions, Inc. -zlib Copyright (C) 1995-2002 Jean-loup Gailly and Mark Adler. -google-perftools Copyright (c) 2005, Google Inc. + 3Dconnexion SDK Copyright (C) 1992-2009 3Dconnexion + APR Copyright (C) 2011 The Apache Software Foundation + Collada DOM Copyright 2006 Sony Computer Entertainment Inc. + cURL Copyright (C) 1996-2010, Daniel Stenberg, (daniel@haxx.se) + DBus/dbus-glib Copyright (C) 2002, 2003 CodeFactory AB / Copyright (C) 2003, 2004 Red Hat, Inc. + expat Copyright (C) 1998, 1999, 2000 Thai Open Source Software Center Ltd. + FreeType Copyright (C) 1996-2002, 2006 David Turner, Robert Wilhelm, and Werner Lemberg. + GL Copyright (C) 1999-2004 Brian Paul. + GLOD Copyright (C) 2003-04 Jonathan Cohen, Nat Duca, Chris Niski, Johns Hopkins University and David Luebke, Brenden Schubert, University of Virginia. + google-perftools Copyright (c) 2005, Google Inc. + Havok.com(TM) Copyright (C) 1999-2001, Telekinesys Research Limited. + jpeg2000 Copyright (C) 2001, David Taubman, The University of New South Wales (UNSW) + jpeglib Copyright (C) 1991-1998, Thomas G. Lane. + ogg/vorbis Copyright (C) 2002, Xiphophorus + OpenSSL Copyright (C) 1998-2008 The OpenSSL Project. + PCRE Copyright (c) 1997-2012 University of Cambridge + SDL Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga + SSLeay Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + xmlrpc-epi Copyright (C) 2000 Epinions, Inc. + zlib Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler. + + 第二人生 Viewer 採用 Havok (TM) 物理引擎。 (c)Copyright 1999-2010 Havok.com Inc.(及其放照人)。 保留一切權利。 詳情見 www.havok.com。 -第二人生 Viewer 採用 Havok (TM) 物理引擎。 (c)Copyright 1999-2010 Havok.com Inc.(及其放照人)。 保留一切權利。 詳情見 www.havok.com。 + 本軟體含有 NVIDIA Corporation 提供的源程式碼。 -保留一切權利。 詳情見 licenses.txt。 + 保留一切權利。 詳情見 licenses.txt。 -語音聊天音頻技術:Polycom(R) Siren14(TM) (ITU-T Rec. G.722.1 Annex C) + 語音聊天音頻技術:Polycom(R) Siren14(TM) (ITU-T Rec. G.722.1 Annex C) </text_editor> </panel> </tab_container> diff --git a/indra/newview/skins/default/xui/zh/floater_about_land.xml b/indra/newview/skins/default/xui/zh/floater_about_land.xml index 9dbc7dbdd7..76db621951 100644 --- a/indra/newview/skins/default/xui/zh/floater_about_land.xml +++ b/indra/newview/skins/default/xui/zh/floater_about_land.xml @@ -129,15 +129,15 @@ 流量: </text> <text name="DwellText"> - 0 + 載入中... </text> <button label="購買土地" name="Buy Land..."/> + <button label="Linden 出售" name="Linden Sale..." tool_tip="土地必須有人擁有、已設有內容,並且不在拍賣中。"/> <button label="腳本資訊" name="Scripts..."/> <button label="為群組購買" name="Buy For Group..."/> <button label="購買通行權" name="Buy Pass..." tool_tip="通行權允許你暫時可出入這塊土地。"/> <button label="放棄土地" name="Abandon Land..."/> <button label="收回土地" name="Reclaim Land..."/> - <button label="Linden 出售" name="Linden Sale..." tool_tip="土地必須有人擁有、已設有內容,並且不在拍賣中。"/> </panel> <panel label="契約" name="land_covenant_panel"> <panel.string name="can_resell"> @@ -212,19 +212,19 @@ 地區物件負荷倍數:[BONUS] </text> <text name="Simulator primitive usage:"> - 幾何元件使用: + 地區容納量: </text> <text name="objects_available"> 使用 [MAX] 中的 [COUNT] (剩餘 [AVAILABLE] 可用) </text> <text name="Primitives parcel supports:"> - 地段所提供的幾何元件數: + 地段土地容納量: </text> <text name="object_contrib_text"> [COUNT] </text> <text name="Primitives on parcel:"> - 地段上的幾何元件數: + 地段土地衝擊量: </text> <text name="total_objects_text"> [COUNT] @@ -309,8 +309,10 @@ <text name="allow_label"> 允許其他居民去: </text> - <check_box label="編輯地形" name="edit land check" tool_tip="如果勾選,任何人皆可使你的土地變形。 最好不勾選,因為你隨時可以自行編輯你自己的土地。"/> - <check_box label="飛行" name="check fly" tool_tip="如果勾選,居民可在你土地上飛行。 如果不勾選,居民僅可飛越你土地。"/> + <text name="allow_label0"> + 飛行: + </text> + <check_box label="任何人" name="check fly" tool_tip="如果勾選,居民可在你土地上飛行。 如果不勾選,居民僅可飛越你土地。"/> <text name="allow_label2"> 建造: </text> @@ -326,9 +328,6 @@ </text> <check_box label="任何人" name="check other scripts"/> <check_box label="群組" name="check group scripts"/> - <text name="land_options_label"> - 土地選項: - </text> <check_box label="安全(無傷害)" name="check safe" tool_tip="若勾選,將把土地設為安全,禁絕傷害性的戰鬥。 若未勾選,則允許傷害性的戰鬥。"/> <check_box label="禁止推撞" name="PushRestrictCheck" tool_tip="禁止使用腳本推撞。 勾選這選項可有效防止你土地上出現滋事行為。"/> <check_box label="將地點刊登顯示在搜尋中(L$30 / 每週)" name="ShowDirectoryCheck" tool_tip="讓其他人可以在搜尋結果中看到這塊地段"/> @@ -368,6 +367,10 @@ 快照: </text> <texture_picker name="snapshot_ctrl" tool_tip="點按以挑選圖片"/> + <text name="allow_label5"> + 其他地段的化身可以看見本地段裡的化身,並與之交談 + </text> + <check_box label="察看化身" name="SeeAvatarsCheck" tool_tip="允許其他地段的化身看到本地段包括你在內的化身,並可互相交談。"/> <text name="landing_point"> 登陸點:[LANDING] </text> @@ -424,6 +427,11 @@ 聲音: </text> <check_box label="將姿勢和物件的聲音限制於此地段" name="check sound local"/> + <text name="Avatar Sounds:"> + 化身聲音: + </text> + <check_box label="任何人" name="all av sound check"/> + <check_box label="群組" name="group av sound check"/> <text name="Voice settings:"> 語音: </text> @@ -435,20 +443,15 @@ <panel.string name="access_estate_defined"> (由領地定義) </panel.string> - <panel.string name="allow_public_access"> - 允許公開出入([MATURITY])(注意:若未勾選,將設立禁越線) - </panel.string> <panel.string name="estate_override"> 至少一個選項在領地的層級設定 </panel.string> - <text name="Limit access to this parcel to:"> - 此地段出入權 - </text> + <check_box label="允許公開出入(若未勾選,將設立禁越線)" name="public_access"/> <text name="Only Allow"> - 僅允許經過如下驗證的居民出入: + 僅允許符合以下條件的居民進入: </text> - <check_box label="預留付款資料 [ESTATE_PAYMENT_LIMIT]" name="limit_payment" tool_tip="禁絕身份不明居民。"/> - <check_box label="年齡驗證 [ESTATE_AGE_LIMIT]" name="limit_age_verified" tool_tip="禁止尚未驗證年齡的居民。 參閱 [SUPPORT_SITE] 獲取進一步資訊。"/> + <check_box label="已預留付款資料 [ESTATE_PAYMENT_LIMIT]" name="limit_payment" tool_tip="居民必須提供付款資料才能進入這地段。 參閱 [SUPPORT_SITE] 獲取進一步資訊。"/> + <check_box label="年滿 18 歲 [ESTATE_AGE_LIMIT]" name="limit_age_verified" tool_tip="居民必須年滿 18 歲才能進入這地段。 參閱 [SUPPORT_SITE] 獲取進一步資訊。"/> <check_box label="允許出入的群組:[GROUP]" name="GroupCheck" tool_tip="設定群組於一般頁籤。"/> <check_box label="出售通行權給:" name="PassCheck" tool_tip="允許暫時出入這個地段"/> <combo_box name="pass_combo"> diff --git a/indra/newview/skins/default/xui/zh/floater_animation_anim_preview.xml b/indra/newview/skins/default/xui/zh/floater_animation_anim_preview.xml new file mode 100644 index 0000000000..76cb9079c4 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_animation_anim_preview.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Anim Preview" title="ANIMATION.ANIM"> + <text name="name_label"> + 名稱: + </text> + <text name="description_label"> + 描述: + </text> + <button label="上傳(L$[AMOUNT])" name="ok_btn"/> + <button label="取消" label_selected="取消" name="cancel_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_animation_bvh_preview.xml b/indra/newview/skins/default/xui/zh/floater_animation_bvh_preview.xml new file mode 100644 index 0000000000..ffb0de8a68 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_animation_bvh_preview.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Animation Preview"> + <floater.string name="failed_to_initialize"> + 動作初始化失敗 + </floater.string> + <floater.string name="anim_too_long"> + 動作檔長度為 [LENGTH] 秒。 + +動作檔長度最多可為 [MAX_LENGTH] 秒。 + </floater.string> + <floater.string name="failed_file_read"> + 無法讀取動作檔。 + +[STATUS] + </floater.string> + <floater.string name="E_ST_OK"> + 確定 + </floater.string> + <floater.string name="E_ST_EOF"> + 檔案結尾不正常。 + </floater.string> + <floater.string name="E_ST_NO_CONSTRAINT"> + 無法讀取約束定義。 + </floater.string> + <floater.string name="E_ST_NO_FILE"> + 無法開啟 BVH 檔案。 + </floater.string> + <floater.string name="E_ST_NO_HIER"> + HIERARCHY 檔頭無效。 + </floater.string> + <floater.string name="E_ST_NO_JOINT"> + 找不到 ROOT 或 JOINT。 + </floater.string> + <floater.string name="E_ST_NO_NAME"> + 無法取得 JOINT 名稱。 + </floater.string> + <floater.string name="E_ST_NO_OFFSET"> + 無法尋找位移。 + </floater.string> + <floater.string name="E_ST_NO_CHANNELS"> + 找不到頻道。 + </floater.string> + <floater.string name="E_ST_NO_ROTATION"> + 無法取得旋轉序。 + </floater.string> + <floater.string name="E_ST_NO_AXIS"> + 無法取得旋轉軸。 + </floater.string> + <floater.string name="E_ST_NO_MOTION"> + 找不到動作。 + </floater.string> + <floater.string name="E_ST_NO_FRAMES"> + 無法取得幀數。 + </floater.string> + <floater.string name="E_ST_NO_FRAME_TIME"> + 無法取得幀時間。 + </floater.string> + <floater.string name="E_ST_NO_POS"> + 無法取得位置值。 + </floater.string> + <floater.string name="E_ST_NO_ROT"> + 無法取得旋轉值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_FILE"> + 無法開啟平移檔案。 + </floater.string> + <floater.string name="E_ST_NO_XLT_HEADER"> + 無法讀取平移檔頭。 + </floater.string> + <floater.string name="E_ST_NO_XLT_NAME"> + 無法讀取平移名稱。 + </floater.string> + <floater.string name="E_ST_NO_XLT_IGNORE"> + 無法讀取平移忽略值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_RELATIVE"> + 無法讀取平移相對值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_OUTNAME"> + 無法讀取平移輸出名稱。 + </floater.string> + <floater.string name="E_ST_NO_XLT_MATRIX"> + 無法讀取平移矩陣。 + </floater.string> + <floater.string name="E_ST_NO_XLT_MERGECHILD"> + 無法取得 mergechild 名稱。 + </floater.string> + <floater.string name="E_ST_NO_XLT_MERGEPARENT"> + 無法取得 mergeparent 名稱。 + </floater.string> + <floater.string name="E_ST_NO_XLT_PRIORITY"> + 無法取得優先值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_LOOP"> + 無法取得迴圈值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_EASEIN"> + 無法取得 easeIn 值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_EASEOUT"> + 無法取得 easeOut 值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_HAND"> + 無法取得 hand morph 值。 + </floater.string> + <floater.string name="E_ST_NO_XLT_EMOTE"> + 無法讀取表情符號名稱。 + </floater.string> + <floater.string name="E_ST_BAD_ROOT"> + root joint 名稱不正確,使用 "hip"。 + </floater.string> + <text name="name_label"> + 名稱: + </text> + <text name="description_label"> + 描述: + </text> + <spinner label="優先度" name="priority" tool_tip="其他動作的控制可被此動作強行取代"/> + <check_box label="連續" name="loop_check" tool_tip="讓此動作不斷重覆演繹"/> + <spinner label="入(%)" name="loop_in_point" tool_tip="設定動作中重覆演繹的起始點"/> + <spinner label="出(%)" name="loop_out_point" tool_tip="設定動作中重覆演繹的結束點"/> + <text name="hand_label"> + 手部姿勢 + </text> + <combo_box name="hand_pose_combo" tool_tip="控制動作演繹時雙手的姿勢"> + <combo_box.item label="張開" name="Spread"/> + <combo_box.item label="放鬆" name="Relaxed"/> + <combo_box.item label="雙手伸出指頭" name="PointBoth"/> + <combo_box.item label="拳頭" name="Fist"/> + <combo_box.item label="左邊放鬆" name="RelaxedLeft"/> + <combo_box.item label="左邊伸指" name="PointLeft"/> + <combo_box.item label="左邊握拳" name="FistLeft"/> + <combo_box.item label="右邊放鬆" name="RelaxedRight"/> + <combo_box.item label="右邊伸指" name="PointRight"/> + <combo_box.item label="右邊握拳" name="FistRight"/> + <combo_box.item label="右手敬禮" name="SaluteRight"/> + <combo_box.item label="打字" name="Typing"/> + <combo_box.item label="右手比出和平手勢" name="PeaceRight"/> + </combo_box> + <text name="emote_label"> + 表情 + </text> + <combo_box name="emote_combo" tool_tip="控制動作演繹時臉部的姿態"> + <item label="(無)" name="[None]" value=""/> + <item label="張口吶喊貌" name="Aaaaah" value="張口吶喊貌"/> + <item label="害怕" name="Afraid" value="害怕"/> + <item label="生氣" name="Angry" value="生氣"/> + <item label="燦爛笑容" name="BigSmile" value="燦爛笑容"/> + <item label="無聊" name="Bored" value="無聊"/> + <item label="哭泣" name="Cry" value="哭泣"/> + <item label="鄙視" name="Disdain" value="鄙視"/> + <item label="尷尬" name="Embarrassed" value="尷尬"/> + <item label="皺眉" name="Frown" value="皺眉"/> + <item label="親吻" name="Kiss" value="親吻"/> + <item label="笑" name="Laugh" value="笑"/> + <item label="嫌惡貌" name="Plllppt" value="嫌惡貌"/> + <item label="作噁" name="Repulsed" value="作噁"/> + <item label="傷心" name="Sad" value="傷心"/> + <item label="聳聳肩" name="Shrug" value="聳聳肩"/> + <item label="微笑" name="Smile" value="微笑"/> + <item label="驚喜" name="Surprise" value="驚喜"/> + <item label="眨眼" name="Wink" value="眨眼"/> + <item label="擔心" name="Worry" value="擔心"/> + </combo_box> + <text name="preview_label"> + 預覽… + </text> + <combo_box name="preview_base_anim" tool_tip="用這個來測試你的化身從一般動作轉入動作演繹時的情況。"> + <item label="站立" name="Standing" value="站立"/> + <item label="步行中" name="Walking" value="步行中"/> + <item label="坐著" name="Sitting" value="坐著"/> + <item label="飛行" name="Flying" value="飛行"/> + </combo_box> + <spinner label="淡入(秒)" name="ease_in_time" tool_tip="動作攙混植入的時間長度(秒)"/> + <spinner label="淡出(秒)" name="ease_out_time" tool_tip="動作攙混淡出的時間長度(秒)"/> + <button name="play_btn" tool_tip="播放你的動作"/> + <button name="pause_btn" tool_tip="暫停你的動做"/> + <button name="stop_btn" tool_tip="停止播放動作"/> + <text name="bad_animation_text"> + 無法讀取動作檔。 + +我們建議採用由 Poser 4 匯出的 BVH 檔案格式。 + </text> + <button label="上傳(L$[AMOUNT])" name="ok_btn"/> + <button label="取消" name="cancel_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_autoreplace.xml b/indra/newview/skins/default/xui/zh/floater_autoreplace.xml new file mode 100644 index 0000000000..4ee07e6295 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_autoreplace.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="autoreplace_floater" title="自動取代設定"> + <check_box label="啟用自動取代" name="autoreplace_enable" tool_tip="在輸入聊天內容的同時,將輸入的關鍵字代換為相應的取代文字。"/> + <button label="匯入清單…" name="autoreplace_import_list" tool_tip="從檔案載入先前匯出過的清單。"/> + <button label="匯出清單…" name="autoreplace_export_list" tool_tip="將所選清單儲存到檔案以便和他人分享。"/> + <button label="新的清單…" name="autoreplace_new_list" tool_tip="新建一個清單。"/> + <button label="刪除清單" name="autoreplace_delete_list" tool_tip="刪除所選清單。"/> + <button name="autoreplace_list_up" tool_tip="提高此清單的優先次序。"/> + <button name="autoreplace_list_down" tool_tip="降低此清單的優先次序。"/> + <scroll_list name="autoreplace_list_replacements"> + <scroll_list.columns label="關鍵字" name="keyword"/> + <scroll_list.columns label="取代文字" name="replacement"/> + </scroll_list> + <button label="添加..." name="autoreplace_add_entry"/> + <button label="移除" name="autoreplace_delete_entry"/> + <button label="儲存項目" name="autoreplace_save_entry" tool_tip="儲存此項目。"/> + <button label="儲存變更" name="autoreplace_save_changes" tool_tip="儲存所有變更。"/> + <button label="取消" name="autoreplace_cancel" tool_tip="放棄所有變更。"/> +</floater> +<!-- + <text + top_pad="10" + left="10" + height="16" + width="260" + follows="left|top" + halign="center" + mouse_opaque="true" + name="autoreplace_text2"> + Entries + </text> +--> diff --git a/indra/newview/skins/default/xui/zh/floater_avatar.xml b/indra/newview/skins/default/xui/zh/floater_avatar.xml new file mode 100644 index 0000000000..55b1a95a41 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_avatar.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Avatar" title="選擇一個化身"/> diff --git a/indra/newview/skins/default/xui/zh/floater_build_options.xml b/indra/newview/skins/default/xui/zh/floater_build_options.xml index b37cbdaa33..29f36b461c 100644 --- a/indra/newview/skins/default/xui/zh/floater_build_options.xml +++ b/indra/newview/skins/default/xui/zh/floater_build_options.xml @@ -1,7 +1,30 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="build options floater" title="格線選項"> - <spinner label="格線單位(公尺)" name="GridResolution"/> - <spinner label="格線延伸範圍(公尺)" name="GridDrawSize"/> + <floater.string name="grid_screen_text"> + 螢幕 + </floater.string> + <floater.string name="grid_local_text"> + 本地 + </floater.string> + <floater.string name="grid_world_text"> + 世界 + </floater.string> + <floater.string name="grid_reference_text"> + 參考 + </floater.string> + <floater.string name="grid_attachment_text"> + 附件 + </floater.string> + <text name="grid_mode_label" tool_tip="格線不透明度"> + 模式 + </text> + <combo_box name="combobox grid mode" tool_tip="選擇物件定位參考的格線尺度類型"> + <combo_box.item label="世界格線" name="World"/> + <combo_box.item label="地方格線" name="Local"/> + <combo_box.item label="參考格線" name="Reference"/> + </combo_box> + <spinner label="單位(公尺)" name="GridResolution"/> + <spinner label="範圍(公尺)" name="GridDrawSize"/> <check_box label="貼齊至子單位" name="GridSubUnit"/> <check_box label="檢視橫剖面" name="GridCrossSection"/> <text name="grid_opacity_label" tool_tip="格線不透明度"> diff --git a/indra/newview/skins/default/xui/zh/floater_buy_currency.xml b/indra/newview/skins/default/xui/zh/floater_buy_currency.xml index c2cfe5c880..fcf2800728 100644 --- a/indra/newview/skins/default/xui/zh/floater_buy_currency.xml +++ b/indra/newview/skins/default/xui/zh/floater_buy_currency.xml @@ -46,7 +46,7 @@ L$ [AMT] </text> <text name="currency_links"> - [http://www.secondlife.com/my/account/payment_method_management.php 付費方法] | [http://www.secondlife.com/my/account/currency.php 幣種] | [http://www.secondlife.com/my/account/exchange_rates.php 匯率] + [http://www.secondlife.com/my/account/payment_method_management.php 付費方式] | [http://www.secondlife.com/my/account/currency.php 幣種] </text> <text name="exchange_rate_note"> 重新輸入金額即可察看最新的匯率。 diff --git a/indra/newview/skins/default/xui/zh/floater_camera.xml b/indra/newview/skins/default/xui/zh/floater_camera.xml index 4319d96e89..becb7b9546 100644 --- a/indra/newview/skins/default/xui/zh/floater_camera.xml +++ b/indra/newview/skins/default/xui/zh/floater_camera.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="camera_floater"> +<floater name="camera_floater" title="攝影機控制"> <floater.string name="rotate_tooltip"> 繞著焦點轉動攝影機 </floater.string> diff --git a/indra/newview/skins/default/xui/zh/floater_chat_bar.xml b/indra/newview/skins/default/xui/zh/floater_chat_bar.xml new file mode 100644 index 0000000000..f1a69a7688 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_chat_bar.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="chat_bar" title="附近的聊天"> + <panel name="bottom_panel"> + <line_editor label="點按此處開始聊天。" name="chat_box" tool_tip="按下 Enter 鍵來說或按下 Ctrl+Enter 來喊叫"/> + <button name="show_nearby_chat" tool_tip="顯示 / 隱藏 附近的聊天紀錄"/> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_delete_env_preset.xml b/indra/newview/skins/default/xui/zh/floater_delete_env_preset.xml new file mode 100644 index 0000000000..4aafb31952 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_delete_env_preset.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<floater name="Delete Env Preset" title="刪除環境自訂配置"> + <string name="title_water"> + 刪除水的自訂配置 + </string> + <string name="title_sky"> + 刪除天空自訂配置 + </string> + <string name="title_day_cycle"> + 刪除日循環 + </string> + <string name="label_water"> + 自訂配置: + </string> + <string name="label_sky"> + 自訂配置: + </string> + <string name="label_day_cycle"> + 日循環: + </string> + <string name="msg_confirm_deletion"> + 確定要刪除所選自訂配置? + </string> + <string name="msg_sky_is_referenced"> + 無法刪除日循環有所指涉的自訂配置。 + </string> + <string name="combo_label"> + -選擇一個自訂配置- + </string> + <text name="label"> + 自訂配置: + </text> + <button label="刪除" name="delete"/> + <button label="取消" name="cancel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_destinations.xml b/indra/newview/skins/default/xui/zh/floater_destinations.xml new file mode 100644 index 0000000000..f50a6a631a --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_destinations.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Destinations" title="目的地"/> diff --git a/indra/newview/skins/default/xui/zh/floater_edit_day_cycle.xml b/indra/newview/skins/default/xui/zh/floater_edit_day_cycle.xml new file mode 100644 index 0000000000..b84a4027ea --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_edit_day_cycle.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Edit Day cycle" title="編輯日循環"> + <string name="title_new"> + 新建一個日循環 + </string> + <string name="title_edit"> + 編輯日循環 + </string> + <string name="hint_new"> + 為日循環定名,調整各項控制確定細節,再點按「儲存」。 + </string> + <string name="hint_edit"> + 若要編輯你的日循環,請調整下方各項控制,再點按「儲存」。 + </string> + <string name="combo_label"> + -選擇一個自訂配置- + </string> + <text name="label"> + 自訂配置名稱: + </text> + <text name="note"> + 注意:更改自訂配置的名稱將會新建一個自訂配置,不會改變原有的自訂配置。 + </text> + <text name="hint_item1"> + - 點按一個頁籤,編輯特定的天空設定和時間。 + </text> + <text name="hint_item2"> + - 點按並拖曳各個頁籤,即可設定過渡時間。 + </text> + <text name="hint_item3"> + - 使用 scrubber 預覽你的日循環。 + </text> + <panel name="day_cycle_slider_panel"> + <multi_slider initial_value="0" name="WLTimeSlider"/> + <multi_slider initial_value="0" name="WLDayCycleKeys"/> + <button label="新增鍵" label_selected="新增鍵" name="WLAddKey"/> + <button label="刪除鍵" label_selected="刪除鍵" name="WLDeleteKey"/> + <text name="WL12am"> + 午夜 12 點 + </text> + <text name="WL3am"> + 凌晨 3 點 + </text> + <text name="WL6am"> + 上午 6 點 + </text> + <text name="WL9amHash"> + 上午 9 點 + </text> + <text name="WL12pmHash"> + 中午 12 點 + </text> + <text name="WL3pm"> + 下午 3 點 + </text> + <text name="WL6pm"> + 下午 6 點 + </text> + <text name="WL9pm"> + 下午 9 點 + </text> + <text name="WL12am2"> + 午夜 12 點 + </text> + <text name="WL12amHash"> + | + </text> + <text name="WL3amHash"> + I + </text> + <text name="WL6amHash"> + | + </text> + <text name="WL9amHash2"> + I + </text> + <text name="WL12pmHash2"> + | + </text> + <text name="WL3pmHash"> + I + </text> + <text name="WL6pmHash"> + | + </text> + <text name="WL9pmHash"> + I + </text> + <text name="WL12amHash2"> + | + </text> + </panel> + <text name="WLCurKeyPresetText"> + 天空設定: + </text> + <combo_box label="預設值" name="WLSkyPresets"/> + <text name="WLCurKeyTimeText"> + 時間: + </text> + <time name="time" value="上午 6 點"/> + <check_box label="根據這設定變更我的日循環" name="make_default_cb"/> + <button label="儲存" name="save"/> + <button label="取消" name="cancel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_edit_sky_preset.xml b/indra/newview/skins/default/xui/zh/floater_edit_sky_preset.xml new file mode 100644 index 0000000000..1ff832cdc4 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_edit_sky_preset.xml @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Edit Sky Preset" title="編輯天空自訂配置"> + <string name="title_new"> + 建立新的天空自訂配置 + </string> + <string name="title_edit"> + 編輯天空自訂配置 + </string> + <string name="hint_new"> + 為自訂配置定名,調整各項控制確定配置細節,完成後點按「儲存」。 + </string> + <string name="hint_edit"> + 若要編輯你的天空自訂配置,請調整各項控制,再點按「儲存」。 + </string> + <string name="combo_label"> + -選擇一個自訂配置- + </string> + <text name="hint"> + 若要編輯你的自訂配置,請調整各項控制,再點按「儲存」。 + </text> + <text name="label"> + 自訂配置名稱: + </text> + <text name="note"> + 注意:更改自訂配置的名稱將會新建一個自訂配置,不會改變原有的自訂配置。 + </text> + <tab_container name="WindLight Tabs"> + <panel label="大氣" name="Atmosphere"> + <text name="BHText"> + 藍天水平線 + </text> + <text name="BDensText"> + 陰霾水平線 + </text> + <text name="BDensText2"> + 藍天密度 + </text> + <text name="HDText"> + 陰霾密度 + </text> + <text name="DensMultText"> + 密度倍增 + </text> + <text name="WLDistanceMultText"> + 距離倍增 + </text> + <text name="MaxAltText"> + 最大高度 + </text> + </panel> + <panel label="照明" name="Lighting"> + <text name="SLCText"> + 日/月 顏色 + </text> + <text name="WLAmbientText"> + 環境 + </text> + <text name="SunGlowText"> + 太陽光輝 + </text> + <slider label="聚焦" name="WLGlowB"/> + <slider label="尺寸" name="WLGlowR"/> + <text name="WLStarText"> + 星空亮度 + </text> + <text name="SceneGammaText"> + 場景 Gamma 值 + </text> + <text name="TODText"> + 日/月 位置 + </text> + <multi_slider initial_value="0" name="WLSunPos"/> + <text name="WL12amHash"> + | + </text> + <text name="WL6amHash"> + | + </text> + <text name="WL12pmHash2"> + | + </text> + <text name="WL6pmHash"> + | + </text> + <text name="WL12amHash2"> + | + </text> + <text name="WL12am"> + 午夜 12 點 + </text> + <text name="WL6am"> + 上午 6 點 + </text> + <text name="WL12pmHash"> + 中午 12 點 + </text> + <text name="WL6pm"> + 下午 6 點 + </text> + <text name="WL12am2"> + 午夜 12 點 + </text> + <time name="WLDayTime" value="上午 6 點"/> + <text name="WLEastAngleText"> + 東升角度 + </text> + </panel> + <panel label="雲彩" name="Clouds"> + <text name="WLCloudColorText"> + 雲彩顏色 + </text> + <text name="WLCloudColorText2"> + 雲彩 XY 軸 / 密度 + </text> + <slider label="X" name="WLCloudX"/> + <slider label="Y" name="WLCloudY"/> + <slider label="D" name="WLCloudDensity"/> + <text name="WLCloudCoverageText"> + 雲彩覆蓋 + </text> + <text name="WLCloudScaleText"> + 雲彩規模 + </text> + <text name="WLCloudDetailText"> + 雲彩細節(XY 軸 / 密度) + </text> + <slider label="X" name="WLCloudDetailX"/> + <slider label="Y" name="WLCloudDetailY"/> + <slider label="D" name="WLCloudDetailDensity"/> + <text name="WLCloudScrollXText"> + 雲彩 X 滾軸 + </text> + <check_box label="鎖定" name="WLCloudLockX"/> + <text name="WLCloudScrollYText"> + 雲彩 Y 滾軸 + </text> + <check_box label="鎖定" name="WLCloudLockY"/> + </panel> + </tab_container> + <check_box label="根據這自訂配置變更我的天空設定" name="make_default_cb"/> + <button label="儲存" name="save"/> + <button label="取消" name="cancel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_edit_water_preset.xml b/indra/newview/skins/default/xui/zh/floater_edit_water_preset.xml new file mode 100644 index 0000000000..7943866e72 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_edit_water_preset.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Edit Water Preset" title="編輯水的自訂配置"> + <string name="title_new"> + 新建水的自訂配置 + </string> + <string name="title_edit"> + 編輯水的自訂配置 + </string> + <string name="hint_new"> + 為自訂配置定名,調整各項控制確定配置細節,完成後點按「儲存」。 + </string> + <string name="hint_edit"> + 若要編輯水的自訂配置,請調整各項控制,再點按「儲存」。 + </string> + <string name="combo_label"> + -選擇一個自訂配置- + </string> + <text name="hint"> + 若要編輯你的自訂配置,請調整各項控制,再點按「儲存」。 + </text> + <text name="label"> + 自訂配置名稱: + </text> + <text name="note"> + 注意:更改自訂配置的名稱將會新建一個自訂配置,不會改變原有的自訂配置。 + </text> + <panel name="panel_water_preset"> + <text name="water_color_label"> + 水霧顏色 + </text> + <text name="water_fog_density_label"> + 霧密度指數 + </text> + <text name="underwater_fog_modifier_label"> + 水底霧修飾元 + </text> + <text name="BHText"> + 大波浪方向 + </text> + <slider label="X" name="WaterWave1DirX"/> + <slider label="Y" name="WaterWave1DirY"/> + <text name="BDensText"> + 反射子波比例 + </text> + <text name="HDText"> + 菲涅耳比例 + </text> + <text name="FresnelOffsetText"> + 菲涅耳偏距 + </text> + <text name="BHText2"> + 小波浪方向 + </text> + <slider label="X" name="WaterWave2DirX"/> + <slider label="Y" name="WaterWave2DirY"/> + <text name="DensMultText"> + 上折射比例 + </text> + <text name="WaterScaleBelowText"> + 下折射比例 + </text> + <text name="MaxAltText"> + 模糊倍數 + </text> + <text name="BHText3"> + 正常地圖 + </text> + </panel> + <check_box label="根據這自訂配置變更我水的設定" name="make_default_cb"/> + <button label="儲存" name="save"/> + <button label="取消" name="cancel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_environment_settings.xml b/indra/newview/skins/default/xui/zh/floater_environment_settings.xml new file mode 100644 index 0000000000..1c6f2f936d --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_environment_settings.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Environment Editor Floater" title="環境設定"> + <text name="note"> + 使用以下的選項自訂你 Viewer 的環境設定。 + </text> + <radio_group name="region_settings_radio_group"> + <radio_item label="使用地區設定" name="use_region_settings"/> + <radio_item label="自訂我的環境" name="use_my_settings"/> + </radio_group> + <panel name="user_environment_settings"> + <text name="note"> + 注意:你的自訂設定不會被其他使用者看見。 + </text> + <text name="water_settings_title"> + 水的設定 + </text> + <combo_box name="water_settings_preset_combo"> + <combo_box.item label="-選擇一個自訂配置-" name="item0"/> + </combo_box> + <text name="sky_dayc_settings_title"> + 天空 / 日循環 + </text> + <radio_group name="sky_dayc_settings_radio_group"> + <radio_item label="固定天空" name="my_sky_settings"/> + <radio_item label="日循環" name="my_dayc_settings"/> + </radio_group> + <combo_box name="sky_settings_preset_combo"> + <combo_box.item label="-選擇一個自訂配置-" name="item0"/> + </combo_box> + <combo_box name="dayc_settings_preset_combo"> + <combo_box.item label="-選擇一個自訂配置-" name="item0"/> + </combo_box> + </panel> + <button label="確定" name="ok_btn"/> + <button label="取消" name="cancel_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_fast_timers.xml b/indra/newview/skins/default/xui/zh/floater_fast_timers.xml new file mode 100644 index 0000000000..871849305c --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_fast_timers.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="fast_timers"> + <string name="pause"> + 暫停 + </string> + <string name="run"> + 跑步 + </string> + <button label="暫停" name="pause_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_hardware_settings.xml b/indra/newview/skins/default/xui/zh/floater_hardware_settings.xml index 00d7590b9b..9e3bb88ac0 100644 --- a/indra/newview/skins/default/xui/zh/floater_hardware_settings.xml +++ b/indra/newview/skins/default/xui/zh/floater_hardware_settings.xml @@ -4,7 +4,7 @@ 過濾: </text> <check_box label="各向異性過濾(若啟用會變慢)" name="ani"/> - <text name="Antialiasing:"> + <text name="antialiasing label"> 消除鋸齒: </text> <combo_box label="消除鋸齒" name="fsaa"> @@ -25,6 +25,10 @@ 啟用頂點緩衝物件(VBO): </text> <check_box initial_value="true" label="啟用 OpenGL 頂點緩衝物件(VBO)" name="vbo" tool_tip="在較新硬體上啟用,可提升效能。 但是,較舊硬體的 VBO 實作不佳,若啟用可能導致當機。"/> + <text name="tc label"> + 啟用 S3TC: + </text> + <check_box initial_value="true" label="啟用材質壓縮(須重新啟動)" name="texture compression" tool_tip="在影片記憶體中壓縮材質,讓高解析度材質可以載入,但色彩品質稍差。"/> <slider label="材質記憶體(MB):" name="GraphicsCardTextureMemory" tool_tip="配置給材質使用的記憶體量。 預設為顯像卡記憶體。 降低此值可以提升效能,但材質也會變模糊。"/> <spinner label="霧距離比率:" name="fog"/> <button label="確定" label_selected="確定" name="OK"/> diff --git a/indra/newview/skins/default/xui/zh/floater_how_to.xml b/indra/newview/skins/default/xui/zh/floater_how_to.xml new file mode 100644 index 0000000000..e033327165 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_how_to.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_how_to" title="簡易教學"/> diff --git a/indra/newview/skins/default/xui/zh/floater_map.xml b/indra/newview/skins/default/xui/zh/floater_map.xml index de39fc635d..8a030b3b3f 100644 --- a/indra/newview/skins/default/xui/zh/floater_map.xml +++ b/indra/newview/skins/default/xui/zh/floater_map.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="Map" title=""> +<floater name="Map" title="迷你地圖"> <floater.string name="ToolTipMsg"> [REGION](雙擊以開啟地圖,按下 shift 鍵拖曳來平移) </floater.string> diff --git a/indra/newview/skins/default/xui/zh/floater_merchant_outbox.xml b/indra/newview/skins/default/xui/zh/floater_merchant_outbox.xml new file mode 100644 index 0000000000..6b6126c8e0 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_merchant_outbox.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_merchant_outbox" title="商家發件匣"> + <string name="OutboxFolderCount1"> + 1 個資料夾 + </string> + <string name="OutboxFolderCountN"> + [NUM] 個資料夾 + </string> + <string name="OutboxImporting"> + 正在傳送資料夾… + </string> + <string name="OutboxInitializing"> + 正在初始化… + </string> + <panel label=""> + <panel> + <panel name="outbox_inventory_placeholder_panel"> + <text name="outbox_inventory_placeholder_title"> + 載入中... + </text> + </panel> + </panel> + <panel> + <button label="送往第二人生購物市集" name="outbox_import_btn" tool_tip="推到我第二人生購物市集的店面"/> + </panel> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_model_preview.xml b/indra/newview/skins/default/xui/zh/floater_model_preview.xml index 970e9e6f4f..22b3d3b065 100644 --- a/indra/newview/skins/default/xui/zh/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/zh/floater_model_preview.xml @@ -1,7 +1,11 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="Model Preview" title="上傳模型"> - <string name="status_idle"> - 閒置中 + <string name="status_idle"/> + <string name="status_parse_error"> + 錯誤:剖析 dae 時出錯,詳見記錄檔。 + </string> + <string name="status_material_mismatch"> + 錯誤:模型材料並非參考模型的子集合。 </string> <string name="status_reading_file"> 載入中... @@ -12,6 +16,9 @@ <string name="status_vertex_number_overflow"> 錯誤:頂點數目超過 65534,程序已中止! </string> + <string name="bad_element"> + 錯誤:無效的元件 + </string> <string name="high"> 高 </string> @@ -45,6 +52,9 @@ <string name="mesh_status_missing_lod"> 缺乏需要的細節層次。 </string> + <string name="mesh_status_invalid_material_list"> + 細節層次材料並非參考模型的子集合。 + </string> <string name="layer_all"> 全部 </string> @@ -54,178 +64,249 @@ <string name="simplifying"> 簡化中… </string> - <text name="name_label"> - 名稱: - </text> - <text name="lod_label"> - 預覽: - </text> - <combo_box name="preview_lod_combo" tool_tip="要在呈像預覽中察看的細節層次"> - <combo_item name="high"> - 細節層次:高度 - </combo_item> - <combo_item name="medium"> - 細節層次:中度 - </combo_item> - <combo_item name="low"> - 細節層次:低度 - </combo_item> - <combo_item name="lowest"> - 細節層次:最低 - </combo_item> - </combo_box> - <panel> - <text name="streaming cost"> - 資源花費:[COST] - </text> - <text name="physics cost"> - 物理花費:[COST] - </text> - <text name="upload fee"> - 上傳費:無 - </text> - </panel> - <text name="status"> - [STATUS] - </text> - <button label="預設值" name="reset_btn" tool_tip="重設為預設值"/> - <button label="上傳" name="ok_btn" tool_tip="上傳至模擬器"/> - <button label="取消" name="cancel_btn"/> - <tab_container name="import_tab"> - <panel label="細節層次" name="lod_panel"> - <text name="lod_table_header"> - 選擇細節層次: - </text> - <text name="high_label" value="高"/> - <text name="high_triangles" value="0"/> - <text name="high_vertices" value="0"/> - <text name="medium_label" value="中"/> - <text name="medium_triangles" value="0"/> - <text name="medium_vertices" value="0"/> - <text name="low_label" value="低"/> - <text name="low_triangles" value="0"/> - <text name="low_vertices" value="0"/> - <text name="lowest_label" value="最低"/> - <text name="lowest_triangles" value="0"/> - <text name="lowest_vertices" value="0"/> - <text name="lod_table_footer"> - 細節層次:[DETAIL] - </text> - <radio_group name="lod_file_or_limit" value="lod_from_file"> - <radio_item label="從檔案載入" name="lod_from_file"/> - <radio_item label="自動生成" name="lod_auto_generate"/> - <radio_item label="無" name="lod_none"/> - </radio_group> - <button label="瀏覽…" name="lod_browse"/> - <combo_box name="lod_mode"> - <combo_item name="triangle_limit"> - 三角形上限 - </combo_item> - <combo_item name="error_threshold"> - 錯誤門檻 - </combo_item> - </combo_box> - <text name="build_operator_text"> - 建製操作元: - </text> - <text name="queue_mode_text"> - 佇列模式: - </text> - <combo_box name="build_operator"> - <combo_item name="edge_collapse"> - 側邊摺疊 - </combo_item> - <combo_item name="half_edge_collapse"> - 半側邊摺疊 - </combo_item> - </combo_box> - <combo_box name="queue_mode"> - <combo_item name="greedy"> - 儘量最大 - </combo_item> - <combo_item name="lazy"> - 儘量最小 - </combo_item> - <combo_item name="independent"> - 獨立 - </combo_item> - </combo_box> - <text name="border_mode_text"> - 邊界模式: + <string name="tbd"> + (未定) + </string> + <panel name="left_panel"> + <panel name="model_name_representation_panel"> + <text name="name_label"> + 模型名稱: </text> - <text name="share_tolderance_text"> - 分享容忍: + <text name="model_category_label"> + 這模型代表… </text> - <combo_box name="border_mode"> - <combo_item name="border_unlock"> - 解鎖 - </combo_item> - <combo_item name="border_lock"> - 鎖定 - </combo_item> + <combo_box name="model_category_combo"> + <combo_item label="選擇一項…" name="Choose one"/> + <combo_item label="化身形狀" name="Avatar shape"/> + <combo_item label="化身附件" name="Avatar attachment"/> + <combo_item label="會移動的物件(車輛、動物)" name="Moving object (vehicle, animal)"/> + <combo_item label="建製元件" name="Building Component"/> + <combo_item label="大型、不會移動等類型" name="Large, non moving etc"/> + <combo_item label="較小型、不會移動等類型" name="Smaller, non-moving etc"/> + <combo_item label="並非其中任何一個" name="Not really any of these"/> </combo_box> - <text name="crease_label"> - 皺褶角度: - </text> - <spinner name="crease_angle" value="75"/> </panel> - <panel label="物理" name="physics_panel"> - <panel name="physics geometry"> - <radio_group name="physics_load_radio" value="physics_load_from_file"> - <radio_item label="檔案:" name="physics_load_from_file"/> - <radio_item label="使用細節層次:" name="physics_use_lod"/> - </radio_group> - <combo_box name="physics_lod_combo" tool_tip="物理形狀所採用的細節層次"> - <combo_item name="physics_lowest"> - 最低 - </combo_item> - <combo_item name="physics_low"> - 低 - </combo_item> - <combo_item name="physics_medium"> - 中 - </combo_item> - <combo_item name="physics_high"> - 高 - </combo_item> + <tab_container name="import_tab"> + <panel label="細節層次" name="lod_panel" title="細節層次"> + <text initial_value="來源" name="source" value="來源"/> + <text initial_value="三角形" name="triangles" value="三角形"/> + <text initial_value="頂點" name="vertices" value="頂點"/> + <text initial_value="高" name="high_label" value="高"/> + <combo_box name="lod_source_high"> + <item name="Load from file" value="從檔案載入"/> + <item name="Generate" value="產生"/> </combo_box> - <button label="瀏覽…" name="physics_browse"/> - </panel> - <panel name="physics analysis"> - <slider label="平滑:" name="Smooth"/> - <check_box label="關閉洞口(慢)" name="Close Holes (Slow)"/> - <button label="分析" name="Decompose"/> - <button label="取消" name="decompose_cancel"/> + <button label="瀏覽…" name="lod_browse_high"/> + <combo_box name="lod_mode_high"> + <item name="Triangle Limit" value="三角形上限"/> + <item name="Error Threshold" value="錯誤門檻"/> + </combo_box> + <text initial_value="0" name="high_triangles" value="0"/> + <text initial_value="0" name="high_vertices" value="0"/> + <text initial_value="中" name="medium_label" value="中"/> + <combo_box name="lod_source_medium"> + <item name="Load from file" value="從檔案載入"/> + <item name="Generate" value="產生"/> + <item name="Use LoD above" value="以上使用低階細節"/> + </combo_box> + <button label="瀏覽…" name="lod_browse_medium"/> + <combo_box name="lod_mode_medium"> + <item name="Triangle Limit" value="三角形上限"/> + <item name="Error Threshold" value="錯誤門檻"/> + </combo_box> + <text initial_value="0" name="medium_triangles" value="0"/> + <text initial_value="0" name="medium_vertices" value="0"/> + <text initial_value="低" name="low_label" value="低"/> + <combo_box name="lod_source_low"> + <item name="Load from file" value="從檔案載入"/> + <item name="Generate" value="產生"/> + <item name="Use LoD above" value="以上使用低階細節"/> + </combo_box> + <button label="瀏覽…" name="lod_browse_low"/> + <combo_box name="lod_mode_low"> + <item name="Triangle Limit" value="三角形上限"/> + <item name="Error Threshold" value="錯誤門檻"/> + </combo_box> + <text initial_value="0" name="low_triangles" value="0"/> + <text initial_value="0" name="low_vertices" value="0"/> + <text initial_value="最低" name="lowest_label" value="最低"/> + <combo_box name="lod_source_lowest"> + <item name="Load from file" value="從檔案載入"/> + <item name="Generate" value="產生"/> + <item name="Use LoD above" value="以上使用低階細節"/> + </combo_box> + <button label="瀏覽…" name="lod_browse_lowest"/> + <combo_box name="lod_mode_lowest"> + <item name="Triangle Limit" value="三角形上限"/> + <item name="Error Threshold" value="錯誤門檻"/> + </combo_box> + <text initial_value="0" name="lowest_triangles" value="0"/> + <text initial_value="0" name="lowest_vertices" value="0"/> + <check_box label="產生法線" name="gen_normals"/> + <text initial_value="皺褶角度:" name="crease_label" value="皺褶角度:"/> + <spinner name="crease_angle" value="75"/> </panel> - <panel name="physics simplification"> - <slider label="階段數:" name="Combine Quality"/> - <slider label="細節比例:" name="Detail Scale"/> - <slider label="保留:" name="Retain%"/> - <button label="簡化" name="Simplify"/> - <button label="取消" name="simplify_cancel"/> + <panel label="物理" name="physics_panel"> + <panel name="physics geometry"> + <text name="first_step_name"> + 步驟 1:細節層次 + </text> + <combo_box name="physics_lod_combo" tool_tip="物理形狀所採用的細節層次"> + <combo_item name="choose_one"> + 選擇一項… + </combo_item> + <combo_item name="physics_high"> + 高 + </combo_item> + <combo_item name="physics_medium"> + 中 + </combo_item> + <combo_item name="physics_low"> + 低 + </combo_item> + <combo_item name="physics_lowest"> + 最低 + </combo_item> + <combo_item name="load_from_file"> + 來自檔案 + </combo_item> + </combo_box> + <button label="瀏覽…" name="physics_browse"/> + </panel> + <panel name="physics analysis"> + <text name="method_label"> + 步驟 2:分析 + </text> + <text name="analysis_method_label"> + 方法: + </text> + <text name="quality_label"> + 品質: + </text> + <text name="smooth_method_label"> + 平滑: + </text> + <check_box label="關閉洞口" name="Close Holes (Slow)"/> + <button label="分析" name="Decompose"/> + <button label="取消" name="decompose_cancel"/> + </panel> + <panel name="physics simplification"> + <text name="second_step_label"> + 步驟 3:簡化 + </text> + <text name="simp_method_header"> + 方法: + </text> + <text name="pass_method_header"> + 階段數: + </text> + <text name="Detail Scale label"> + 細節比例: + </text> + <text name="Retain%_label"> + 保留: + </text> + <combo_box name="Combine Quality" value="1"/> + <button label="簡化" name="Simplify"/> + <button label="取消" name="simplify_cancel"/> + </panel> + <panel name="physics info"> + <text name="results_text"> + 結果: + </text> + <text name="physics_triangles"> + 三角形:[TRIANGLES], + </text> + <text name="physics_points"> + 頂點:[POINTS], + </text> + <text name="physics_hulls"> + 殼面:[HULLS] + </text> + </panel> </panel> - <panel name="physics info"> - <slider label="預覽伸展:" name="physics_explode"/> - <text name="physics_triangles"> - 三角形:[TRIANGLES] + <panel label="上傳選項" name="modifiers_panel"> + <text name="scale_label"> + 比例(1 = 原比例): </text> - <text name="physics_points"> - 頂點:[POINTS] + <spinner name="import_scale" value="1.0"/> + <text name="dimensions_label"> + 規格: </text> - <text name="physics_hulls"> - 殼面:[HULLS] + <text name="import_dimensions"> + [X] X [Y] X [Z] </text> + <check_box label="包含材質" name="upload_textures"/> + <text name="include_label"> + 僅限化身模型: + </text> + <check_box label="包含表皮重量" name="upload_skin"/> + <check_box label="包含接點位置" name="upload_joints"/> + <text name="pelvis_offset_label"> + Z 偏距(升高或降低化身): + </text> + <spinner name="pelvis_offset" value="0.0"/> </panel> - </panel> - <panel label="修飾器" name="modifiers_panel"> - <spinner name="import_scale" value="1.0"/> - <text name="import_dimensions"> - [X] x [Y] x [Z] 公尺 + </tab_container> + <panel name="weights_and_warning_panel"> + <button label="計算重量和費用" name="calculate_btn" tool_tip="計算重量和費用"/> + <button label="取消" name="cancel_btn"/> + <button label="上傳" name="ok_btn" tool_tip="上傳至模擬器"/> + <button label="清除設定並重設形式" name="reset_btn"/> + <text name="upload_fee"> + 上傳費用:L$ [FEE] + </text> + <text name="prim_weight"> + 土地衝擊量:[EQ] + </text> + <text name="download_weight"> + 下載:[ST] + </text> + <text name="physics_weight"> + 物理:[PH] + </text> + <text name="server_weight"> + 伺服器:[SIM] + </text> + <text name="warning_title"> + 附註: + </text> + <text name="warning_message"> + 你無權上傳網面模型。 [[VURL] 瞭解如何]通過認證。 + </text> + <text name="status"> + [STATUS] </text> - <check_box label="材質" name="upload_textures"/> - <check_box label="表皮重量" name="upload_skin"/> - <check_box label="接點位置" name="upload_joints"/> - <spinner name="pelvis_offset" value="0.0"/> </panel> - </tab_container> + </panel> + <text name="lod_label"> + 預覽: + </text> + <panel name="right_panel"> + <combo_box name="preview_lod_combo" tool_tip="要在呈像預覽中察看的細節層次"> + <combo_item name="high"> + 高 + </combo_item> + <combo_item name="medium"> + 中 + </combo_item> + <combo_item name="low"> + 低 + </combo_item> + <combo_item name="lowest"> + 最低 + </combo_item> + </combo_box> + <text name="label_display"> + 顯示… + </text> + <check_box label="邊" name="show_edges"/> + <check_box label="物理" name="show_physics"/> + <check_box label="材質" name="show_textures"/> + <check_box label="表皮重量" name="show_skin_weight"/> + <check_box label="接點" name="show_joint_positions"/> + <text name="physics_explode_label"> + 預覽伸展: + </text> + </panel> </floater> diff --git a/indra/newview/skins/default/xui/zh/floater_moveview.xml b/indra/newview/skins/default/xui/zh/floater_moveview.xml index 18ef63d00e..0b267e8fae 100644 --- a/indra/newview/skins/default/xui/zh/floater_moveview.xml +++ b/indra/newview/skins/default/xui/zh/floater_moveview.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="move_floater"> +<floater name="move_floater" title="行走 / 跑步 / 飛行"> <string name="walk_forward_tooltip"> 向前走(按下向上箭頭或 W 鍵) </string> diff --git a/indra/newview/skins/default/xui/zh/floater_my_appearance.xml b/indra/newview/skins/default/xui/zh/floater_my_appearance.xml new file mode 100644 index 0000000000..217ea5d1f9 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_my_appearance.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_my_appearance" title="外觀"> + <panel label="編輯外觀" name="main_panel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_my_inventory.xml b/indra/newview/skins/default/xui/zh/floater_my_inventory.xml new file mode 100644 index 0000000000..187597f4eb --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_my_inventory.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_my_inventory" title="收納區"/> diff --git a/indra/newview/skins/default/xui/zh/floater_object_weights.xml b/indra/newview/skins/default/xui/zh/floater_object_weights.xml new file mode 100644 index 0000000000..d2875b24b4 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_object_weights.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="object_weights" title="進階"> + <floater.string name="nothing_selected" value="--"/> + <text name="selected_text" value="已選"/> + <text name="objects" value="--"/> + <text name="objects_label" value="物件"/> + <text name="prims" value="--"/> + <text name="prims_label" value="幾何元件"/> + <text name="weights_of_selected_text" value="所選物重量"/> + <text name="download" value="--"/> + <text name="download_label" value="下載"/> + <text name="physics" value="--"/> + <text name="physics_label" value="物理"/> + <text name="server" value="--"/> + <text name="server_label" value="伺服器"/> + <text name="display" value="--"/> + <text name="display_label" value="顯示"/> + <text name="land_impacts_text" value="土地衝擊量"/> + <text name="selected" value="--"/> + <text name="selected_label" value="選擇"/> + <text name="rezzed_on_land" value="--"/> + <text name="rezzed_on_land_label" value="已產生到土地上"/> + <text name="remaining_capacity" value="--"/> + <text name="remaining_capacity_label" value="剩餘容納量"/> + <text name="total_capacity" value="--"/> + <text name="total_capacity_label" value="總容納量"/> + <text name="help_SLURL" value="[secondlife:///app/help/object_weights 這是什麼?]"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_pathfinding_characters.xml b/indra/newview/skins/default/xui/zh/floater_pathfinding_characters.xml new file mode 100644 index 0000000000..e6971d111f --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_pathfinding_characters.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_pathfinding_characters" title="尋徑角色"> + <floater.string name="messaging_get_inprogress"> + 尋徑角色查詢中… + </floater.string> + <floater.string name="messaging_get_error"> + 查詢尋徑角色時出錯。 + </floater.string> + <floater.string name="messaging_complete_none_found"> + 沒有尋徑角色。 + </floater.string> + <floater.string name="messaging_complete_available"> + 從 [NUM_TOTAL] 個角色中選取了 [NUM_SELECTED] 個。 + </floater.string> + <floater.string name="messaging_not_enabled"> + 這地區並未啟用尋徑。 + </floater.string> + <floater.string name="character_cpu_time"> + [CPU_TIME] 微秒 + </floater.string> + <floater.string name="character_owner_loading"> + [Loading] + </floater.string> + <floater.string name="character_owner_unknown"> + [Unknown] + </floater.string> + <floater.string name="character_owner_group"> + [group] + </floater.string> + <panel> + <scroll_list name="objects_scroll_list"> + <scroll_list.columns label="名稱" name="name"/> + <scroll_list.columns label="描述" name="description"/> + <scroll_list.columns label="所有人" name="owner"/> + <scroll_list.columns label="中央處理器" name="cpu_time"/> + <scroll_list.columns label="高度" name="altitude"/> + </scroll_list> + <text name="messaging_status"> + 角色: + </text> + <button label="刷新清單" name="refresh_objects_list"/> + <button label="全選" name="select_all_objects"/> + <button label="全都不選" name="select_none_objects"/> + </panel> + <panel> + <text name="actions_label"> + 所選角色所採動作: + </text> + <check_box label="顯示指標" name="show_beacon"/> + <check_box label="顯示物理囊" name="show_physics_capsule"/> + <button label="取得" name="take_objects"/> + <button label="拿取副本" name="take_copy_objects"/> + <button label="瞬間傳送我到那裡" name="teleport_me_to_object" tool_tip="只在選取了一個角色時啟用。"/> + <button label="退回" name="return_objects"/> + <button label="刪除" name="delete_objects"/> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_pathfinding_console.xml b/indra/newview/skins/default/xui/zh/floater_pathfinding_console.xml new file mode 100644 index 0000000000..be009b54d8 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_pathfinding_console.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_pathfinding_console" title="尋徑察看 / 測試"> + <floater.string name="navmesh_viewer_status_library_not_implemented"> + 找不到尋徑函式庫實作。 + </floater.string> + <floater.string name="navmesh_viewer_status_region_not_enabled"> + 這地區並未啟用尋徑。 + </floater.string> + <floater.string name="navmesh_viewer_status_region_loading"> + 等待地區完成載入。 + </floater.string> + <floater.string name="navmesh_viewer_status_checking_version"> + 正在檢查導航網面狀態。 + </floater.string> + <floater.string name="navmesh_viewer_status_downloading"> + 正在下載導航網面。 + </floater.string> + <floater.string name="navmesh_viewer_status_updating"> + 伺服器上的導航網面已變更。 正在下載最新的導航網面。 + </floater.string> + <floater.string name="navmesh_viewer_status_has_navmesh"> + 最新的導航網面下載完成。 + </floater.string> + <floater.string name="navmesh_viewer_status_error"> + 無法完成導航網面下載。 + </floater.string> + <floater.string name="navmesh_simulator_status_pending"> + 導航網面有變更待存。 + </floater.string> + <floater.string name="navmesh_simulator_status_building"> + 正在建構導航網面。 + </floater.string> + <floater.string name="navmesh_simulator_status_some_pending"> + 某些導航網面地區有變更待存。 + </floater.string> + <floater.string name="navmesh_simulator_status_some_building"> + 正在建構某些導航網面地區。 + </floater.string> + <floater.string name="navmesh_simulator_status_pending_and_building"> + 某些導航網面地區有變更待存,其他的正在建構中。 + </floater.string> + <floater.string name="navmesh_simulator_status_complete"> + 導航網面已全面更新。 + </floater.string> + <floater.string name="pathing_library_not_implemented"> + 找不到尋徑函式庫實作。 + </floater.string> + <floater.string name="pathing_region_not_enabled"> + 這地區並未啟用尋徑。 + </floater.string> + <floater.string name="pathing_choose_start_and_end_points"> + 請選擇起點和終點。 + </floater.string> + <floater.string name="pathing_choose_start_point"> + 請選擇起點。 + </floater.string> + <floater.string name="pathing_choose_end_point"> + 請選擇終點。 + </floater.string> + <floater.string name="pathing_path_valid"> + 路徑以橘色顯示。 + </floater.string> + <floater.string name="pathing_path_invalid"> + 在所選的點之間找不到路徑。 + </floater.string> + <floater.string name="pathing_error"> + 產生路徑時出錯。 + </floater.string> + <tab_container name="view_test_tab_container"> + <panel label="視角" name="view_panel"> + <text name="show_label"> + 顯示: + </text> + <check_box label="世界" name="show_world"/> + <check_box label="僅限可移動的" name="show_world_movables_only"/> + <check_box label="導航網面" name="show_navmesh"/> + <text name="show_walkability_label"> + 顯示可行走地圖: + </text> + <combo_box name="show_heatmap_mode"> + <combo_box.item label="不顯示" name="show_heatmap_mode_none"/> + <combo_box.item label="類型 A 角色" name="show_heatmap_mode_a"/> + <combo_box.item label="類型 B 角色" name="show_heatmap_mode_b"/> + <combo_box.item label="類型 C 角色" name="show_heatmap_mode_c"/> + <combo_box.item label="類型 D 角色" name="show_heatmap_mode_d"/> + </combo_box> + <check_box label="可行走的" name="show_walkables"/> + <check_box label="實質體積" name="show_material_volumes"/> + <check_box label="靜態障礙" name="show_static_obstacles"/> + <check_box label="排除體積" name="show_exclusion_volumes"/> + <check_box label="水平面" name="show_water_plane"/> + <check_box label="具 X 光透視力" name="show_xray"/> + </panel> + <panel label="測試路徑" name="test_panel"> + <text name="ctrl_click_label"> + 按住 Ctrl 並點按即可選擇起點。 + </text> + <text name="shift_click_label"> + 按住 Shift 並點按即可選擇終點。 + </text> + <text name="character_width_label"> + 角色寬度 + </text> + <slider name="character_width" value="1"/> + <text name="character_width_unit_label"> + 公尺 + </text> + <text name="character_type_label"> + 角色類型 + </text> + <combo_box name="path_character_type"> + <combo_box.item label="無" name="path_character_type_none"/> + <combo_box.item label="A" name="path_character_type_a"/> + <combo_box.item label="B" name="path_character_type_b"/> + <combo_box.item label="C" name="path_character_type_c"/> + <combo_box.item label="D" name="path_character_type_d"/> + </combo_box> + <button label="清除路徑" name="clear_path"/> + </panel> + </tab_container> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_pathfinding_linksets.xml b/indra/newview/skins/default/xui/zh/floater_pathfinding_linksets.xml new file mode 100644 index 0000000000..22e5d2e846 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_pathfinding_linksets.xml @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_pathfinding_linksets" title="尋徑聯結集"> + <floater.string name="messaging_get_inprogress"> + 尋徑聯結集查詢中… + </floater.string> + <floater.string name="messaging_get_error"> + 查詢尋徑聯結集時出錯。 + </floater.string> + <floater.string name="messaging_set_inprogress"> + 正在修改所選尋徑聯結集… + </floater.string> + <floater.string name="messaging_set_error"> + 修改所選尋徑聯結集時出錯。 + </floater.string> + <floater.string name="messaging_complete_none_found"> + 沒有尋徑聯結集。 + </floater.string> + <floater.string name="messaging_complete_available"> + 從 [NUM_TOTAL] 個聯結集中選取了 [NUM_SELECTED] 個。 + </floater.string> + <floater.string name="messaging_not_enabled"> + 這地區並未啟用尋徑。 + </floater.string> + <floater.string name="linkset_terrain_name"> + [Terrain] + </floater.string> + <floater.string name="linkset_terrain_description"> + -- + </floater.string> + <floater.string name="linkset_terrain_owner"> + -- + </floater.string> + <floater.string name="linkset_terrain_scripted"> + -- + </floater.string> + <floater.string name="linkset_terrain_land_impact"> + -- + </floater.string> + <floater.string name="linkset_terrain_dist_from_you"> + -- + </floater.string> + <floater.string name="linkset_owner_loading"> + [Loading] + </floater.string> + <floater.string name="linkset_owner_unknown"> + [Unknown] + </floater.string> + <floater.string name="linkset_owner_group"> + [group] + </floater.string> + <floater.string name="linkset_is_scripted"> + 是 + </floater.string> + <floater.string name="linkset_is_not_scripted"> + 否 + </floater.string> + <floater.string name="linkset_is_unknown_scripted"> + 未知 + </floater.string> + <floater.string name="linkset_use_walkable"> + 可行走的 + </floater.string> + <floater.string name="linkset_use_static_obstacle"> + 靜態障礙 + </floater.string> + <floater.string name="linkset_use_dynamic_obstacle"> + 可移動障礙 + </floater.string> + <floater.string name="linkset_use_material_volume"> + 實質體積 + </floater.string> + <floater.string name="linkset_use_exclusion_volume"> + 排除體積 + </floater.string> + <floater.string name="linkset_use_dynamic_phantom"> + 可移動幻影 + </floater.string> + <floater.string name="linkset_is_terrain"> + [unmodifiable] + </floater.string> + <floater.string name="linkset_is_restricted_state"> + [restricted] + </floater.string> + <floater.string name="linkset_is_non_volume_state"> + [concave] + </floater.string> + <floater.string name="linkset_is_restricted_non_volume_state"> + [restricted,concave] + </floater.string> + <floater.string name="linkset_choose_use"> + 選擇聯結集的使用… + </floater.string> + <panel> + <combo_box name="filter_by_linkset_use"> + <combo_box.item label="按聯結集的使用來過濾…" name="filter_by_linkset_use_none"/> + <combo_box.item label="可行走的" name="filter_by_linkset_use_walkable"/> + <combo_box.item label="靜態障礙" name="filter_by_linkset_use_static_obstacle"/> + <combo_box.item label="可移動障礙" name="filter_by_linkset_use_dynamic_obstacle"/> + <combo_box.item label="實質體積" name="filter_by_linkset_use_material_volume"/> + <combo_box.item label="排除體積" name="filter_by_linkset_use_exclusion_volume"/> + <combo_box.item label="可移動幻影" name="filter_by_linkset_use_dynamic_phantom"/> + </combo_box> + <button label="套用" name="apply_filters"/> + <button label="清除" name="clear_filters"/> + <scroll_list name="objects_scroll_list"> + <scroll_list.columns label="名稱(根幾何元件)" name="name"/> + <scroll_list.columns label="描述(根幾何元件)" name="description"/> + <scroll_list.columns label="所有人" name="owner"/> + <scroll_list.columns label="有腳本" name="scripted"/> + <scroll_list.columns label="衝擊" name="land_impact"/> + <scroll_list.columns label="距離" name="dist_from_you"/> + <scroll_list.columns label="聯結集的使用" name="linkset_use"/> + <scroll_list.columns label="A %" name="a_percent"/> + <scroll_list.columns label="B %" name="b_percent"/> + <scroll_list.columns label="C %" name="c_percent"/> + <scroll_list.columns label="D %" name="d_percent"/> + </scroll_list> + <text name="messaging_status"> + 聯結集: + </text> + <button label="刷新清單" name="refresh_objects_list"/> + <button label="全選" name="select_all_objects"/> + <button label="全都不選" name="select_none_objects"/> + </panel> + <panel> + <check_box label="顯示指標" name="show_beacon"/> + <button label="取得" name="take_objects"/> + <button label="拿取副本" name="take_copy_objects"/> + <button label="瞬間傳送我到那裡" name="teleport_me_to_object"/> + <button label="退回" name="return_objects"/> + <button label="刪除" name="delete_objects"/> + </panel> + <panel> + <text name="walkability_coefficients_label"> + 可行走性: + </text> + <text name="edit_a_label"> + A + </text> + <line_editor name="edit_a_value" tool_tip="A 類型角色的可行走性。以類人類為範例角色類型。"/> + <text name="edit_b_label"> + B + </text> + <line_editor name="edit_b_value" tool_tip="B 類型角色的可行走性。以獸類為範例角色類型。"/> + <text name="edit_c_label"> + C + </text> + <line_editor name="edit_c_value" tool_tip="C 類型角色的可行走性。以機械類為範例角色類型。"/> + <text name="edit_d_label"> + D + </text> + <line_editor name="edit_d_value" tool_tip="D 類型角色的可行走性。以其他種類為範例角色類型。"/> + <button label="套用變更" name="apply_edit_values"/> + <text name="suggested_use_a_label"> + (類人類) + </text> + <text name="suggested_use_b_label"> + (獸類) + </text> + <text name="suggested_use_c_label"> + (機械類) + </text> + <text name="suggested_use_d_label"> + (其他) + </text> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_people.xml b/indra/newview/skins/default/xui/zh/floater_people.xml new file mode 100644 index 0000000000..f629f2f184 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_people.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_people" title="人群"> + <panel_container name="main_panel"> + <panel label="群組檔案" name="panel_group_info_sidetray"/> + <panel label="被封鎖的居民與物件" name="panel_block_list_sidetray"/> + </panel_container> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_picks.xml b/indra/newview/skins/default/xui/zh/floater_picks.xml new file mode 100644 index 0000000000..a8bfcd99e3 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_picks.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_picks" title="精選地點"/> diff --git a/indra/newview/skins/default/xui/zh/floater_places.xml b/indra/newview/skins/default/xui/zh/floater_places.xml new file mode 100644 index 0000000000..f6ef1e2141 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_places.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_places" title="地點"> + <panel label="地點" name="main_panel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_preferences_proxy.xml b/indra/newview/skins/default/xui/zh/floater_preferences_proxy.xml new file mode 100644 index 0000000000..f91d5c5cdb --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_preferences_proxy.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Proxy Settings Floater" title="代理伺服器設定"> + <check_box initial_value="false" label="使用 HTTP 代理伺服器瀏覽網頁" name="web_proxy_enabled"/> + <text name="http_proxy_label"> + HTTP 代理伺服器: + </text> + <line_editor name="web_proxy_editor" tool_tip="你希望使用的 HTTP 代理伺服器的 DNS 名稱或 IP 位址。"/> + <spinner label="埠號:" name="web_proxy_port" tool_tip="你希望使用的 HTTP 代理伺服器的埠號。"/> + <check_box label="使用 SOCKS 5 代理伺服器處理 UDP 資料流" name="socks_proxy_enabled"/> + <text name="socks5_proxy_label"> + SOCKS 5 代理伺服器: + </text> + <line_editor name="socks_proxy_editor" tool_tip="你希望使用的 SOCKS 5 代理伺服器的 DNS 名稱或 IP 位址。"/> + <spinner label="埠號:" name="socks_proxy_port" tool_tip="你希望使用的 SOCKS 5 代理伺服器的埠號。"/> + <text name="socks_auth_label"> + SOCKS 鑒認: + </text> + <radio_group name="socks5_auth_type"> + <radio_item label="無鑒認" name="Socks5NoAuth" tool_tip="Socks5 代理伺服器無需鑒認。" value="無"/> + <radio_item label="使用者名稱 / 密碼" name="Socks5UserPass" tool_tip="Socks5 代理伺服器需要透過使用者名稱 / 密碼鑒認。" value="UserPass"/> + </radio_group> + <text name="socks5_username_label"> + 使用者名稱: + </text> + <text name="socks5_password_label"> + 密碼: + </text> + <line_editor name="socks5_username" tool_tip="你的 SOCKS 5 伺服器鑒認所用的使用者名稱"/> + <line_editor name="socks5_password" tool_tip="你的 SOCKS 5 伺服器鑒認所用的密碼"/> + <text name="other_proxy_label"> + 其他 HTTP 資料流代理伺服器: + </text> + <radio_group name="other_http_proxy_type"> + <radio_item label="不使用代理伺服器" name="OtherNoProxy" tool_tip="非網頁的 HTTP 資料流不會被導向任何代理伺服器。" value="無"/> + <radio_item label="使用 HTTP 代理伺服器" name="OtherHTTPProxy" tool_tip="非網頁的 HTTP 資料流將導向所設的網頁代理伺服器。" value="網頁"/> + <radio_item label="使用 SOCKS 5 代理伺服器" name="OtherSocksProxy" tool_tip="非網頁的 HTTP 資料流將導向所設的 Socks 5 代理伺服器。" value="網路套接"/> + </radio_group> + <button label="確定" label_selected="確定" name="OK"/> + <button label="取消" label_selected="取消" name="Cancel"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_preview_animation.xml b/indra/newview/skins/default/xui/zh/floater_preview_animation.xml index 3be6f7a35e..b94d67b7ef 100644 --- a/indra/newview/skins/default/xui/zh/floater_preview_animation.xml +++ b/indra/newview/skins/default/xui/zh/floater_preview_animation.xml @@ -6,6 +6,6 @@ <text name="desc txt"> 描述: </text> - <button label="在虛擬世界播放" label_selected="停止" name="Anim play btn" tool_tip="播放此動作讓他人看見"/> - <button label="在本地播放" label_selected="停止" name="Anim audition btn" tool_tip="播放此動作,只給自己看"/> + <button label="在虛擬世界播放" label_selected="停止" name="Inworld" tool_tip="播放此動作讓他人看見"/> + <button label="在本地播放" label_selected="停止" name="Locally" tool_tip="播放此動作,只給自己看"/> </floater> diff --git a/indra/newview/skins/default/xui/zh/floater_search.xml b/indra/newview/skins/default/xui/zh/floater_search.xml index d5abf37b86..3e85a529ae 100644 --- a/indra/newview/skins/default/xui/zh/floater_search.xml +++ b/indra/newview/skins/default/xui/zh/floater_search.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_search" title="尋找"> +<floater name="floater_search" title=""> <floater.string name="loading_text"> 載入中... </floater.string> diff --git a/indra/newview/skins/default/xui/zh/floater_snapshot.xml b/indra/newview/skins/default/xui/zh/floater_snapshot.xml index cfdaeeedb0..e86e20829f 100644 --- a/indra/newview/skins/default/xui/zh/floater_snapshot.xml +++ b/indra/newview/skins/default/xui/zh/floater_snapshot.xml @@ -3,72 +3,63 @@ <floater.string name="unknown"> 未知 </floater.string> - <radio_group label="快照類型" name="snapshot_type_radio"> - <radio_item label="電郵" name="postcard"/> - <radio_item label="我的收納區(L$[AMOUNT])" name="texture"/> - <radio_item label="儲存到電腦上" name="local"/> - </radio_group> + <string name="postcard_progress_str"> + 正在發送電郵 + </string> + <string name="profile_progress_str"> + 發佈 + </string> + <string name="inventory_progress_str"> + 儲存到收納區 + </string> + <string name="local_progress_str"> + 正在存到電腦 + </string> + <string name="profile_succeeded_str"> + 圖像已上傳 + </string> + <string name="postcard_succeeded_str"> + 電郵發送成功! + </string> + <string name="inventory_succeeded_str"> + 成功存入收納區! + </string> + <string name="local_succeeded_str"> + 成功存入電腦! + </string> + <string name="profile_failed_str"> + 上傳圖像到你的檔案訊息發佈時出錯。 + </string> + <string name="postcard_failed_str"> + 電郵傳送失敗。 + </string> + <string name="inventory_failed_str"> + 無法存入收納區。 + </string> + <string name="local_failed_str"> + 無法儲入電腦。 + </string> + <button name="advanced_options_btn" tool_tip="進階選項"/> + <text name="image_res_text"> + [WIDTH] x [HEIGHT] 像素 + </text> <text name="file_size_label"> [SIZE] KB </text> - <button label="送出" name="send_btn"/> - <button label="儲存(L$[AMOUNT])" name="upload_btn"/> - <flyout_button label="儲存" name="save_btn" tool_tip="儲存圖像到檔案"> - <flyout_button.item label="儲存" name="save_item"/> - <flyout_button.item label="另存..." name="saveas_item"/> - </flyout_button> - <button label="更多" name="more_btn" tool_tip="進階選項"/> - <button label="更少" name="less_btn" tool_tip="進階選項"/> - <button label="取消" name="discard_btn"/> - <text name="type_label2"> - 尺寸 - </text> - <text name="format_label"> - 格式 - </text> - <combo_box label="解析度" name="postcard_size_combo"> - <combo_box.item label="目前視窗" name="CurrentWindow"/> - <combo_box.item label="640x480" name="640x480"/> - <combo_box.item label="800x600" name="800x600"/> - <combo_box.item label="1024x768" name="1024x768"/> - <combo_box.item label="自訂" name="Custom"/> - </combo_box> - <combo_box label="解析度" name="texture_size_combo"> - <combo_box.item label="目前視窗" name="CurrentWindow"/> - <combo_box.item label="小(128x128)" name="Small(128x128)"/> - <combo_box.item label="中(256x256)" name="Medium(256x256)"/> - <combo_box.item label="大(512x512)" name="Large(512x512)"/> - <combo_box.item label="自訂" name="Custom"/> - </combo_box> - <combo_box label="解析度" name="local_size_combo"> - <combo_box.item label="目前視窗" name="CurrentWindow"/> - <combo_box.item label="320x240" name="320x240"/> - <combo_box.item label="640x480" name="640x480"/> - <combo_box.item label="800x600" name="800x600"/> - <combo_box.item label="1024x768" name="1024x768"/> - <combo_box.item label="1280x1024" name="1280x1024"/> - <combo_box.item label="1600x1200" name="1600x1200"/> - <combo_box.item label="自訂" name="Custom"/> - </combo_box> - <combo_box label="格式" name="local_format_combo"> - <combo_box.item label="PNG" name="PNG"/> - <combo_box.item label="JPEG" name="JPEG"/> - <combo_box.item label="BMP" name="BMP"/> - </combo_box> - <spinner label="寬" name="snapshot_width"/> - <spinner label="高度" name="snapshot_height"/> - <check_box label="鎖住比例" name="keep_aspect_check"/> - <slider label="圖像品質" name="image_quality_slider"/> - <text name="layer_type_label"> - 擷取快照: - </text> - <combo_box label="圖層" name="layer_types"> - <combo_box.item label="顏色" name="Colors"/> - <combo_box.item label="深度" name="Depth"/> - </combo_box> - <check_box label="介面" name="ui_check"/> - <check_box label="擡頭顯示" name="hud_check"/> - <check_box label="儲存後保持打開狀態" name="keep_open_check"/> - <check_box label="將幀凍結(全螢幕)" name="freeze_frame_check"/> - <check_box label="自動刷新" name="auto_snapshot_check"/> + <panel name="advanced_options_panel"> + <text name="advanced_options_label"> + 進階選項 + </text> + <text name="layer_type_label"> + 擷取快照: + </text> + <combo_box label="圖層" name="layer_types"> + <combo_box.item label="顏色" name="Colors"/> + <combo_box.item label="深度" name="Depth"/> + </combo_box> + <check_box label="介面" name="ui_check"/> + <check_box label="擡頭顯示" name="hud_check"/> + <check_box label="將幀凍結(全螢幕)" name="freeze_frame_check"/> + <check_box label="自動刷新" name="auto_snapshot_check"/> + </panel> </floater> diff --git a/indra/newview/skins/default/xui/zh/floater_spellcheck.xml b/indra/newview/skins/default/xui/zh/floater_spellcheck.xml new file mode 100644 index 0000000000..f5a6665844 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_spellcheck.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="spellcheck_floater" title="拼字檢查設定"> + <check_box label="啟用拼字檢查" name="spellcheck_enable"/> + <text name="spellcheck_main"> + 主要字典: + </text> + <text label="記錄:" name="spellcheck_additional"> + 附加字典: + </text> + <text name="spellcheck_available"> + 可用的 + </text> + <text name="spellcheck_active"> + 可用 + </text> + <button label="移除" name="spellcheck_remove_btn"/> + <button label="匯入…" name="spellcheck_import_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_spellcheck_import.xml b/indra/newview/skins/default/xui/zh/floater_spellcheck_import.xml new file mode 100644 index 0000000000..6094a3bbce --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_spellcheck_import.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="spellcheck_import" title="匯入字典"> + <button label="瀏覽" label_selected="瀏覽" name="dictionary_path_browse"/> + <button label="匯入" name="ok_btn"/> + <button label="取消" name="cancel_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_stats.xml b/indra/newview/skins/default/xui/zh/floater_stats.xml index 74a6fd1d9f..4af5684ec1 100644 --- a/indra/newview/skins/default/xui/zh/floater_stats.xml +++ b/indra/newview/skins/default/xui/zh/floater_stats.xml @@ -10,12 +10,15 @@ </stat_view> <stat_view label="進階" name="advanced"> <stat_view label="呈像" name="render"> - <stat_bar label="KTris 繪出" name="ktrisframe"/> - <stat_bar label="KTris 繪出" name="ktrissec"/> + <stat_bar label="繪出的 KTris(每幀)" name="ktrisframe"/> + <stat_bar label="繪出的 KTris(每秒)" name="ktrissec"/> <stat_bar label="物件總計" name="objs"/> <stat_bar label="新物件" name="newobjs"/> + <stat_bar label="物件快取讀取率" name="object_cache_hits"/> </stat_view> <stat_view label="材質" name="texture"> + <stat_bar label="快取讀取率" name="texture_cache_hits"/> + <stat_bar label="快取讀取延遲" name="texture_cache_read_latency"/> <stat_bar label="計數" name="numimagesstat"/> <stat_bar label="原始計數" name="numrawimagesstat"/> <stat_bar label="GL 記憶" name="gltexmemstat"/> @@ -50,7 +53,13 @@ <stat_bar label="物件" name="simobjects"/> <stat_bar label="使用中物件" name="simactiveobjects"/> <stat_bar label="使用中腳本" name="simactivescripts"/> + <stat_bar label="腳本執行" name="simpctscriptsrun"/> <stat_bar label="腳本事件" name="simscripteps"/> + <stat_view label="尋徑" name="simpathfinding"> + <stat_bar label="人工智慧步驟時間" name="simsimaistepmsec"/> + <stat_bar label="已略過輪廓步驟" name="simsimskippedsilhouettesteps"/> + <stat_bar label="角色已更新" name="simsimpctsteppedcharacters"/> + </stat_view> <stat_bar label="進入封包" name="siminpps"/> <stat_bar label="出去封包" name="simoutpps"/> <stat_bar label="擱置下載" name="simpendingdownloads"/> @@ -64,6 +73,14 @@ <stat_bar label="用戶時間" name="simagentmsec"/> <stat_bar label="圖像時間" name="simimagesmsec"/> <stat_bar label="腳本時間" name="simscriptmsec"/> + <stat_bar label="閒置時間" name="simsparemsec"/> + <stat_view label="時間細節(毫秒)" name="timedetails"> + <stat_bar label="物理步驟" name="simsimphysicsstepmsec"/> + <stat_bar label="更新物理形狀" name="simsimphysicsshapeupdatemsec"/> + <stat_bar label="物理(其他)" name="simsimphysicsothermsec"/> + <stat_bar label="睡眠時間" name="simsleepmsec"/> + <stat_bar label="Pump IO" name="simpumpiomsec"/> + </stat_view> </stat_view> </stat_view> </container_view> diff --git a/indra/newview/skins/default/xui/zh/floater_test_layout_stacks.xml b/indra/newview/skins/default/xui/zh/floater_test_layout_stacks.xml new file mode 100644 index 0000000000..68fcf3f7f7 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_test_layout_stacks.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Test Floater" title="LAYOUTSTACK 測試"/> diff --git a/indra/newview/skins/default/xui/zh/floater_test_text_vertical_aligment.xml b/indra/newview/skins/default/xui/zh/floater_test_text_vertical_aligment.xml new file mode 100644 index 0000000000..83b6df6fe5 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_test_text_vertical_aligment.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Test Floater" title="測試浮動視窗"/> diff --git a/indra/newview/skins/default/xui/zh/floater_texture_ctrl.xml b/indra/newview/skins/default/xui/zh/floater_texture_ctrl.xml index 16b3409a82..e909a67e2c 100644 --- a/indra/newview/skins/default/xui/zh/floater_texture_ctrl.xml +++ b/indra/newview/skins/default/xui/zh/floater_texture_ctrl.xml @@ -9,15 +9,27 @@ <text name="Multiple"> 多重材質 </text> + <radio_group name="mode_selection"> + <radio_item label="收納區" name="inventory" value="0"/> + <radio_item label="本地" name="local" value="1"/> + </radio_group> <text name="unknown"> 尺寸:[DIMENSIONS] </text> <button label="預設" label_selected="預設" name="Default"/> - <button label="無" label_selected="無" name="None"/> <button label="空白" label_selected="空白" name="Blank"/> - <check_box initial_value="true" label="立即套用" name="apply_immediate_check"/> + <button label="無" label_selected="無" name="None"/> + <check_box initial_value="true" label="實時預覽" name="apply_immediate_check"/> + <text name="preview_disabled" value="已停用預覽"/> <filter_editor label="材質過濾器" name="inventory search editor"/> <check_box initial_value="false" label="顯示資料夾" name="show_folders_check"/> + <button label="添加" label_selected="添加" name="l_add_btn"/> + <button label="移除" label_selected="移除" name="l_rem_btn"/> + <button label="上傳" label_selected="上傳" name="l_upl_btn"/> + <scroll_list name="l_name_list"> + <column label="名稱" name="unit_name"/> + <column label="ID" name="unit_id_HIDDEN"/> + </scroll_list> <button label="確定" label_selected="確定" name="Select"/> <button label="取消" label_selected="取消" name="Cancel"/> </floater> diff --git a/indra/newview/skins/default/xui/zh/floater_texture_fetch_debugger.xml b/indra/newview/skins/default/xui/zh/floater_texture_fetch_debugger.xml new file mode 100644 index 0000000000..0dcac17a75 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_texture_fetch_debugger.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="TexFetchDebugger" title="材質擷取除錯器"> + <text name="total_num_fetched_label"> + 1. 已擷取材質總數:[NUM] + </text> + <text name="total_num_fetching_requests_label"> + 2. 總擷取請求數:[NUM] + </text> + <text name="total_num_cache_hits_label"> + 3. 快取總讀取數:[NUM] + </text> + <text name="total_num_visible_tex_label"> + 4. 可見材質總數:[NUM] + </text> + <text name="total_num_visible_tex_fetch_req_label"> + 5. 可見材質擷取總請求數:[NUM] + </text> + <text name="total_fetched_data_label"> + 6. 總擷取資料量:[SIZE1] KB,解碼資料量:[SIZE2] KB,[PIXEL] 百萬像素 + </text> + <text name="total_fetched_vis_data_label"> + 7. 可見資料總量:[SIZE1] KB,解碼資料量:[SIZE2] KB + </text> + <text name="total_fetched_rendered_data_label"> + 8. 總呈像資料量:[SIZE1] KB,解碼資料量:[SIZE2] KB,[PIXEL] 百萬像素 + </text> + <text name="total_time_cache_read_label"> + 9. 快取讀取總時間:[TIME] 秒 + </text> + <text name="total_time_cache_write_label"> + 10. 快取寫入總時間:[TIME] 秒 + </text> + <text name="total_time_decode_label"> + 11. 解碼總時間:[TIME] 秒 + </text> + <text name="total_time_gl_label"> + 12. 建立 gl 材質總時間:[TIME] 秒 + </text> + <text name="total_time_http_label"> + 13. HTTP 擷取總時間:[TIME] 秒 + </text> + <text name="total_time_fetch_label"> + 14. 所有擷取動作總時間:[TIME] 秒 + </text> + <text name="total_time_refetch_vis_cache_label"> + 15. 自快取重新擷取可見材質,時間:[TIME] 秒,擷取量:[SIZE] KB,[PIXEL] 百萬像素 + </text> + <text name="total_time_refetch_all_cache_label"> + 16. 從快取重新擷取所有材質,時間:[TIME] 秒,擷取量:[SIZE] KB,[PIXEL] 百萬像素 + </text> + <text name="total_time_refetch_vis_http_label"> + 17. 自 HTTP 重新擷取可見材質,時間:[TIME] 秒,擷取量:[SIZE] KB,[PIXEL] 百萬像素 + </text> + <text name="total_time_refetch_all_http_label"> + 18. 自 HTTP 重新擷取所有材質,時間:[TIME] 秒,擷取量:[SIZE] KB,[PIXEL] 百萬像素 + </text> + <spinner label="19. 材質/像素比率:" name="texel_pixel_ratio"/> + <text name="texture_source_label"> + 20. 材質來源: + </text> + <radio_group name="texture_source"> + <radio_item label="快取 + HTTP" name="0"/> + <radio_item label="僅限 HTTP" name="1"/> + </radio_group> + <button label="開始" name="start_btn"/> + <button label="重設" name="clear_btn"/> + <button label="關閉" name="close_btn"/> + <button label="快取讀取" name="cacheread_btn"/> + <button label="快取寫入" name="cachewrite_btn"/> + <button label="HTTP" name="http_btn"/> + <button label="解碼" name="decode_btn"/> + <button label="GL 材質" name="gl_btn"/> + <button label="快取重取可見材質" name="refetchviscache_btn"/> + <button label="重新擷取所有快取" name="refetchallcache_btn"/> + <button label="HTTP 重取可見材質" name="refetchvishttp_btn"/> + <button label="重新擷取所有 HTTP" name="refetchallhttp_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_tools.xml b/indra/newview/skins/default/xui/zh/floater_tools.xml index 62d1b6a820..33c9ebeaf0 100644 --- a/indra/newview/skins/default/xui/zh/floater_tools.xml +++ b/indra/newview/skins/default/xui/zh/floater_tools.xml @@ -1,5 +1,20 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="toolbox floater" short_title="建造工具"> + <floater.string name="grid_screen_text"> + 螢幕 + </floater.string> + <floater.string name="grid_local_text"> + 本地 + </floater.string> + <floater.string name="grid_world_text"> + 世界 + </floater.string> + <floater.string name="grid_reference_text"> + 參考 + </floater.string> + <floater.string name="grid_attachment_text"> + 附件 + </floater.string> <floater.string name="status_rotate"> 拖曳色條可以旋轉物件 </floater.string> @@ -24,20 +39,11 @@ <floater.string name="status_selectland"> 按住並拖曳,可以選取土地 </floater.string> - <floater.string name="grid_screen_text"> - 螢幕 + <floater.string name="status_selectcount"> + 選取了 [OBJ_COUNT] 個物件,土地衝擊量 [LAND_IMPACT] </floater.string> - <floater.string name="grid_local_text"> - 本地 - </floater.string> - <floater.string name="grid_world_text"> - 世界 - </floater.string> - <floater.string name="grid_reference_text"> - 參考 - </floater.string> - <floater.string name="grid_attachment_text"> - 附件 + <floater.string name="status_remaining_capacity"> + 剩餘容納量 [LAND_CAPACITY]。 </floater.string> <button name="button focus" tool_tip="聚焦"/> <button name="button move" tool_tip="移動"/> @@ -67,20 +73,17 @@ <check_box label="編輯聯結部分" name="checkbox edit linked parts"/> <button label="聯結" name="link_btn"/> <button label="取消聯結" name="unlink_btn"/> - <text name="RenderingCost" tool_tip="顯示這物件經計算所得的呈像成本"> - þ:[COUNT] - </text> <text label="同時伸展兩側" name="checkbox uniform label"> 同時伸展兩側 </text> <check_box initial_value="true" label="伸展材質" name="checkbox stretch textures"/> - <check_box initial_value="true" label="貼齊格線" name="checkbox snap to grid"/> + <check_box initial_value="true" label="Snap" name="checkbox snap to grid"/> <combo_box name="combobox grid mode" tool_tip="選擇物件定位參考的格線尺度類型"> - <combo_box.item label="世界格線" name="World"/> - <combo_box.item label="地方格線" name="Local"/> - <combo_box.item label="參考格線" name="Reference"/> + <combo_box.item label="世界" name="World"/> + <combo_box.item label="本地" name="Local"/> + <combo_box.item label="參考" name="Reference"/> </combo_box> - <button name="Options..." tool_tip="察看更多格線選項"/> + <button label="" name="Options..." tool_tip="察看更多格線選項"/> <button name="ToolCube" tool_tip="立方體"/> <button name="ToolPrism" tool_tip="稜鏡體"/> <button name="ToolPyramid" tool_tip="金字塔"/> @@ -121,23 +124,11 @@ </text> <slider_bar initial_value="0.00" name="slider force"/> <button label="套用" label_selected="套用" name="button apply to selection" tool_tip="修改所選擇的土地"/> - <text name="obj_count"> - 物件: [COUNT] - </text> - <text name="prim_count"> - 幾何元件: [COUNT] - </text> - <text name="linked_set_count"> - 聯結集合:[COUNT] - </text> - <text name="linked_set_cost" tool_tip="目前所選聯結幾何 [prims],[physics complexity] 的費用"> - 費用:[COST] / [PHYSICS] - </text> - <text name="object_count"> - 物件: [COUNT] + <text name="selection_empty"> + 未做任何選擇。 </text> - <text name="object_cost" tool_tip="目前所選物件 [prims] / [physics complexity] 的費用"> - 費用:[COST] / [PHYSICS] + <text name="remaining_capacity"> + [CAPACITY_STRING] [secondlife:///app/openfloater/object_weights 詳情] </text> <tab_container name="Object Info Tabs"> <panel label="一般" name="General"> @@ -159,6 +150,12 @@ <panel.string name="text modify info 4"> 你不能修改這些物件 </panel.string> + <panel.string name="text modify info 5"> + 無法跨地區修改這個物件 + </panel.string> + <panel.string name="text modify info 6"> + 無法跨地區修改這些物件 + </panel.string> <panel.string name="text modify warning"> 你必須選取整個物件以設定權限 </panel.string> @@ -208,12 +205,12 @@ <combo_box.item label="縮放" name="Zoom"/> </combo_box> <check_box label="出售中:" name="checkbox for sale"/> + <spinner label="L$" name="Edit Cost"/> <combo_box name="sale type"> <combo_box.item label="恚庨" name="Copy"/> <combo_box.item label="內容" name="Contents"/> <combo_box.item label="原件" name="Original"/> </combo_box> - <spinner label="價格: L$" name="Edit Cost"/> <check_box label="顯示在搜尋中" name="search_check" tool_tip="讓其他人可以在搜尋結果中察看到此物件"/> <panel name="perms_build"> <text name="perm_modify"> @@ -249,6 +246,11 @@ F: </text> </panel> + <panel name="pathfinding_attrs_panel"> + <text name="pathfinding_attributes_label"> + 尋徑屬性: + </text> + </panel> </panel> <panel label="物件" name="Object"> <check_box label="已鎖住" name="checkbox locked" tool_tip="避免物件被移動或刪除。 這在建製過程中非常有用,可避免意外的編輯更動。"/> @@ -350,12 +352,10 @@ 縫合類型 </text> <combo_box name="sculpt type control"> - <combo_box.item label="(無)" name="None"/> <combo_box.item label="球體" name="Sphere"/> <combo_box.item label="環面" name="Torus"/> <combo_box.item label="平面" name="Plane"/> <combo_box.item label="圓柱體" name="Cylinder"/> - <combo_box.item label="網面" name="Mesh"/> </combo_box> </panel> <panel label="特性" name="Features"> @@ -407,7 +407,7 @@ </combo_box> <spinner label="重力" name="Physics Gravity"/> <spinner label="摩擦" name="Physics Friction"/> - <spinner label="密度" name="Physics Density"/> + <spinner label="密度(100 公斤 / 立方公尺)" name="Physics Density"/> <spinner label="恢復" name="Physics Restitution"/> </panel> <panel label="材質" name="Texture"> diff --git a/indra/newview/skins/default/xui/zh/floater_top_objects.xml b/indra/newview/skins/default/xui/zh/floater_top_objects.xml index ad12ae6720..6f50be0855 100644 --- a/indra/newview/skins/default/xui/zh/floater_top_objects.xml +++ b/indra/newview/skins/default/xui/zh/floater_top_objects.xml @@ -9,9 +9,6 @@ <floater.string name="scripts_score_label"> 時間 </floater.string> - <floater.string name="scripts_mono_time_label"> - Mono 時間 - </floater.string> <floater.string name="top_colliders_title"> 最常碰撞物項 </floater.string> @@ -32,9 +29,10 @@ <scroll_list.columns label="名稱" name="name"/> <scroll_list.columns label="所有人" name="owner"/> <scroll_list.columns label="位置" name="location"/> + <scroll_list.columns label="地段" name="parcel"/> <scroll_list.columns label="時間" name="time"/> - <scroll_list.columns label="Mono 時間" name="mono_time"/> <scroll_list.columns label="URL" name="URLs"/> + <scroll_list.columns label="記憶體 (KB)" name="memory"/> </scroll_list> <text name="id_text"> 物件 ID: @@ -48,6 +46,10 @@ 所有人: </text> <button label="過濾器" name="filter_owner_btn"/> + <text name="parcel_name_text"> + 地段: + </text> + <button label="過濾器" name="filter_parcel_btn"/> <button label="退回所選擇的" name="return_selected_btn"/> <button label="全部退回" name="return_all_btn"/> <button label="停用所選的" name="disable_selected_btn"/> diff --git a/indra/newview/skins/default/xui/zh/floater_toybox.xml b/indra/newview/skins/default/xui/zh/floater_toybox.xml new file mode 100644 index 0000000000..fc6b6ea611 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_toybox.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="Toybox" title="工具列按鈕"> + <text name="toybox label 1"> + 將按鈕拖入或拖離工具列,即可新增或移除該按鈕。 + </text> + <text name="toybox label 2"> + 按鈕將根據每一個工具列的設定,僅顯示圖示或一併顯示文字標籤。 + </text> + <button label="清除所有工具列" label_selected="清除所有工具列" name="btn_clear_all"/> + <button label="還原預設值" label_selected="還原預設值" name="btn_restore_defaults"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_translation_settings.xml b/indra/newview/skins/default/xui/zh/floater_translation_settings.xml new file mode 100644 index 0000000000..fe84872557 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/floater_translation_settings.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<floater name="floater_translation_settings" title="聊天內容翻譯設定"> + <string name="bing_api_key_not_verified"> + Bing AppID 未通過驗證。 請再試一次。 + </string> + <string name="google_api_key_not_verified"> + Google API 鍵值未通過驗證。 請再試一次。 + </string> + <string name="bing_api_key_verified"> + Bing AppID 驗證成功。 + </string> + <string name="google_api_key_verified"> + Google API 鍵值驗證成功。 + </string> + <check_box label="聊天時啟用機器翻譯" name="translate_chat_checkbox"/> + <text name="translate_language_label"> + 將聊天內容翻譯成: + </text> + <combo_box name="translate_language_combo"> + <combo_box.item label="系統預設" name="System Default Language"/> + <combo_box.item label="英語" name="English"/> + <combo_box.item label="Dansk(丹麥語)" name="Danish"/> + <combo_box.item label="Deutsch(德語)" name="German"/> + <combo_box.item label="Español(西班牙語)" name="Spanish"/> + <combo_box.item label="Français(法語)" name="French"/> + <combo_box.item label="Italiano(義大利語)" name="Italian"/> + <combo_box.item label="Magyar(匈牙利語)" name="Hungarian"/> + <combo_box.item label="Nederlands(荷蘭語)" name="Dutch"/> + <combo_box.item label="Polski(波蘭語)" name="Polish"/> + <combo_box.item label="Português(葡萄牙語)" name="Portugese"/> + <combo_box.item label="Русский(俄羅斯語)" name="Russian"/> + <combo_box.item label="Türkçe(土耳其語)" name="Turkish"/> + <combo_box.item label="Українська(烏克蘭語)" name="Ukrainian"/> + <combo_box.item label="中文(正體)" name="Chinese"/> + <combo_box.item label="日本語(日語)" name="Japanese"/> + <combo_box.item label="한국어(漢語)" name="Korean"/> + </combo_box> + <text name="tip"> + 選擇翻譯服務: + </text> + <radio_group name="translation_service_rg"> + <radio_item initial_value="bing" label="Bing 翻譯器" name="bing"/> + <radio_item initial_value="google" label="Google 翻譯" name="google"/> + </radio_group> + <text name="bing_api_key_label"> + Bing [http://www.bing.com/developers/createapp.aspx AppID]: + </text> + <button label="驗證" name="verify_bing_api_key_btn"/> + <text name="google_api_key_label"> + Google [http://code.google.com/apis/language/translate/v2/getting_started.html#auth API 鍵值]: + </text> + <button label="驗證" name="verify_google_api_key_btn"/> + <text name="google_links_text"> + [http://code.google.com/apis/language/translate/v2/pricing.html 價格表] | [https://code.google.com/apis/console 統計] + </text> + <button label="確定" name="ok_btn"/> + <button label="取消" name="cancel_btn"/> +</floater> diff --git a/indra/newview/skins/default/xui/zh/floater_voice_controls.xml b/indra/newview/skins/default/xui/zh/floater_voice_controls.xml index fa724a4736..a3a7679957 100644 --- a/indra/newview/skins/default/xui/zh/floater_voice_controls.xml +++ b/indra/newview/skins/default/xui/zh/floater_voice_controls.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="floater_voice_controls" title="語音控制"> <string name="title_nearby"> - 附近的語音 + 語音設定 </string> <string name="title_group"> 與 [GROUP] 進行群組通話 @@ -10,7 +10,7 @@ 多方通話 </string> <string name="title_peer_2_peer"> - 與 [NAME] 進行通話 + 和 [NAME] 通話 </string> <string name="no_one_near"> 附近沒有一人開啟語音 diff --git a/indra/newview/skins/default/xui/zh/floater_voice_effect.xml b/indra/newview/skins/default/xui/zh/floater_voice_effect.xml index 184ee6edd5..81e0204262 100644 --- a/indra/newview/skins/default/xui/zh/floater_voice_effect.xml +++ b/indra/newview/skins/default/xui/zh/floater_voice_effect.xml @@ -12,6 +12,135 @@ <string name="new_voice_effect"> (新的!) </string> + <string name="effect_Arena"> + 競技場 + </string> + <string name="effect_Beast"> + 野獸 + </string> + <string name="effect_Buff"> + Buff + </string> + <string name="effect_Buzz"> + Buzz + </string> + <string name="effect_Camille"> + 卡蜜兒 + </string> + <string name="effect_Creepy"> + 怪異 + </string> + <string name="effect_CreepyBot"> + 怪異機器人 + </string> + <string name="effect_Cyber"> + 科幻 + </string> + <string name="effect_DeepBot"> + 深沉機器人 + </string> + <string name="effect_Demon"> + 魔鬼 + </string> + <string name="effect_Female Elf"> + 女性 - 小精靈 + </string> + <string name="effect_Flirty"> + 調情 + </string> + <string name="effect_Foxy"> + 香豔 + </string> + <string name="effect_Halloween 2010 Bonus"> + 2010萬聖節加贈 + </string> + <string name="effect_Helium"> + 氦氣 + </string> + <string name="effect_Husky"> + 沙啞 + </string> + <string name="effect_Husky Whisper"> + 沙啞耳語 + </string> + <string name="effect_Intercom"> + 對講機 + </string> + <string name="effect_Julia"> + 茱莉亞 + </string> + <string name="effect_Lo Lilt"> + 輕微抑揚頓挫 + </string> + <string name="effect_Macho"> + 陽剛 + </string> + <string name="effect_Micro"> + Micro + </string> + <string name="effect_Mini"> + 迷你 + </string> + <string name="effect_Model"> + 模型 + </string> + <string name="effect_Nano"> + Nano + </string> + <string name="effect_Nightmare"> + 惡夢 + </string> + <string name="effect_PopBot"> + PopBot + </string> + <string name="effect_Rachel"> + 瑞秋 + </string> + <string name="effect_Radio"> + 收音機 + </string> + <string name="effect_Robot"> + 機器人 + </string> + <string name="effect_Roxanne"> + 蘿姍 + </string> + <string name="effect_Rumble"> + 低沉隆隆聲 + </string> + <string name="effect_Sabrina"> + 薩賓娜 + </string> + <string name="effect_Samantha"> + 姍曼莎 + </string> + <string name="effect_Sexy"> + 性感 + </string> + <string name="effect_Shorty"> + 矮個兒 + </string> + <string name="effect_Smaller"> + 較小 + </string> + <string name="effect_Sneaky"> + 鬼祟 + </string> + <string name="effect_Stallion"> + 種馬 + </string> + <string name="effect_Sultry"> + 勾魂 + </string> + <string name="effect_Thunder"> + 雷聲 + </string> + <string name="effect_Vixen"> + 潑婦 + </string> + <string name="effect_WhinyBot"> + 哭鬧機器人 + </string> <text name="preview_text"> 預覽 </text> diff --git a/indra/newview/skins/default/xui/zh/floater_window_size.xml b/indra/newview/skins/default/xui/zh/floater_window_size.xml index 54b72afccc..3942f72d76 100644 --- a/indra/newview/skins/default/xui/zh/floater_window_size.xml +++ b/indra/newview/skins/default/xui/zh/floater_window_size.xml @@ -7,10 +7,17 @@ 設定視窗尺寸大小: </text> <combo_box name="window_size_combo" tool_tip="寬度 x 高度"> - <combo_box.item label="1000 x 700 (預設)" name="item0"/> - <combo_box.item label="1024 x 768" name="item1"/> - <combo_box.item label="1280 x 720 (720p)" name="item2"/> - <combo_box.item label="1920 x 1080 (1080p)" name="item3"/> + <combo_box.item label="1000 x 700 (預設)" name="item1"/> + <combo_box.item label="1024 x 768 (4:3 XGA)" name="item2"/> + <combo_box.item label="1280 x 720 (16:9 HDTV)" name="item3"/> + <combo_box.item label="1280 x 800 (5:8 WXGA)" name="item4"/> + <combo_box.item label="1280 x 1024 (5:4 SXGA)" name="item5"/> + <combo_box.item label="1440 x 900 (8:5 WSXGA)" name="item7"/> + <combo_box.item label="1600 x 900 (16:9 HD+)" name="item8"/> + <combo_box.item label="1600 x 1200 (4:3 UXGA)" name="item9"/> + <combo_box.item label="1680 x 1050 (8:5 WSXGA+)" name="item10"/> + <combo_box.item label="1920 x 1080 (16:9 HDTV)" name="item11"/> + <combo_box.item label="1920 x 1200 (8:5 WUXGA)" name="item12"/> </combo_box> <button label="設定" name="set_btn"/> <button label="取消" name="cancel_btn"/> diff --git a/indra/newview/skins/default/xui/zh/menu_hide_navbar.xml b/indra/newview/skins/default/xui/zh/menu_hide_navbar.xml index dbb8ececaa..3f6820cb32 100644 --- a/indra/newview/skins/default/xui/zh/menu_hide_navbar.xml +++ b/indra/newview/skins/default/xui/zh/menu_hide_navbar.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <menu name="hide_navbar_menu"> - <menu_item_check label="顯示導覽列" name="ShowNavbarNavigationPanel"/> + <menu_item_check label="顯示導覽列和我的最愛" name="ShowNavbarNavigationPanel"/> <menu_item_check label="顯示最愛列" name="ShowNavbarFavoritesPanel"/> <menu_item_check label="顯示迷你位置列" name="ShowMiniLocationPanel"/> </menu> diff --git a/indra/newview/skins/default/xui/zh/menu_inspect_object_gear.xml b/indra/newview/skins/default/xui/zh/menu_inspect_object_gear.xml index cedc781fa2..abff0f64ac 100644 --- a/indra/newview/skins/default/xui/zh/menu_inspect_object_gear.xml +++ b/indra/newview/skins/default/xui/zh/menu_inspect_object_gear.xml @@ -12,6 +12,7 @@ <menu_item_call label="添加" name="add"/> <menu_item_call label="回報" name="report"/> <menu_item_call label="封鎖" name="block"/> + <menu_item_call label="解除封鎖" name="unblock"/> <menu_item_call label="放大" name="zoom_in"/> <menu_item_call label="移除" name="remove"/> <menu_item_call label="更多資訊" name="more_info"/> diff --git a/indra/newview/skins/default/xui/zh/menu_inventory.xml b/indra/newview/skins/default/xui/zh/menu_inventory.xml index 63ec001e4a..7f745ffaa7 100644 --- a/indra/newview/skins/default/xui/zh/menu_inventory.xml +++ b/indra/newview/skins/default/xui/zh/menu_inventory.xml @@ -59,6 +59,7 @@ <menu_item_call label="屬性" name="Properties"/> <menu_item_call label="更名" name="Rename"/> <menu_item_call label="覆製資產 UUID" name="Copy Asset UUID"/> + <menu_item_call label="剪下" name="Cut"/> <menu_item_call label="恚庨" name="Copy"/> <menu_item_call label="貼上" name="Paste"/> <menu_item_call label="以連結格式貼上" name="Paste As Link"/> @@ -67,6 +68,7 @@ <menu_item_call label="刪除系統資料夾" name="Delete System Folder"/> <menu_item_call label="發起多方通話" name="Conference Chat Folder"/> <menu_item_call label="播放" name="Sound Play"/> + <menu_item_call label="覆製 SLurl" name="url_copy"/> <menu_item_call label="添加地標" name="About Landmark"/> <menu_item_call label="在虛擬世界播放" name="Animation Play"/> <menu_item_call label="在本地播放" name="Animation Audition"/> @@ -83,5 +85,7 @@ <menu_item_call label="編輯" name="Wearable Edit"/> <menu_item_call label="添加" name="Wearable Add"/> <menu_item_call label="脫下" name="Take Off"/> + <menu_item_call label="複製到商家發件匣" name="Merchant Copy"/> + <menu_item_call label="送往第二人生購物市集" name="Marketplace Send"/> <menu_item_call label="-- 無選項 --" name="--no options--"/> </menu> diff --git a/indra/newview/skins/default/xui/zh/menu_login.xml b/indra/newview/skins/default/xui/zh/menu_login.xml index f7b2cd7b8b..c327b132a1 100644 --- a/indra/newview/skins/default/xui/zh/menu_login.xml +++ b/indra/newview/skins/default/xui/zh/menu_login.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <menu_bar name="Login Menu"> <menu label="我自己" name="File"> - <menu_item_call label="偏好設定" name="Preferences..."/> + <menu_item_call label="偏好設定…" name="Preferences..."/> <menu_item_call label="退出 [APP_NAME]" name="Quit"/> </menu> <menu label="幫助" name="Help"> @@ -17,8 +17,8 @@ <menu_item_call label="設定視窗大小..." name="Set Window Size..."/> <menu_item_call label="顯示 TOS" name="TOS"/> <menu_item_call label="顯示嚴重訊息" name="Critical"/> - <menu_item_call label="媒體瀏覽器測試" name="Web Browser Test"/> - <menu_item_call label="網頁內容浮動視窗測試" name="Web Content Floater Test"/> + <menu_item_call label="網頁內容浮動視窗除錯測試" name="Web Content Floater Debug Test"/> + <menu label="設定記錄細節" name="Set Logging Level"/> <menu_item_check label="顯示網格挑選器" name="Show Grid Picker"/> <menu_item_call label="顯示通知控制台" name="Show Notifications Console"/> </menu> diff --git a/indra/newview/skins/default/xui/zh/menu_media_ctrl.xml b/indra/newview/skins/default/xui/zh/menu_media_ctrl.xml index bab1bbb8f9..d222d7f658 100644 --- a/indra/newview/skins/default/xui/zh/menu_media_ctrl.xml +++ b/indra/newview/skins/default/xui/zh/menu_media_ctrl.xml @@ -3,4 +3,5 @@ <menu_item_call label="剪下" name="Cut"/> <menu_item_call label="恚庨" name="Copy"/> <menu_item_call label="貼上" name="Paste"/> + <menu_item_call label="開啟網頁偵查器" name="open_webinspector"/> </context_menu> diff --git a/indra/newview/skins/default/xui/zh/menu_object.xml b/indra/newview/skins/default/xui/zh/menu_object.xml index 94172eb531..4282c1e131 100644 --- a/indra/newview/skins/default/xui/zh/menu_object.xml +++ b/indra/newview/skins/default/xui/zh/menu_object.xml @@ -3,6 +3,8 @@ <menu_item_call label="觸碰" name="Object Touch"/> <menu_item_call label="編輯" name="Edit..."/> <menu_item_call label="建造" name="Build"/> + <menu_item_call label="在聯結集裡顯示" name="show_in_linksets"/> + <menu_item_call label="在角色中顯示" name="show_in_characters"/> <menu_item_call label="打開" name="Open"/> <menu_item_call label="坐在這裡" name="Object Sit"/> <menu_item_call label="起立" name="Object Stand Up"/> diff --git a/indra/newview/skins/default/xui/zh/menu_people_nearby_view_sort.xml b/indra/newview/skins/default/xui/zh/menu_people_nearby_view_sort.xml index 8716a4b894..6cb0ac3c89 100644 --- a/indra/newview/skins/default/xui/zh/menu_people_nearby_view_sort.xml +++ b/indra/newview/skins/default/xui/zh/menu_people_nearby_view_sort.xml @@ -4,5 +4,6 @@ <menu_item_check label="依名稱排序" name="sort_name"/> <menu_item_check label="依距離排序" name="sort_distance"/> <menu_item_check label="察看人群圖示" name="view_icons"/> + <menu_item_check label="察看地圖" name="view_map"/> <menu_item_call label="顯示被封鎖的居民與物件" name="show_blocked_list"/> </toggleable_menu> diff --git a/indra/newview/skins/default/xui/zh/menu_text_editor.xml b/indra/newview/skins/default/xui/zh/menu_text_editor.xml index be48a2ce49..febc0b8b67 100644 --- a/indra/newview/skins/default/xui/zh/menu_text_editor.xml +++ b/indra/newview/skins/default/xui/zh/menu_text_editor.xml @@ -1,5 +1,12 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <context_menu name="Text editor context menu"> + <menu_item_call label="(未知)" name="Suggestion 1"/> + <menu_item_call label="(未知)" name="Suggestion 2"/> + <menu_item_call label="(未知)" name="Suggestion 3"/> + <menu_item_call label="(未知)" name="Suggestion 4"/> + <menu_item_call label="(未知)" name="Suggestion 5"/> + <menu_item_call label="新增到字典" name="Add to Dictionary"/> + <menu_item_call label="新增到忽略清單" name="Add to Ignore"/> <menu_item_call label="剪下" name="Cut"/> <menu_item_call label="恚庨" name="Copy"/> <menu_item_call label="貼上" name="Paste"/> diff --git a/indra/newview/skins/default/xui/zh/menu_toolbars.xml b/indra/newview/skins/default/xui/zh/menu_toolbars.xml new file mode 100644 index 0000000000..d318992dd5 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/menu_toolbars.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<context_menu name="Toolbars Popup"> + <menu_item_call label="移除此按鈕" name="Remove button"/> + <menu_item_call label="工具列按鈕…" name="Choose Buttons"/> + <menu_item_check label="圖示和標籤" name="icons_with_text"/> + <menu_item_check label="僅用圖示" name="icons_only"/> +</context_menu> diff --git a/indra/newview/skins/default/xui/zh/menu_viewer.xml b/indra/newview/skins/default/xui/zh/menu_viewer.xml index 262cc2440c..ac0e9e7e35 100644 --- a/indra/newview/skins/default/xui/zh/menu_viewer.xml +++ b/indra/newview/skins/default/xui/zh/menu_viewer.xml @@ -1,51 +1,60 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <menu_bar name="Main Menu"> <menu label="我自己" name="Me"> - <menu_item_call label="偏好設定" name="Preferences"/> - <menu_item_call label="我的塗鴉牆" name="Manage My Account"/> - <menu_item_call label="購買 L$" name="Buy and Sell L$"/> - <menu_item_call label="我的個人檔案" name="Profile"/> - <menu_item_call label="我的外觀" name="ChangeOutfit"/> - <menu_item_check label="我的收納區" name="Inventory"/> - <menu_item_check label="我的收納區" name="ShowSidetrayInventory"/> - <menu_item_check label="我的姿勢" name="Gestures"/> - <menu_item_check label="我的聲音" name="ShowVoice"/> + <menu_item_call label="檔案..." name="Profile"/> + <menu_item_call label="外觀…" name="ChangeOutfit"/> + <menu_item_call label="選擇一個化身…" name="Avatar Picker"/> + <menu_item_check label="收納區…" name="Inventory"/> + <menu_item_call label="新收納區視窗" name="NewInventoryWindow"/> + <menu_item_call label="地點…" name="Places"/> + <menu_item_call label="精選地點…" name="Picks"/> + <menu_item_call label="攝影機控制…" name="Camera Controls"/> <menu label="動作" name="Movement"> <menu_item_call label="坐下" name="Sit Down Here"/> <menu_item_check label="飛行" name="Fly"/> <menu_item_check label="以跑代步" name="Always Run"/> <menu_item_call label="停止我身上的動作" name="Stop Animating My Avatar"/> + <menu_item_call label="行走 / 跑步 / 飛行…" name="Walk / run / fly"/> </menu> - <menu label="我的狀態" name="Status"> + <menu label="狀態" name="Status"> <menu_item_call label="離開" name="Set Away"/> <menu_item_call label="忙碌" name="Set Busy"/> </menu> - <menu_item_call label="要求管理員狀態" name="Request Admin Options"/> - <menu_item_call label="離開管理員狀態" name="Leave Admin Options"/> + <menu_item_call label="購買 L$…" name="Buy and Sell L$"/> + <menu_item_call label="商家發件匣…" name="MerchantOutbox"/> + <menu_item_call label="帳戶主控臺…" name="Manage My Account"/> + <menu_item_call label="偏好設定…" name="Preferences"/> + <menu_item_call label="工具列按鈕…" name="Toolbars"/> + <menu_item_call label="隱藏所有控制" name="Hide UI"/> + <menu_item_check label="顯示 HUD 附件" name="Show HUD Attachments"/> <menu_item_call label="退出 [APP_NAME]" name="Quit"/> </menu> <menu label="溝通" name="Communicate"> - <menu_item_call label="我的朋友" name="My Friends"/> - <menu_item_call label="我的群組" name="My Groups"/> - <menu_item_check label="附近的聊天" name="Nearby Chat"/> + <menu_item_check label="聊天…" name="Nearby Chat"/> + <menu_item_check label="說話" name="Speak"/> + <menu_item_check label="語音設定…" name="Nearby Voice"/> + <menu_item_check label="語音變聲…" name="ShowVoice"/> + <menu_item_check label="姿勢…" name="Gestures"/> + <menu_item_call label="朋友" name="My Friends"/> + <menu_item_call label="群組" name="My Groups"/> <menu_item_call label="附近的人群" name="Active Speakers"/> + <menu_item_call label="封鎖清單" name="Block List"/> </menu> <menu label="世界" name="World"> - <menu_item_check label="迷你地圖" name="Mini-Map"/> + <menu_item_call label="將此處記為地標" name="Create Landmark Here"/> + <menu_item_call label="目的地…" name="Destinations"/> <menu_item_check label="世界地圖" name="World Map"/> + <menu_item_check label="迷你地圖" name="Mini-Map"/> <menu_item_check label="搜尋" name="Search"/> + <menu_item_call label="瞬間傳送回家" name="Teleport Home"/> + <menu_item_call label="把這裡設為我的家" name="Set Home to Here"/> <menu_item_call label="快照" name="Take Snapshot"/> - <menu_item_call label="將此處記為地標" name="Create Landmark Here"/> - <menu label="地點檔案" name="Land"> - <menu_item_call label="地點檔案" name="Place Profile"/> - <menu_item_call label="土地資料" name="About Land"/> - <menu_item_call label="地區/領地" name="Region/Estate"/> - </menu> + <menu_item_call label="地點小檔案" name="Place Profile"/> + <menu_item_call label="土地資料" name="About Land"/> + <menu_item_call label="地區/領地" name="Region/Estate"/> + <menu_item_call label="我所擁有的土地…" name="My Land"/> <menu_item_call label="購買這塊土地" name="Buy Land"/> - <menu_item_call label="我的土地" name="My Land"/> <menu label="顯示" name="LandShow"> - <menu_item_check label="移動控制" name="Movement Controls"/> - <menu_item_check label="視角控制" name="Camera Controls"/> <menu_item_check label="禁越線" name="Ban Lines"/> <menu_item_check label="指標" name="beacons"/> <menu_item_check label="地產邊界" name="Property Lines"/> @@ -54,15 +63,30 @@ <menu_item_check label="地段屬性" name="Parcel Properties"/> <menu_item_check label="進階選單" name="Show Advanced Menu"/> </menu> - <menu_item_call label="瞬間傳送回家" name="Teleport Home"/> - <menu_item_call label="設定家在此處" name="Set Home to Here"/> - <menu label="太陽" name="Environment Settings"> + <menu label="太陽" name="Sun"> <menu_item_call label="日出" name="Sunrise"/> <menu_item_call label="中午" name="Noon"/> <menu_item_call label="日落" name="Sunset"/> <menu_item_call label="午夜" name="Midnight"/> - <menu_item_call label="領地時間" name="Revert to Region Default"/> - <menu_item_call label="環境編輯器" name="Environment Editor"/> + <menu_item_call label="使用地區設定" name="Use Region Settings"/> + </menu> + <menu label="環境編輯器" name="Environment Editor"> + <menu_item_call label="環境設定…" name="Environment Settings"/> + <menu label="水的自訂配置" name="Water Presets"> + <menu_item_call label="新的自訂配置…" name="new_water_preset"/> + <menu_item_call label="編輯自訂配置…" name="edit_water_preset"/> + <menu_item_call label="刪除自訂配置…" name="delete_water_preset"/> + </menu> + <menu label="天空自訂配置" name="Sky Presets"> + <menu_item_call label="新的自訂配置…" name="new_sky_preset"/> + <menu_item_call label="編輯自訂配置…" name="edit_sky_preset"/> + <menu_item_call label="刪除自訂配置…" name="delete_sky_preset"/> + </menu> + <menu label="日的自訂配置" name="Day Presets"> + <menu_item_call label="新的自訂配置…" name="new_day_preset"/> + <menu_item_call label="編輯自訂配置…" name="edit_day_preset"/> + <menu_item_call label="刪除自訂配置…" name="delete_day_preset"/> + </menu> </menu> </menu> <menu label="建造" name="BuildTools"> @@ -100,6 +124,11 @@ <menu_item_call label="設定腳本為執行中" name="Set Scripts to Running"/> <menu_item_call label="設定腳本為非執行中" name="Set Scripts to Not Running"/> </menu> + <menu label="尋徑" name="Pathfinding"> + <menu_item_call label="聯結集…" name="pathfinding_linksets_menu_item"/> + <menu_item_call label="角色…" name="pathfinding_characters_menu_item"/> + <menu_item_call label="察看 / 測試…" name="pathfinding_console_menu_item"/> + </menu> <menu label="選項" name="Options"> <menu_item_check label="顯示進階權限" name="DebugPermissions"/> <menu_item_check label="只選取我的物件" name="Select Only My Objects"/> @@ -119,7 +148,6 @@ <menu_item_call label="聲音(L$[COST])..." name="Upload Sound"/> <menu_item_call label="動作(L$[COST])..." name="Upload Animation"/> <menu_item_call label="模型…" name="Upload Model"/> - <menu_item_call label="模型精靈…" name="Upload Model Wizard"/> <menu_item_call label="批量(每檔案 L$[COST] )..." name="Bulk Upload"/> <menu_item_call label="設定預設上傳權限" name="perm prefs"/> </menu> @@ -127,8 +155,8 @@ <menu_item_call label="重做" name="Redo"/> </menu> <menu label="幫助" name="Help"> + <menu_item_call label="簡易教學…" name="How To"/> <menu_item_call label="[SECOND_LIFE] 幫助" name="Second Life Help"/> - <menu_item_check label="啟用提示" name="Enable Hints"/> <menu_item_call label="違規舉報" name="Report Abuse"/> <menu_item_call label="回報臭蟲" name="Report Bug"/> <menu_item_call label="關於 [APP_NAME]" name="About Second Life"/> @@ -141,37 +169,35 @@ <menu_item_check label="取消鏡頭限制" name="Disable Camera Distance"/> <menu_item_check label="高解析度快照" name="HighResSnapshot"/> <menu_item_check label="靜音拍攝快照到硬碟" name="QuietSnapshotsToDisk"/> - <menu_item_check label="壓縮快照存到硬碟" name="CompressSnapshotsToDisk"/> <menu label="效能工具" name="Performance Tools"> <menu_item_call label="Lag 測量器" name="Lag Meter"/> <menu_item_check label="統計列" name="Statistics Bar"/> - <menu_item_check label="顯示化身呈像成本" name="Avatar Rendering Cost"/> + <menu_item_check label="顯示化身的繪製重量" name="Avatar Rendering Cost"/> </menu> <menu label="高亮顯示與可見度" name="Highlighting and Visibility"> <menu_item_check label="Cheesy 指標" name="Cheesy Beacon"/> <menu_item_check label="隱藏粒子效果" name="Hide Particles"/> <menu_item_check label="隱藏所選擇的" name="Hide Selected"/> <menu_item_check label="高亮顯示透明物件" name="Highlight Transparent"/> - <menu_item_check label="顯示 HUD 附件" name="Show HUD Attachments"/> <menu_item_check label="顯示第一人稱視角準星" name="ShowCrosshairs"/> </menu> <menu label="呈像類型" name="Rendering Types"> - <menu_item_check label="簡單" name="Simple"/> - <menu_item_check label="半透明" name="Alpha"/> - <menu_item_check label="樹木" name="Tree"/> - <menu_item_check label="化身" name="Character"/> - <menu_item_check label="表面修補" name="Surface Patch"/> - <menu_item_check label="天空" name="Sky"/> - <menu_item_check label="水文" name="Water"/> - <menu_item_check label="地面" name="Ground"/> - <menu_item_check label="體積" name="Volume"/> - <menu_item_check label="草地" name="Grass"/> - <menu_item_check label="雲彩" name="Clouds"/> - <menu_item_check label="粒子效果" name="Particles"/> - <menu_item_check label="碰撞" name="Bump"/> + <menu_item_check label="簡單" name="Rendering Type Simple"/> + <menu_item_check label="半透明" name="Rendering Type Alpha"/> + <menu_item_check label="樹木" name="Rendering Type Tree"/> + <menu_item_check label="化身" name="Rendering Type Character"/> + <menu_item_check label="表面修補" name="Rendering Type Surface Patch"/> + <menu_item_check label="天空" name="Rendering Type Sky"/> + <menu_item_check label="水文" name="Rendering Type Water"/> + <menu_item_check label="地面" name="Rendering Type Ground"/> + <menu_item_check label="體積" name="Rendering Type Volume"/> + <menu_item_check label="草地" name="Rendering Type Grass"/> + <menu_item_check label="雲彩" name="Rendering Type Clouds"/> + <menu_item_check label="粒子效果" name="Rendering Type Particles"/> + <menu_item_check label="碰撞" name="Rendering Type Bump"/> </menu> <menu label="呈像功能" name="Rendering Features"> - <menu_item_check label="使用者界面" name="UI"/> + <menu_item_check label="使用者界面" name="ToggleUI"/> <menu_item_check label="選擇" name="Selected"/> <menu_item_check label="高亮顯示" name="Highlighted"/> <menu_item_check label="動態材質" name="Dynamic Textures"/> @@ -183,11 +209,8 @@ <menu_item_check label="使用外卦讀取緒" name="Use Plugin Read Thread"/> <menu_item_call label="清除群組快取資料" name="ClearGroupCache"/> <menu_item_check label="滑鼠平滑移動" name="Mouse Smoothing"/> + <menu_item_call label="釋出按鍵" name="Release Keys"/> <menu label="快速鍵" name="Shortcuts"> - <menu_item_call label="圖像(L$[COST])..." name="Upload Image"/> - <menu_item_check label="搜尋" name="Search"/> - <menu_item_call label="釋出按鍵" name="Release Keys"/> - <menu_item_call label="以預設值設定使用者界面大小" name="Set UI Size to Default"/> <menu_item_check label="顯示進階選單 - 舊版捷徑" name="Show Advanced Menu - legacy shortcut"/> <menu_item_call label="關閉視窗" name="Close Window"/> <menu_item_call label="關閉全部視窗" name="Close All Windows"/> @@ -196,13 +219,6 @@ <menu_item_check label="搖桿移動攝影機" name="Joystick Flycam"/> <menu_item_call label="重設視角" name="Reset View"/> <menu_item_call label="注視上一位聊天者" name="Look at Last Chatter"/> - <menu label="選擇建造工具" name="Select Tool"> - <menu_item_call label="聚焦工具" name="Focus"/> - <menu_item_call label="移動工具" name="Move"/> - <menu_item_call label="編輯工具" name="Edit"/> - <menu_item_call label="創造工具" name="Create"/> - <menu_item_call label="土地工具" name="Land"/> - </menu> <menu_item_call label="放大" name="Zoom In"/> <menu_item_call label="縮放恢復預設" name="Zoom Default"/> <menu_item_call label="縮小" name="Zoom Out"/> @@ -215,11 +231,10 @@ <menu_item_check label="材質控制台" name="Texture Console"/> <menu_item_check label="除錯控制台" name="Debug Console"/> <menu_item_call label="通知控制台" name="Notifications"/> - <menu_item_check label="材質尺寸控制台" name="Texture Size"/> - <menu_item_check label="材質分類控制台" name="Texture Category"/> <menu_item_check label="快速碼錶" name="Fast Timers"/> <menu_item_check label="記憶體" name="Memory"/> <menu_item_check label="場景統計資料" name="Scene Statistics"/> + <menu_item_call label="材質擷取除錯控制台" name="Texture Fetch Debug Console"/> <menu_item_call label="地區資訊傳至除錯控制台" name="Region Info to Debug Console"/> <menu_item_call label="群組資訊至除錯控制台" name="Group Info to Debug Console"/> <menu_item_call label="性能資訊傳至除錯控制台" name="Capabilities Info to Debug Console"/> @@ -236,6 +251,7 @@ <menu_item_check label="顯示矩陣" name="Show Matrices"/> <menu_item_check label="游標下顯示顏色" name="Show Color Under Cursor"/> <menu_item_check label="顯示記憶體" name="Show Memory"/> + <menu_item_check label="顯示私區記憶體資訊" name="Show Private Mem Info"/> <menu_item_check label="顯示更新到物件" name="Show Updates"/> </menu> <menu label="強制錯誤" name="Force Errors"> @@ -272,7 +288,16 @@ <menu_item_check label="燈光" name="Lights"/> <menu_item_check label="碰撞骨架" name="Collision Skeleton"/> <menu_item_check label="光線投射" name="Raycast"/> + <menu_item_check label="風力向量" name="Wind Vectors"/> + <menu_item_check label="繪出複雜度" name="rendercomplexity"/> + <menu_item_check label="附件位元組" name="attachment bytes"/> <menu_item_check label="雕刻" name="Sculpt"/> + <menu label="材質密度" name="Texture Density"> + <menu_item_check label="無" name="None"/> + <menu_item_check label="目前" name="Current"/> + <menu_item_check label="理想" name="Desired"/> + <menu_item_check label="完全" name="Full"/> + </menu> </menu> <menu label="呈像" name="Rendering"> <menu_item_check label="軸" name="Axes"/> @@ -283,7 +308,6 @@ <menu_item_check label="光線和陰影" name="Lighting and Shadows"/> <menu_item_check label="來自日/月/投影物的陰影" name="Shadows from Sun/Moon/Projectors"/> <menu_item_check label="屏幕空間環境光遮蔽和陰影平滑技術" name="SSAO and Shadow Smoothing"/> - <menu_item_check label="全域照明(實驗性質)" name="Global Illumination"/> <menu_item_check label="GL 除錯" name="Debug GL"/> <menu_item_check label="管線除錯" name="Debug Pipeline"/> <menu_item_check label="自動半透明遮罩(遞延)" name="Automatic Alpha Masks (deferred)"/> @@ -291,7 +315,6 @@ <menu_item_check label="動作材質" name="Animation Textures"/> <menu_item_check label="關閉材質" name="Disable Textures"/> <menu_item_check label="全解析度材質" name="Rull Res Textures"/> - <menu_item_check label="查驗材質" name="Audit Textures"/> <menu_item_check label="材質圖集(實驗性質)" name="Texture Atlas"/> <menu_item_check label="使附著燈光呈像" name="Render Attached Lights"/> <menu_item_check label="使附著例子效果呈像" name="Render Attached Particles"/> @@ -314,9 +337,8 @@ <menu_item_call label="開始錄製" name="Start Record"/> <menu_item_call label="停止錄製" name="Stop Record"/> </menu> - <menu label="世界" name="World"> + <menu label="世界" name="DevelopWorld"> <menu_item_check label="模擬器太陽設定覆蓋" name="Sim Sun Override"/> - <menu_item_check label="Cheesy 指標" name="Cheesy Beacon"/> <menu_item_check label="固定天氣" name="Fixed Weather"/> <menu_item_call label="傾印地區物件快取" name="Dump Region Object Cache"/> </menu> @@ -348,18 +370,17 @@ </menu> <menu label="化身" name="Character"> <menu label="抓取已產出材質" name="Grab Baked Texture"> - <menu_item_call label="虹膜" name="Iris"/> - <menu_item_call label="頭部" name="Head"/> - <menu_item_call label="上半身" name="Upper Body"/> - <menu_item_call label="下半身" name="Lower Body"/> - <menu_item_call label="裙子" name="Skirt"/> + <menu_item_call label="虹膜" name="Grab Iris"/> + <menu_item_call label="頭部" name="Grab Head"/> + <menu_item_call label="上半身" name="Grab Upper Body"/> + <menu_item_call label="下半身" name="Grab Lower Body"/> + <menu_item_call label="裙子" name="Grab Skirt"/> </menu> <menu label="字元測試" name="Character Tests"> <menu_item_call label="將外觀轉成 XML" name="Appearance To XML"/> <menu_item_call label="切換字元幾何特性" name="Toggle Character Geometry"/> <menu_item_call label="男性測試" name="Test Male"/> <menu_item_call label="女性測試" name="Test Female"/> - <menu_item_call label="PG 切換" name="Toggle PG"/> <menu_item_check label="允許選擇化身" name="Allow Select Avatar"/> </menu> <menu_item_call label="強制參數為預設值" name="Force Params to Default"/> @@ -380,15 +401,23 @@ <menu_item_check label="HTTP 材質" name="HTTP Textures"/> <menu_item_check label="HTTP 收納區" name="HTTP Inventory"/> <menu_item_call label="壓縮圖像" name="Compress Images"/> + <menu_item_call label="啟用記憶體洩漏視覺偵測器" name="Enable Visual Leak Detector"/> <menu_item_check label="輸出除錯小型傾印" name="Output Debug Minidump"/> <menu_item_check label="下次執行時顯示控制台視窗" name="Console Window"/> + <menu label="設定記錄細節" name="Set Logging Level"> + <menu_item_check label="除錯" name="Debug"/> + <menu_item_check label="資訊" name="Info"/> + <menu_item_check label="警告" name="Warning"/> + <menu_item_check label="錯誤" name="Error"/> + <menu_item_check label="無" name="None"/> + </menu> <menu_item_call label="要求管理員狀態" name="Request Admin Options"/> <menu_item_call label="離開管理員狀態" name="Leave Admin Options"/> <menu_item_check label="顯示管理員選單" name="View Admin Options"/> </menu> <menu label="管理員" name="Admin"> - <menu label="Object"> - <menu_item_call label="取得副本" name="Take Copy"/> + <menu label="物件" name="AdminObject"> + <menu_item_call label="取得副本" name="Admin Take Copy"/> <menu_item_call label="將所有人強設為我自己" name="Force Owner To Me"/> <menu_item_call label="強設為「准許所有人」" name="Force Owner Permissive"/> <menu_item_call label="刪除" name="Delete"/> @@ -424,7 +453,7 @@ <menu_item_call label="身體物理" name="Physics"/> <menu_item_call label="全部衣服" name="All Clothes"/> </menu> - <menu label="幫助" name="Help"> + <menu label="幫助" name="DeprecatedHelp"> <menu_item_call label="林登官方部落格" name="Official Linden Blog"/> <menu_item_call label="腳本門戶" name="Scripting Portal"/> <menu label="臭蟲回報" name="Bug Reporting"> diff --git a/indra/newview/skins/default/xui/zh/menu_wearing_gear.xml b/indra/newview/skins/default/xui/zh/menu_wearing_gear.xml index d9f4acb27b..6184f956d1 100644 --- a/indra/newview/skins/default/xui/zh/menu_wearing_gear.xml +++ b/indra/newview/skins/default/xui/zh/menu_wearing_gear.xml @@ -2,4 +2,5 @@ <toggleable_menu name="Gear Wearing"> <menu_item_call label="編輯裝扮" name="edit"/> <menu_item_call label="脫下" name="takeoff"/> + <menu_item_call label="複製裝扮清單到剪貼簿" name="copy"/> </toggleable_menu> diff --git a/indra/newview/skins/default/xui/zh/notifications.xml b/indra/newview/skins/default/xui/zh/notifications.xml index 97a1bd6c84..9fecf2c104 100644 --- a/indra/newview/skins/default/xui/zh/notifications.xml +++ b/indra/newview/skins/default/xui/zh/notifications.xml @@ -37,6 +37,12 @@ <button name="Help" text="$helptext"/> </form> </template> + <template name="okhelpignore"> + <form> + <button name="OK_okhelpignore" text="$yestext"/> + <button name="Help_okhelpignore" text="$helptext"/> + </form> + </template> <template name="yesnocancelbuttons"> <form> <button name="Yes" text="$yestext"/> @@ -85,6 +91,40 @@ 除存變更到目前的衣服/身體部位? <usetemplate canceltext="取消" name="yesnocancelbuttons" notext="不要儲存" yestext="儲存"/> </notification> + <notification name="ConfirmNoCopyToOutbox"> + 你無權將至少一件物項複製到第二人生購物市集的發件匣。 你可以移動這些物項,或保留不動。 + <usetemplate name="okcancelbuttons" notext="保留不移動物項" yestext="移動物項"/> + </notification> + <notification name="OutboxFolderCreated"> + 已經為你所轉移到商家發件匣頂層的每一個物項,各自建立了一個新資料夾。 + <usetemplate ignoretext="商家發件匣裡成功建立了一個新資料夾" name="okignore" yestext="確定"/> + </notification> + <notification name="OutboxImportComplete"> + 成功 + +所有資料夾已成功送往第二人生購物市集。 + <usetemplate ignoretext="所有資料夾已送往第二人生購物市集" name="okignore" yestext="確定"/> + </notification> + <notification name="OutboxImportHadErrors"> + 一部分的資料夾無法轉移 + +將一部分資料夾送往第二人生購物市集時出錯。 這些資料夾仍在你的商家發件匣。 + +詳情請參閱[[MARKETPLACE_IMPORTS_URL]錯誤記錄]。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="OutboxImportFailed"> + 轉移失敗 + +未將任何資料夾送往第二人生購物市集,系統或網路出錯。 請稍候再試一次。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="OutboxInitFailed"> + 第二人生購物市集初始化失敗 + +第二人生購物市集初始化失敗,系統或網路出錯。 請稍候再試一次。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> <notification name="CompileQueueSaveText"> 上傳腳本文字時出問題,原因:[REASON]。 請稍候再試一次。 </notification> @@ -326,13 +366,19 @@ 進入 [SECOND_LIFE] 需要一個帳號。 你現在要不要新建一個? <url name="url"> - http://join.secondlife.com/ + [create_account_url] </url> <usetemplate name="okcancelbuttons" notext="再試一次" yestext="創造新帳戶"/> </notification> <notification name="InvalidCredentialFormat"> 你必須在「使用者名稱」欄位裡輸入使用者名稱,或輸入化身的名和姓,然後再登入。 </notification> + <notification name="InvalidGrid"> + '[GRID]' 不是有效的網格辨識元。 + </notification> + <notification name="InvalidLocationSLURL"> + 你的開始位置所指定的網格無效。 + </notification> <notification name="DeleteClassified"> 刪除個人廣告「[NAME]」? 已付費用恕不退回。 @@ -438,7 +484,7 @@ 儲存編譯腳本時出問題,原因:[REASON]。 請稍後再嘗試儲存腳本。 </notification> <notification name="StartRegionEmpty"> - 糟糕,你的起始區域尚未定義。 + 你的起始地區尚未定義。 請在「開始位置」框裡輸入區域名,或選擇「我上一次位置」或「我的家」作為開始位置。 <usetemplate name="okbutton" yestext="確定"/> </notification> @@ -461,6 +507,15 @@ </url> <usetemplate ignoretext="我的電腦硬體並不支援" name="okcancelignore" notext="否" yestext="是"/> </notification> + <notification name="IntelOldDriver"> + 你的顯示卡很可能有新版的驅動程式。 更新顯示驅動程式會大幅改善性能。 + + 前往 [_URL] 察看是否有新版驅動程式? + <url name="url"> + http://www.intel.com/p/en_US/support/detect/graphics + </url> + <usetemplate ignoretext="我的顯示驅動程式太老舊" name="okcancelignore" notext="否" yestext="是"/> + </notification> <notification name="UnknownGPU"> 你的系統含有一個 [APP_NAME] 無法辨認的顯像卡。 原因很可能是 [APP_NAME] 尚未針對新硬體完成測試。 這大概不會出問題,但你可能需要調整顯像設定。 @@ -557,6 +612,9 @@ 請確定沒有物件被鎖住,並確定你擁有所有物件。 </notification> + <notification name="CannotLinkPermanent"> + 物件無法跨越地區界限進行聯結。 + </notification> <notification name="CannotLinkDifferentOwners"> 無法聯結;有些物件的所有人不同。 @@ -636,7 +694,7 @@ 無法建立輸出檔:[FILE] </notification> <notification name="DoNotSupportBulkAnimationUpload"> - [APP_NAME] 目前尚不支援動作檔批量上傳。 + [APP_NAME] 目前尚不支援 BVH 格式的動作檔批量上傳。 </notification> <notification name="CannotUploadReason"> 無法上傳 [FILE],原因:[REASON] @@ -935,6 +993,40 @@ <button name="Cancel" text="取消"/> </form> </notification> + <notification label="新增自動取代清單" name="AddAutoReplaceList"> + 新清單名稱: + <form name="form"> + <button name="SetName" text="確定"/> + </form> + </notification> + <notification label="更改自動取代清單的名稱" name="RenameAutoReplaceList"> + 「[DUPNAME]」名稱已有人使用。 + 輸入另一個獨特的名稱: + <form name="form"> + <button name="ReplaceList" text="取代目前的清單"/> + <button name="SetName" text="使用新名稱"/> + </form> + </notification> + <notification name="InvalidAutoReplaceEntry"> + 關鍵字必須是一個字,取代字不得空白。 + </notification> + <notification name="InvalidAutoReplaceList"> + 該取代清單無效。 + </notification> + <notification name="SpellingDictImportRequired"> + 你必須指定一個檔案、一個名稱和一種語言。 + </notification> + <notification name="SpellingDictIsSecondary"> + [DIC_NAME] 字典似乎沒有 "aff" 檔案,這表示該字典是「次級」字典。 +它可用做附加字典,但不得作為主要字典。 + +參見 https://wiki.secondlife.com/wiki/Adding_Spelling_Dictionaries + </notification> + <notification name="SpellingDictImportFailed"> + 無法複製 [FROM_NAME] + + 到 [TO_NAME] + </notification> <notification label="儲存裝扮" name="SaveOutfitAs"> 儲存我正在穿的為新裝扮: <form name="form"> @@ -966,7 +1058,7 @@ </form> </notification> <notification name="RemoveFromFriends"> - 你確定要從朋友名單中移除 [NAME] 嗎? + 確定要從朋友名單中移除 <nolink>[NAME]</nolink>? <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> </notification> <notification name="RemoveMultipleFromFriends"> @@ -1091,8 +1183,13 @@ <notification name="DisplaySetToSafe"> 因為你指定了 -safe 選項,已將顯示設為安全等級。 </notification> - <notification name="DisplaySetToRecommended"> - 根據你的系統設置,已將顯示設定為推薦採用的等級。 + <notification name="DisplaySetToRecommendedGPUChange"> + 由於你的顯像卡有所變更,已將顯示狀態設為推薦採用的等級。 +原顯像卡:[LAST_GPU] +新顯像卡:[THIS_GPU] + </notification> + <notification name="DisplaySetToRecommendedFeatureChange"> + 由於呈像子系統有所變更,已將顯示狀態設為推薦的設定。 </notification> <notification name="ErrorMessage"> [ERROR_MESSAGE] @@ -1103,7 +1200,7 @@ 你已被移往一個鄰近地區。 </notification> <notification name="AvatarMovedLast"> - 你的上一個地點目前無法前往。 + 你所請求的地點目前無法前往。 你已被移往一個鄰近地區。 </notification> <notification name="AvatarMovedHome"> @@ -1122,8 +1219,7 @@ [APP_NAME] 安裝完成。 如果你是第一次使用 [SECOND_LIFE],你將需要建立新帳號才可登入。 -返回 [http://join.secondlife.com secondlife.com] 建立新帳號? - <usetemplate name="okcancelbuttons" notext="繼續" yestext="新帳戶..."/> + <usetemplate name="okcancelbuttons" notext="繼續" yestext="建立帳號…"/> </notification> <notification name="LoginPacketNeverReceived"> 連線出現問題。 問題可能出在你的網路連線或 [SECOND_LIFE_GRID]。 @@ -1409,7 +1505,7 @@ SHA1 指紋:[MD5_DIGEST] <usetemplate ignoretext="在我退回物件給它們的所有人前確認" name="okcancelignore" notext="取消" yestext="確定"/> </notification> <notification name="GroupLeaveConfirmMember"> - 你目前是 [GROUP] 群組的成員。 + 你目前是 <nolink>[GROUP]</nolink> 群組的成員。 是否要離開群組? <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> </notification> @@ -1544,6 +1640,11 @@ SHA1 指紋:[MD5_DIGEST] <button name="Cancel" text="取消"/> </form> </notification> + <notification name="TooManyTeleportOffers"> + 你試圖送出 [OFFERS] 個瞬間傳送邀請。 +超過了 [LIMIT] 個的上限。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> <notification name="OfferTeleportFromGod"> 用神的權力把居民召到你的位置? <form name="form"> @@ -1633,83 +1734,128 @@ SHA1 指紋:[MD5_DIGEST] <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> </notification> <notification name="RegionEntryAccessBlocked"> - 由於你的內容分級,你不能到該地區。 原因可能是我們沒有足夠資訊驗證你的年齡。 - -請確定你已安裝最新版的 Viewer,並前往知識庫瞭解如何出入這種內容分級的區域。 + 你所欲前往的地區含有超過你目前偏好的分級的內容。 你可以到「我自己 > 偏好設定 > 一般設定」變更你的偏好設定。 <usetemplate name="okbutton" yestext="確定"/> </notification> - <notification name="RegionEntryAccessBlocked_KB"> - 由於你的內容分級,你不能到該地區。 - -前往知識庫進一步瞭解內容分級? + <notification name="RegionEntryAccessBlocked_AdultsOnlyContent"> + 你所欲前往的地區含有 [REGIONMATURITY] 的分級內容,僅限成人。 <url name="url"> http://wiki.secondlife.com/wiki/Linden_Lab_Official:Maturity_ratings:_an_overview </url> - <usetemplate ignoretext="礙於內容分級的限制,我無法進入這個地區" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> + <usetemplate ignoretext="跨越地區:你所欲前往的地區含有限制給成人的內容。" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> </notification> <notification name="RegionEntryAccessBlocked_Notify"> - 由於你的內容分級,你不能到該地區。 + 你所欲前往的地區包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 + </notification> + <notification name="RegionEntryAccessBlocked_NotifyAdultsOnly"> + 你所欲前往的地區含有 [REGIONMATURITY] 的分級內容,僅限成人。 </notification> <notification name="RegionEntryAccessBlocked_Change"> - 根據你所設定的內容分級,你不能到該地區。 - -要進入該地區,請更改你的內容分級偏好。 這將允許你搜尋並接觸 [REGIONMATURITY] 內容。 要還原任何變更,請到「我自己 > 偏好設定 > 一般設定」。 + 你所欲前往的地區包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 你可以變更你的偏好設定,或取消前往。 你的偏好設定變更後,你可以試圖再進入該地區。 + <form name="form"> + <button name="OK" text="變更偏好設定"/> + <button name="Cancel" text="取消"/> + <ignore name="ignore" text="跨越地區:你所欲前往的地區含有被你目前的偏好設定排除的分級內容。"/> + </form> + </notification> + <notification name="RegionEntryAccessBlocked_PreferencesOutOfSync"> + 發生技術問題,你的偏好設定和伺服器上的不一致。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="TeleportEntryAccessBlocked"> + 你所欲前往的地區含有超過你目前偏好的分級的內容。 你可以到「我自己 > 偏好設定 > 一般設定」變更你的偏好設定。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="TeleportEntryAccessBlocked_AdultsOnlyContent"> + 你所欲前往的地區含有 [REGIONMATURITY] 的分級內容,僅限成人。 + <url name="url"> + http://wiki.secondlife.com/wiki/Linden_Lab_Official:Maturity_ratings:_an_overview + </url> + <usetemplate ignoretext="瞬間傳送:你所欲前往的地區含有限制給成人的內容。" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> + </notification> + <notification name="TeleportEntryAccessBlocked_Notify"> + 你所欲前往的地區包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 + </notification> + <notification name="TeleportEntryAccessBlocked_NotifyAdultsOnly"> + 你所欲前往的地區含有 [REGIONMATURITY] 的分級內容,僅限成人。 + </notification> + <notification name="TeleportEntryAccessBlocked_ChangeAndReTeleport"> + 你所欲前往的地區包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 我們可以變更你的偏好設定好讓你繼續瞬間傳送,你也可取消這動作。 + <form name="form"> + <button name="OK" text="變更後繼續"/> + <button name="Cancel" text="取消"/> + <ignore name="ignore" text="瞬間傳送(可重啟):你所欲前往的地區含有被你目前的偏好設定排除的分級內容。"/> + </form> + </notification> + <notification name="TeleportEntryAccessBlocked_Change"> + 你所欲前往的地區包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 我們可以變更你的偏好設定,你也可取消瞬間傳送。 你的偏好設定變更後,你可以再嘗試瞬間傳送。 <form name="form"> <button name="OK" text="變更偏好設定"/> - <button name="Cancel" text="關閉"/> - <ignore name="ignore" text="我所選擇的內容分級不允許我進入一個地區"/> + <button name="Cancel" text="取消"/> + <ignore name="ignore" text="瞬間傳送(不可重啟):你所欲前往的地區含有被你目前的偏好設定排除的分級內容。"/> </form> </notification> + <notification name="TeleportEntryAccessBlocked_PreferencesOutOfSync"> + 發生技術問題,你的偏好設定和伺服器上的不一致。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> <notification name="PreferredMaturityChanged"> - 你的內容分級偏好現在設為 [RATING]。 + 你將不再收到通知,告知你即將進入一個 [RATING] 內容分級的地區。 你可以到選單列底下的「我自己 > 偏好設定 > 一般設定」變更你的內容偏好。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="MaturityChangeError"> + 我們此時無法變更你的偏好設定,讓你觀看 [PREFERRED_MATURITY] 分級的內容。 你的偏好設定已經重設,可觀看 [ACTUAL_MATURITY] 的分級內容。 你可以到選單列的「我自己 > 偏好設定 > 一般設定」再次變更你的偏好。 + <usetemplate name="okbutton" yestext="確定"/> </notification> <notification name="LandClaimAccessBlocked"> - 由於你的內容分級,你無法收取這塊土地。 原因可能是我們沒有足夠資訊驗證你的年齡。 - -請確定你已安裝最新版的 Viewer,並前往知識庫瞭解如何出入這種內容分級的區域。 + 你所欲收取的土地含有超過你目前偏好的分級內容。 你可以到「我自己 > 偏好設定 > 一般設定」變更你的偏好設定。 <usetemplate name="okbutton" yestext="確定"/> </notification> - <notification name="LandClaimAccessBlocked_KB"> - 由於你的內容分級,你無法收取這塊土地。 - -前往知識庫進一步瞭解內容分級? + <notification name="LandClaimAccessBlocked_AdultsOnlyContent"> + 只有成人才能收取這土地。 <url name="url"> http://wiki.secondlife.com/wiki/Linden_Lab_Official:Maturity_ratings:_an_overview </url> - <usetemplate ignoretext="礙於內容分級的限制,我無法收取這塊土地" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> + <usetemplate ignoretext="只有成人才能收取這土地。" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> </notification> <notification name="LandClaimAccessBlocked_Notify"> - 由於你的內容分級,你無法收取這塊土地。 + 你所欲收取的土地包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 + </notification> + <notification name="LandClaimAccessBlocked_NotifyAdultsOnly"> + 你所欲收取的土地包含 [REGIONMATURITY] 的分級內容,僅限成人。 </notification> <notification name="LandClaimAccessBlocked_Change"> - 礙於你所設的內容分級偏好,你無法收取這塊土地。 - -你可以現在點按「更改偏好設定」提升內容分級,即可進入。 從現在起,你可以搜尋並接觸 [REGIONMATURITY] 內容。 往後如果你要還原這項變更,請到「我自己 > 偏好設定 > 一般設定」。 - <usetemplate ignoretext="我所選擇的內容分級不允許我收取土地" name="okcancelignore" notext="關閉" yestext="變更偏好設定"/> + 你所欲收取的土地包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 我們可以變更你的偏好,讓你再試圖收取土地。 + <form name="form"> + <button name="OK" text="變更偏好設定"/> + <button name="Cancel" text="取消"/> + <ignore name="ignore" text="你所欲收取的土地含有被你目前的偏好所排除的分級內容。"/> + </form> </notification> <notification name="LandBuyAccessBlocked"> - 由於你的內容分級,你無法購買這塊土地。 原因可能是我們沒有足夠資訊驗證你的年齡。 - -請確定你已安裝最新版的 Viewer,並前往知識庫瞭解如何出入這種內容分級的區域。 + 你所欲購買的土地的內容分級超過你目前所設偏好。 你可以到「我自己 > 偏好設定 > 一般設定」變更你的偏好設定。 <usetemplate name="okbutton" yestext="確定"/> </notification> - <notification name="LandBuyAccessBlocked_KB"> - 由於你的內容分級,你無法購買這塊土地。 - -前往知識庫進一步瞭解內容分級? + <notification name="LandBuyAccessBlocked_AdultsOnlyContent"> + 只有成人才能購買這土地。 <url name="url"> http://wiki.secondlife.com/wiki/Linden_Lab_Official:Maturity_ratings:_an_overview </url> - <usetemplate ignoretext="礙於內容分級的限制,我無法購買這塊土地" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> + <usetemplate ignoretext="只有成人才能購買這土地。" name="okcancelignore" notext="關閉" yestext="前往知識庫"/> </notification> <notification name="LandBuyAccessBlocked_Notify"> - 由於你的內容分級,你無法購買這塊土地。 + 你所欲購買的土地包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 + </notification> + <notification name="LandBuyAccessBlocked_NotifyAdultsOnly"> + 你所欲購買的土地含有 [REGIONMATURITY] 分級的內容,僅限成人。 </notification> <notification name="LandBuyAccessBlocked_Change"> - 礙於你所設的內容分級,你無法購買這塊土地。 - -你可以現在點按「更改偏好設定」提升內容分級,即可進入。 從現在起,你可以搜尋並接觸 [REGIONMATURITY] 內容。 往後如果你要還原這項變更,請到「我自己 > 偏好設定 > 一般設定」。 - <usetemplate ignoretext="我所選擇的內容分級不允許我購買土地" name="okcancelignore" notext="關閉" yestext="變更偏好設定"/> + 你所欲購買的土地包含 [REGIONMATURITY] 分級的內容,可是你目前的偏好設定排除了 [REGIONMATURITY] 分級的內容。 我們可以變更你的偏好,讓你再試圖購買土地。 + <form name="form"> + <button name="OK" text="變更偏好設定"/> + <button name="Cancel" text="取消"/> + <ignore name="ignore" text="你所欲購買的土地含有被你目前的偏好所排除的分級內容。"/> + </form> </notification> <notification name="TooManyPrimsSelected"> 選擇了太多項的幾何元件。 請至多選擇 [MAX_PRIM_COUNT] 項幾何元件,再試一次。 @@ -1763,10 +1909,9 @@ SHA1 指紋:[MD5_DIGEST] </form> </notification> <notification label="已變更地區的內容分級" name="RegionMaturityChange"> - 地區的內容分級已經更新。 + 此地區的內容分級已經變更。 可能需要稍候一段時間,地圖才會反映這個變更。 - -居民若要進入完全成人分級的地區,帳號必須先通過年齡驗證或付款驗證。 + <usetemplate name="okbutton" yestext="確定"/> </notification> <notification label="聲音版本不相符" name="VoiceVersionMismatch"> 這版本的 [APP_NAME] 和本地區的語音聊天功能不相容。 想要語音聊天正常運作,你必須更新 [APP_NAME]。 @@ -1883,6 +2028,18 @@ SHA1 指紋:[MD5_DIGEST] 你確定你要結束退出? <usetemplate ignoretext="當我結束退出時進行確認" name="okcancelignore" notext="不要結束退出" yestext="結束退出"/> </notification> + <notification name="ConfirmRestoreToybox"> + 這動作將會恢復你預設的按鈕和工具列。 + +你不能取消這動作。 + <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> + </notification> + <notification name="ConfirmClearAllToybox"> + 這動作將把所有按鈕收入工具箱,你的工具列將會清空。 + +你不能取消這動作。 + <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> + </notification> <notification name="DeleteItems"> [QUESTION] <usetemplate ignoretext="刪除物品前確認" name="okcancelignore" notext="取消" yestext="確定"/> @@ -1963,6 +2120,10 @@ SHA1 指紋:[MD5_DIGEST] 你確定要刪除你的旅行、網頁及搜尋歷史紀錄嗎? <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> </notification> + <notification name="ConfirmClearCache"> + 確定要清除你 Viewer 的快取? + <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> + </notification> <notification name="ConfirmClearCookies"> 你確定要清除你的 cookies 嗎? <usetemplate name="okcancelbuttons" notext="取消" yestext="是"/> @@ -1988,48 +2149,30 @@ SHA1 指紋:[MD5_DIGEST] 你要覆寫已儲存的預設配置嗎? <usetemplate name="okcancelbuttons" notext="否" yestext="是"/> </notification> - <notification name="WLDeletePresetAlert"> - 你要刪除 [SKY]? - <usetemplate name="okcancelbuttons" notext="否" yestext="是"/> - </notification> <notification name="WLNoEditDefault"> 你不能編輯或刪除預設的設定。 </notification> <notification name="WLMissingSky"> 這個「一日循環」檔案參考了一個不存在的天空檔案:[SKY]。 </notification> - <notification name="PPSaveEffectAlert"> - PostProcess 效果已經存在。 你是否仍要把它覆寫掉? - <usetemplate name="okcancelbuttons" notext="否" yestext="是"/> + <notification name="WLRegionApplyFail"> + 抱歉,設定無法套用到地區。 離開地區再返回也許可以解決這個問題。 所得的原因為:[FAIL_REASON] </notification> - <notification name="NewSkyPreset"> - 請為新的天空定一個名稱 - <form name="form"> - <input name="message"> - 新預設配配置 - </input> - <button name="OK" text="確定"/> - <button name="Cancel" text="取消"/> - </form> - </notification> - <notification name="ExistsSkyPresetAlert"> - 預設值已經存在! + <notification name="EnvCannotDeleteLastDayCycleKey"> + 無法刪除此日循環的最後一組設定,日循環不得為空白。 你應該修改最後一組資料,不要試圖刪除,然後再建立新的。 + <usetemplate name="okbutton" yestext="確定"/> </notification> - <notification name="NewWaterPreset"> - 請為水的新預設值定一個名稱 - <form name="form"> - <input name="message"> - 新預設配配置 - </input> - <button name="OK" text="確定"/> - <button name="Cancel" text="取消"/> - </form> + <notification name="DayCycleTooManyKeyframes"> + 你無法新增更多的 keyframe 到這個日循環。 [SCOPE] 範圍的日循環最多允許 [MAX] 個 keyframe。 + <usetemplate name="okbutton" yestext="確定"/> </notification> - <notification name="ExistsWaterPresetAlert"> - 預設值已經存在! + <notification name="EnvUpdateRate"> + 你至多只能每 [WAIT] 秒更新一次地區的環境設定。 請等到這段時間過去了再試一次。 + <usetemplate name="okbutton" yestext="確定"/> </notification> - <notification name="WaterNoEditDefault"> - 你不能編輯或刪除預設的設定。 + <notification name="PPSaveEffectAlert"> + PostProcess 效果已經存在。 你是否仍要把它覆寫掉? + <usetemplate name="okcancelbuttons" notext="否" yestext="是"/> </notification> <notification name="ChatterBoxSessionStartError"> 無法開始一個與 [RECIPIENT] 他的新聊天會話。 @@ -2058,13 +2201,11 @@ SHA1 指紋:[MD5_DIGEST] <usetemplate ignoretext="編輯外觀時能穿上我所創造的服裝" name="okcancelignore" notext="否" yestext="是"/> </notification> <notification name="NotAgeVerified"> - 你必須通過年齡驗證才能進入這區域。 你是否要前往 [SECOND_LIFE] 網站進行年齡驗證? - -[_URL] - <url name="url"> - https://secondlife.com/account/verification.php - </url> - <usetemplate ignoretext="我尚未驗證年齡" name="okcancelignore" notext="否" yestext="是"/> + 你所欲前往的地點設限給年滿 18 歲的居民進入。 + <usetemplate ignoretext="我年齡不滿規定,無法進入有年齡限制的區域。" name="okignore" yestext="確定"/> + </notification> + <notification name="NotAgeVerified_Notify"> + 此地點限制為年滿 18 歲。 </notification> <notification name="Cannot enter parcel: no payment info on file"> 你必須提供付款資料才能進入這區域。 你是否要前往 [SECOND_LIFE] 網站設定付款資料? @@ -2100,10 +2241,10 @@ SHA1 指紋:[MD5_DIGEST] 主旨:[SUBJECT],訊息:[MESSAGE] </notification> <notification name="FriendOnline"> - [NAME] 上線 + <nolink>[NAME]</nolink> 目前在線上 </notification> <notification name="FriendOffline"> - [NAME] 離線 + <nolink>[NAME]</nolink> 目前離線 </notification> <notification name="AddSelfFriend"> 雖然你人很好,你還是不能把自己加為朋友。 @@ -2125,7 +2266,7 @@ SHA1 指紋:[MD5_DIGEST] 地形 .raw 檔已下載 </notification> <notification name="GestureMissing"> - 呃… 姿勢 [NAME] 在資料庫中遺失。 + 姿勢 [NAME] 在資料庫中遺失。 </notification> <notification name="UnableToLoadGesture"> 無法載入姿勢 [NAME]。 @@ -2217,14 +2358,15 @@ SHA1 指紋:[MD5_DIGEST] 你的名片已被拒絕。 </notification> <notification name="TeleportToLandmark"> - 若想瞬間傳送到「[NAME]」等等地點,你可以開啟螢幕右方的「地點」側邊欄,再選擇「地標」頁籤。 -用點按方式選擇任何地標,再按側邊欄底下的「瞬間傳送」。 -(你還可以直接按兩下那個地標,或按滑鼠右鍵,選擇「瞬間傳送」。) + 要瞬間傳送到「[NAME]」等地點,請點按「地點」按鈕, + 然後在開啟的視窗裡,選擇「地標」頁籤。 點按任何 + 地標加以選擇,再點按視窗底下的「瞬間傳送」按鈕。 + (你還可以直接按兩下那個地標,或按滑鼠右鍵,選擇「瞬間傳送」。) </notification> <notification name="TeleportToPerson"> - 若想聯絡「[NAME]」等等居民,你可以開啟螢幕右方的「人群」側邊欄。 -從清單選擇一位居民,再按側邊欄底下的「IM」。 -(你還可以從清單直接按兩下名字,或按滑鼠右鍵,選擇「IM」。) + 要聯絡如「[NAME]」的任何一位居民,請點按「人群」按鈕,從打開的視窗中選擇一位居民,再點按視窗底下的「IM」。 + + (你還可以從清單直接按兩下名字,或按滑鼠右鍵,選擇「IM」。) </notification> <notification name="CantSelectLandFromMultipleRegions"> 無法選擇超出伺服器邊界的土地。 @@ -2245,6 +2387,9 @@ SHA1 指紋:[MD5_DIGEST] <notification name="PaymentSent"> [MESSAGE] </notification> + <notification name="PaymentFailure"> + [MESSAGE] + </notification> <notification name="EventNotification"> 活動通知: @@ -2321,6 +2466,26 @@ SHA1 指紋:[MD5_DIGEST] <notification name="NoBuild"> 這區域禁止建造物件。 你不能在此建造或產生物件。 </notification> + <notification name="PathfindingDirty"> + 這地區的尋徑功能有所變更,待儲存。 如果你有建製權,你可以點按「重新產出地區」按鈕重新產出地區。 + </notification> + <notification name="DynamicPathfindingDisabled"> + 這地區並未啟用動態尋徑。 使用尋徑 LSL 呼叫的帶腳本物件,在此地區可能無法正常運作。 + </notification> + <notification name="PathfindingRebakeNavmesh"> + 更改本地區的某些物件將導致其他移動物件的運作發生問題。 要使移動物件正常運作,請點按「重新產出地區」按鈕。 欲獲知詳情請選擇「幫助」。 + <url name="url"> + http://wiki.secondlife.com/wiki/Pathfinding_Tools_in_the_Second_Life_Viewer + </url> + <usetemplate helptext="幫助" ignoretext="更改本地區的某些物件將導致其他移動物件的運作發生問題。" name="okhelpignore" yestext="確定"/> + </notification> + <notification name="PathfindingCannotRebakeNavmesh"> + 發生錯誤。 問題可能出在網路或伺服器,也可能因為你無權建製物件。 有時,只要登出再登入即能解決這類問題。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SeeAvatars"> + 本地段隱藏其內的化身和聊天文字,其他地段看不到。 你看不見地段外的居民,他們也看不見你。 頻道 0 的聊天文字也被封鎖。 + </notification> <notification name="ScriptsStopped"> 某管理員已暫時停止區域裡的腳本。 </notification> @@ -2336,9 +2501,7 @@ SHA1 指紋:[MD5_DIGEST] 你只能在你所處的區域收取公共土地。 </notification> <notification name="RegionTPAccessBlocked"> - 由於你的內容分級,你不能到該地區。 你可能需要通過年齡驗證,且/或安裝最新版的 Viewer。 - -請前往知識庫瞭解如何出入這種內容分級的區域。 + 你所欲前往的地區含有超過你目前偏好的分級的內容。 你可以到「我自己 > 偏好設定 > 一般設定」變更你的偏好設定。 </notification> <notification name="URBannedFromRegion"> 這個區域禁止你進入。 @@ -2349,11 +2512,11 @@ SHA1 指紋:[MD5_DIGEST] <notification name="ImproperPaymentStatus"> 你沒有適當的付款狀態,不能進入這區域。 </notification> - <notification name="MustGetAgeRgion"> - 你必須通過年齡驗證才能進入這地區。 + <notification name="MustGetAgeRegion"> + 你必須年滿 18 歲才可進入這地區。 </notification> <notification name="MustGetAgeParcel"> - 你必須通過年齡驗證以進入這地段。 + 你必須年滿 18 歲才可進入這地段。 </notification> <notification name="NoDestRegion"> 找不到目的地地區。 @@ -2415,11 +2578,19 @@ SHA1 指紋:[MD5_DIGEST] </notification> <notification name="ObjectGiveItem"> 名為 <nolink>[OBJECTFROMNAME]</nolink>、由 [NAME_SLURL] 擁有的物件給了你這個 [OBJECTTYPE]: -[ITEM_SLURL] +<nolink>[ITEM_SLURL]</nolink> + <form name="form"> + <button name="Keep" text="保留"/> + <button name="Discard" text="丟棄"/> + <button name="Mute" text="封鎖所有人"/> + </form> + </notification> + <notification name="OwnObjectGiveItem"> + 你名為 <nolink>[OBJECTFROMNAME]</nolink> 的物件給了你這個 [OBJECTTYPE]: +<nolink>[ITEM_SLURL]</nolink> <form name="form"> <button name="Keep" text="保留"/> <button name="Discard" text="丟棄"/> - <button name="Mute" text="封鎖"/> </form> </notification> <notification name="UserGiveItem"> @@ -2447,12 +2618,33 @@ SHA1 指紋:[MD5_DIGEST] <notification name="TeleportOffered"> [NAME_SLURL] 想要瞬間傳送你到他的地點: -[MESSAGE] - [MATURITY_STR] <icon>[MATURITY_ICON]</icon> +“[MESSAGE]” +<icon>[MATURITY_ICON]</icon> - [MATURITY_STR] <form name="form"> <button name="Teleport" text="瞬間傳送"/> <button name="Cancel" text="取消"/> </form> </notification> + <notification name="TeleportOffered_MaturityExceeded"> + [NAME_SLURL] 想要瞬間傳送你到他的地點: + +“[MESSAGE]” +<icon>[MATURITY_ICON]</icon> - [MATURITY_STR] + +此地區包含 [REGION_CONTENT_MATURITY] 的分級內容,可是你目前的偏好設定排除了 [REGION_CONTENT_MATURITY] 的分級內容。 我們可以變更你的偏好設定好讓你繼續瞬間傳送,你也可取消這動作。 + <form name="form"> + <button name="Teleport" text="變更後繼續"/> + <button name="Cancel" text="取消"/> + </form> + </notification> + <notification name="TeleportOffered_MaturityBlocked"> + [NAME_SLURL] 想要瞬間傳送你到他的地點: + +“[MESSAGE]” +<icon>[MATURITY_ICON]</icon> - [MATURITY_STR] + +可是,此地區含有僅限成人的內容。 + </notification> <notification name="TeleportOfferSent"> 已向 [TO_NAME] 發出瞬間傳送邀請 </notification> @@ -2488,10 +2680,10 @@ SHA1 指紋:[MD5_DIGEST] </form> </notification> <notification name="FriendshipAccepted"> - [NAME] 接受了你的交友邀請。 + <nolink>[NAME]</nolink> 接受了你的交友邀請。 </notification> <notification name="FriendshipDeclined"> - [NAME] 謝絕你的交友邀請。 + <nolink>[NAME]</nolink> 婉拒了你的交友邀請。 </notification> <notification name="FriendshipAcceptedByMe"> 交友邀請被接受。 @@ -2547,30 +2739,28 @@ SHA1 指紋:[MD5_DIGEST] </form> </notification> <notification name="ScriptQuestionCaution"> - 一個名為 '<nolink>[OBJECTNAME]</nolink>'、由 '[NAME]' 擁有的物件想要: - -[QUESTIONS] -如果你不信任這物件或它的創造人,你應該拒絕這個要求。 - -同意這個請求? + 警告:物件 '<nolink>[OBJECTNAME]</nolink>' 要求全權存取你的林登幣帳戶。 你如果允許存取帳戶,它將可在任何時候從你帳戶取走資金,或完全加以清空,或定期取走部分資金,且不會發出警告。 + +這很可能是種不當的要求。 如果你不完全瞭解它為何要求存取你的帳戶,請勿允准。 <form name="form"> - <button name="Grant" text="同意"/> + <button name="Grant" text="允許全權存取"/> <button name="Deny" text="拒絕"/> - <button name="Details" text="細節..."/> </form> </notification> <notification name="ScriptDialog"> [NAME] 的 '<nolink>[TITLE]</nolink>' [MESSAGE] <form name="form"> - <button name="Ignore" text="忽視"/> + <button name="Client_Side_Mute" text="封鎖"/> + <button name="Client_Side_Ignore" text="忽視"/> </form> </notification> <notification name="ScriptDialogGroup"> [GROUPNAME] 的 '<nolink>[TITLE]</nolink>' [MESSAGE] <form name="form"> - <button name="Ignore" text="忽視"/> + <button name="Client_Side_Mute" text="封鎖"/> + <button name="Client_Side_Ignore" text="忽視"/> </form> </notification> <notification name="BuyLindenDollarSuccess"> @@ -2748,7 +2938,15 @@ SHA1 指紋:[MD5_DIGEST] 選取要分享的居民。 </notification> <notification name="MeshUploadError"> - [LABEL] 上傳失敗:[MESSAGE] [IDENTIFIER] [INVALIDITY_IDENTIFIER] + [LABEL] 上傳失敗:[MESSAGE] [IDENTIFIER] + +詳見記錄檔。 + </notification> + <notification name="MeshUploadPermError"> + 請求網面上傳權限時出錯。 + </notification> + <notification name="RegionCapabilityRequestError"> + 無法取得地區能力 '[CAPABILITY]'。 </notification> <notification name="ShareItemsConfirmation"> 請確定你要和居民分享這些物項: @@ -2760,6 +2958,18 @@ SHA1 指紋:[MD5_DIGEST] [RESIDENTS] <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> </notification> + <notification name="ShareFolderConfirmation"> + 一次只能分享一個資料夾。 + +請確定你要和居民分享這些物項: + +<nolink>[ITEMS]</nolink> + +居民: + +[RESIDENTS] + <usetemplate name="okcancelbuttons" notext="取消" yestext="確定"/> + </notification> <notification name="ItemsShared"> 物品已成功分享。 </notification> @@ -2838,6 +3048,10 @@ SHA1 指紋:[MD5_DIGEST] (存續 [EXISTENCE] 秒鐘) 你在 [TIME] 秒鐘後在本地為 '[BODYREGION]' 更新了一個 [RESOLUTION] 的定貌材質。 </notification> + <notification name="LivePreviewUnavailable"> + 我們無法顯示這個材質的預覽,因為它設為「禁止複製」且 / 或「禁止轉移」。 + <usetemplate ignoretext="「禁止複製」和「禁止轉移」的材質若不能使用實時預覽模式,請給我警示。" name="okignore" yestext="確定"/> + </notification> <notification name="ConfirmLeaveCall"> 你確定要離開這段通話? <usetemplate ignoretext="我結束通話前進行確認" name="okcancelignore" notext="否" yestext="是"/> @@ -2891,6 +3105,12 @@ SHA1 指紋:[MD5_DIGEST] <notification label="你得到林登幣!" name="HintLindenDollar"> 這裡顯示你目前的 L$ 餘額。 點按「購買 L$」可添購林登幣。 </notification> + <notification name="LowMemory"> + 你的可用記憶體很小。 第二人生部分功能將停用,以免當機。 請關閉其他應用程式。 這狀況若持續,請重啟第二人生。 + </notification> + <notification name="ForceQuitDueToLowMemory"> + 記憶體不足,第二人生將於 30 秒後關閉離開。 + </notification> <notification name="PopupAttempt"> 一個突顯式視窗開啟時被阻擋。 <form name="form"> @@ -2898,6 +3118,54 @@ SHA1 指紋:[MD5_DIGEST] <button name="open" text="開啟突顯式視窗"/> </form> </notification> + <notification name="SOCKS_NOT_PERMITTED"> + SOCKS 5 代理伺服器 "[HOST]:[PORT]" 拒絕連通,規則集不允許。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_CONNECT_ERROR"> + SOCKS 5 代理伺服器 "[HOST]:[PORT]" 拒絕連通,無法打開 TCP 頻道。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_NOT_ACCEPTABLE"> + SOCKS 5 代理伺服器 "[HOST]:[PORT]" 拒絕所選的鑒認方法。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_AUTH_FAIL"> + SOCKS 5 代理伺服器 "[HOST]:[PORT]" 回報:你的鑒認資料無效。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_UDP_FWD_NOT_GRANTED"> + SOCKS 5 代理伺服器 "[HOST]:[PORT]" 拒絕 UDP 聯結請求。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_HOST_CONNECT_FAILED"> + 無法連通 SOCKS 5 代理伺服器 "[HOST]:[PORT]"。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_UNKNOWN_STATUS"> + 伺服器 "[HOST]:[PORT]" 發生不明的代理伺服器錯誤。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_INVALID_HOST"> + 無效的 SOCKS 代理伺服器位址或埠號 "[HOST]:[PORT]"。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="SOCKS_BAD_CREDS"> + 無效的 SOCKS 5 使用者名稱或密碼。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="PROXY_INVALID_HTTP_HOST"> + 無效的 HTTP 代理伺服器位址或埠號 "[HOST]:[PORT]"。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="PROXY_INVALID_SOCKS_HOST"> + 無效的 SOCKS 代理伺服器位址或埠號 "[HOST]:[PORT]"。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> + <notification name="ChangeProxySettings"> + 重新啟動 [APP_NAME] 後將採用新的代理伺服器設定。 + <usetemplate name="okbutton" yestext="確定"/> + </notification> <notification name="AuthRequest"> '[REALM]' 領域的 '<nolink>[HOST_NAME]</nolink>' 站點需要使用者名稱和密碼。 <form name="form"> @@ -2907,10 +3175,6 @@ SHA1 指紋:[MD5_DIGEST] <button name="cancel" text="取消"/> </form> </notification> - <notification label="" name="ModeChange"> - 改變劉覽器模式要求你必須結束退出並重新啟動。 - <usetemplate name="okcancelbuttons" notext="不要結束退出" yestext="結束退出"/> - </notification> <notification label="" name="NoClassifieds"> 只有進階模式才能新建或編輯個人廣告。 你是否想要結束離開,以便變更模式? 你可在登入畫面選擇想要的模式。 <usetemplate name="okcancelbuttons" notext="不要結束退出" yestext="結束退出"/> @@ -2955,6 +3219,66 @@ SHA1 指紋:[MD5_DIGEST] 只有進階模式才能搜尋。 你是否要登出並且變更模式? <usetemplate name="okcancelbuttons" notext="不要結束退出" yestext="結束退出"/> </notification> + <notification label="" name="ConfirmHideUI"> + 這將會隱藏所有選單內容和按鈕。 要恢復原狀,再點按 [SHORTCUT] 一次。 + <usetemplate ignoretext="隱藏使用者介面前先確認" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom"> + 所選的一些聯結集的幻影旗標將被切換。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選的一些聯結集的幻影旗標將被切換。" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted"> + 所選某些聯結集因權限問題,無法設定為 '[REQUESTED_TYPE]'。 這些聯結集將被設為 '[RESTRICTED_TYPE]'。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選某些聯結集因權限問題,無法設定。" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnVolume"> + 所選某些聯結集無法設為 '[REQUESTED_TYPE]',因為形狀屬於非凸面。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選某些聯結集因為形狀屬於非凸面,無法設定" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted"> + 所選的一些聯結集的幻影旗標將被切換。 + +所選某些聯結集因權限問題,無法設定為 '[REQUESTED_TYPE]'。 這些聯結集將被設為 '[RESTRICTED_TYPE]'。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選的一些聯結集的幻影旗標可成功切換,其他的則因權限問題而無法設定。" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnVolume"> + 所選的一些聯結集的幻影旗標將被切換。 + +所選某些聯結集無法設為 '[REQUESTED_TYPE]',因為形狀屬於非凸面。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選的一些聯結集的幻影旗標可成功切換,其他的則因形狀屬於非凸面而無法設定" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume"> + 所選某些聯結集因權限問題,無法設定為 '[REQUESTED_TYPE]'。 這些聯結集將被設為 '[RESTRICTED_TYPE]'。 + +所選某些聯結集無法設為 '[REQUESTED_TYPE]',因為形狀屬於非凸面。 這些聯結集的使用類型將維持不變。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選某些聯結集因權限不足,且形狀屬於非凸面,因此無法設定。" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume"> + 所選的一些聯結集的幻影旗標將被切換。 + +所選某些聯結集因權限問題,無法設定為 '[REQUESTED_TYPE]'。 這些聯結集將被設為 '[RESTRICTED_TYPE]'。 + +所選某些聯結集無法設為 '[REQUESTED_TYPE]',因為形狀屬於非凸面。 這些聯結集的使用類型將維持不變。 + +你確定要繼續嗎? + <usetemplate ignoretext="所選的一些聯結集的幻影旗標將被切換,其他則因權限不足且形狀屬於非凸面,因此無法設定。" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> + <notification name="PathfindingLinksets_ChangeToFlexiblePath"> + 所選的物件會影響導航網面。 將它改為彈性路徑,將使它從導航網面中被移除。 + <usetemplate ignoretext="所選的物件會影響導航網面。 將它改為彈性路徑,將使它從導航網面中被移除。" name="okcancelignore" notext="取消" yestext="確定"/> + </notification> <global name="UnsupportedGLRequirements"> 你的硬體設備似乎不符 [APP_NAME] 的要求。 [APP_NAME] 需要可以支援多材質的 OpenGL 顯像卡。 在這狀況下,請確定你的顯像卡安裝了最新的驅動程式,作業系統也安裝了最新的服務包和嵌補程式。 @@ -2979,4 +3303,24 @@ SHA1 指紋:[MD5_DIGEST] <global name="You died and have been teleported to your home location"> 你已經死亡並且被瞬間傳送回你的家的位置。 </global> + <notification name="LocalBitmapsUpdateFileNotFound"> + [FNAME] 無法更新,找不到該檔案。 +未來將不再更新該檔案。 + </notification> + <notification name="LocalBitmapsUpdateFailedFinal"> + [FNAME] 無法開啟或解碼,已嘗試 [NRETRIES] 次,該檔案已被認定為毀壞。 +未來將不再更新該檔案。 + </notification> + <notification name="LocalBitmapsVerifyFail"> + 試圖新增一個無效或無法讀取的圖像檔 [FNAME],該檔無法開啟或解碼。 +已取消這一嘗試。 + </notification> + <notification name="PathfindingReturnMultipleItems"> + 你正退回 [NUM_ITEMS] 個物項。 你確定你要繼續? + <usetemplate ignoretext="確定退回多個物項?" name="okcancelignore" notext="否" yestext="是"/> + </notification> + <notification name="PathfindingDeleteMultipleItems"> + 你正在刪除 [NUM_ITEMS] 個物項。 你確定你要繼續? + <usetemplate ignoretext="確定要刪除多個物項?" name="okcancelignore" notext="否" yestext="是"/> + </notification> </notifications> diff --git a/indra/newview/skins/default/xui/zh/panel_chiclet_bar.xml b/indra/newview/skins/default/xui/zh/panel_chiclet_bar.xml new file mode 100644 index 0000000000..69340349bc --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_chiclet_bar.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="chiclet_bar"> + <layout_stack name="toolbar_stack"> + <layout_panel name="im_well_panel"> + <chiclet_im_well name="im_well"> + <button name="Unread IM messages" tool_tip="交談"/> + </chiclet_im_well> + </layout_panel> + <layout_panel name="notification_well_panel"> + <chiclet_notification name="notification_well"> + <button name="Unread" tool_tip="通知"/> + </chiclet_notification> + </layout_panel> + </layout_stack> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_edit_pick.xml b/indra/newview/skins/default/xui/zh/panel_edit_pick.xml index 006c050dc1..faee42fd0e 100644 --- a/indra/newview/skins/default/xui/zh/panel_edit_pick.xml +++ b/indra/newview/skins/default/xui/zh/panel_edit_pick.xml @@ -29,7 +29,7 @@ <layout_panel name="layout_panel1"> <button label="儲存精選地點" name="save_changes_btn"/> </layout_panel> - <layout_panel name="layout_panel1"> + <layout_panel name="layout_panel2"> <button label="取消" name="cancel_btn"/> </layout_panel> </layout_stack> diff --git a/indra/newview/skins/default/xui/zh/panel_edit_skin.xml b/indra/newview/skins/default/xui/zh/panel_edit_skin.xml index 9f4c02427d..d8552f52f0 100644 --- a/indra/newview/skins/default/xui/zh/panel_edit_skin.xml +++ b/indra/newview/skins/default/xui/zh/panel_edit_skin.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="edit_skin_panel"> <panel name="avatar_skin_color_panel"> - <texture_picker label="頭部刺青" name="Head Tattoos" tool_tip="點按以挑選圖片"/> - <texture_picker label="上半身刺青" name="Upper Tattoos" tool_tip="點按以挑選圖片"/> - <texture_picker label="下半身刺青" name="Lower Tattoos" tool_tip="點按以挑選圖片"/> + <texture_picker label="頭部" name="Head" tool_tip="點按以挑選圖片"/> + <texture_picker label="上半身" name="Upper Body" tool_tip="點按以挑選圖片"/> + <texture_picker label="下半身" name="Lower Body" tool_tip="點按以挑選圖片"/> </panel> <panel name="accordion_panel"> <accordion name="wearable_accordion"> diff --git a/indra/newview/skins/default/xui/zh/panel_group_invite.xml b/indra/newview/skins/default/xui/zh/panel_group_invite.xml index 728311cf51..8921978b20 100644 --- a/indra/newview/skins/default/xui/zh/panel_group_invite.xml +++ b/indra/newview/skins/default/xui/zh/panel_group_invite.xml @@ -9,6 +9,9 @@ <panel.string name="already_in_group"> 你所選的居民有些已經在群組裡,所以不會再對他們發送邀請。 </panel.string> + <panel.string name="invite_selection_too_large"> + 未送出群組邀請:所選的居民人數太多。 群組邀請每次以 100 人為上限。 + </panel.string> <text name="help_text"> 你可以選擇邀請多位居民到你的群組來。 點按「開啟居民選擇工具」開始選擇。 </text> diff --git a/indra/newview/skins/default/xui/zh/panel_login.xml b/indra/newview/skins/default/xui/zh/panel_login.xml index 49c57a4315..672d9bb1a2 100644 --- a/indra/newview/skins/default/xui/zh/panel_login.xml +++ b/indra/newview/skins/default/xui/zh/panel_login.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_login"> - <panel.string name="create_account_url"> - http://join.secondlife.com/ - </panel.string> <panel.string name="forgot_password_url"> http://secondlife.com/account/request.php </panel.string> <layout_stack name="login_widgets"> <layout_panel name="login"> + <text name="log_in_text"> + 登入 + </text> <text name="username_text"> 使用者名稱: </text> @@ -15,15 +15,8 @@ <text name="password_text"> 密碼: </text> - <check_box label="記住密碼:" name="remember_check"/> - <button label="登入" name="connect_btn"/> - <text name="mode_selection_text"> - 模式: - </text> - <combo_box name="mode_combo" tool_tip="請選擇你的模式。 選用基本模式可以快速、簡單地探索與聊天; 選用進階模式則可以使用更多功能。"> - <combo_box.item label="基本" name="Basic"/> - <combo_box.item label="進階" name="Advanced"/> - </combo_box> + </layout_panel> + <layout_panel name="start_location_panel"> <text name="start_location_text"> 開始地點: </text> @@ -33,16 +26,21 @@ <combo_box.item label="<請輸入地區名稱>" name="Typeregionname"/> </combo_box> </layout_panel> - <layout_panel name="links"> - <text name="create_new_account_text"> - 註冊 + <layout_panel name="links_login_panel"> + <text name="login_help"> + 登入時需要幫助? </text> <text name="forgot_password_text"> 忘記你的使用者名稱或密碼? </text> - <text name="login_help"> - 登入時需要幫助? + <button label="登入" name="connect_btn"/> + <check_box label="記住密碼:" name="remember_check"/> + </layout_panel> + <layout_panel name="links"> + <text name="create_account_text"> + 建立你的帳號 </text> + <button label="現在就開始" name="create_new_account_btn"/> </layout_panel> </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_me.xml b/indra/newview/skins/default/xui/zh/panel_me.xml index 3c452b8fa8..aad1348e46 100644 --- a/indra/newview/skins/default/xui/zh/panel_me.xml +++ b/indra/newview/skins/default/xui/zh/panel_me.xml @@ -1,7 +1,4 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel label="我的個人檔案" name="panel_me"> - <tab_container name="tabs"> - <panel label="我的檔案" name="panel_profile"/> - <panel label="我的精選地點" name="panel_picks"/> - </tab_container> + <panel label="我的精選地點" name="panel_picks"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_navigation_bar.xml b/indra/newview/skins/default/xui/zh/panel_navigation_bar.xml index 2f98dd718e..7a11aa961b 100644 --- a/indra/newview/skins/default/xui/zh/panel_navigation_bar.xml +++ b/indra/newview/skins/default/xui/zh/panel_navigation_bar.xml @@ -1,18 +1,23 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="navigation_bar"> - <panel name="navigation_panel"> - <pull_button name="back_btn" tool_tip="前往上一個位置"/> - <pull_button name="forward_btn" tool_tip="前往下一個位置"/> - <button name="home_btn" tool_tip="瞬間返回我的家"/> - <location_input label="位置" name="location_combo"/> - <search_combo_box label="搜尋" name="search_combo_box" tool_tip="搜尋"> - <combo_editor label="搜尋 [SECOND_LIFE]" name="search_combo_editor"/> - </search_combo_box> - </panel> - <favorites_bar name="favorite" tool_tip="拖曳傳送地標到此以便讓你在第二人生中能快速傳送到你最愛的地點!!"> - <label name="favorites_bar_label" tool_tip="拖曳傳送地標到此以便讓你在第二人生中能快速傳送到你最愛的地點!!"> - 最愛列 - </label> - <chevron_button name=">>" tool_tip="顯示更多我的最愛"/> - </favorites_bar> + <layout_stack name="nvp_stack"> + <layout_panel name="navigation_layout_panel"> + <panel name="navigation_panel"> + <pull_button name="back_btn" tool_tip="前往上一個位置"/> + <pull_button name="forward_btn" tool_tip="前往下一個位置"/> + <button name="home_btn" tool_tip="瞬間返回我的家"/> + <location_input label="位置" name="location_combo"/> + </panel> + </layout_panel> + <layout_panel name="favorites_layout_panel"> + <favorites_bar name="favorite" tool_tip="拖曳傳送地標到此以便讓你在第二人生中能快速傳送到你最愛的地點!!"> + <label name="favorites_bar_label" tool_tip="拖曳傳送地標到此以便讓你在第二人生中能快速傳送到你最愛的地點!!"> + 最愛列 + </label> + <more_button name=">>" tool_tip="顯示更多我的最愛"> + 詳情 ▼ + </more_button> + </favorites_bar> + </layout_panel> + </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_navmesh_rebake.xml b/indra/newview/skins/default/xui/zh/panel_navmesh_rebake.xml new file mode 100644 index 0000000000..bb52c13a11 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_navmesh_rebake.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_navmesh_rebake"> + <button label="重新產出地區" name="navmesh_btn" tool_tip="點按即可重新產出該地區的導航網面。"/> + <button label="正在請求重新產出" name="navmesh_btn_sending" tool_tip="正向伺服器發送重新產出請求。"/> + <button label="地區正在重新產出" name="navmesh_btn_baking" tool_tip="地區正在重新產出。 完成後,這個按鈕將會消失。"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_nearby_chat.xml b/indra/newview/skins/default/xui/zh/panel_nearby_chat.xml new file mode 100644 index 0000000000..fc52168bb7 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_nearby_chat.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="nearby_chat"> + <layout_stack name="stack"> + <layout_panel name="translate_chat_checkbox_lp"> + <check_box label="翻譯聊天內容" name="translate_chat_checkbox"/> + </layout_panel> + </layout_stack> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_outbox_inventory.xml b/indra/newview/skins/default/xui/zh/panel_outbox_inventory.xml new file mode 100644 index 0000000000..8de0bb0e4d --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_outbox_inventory.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<outbox_inventory_panel name="inventory_outbox" tool_tip="將物項拖曳並置放到這裡,準備在你的商店出售"/> diff --git a/indra/newview/skins/default/xui/zh/panel_outfits_list.xml b/indra/newview/skins/default/xui/zh/panel_outfits_list.xml index a4b041469b..cfb0180f9c 100644 --- a/indra/newview/skins/default/xui/zh/panel_outfits_list.xml +++ b/indra/newview/skins/default/xui/zh/panel_outfits_list.xml @@ -1,5 +1,9 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="Outfits"> + <accordion name="outfits_accordion"> + <no_matched_tabs_text name="no_matched_outfits_msg" value="找不到你要找的嗎? 請試試 [secondlife:///app/search/places/ 搜尋]。"/> + <no_visible_tabs_text name="no_outfits_msg" value="你還沒有任何裝扮。 請試試[secondlife:///app/search/all/ 搜尋]"/> + </accordion> <panel name="bottom_panel"> <menu_button name="options_gear_btn" tool_tip="顯示額外選項"/> <button name="trash_btn" tool_tip="刪除所選擇的裝扮"/> diff --git a/indra/newview/skins/default/xui/zh/panel_people.xml b/indra/newview/skins/default/xui/zh/panel_people.xml index dbb439afec..59ea7b70e2 100644 --- a/indra/newview/skins/default/xui/zh/panel_people.xml +++ b/indra/newview/skins/default/xui/zh/panel_people.xml @@ -66,16 +66,16 @@ <layout_panel name="view_profile_btn_lp"> <button label="檔案" name="view_profile_btn" tool_tip="顯示圖片、群組與其他居民資訊"/> </layout_panel> - <layout_panel name="chat_btn_lp"> + <layout_panel name="im_btn_lp"> <button label="IM" name="im_btn" tool_tip="開啟即時訊息會話"/> </layout_panel> - <layout_panel name="chat_btn_lp"> + <layout_panel name="call_btn_lp"> <button label="通話" name="call_btn" tool_tip="和這位居民通話"/> </layout_panel> - <layout_panel name="chat_btn_lp"> + <layout_panel name="share_btn_lp"> <button label="分享" name="share_btn" tool_tip="分享一個收納區物品"/> </layout_panel> - <layout_panel name="chat_btn_lp"> + <layout_panel name="teleport_btn_lp"> <button label="瞬間傳送" name="teleport_btn" tool_tip="發出瞬間傳送邀請"/> </layout_panel> </layout_stack> diff --git a/indra/newview/skins/default/xui/zh/panel_place_profile.xml b/indra/newview/skins/default/xui/zh/panel_place_profile.xml index a32c1c17d3..a364f732d8 100644 --- a/indra/newview/skins/default/xui/zh/panel_place_profile.xml +++ b/indra/newview/skins/default/xui/zh/panel_place_profile.xml @@ -68,6 +68,8 @@ <text name="scripts_value" value="啟動"/> <text name="damage_label" value="傷害:"/> <text name="damage_value" value="關閉"/> + <text name="see_avatars_label" value="察看化身:"/> + <text name="see_avatars_value" value="關閉"/> <button label="土地資料" name="about_land_btn"/> </panel> </accordion_tab> diff --git a/indra/newview/skins/default/xui/zh/panel_places.xml b/indra/newview/skins/default/xui/zh/panel_places.xml index ac69b48a81..08cae610f6 100644 --- a/indra/newview/skins/default/xui/zh/panel_places.xml +++ b/indra/newview/skins/default/xui/zh/panel_places.xml @@ -24,7 +24,7 @@ <menu_button name="overflow_btn" tool_tip="顯示額外選項"/> </layout_panel> </layout_stack> - <layout_stack name="bottom_bar_ls3"> + <layout_stack name="bottom_bar_profile_ls"> <layout_panel name="profile_btn_lp"> <button label="檔案" name="profile_btn" tool_tip="顯示地點檔案"/> </layout_panel> diff --git a/indra/newview/skins/default/xui/zh/panel_postcard_message.xml b/indra/newview/skins/default/xui/zh/panel_postcard_message.xml new file mode 100644 index 0000000000..563c4fca3c --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_postcard_message.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_postcard_message"> + <text name="to_label"> + 收件人: + </text> + <text name="name_label"> + 發件人: + </text> + <text name="subject_label"> + 主旨: + </text> + <line_editor label="在此輸入你的主旨。" name="subject_form"/> + <text name="msg_label"> + 訊息: + </text> + <text_editor name="msg_form"> + 在此輸入你的訊息。 + </text_editor> + <button label="取消" name="cancel_btn"/> + <button label="送出" name="send_btn"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_postcard_settings.xml b/indra/newview/skins/default/xui/zh/panel_postcard_settings.xml new file mode 100644 index 0000000000..900ab3a54e --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_postcard_settings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_postcard_settings"> + <combo_box label="解析度" name="postcard_size_combo"> + <combo_box.item label="目前視窗" name="CurrentWindow"/> + <combo_box.item label="640x480" name="640x480"/> + <combo_box.item label="800x600" name="800x600"/> + <combo_box.item label="1024x768" name="1024x768"/> + <combo_box.item label="自訂" name="Custom"/> + </combo_box> + <layout_stack name="postcard_image_params_ls"> + <layout_panel name="postcard_image_size_lp"> + <spinner label="寬" name="postcard_snapshot_width"/> + <spinner label="高度" name="postcard_snapshot_height"/> + <check_box label="鎖住比例" name="postcard_keep_aspect_check"/> + </layout_panel> + <layout_panel name="postcard_image_format_quality_lp"> + <slider label="圖像品質" name="image_quality_slider"/> + <text name="image_quality_level"> + ([QLVL]) + </text> + </layout_panel> + </layout_stack> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/zh/panel_preferences_advanced.xml index c5dce10d63..3a7d79e04b 100644 --- a/indra/newview/skins/default/xui/zh/panel_preferences_advanced.xml +++ b/indra/newview/skins/default/xui/zh/panel_preferences_advanced.xml @@ -3,6 +3,19 @@ <panel.string name="aspect_ratio_text"> [NUM]:[DEN] </panel.string> + <text name="Cache:"> + 快取: + </text> + <spinner label="快取大小 (64 - 9984MB)" name="cachesizespinner"/> + <text name="text_box5"> + MB + </text> + <button label="清除快取" label_selected="清除快取" name="clear_cache"/> + <text name="Cache location"> + 快取位置: + </text> + <button label="瀏覽" label_selected="瀏覽" name="set_cache"/> + <button label="預設位置" label_selected="預設位置" name="default_cache_location"/> <text name="UI Size:"> 使用者界面尺寸: </text> diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_chat.xml b/indra/newview/skins/default/xui/zh/panel_preferences_chat.xml index 82d2c64aaa..cf2f81d313 100644 --- a/indra/newview/skins/default/xui/zh/panel_preferences_chat.xml +++ b/indra/newview/skins/default/xui/zh/panel_preferences_chat.xml @@ -29,29 +29,7 @@ <check_box label="IM 聊天" name="EnableIMChatPopups" tool_tip="當即時訊息抵達時查看突顯式視窗"/> <spinner label="附近聊天內容提示框停駐時間:" name="nearby_toasts_lifetime"/> <spinner label="附近聊天內容提示框消退時間:" name="nearby_toasts_fadingtime"/> - <text name="translate_chb_label"> - 聊天時使用機器自動進行翻譯(由 Google 所提供) - </text> - <text name="translate_language_text"> - 將聊天內容翻譯成: - </text> - <combo_box name="translate_language_combobox"> - <combo_box.item label="系統預設" name="System Default Language"/> - <combo_box.item label="英語" name="English"/> - <combo_box.item label="Dansk(丹麥語)" name="Danish"/> - <combo_box.item label="Deutsch(德語)" name="German"/> - <combo_box.item label="Español(西班牙語)" name="Spanish"/> - <combo_box.item label="Français(法語)" name="French"/> - <combo_box.item label="Italiano(義大利語)" name="Italian"/> - <combo_box.item label="Magyar(匈牙利語)" name="Hungarian"/> - <combo_box.item label="Nederlands(荷蘭語)" name="Dutch"/> - <combo_box.item label="Polski(波蘭語)" name="Polish"/> - <combo_box.item label="Português(葡萄牙語)" name="Portugese"/> - <combo_box.item label="Русский(俄羅斯語)" name="Russian"/> - <combo_box.item label="Türkçe(土耳其語)" name="Turkish"/> - <combo_box.item label="Українська(烏克蘭語)" name="Ukrainian"/> - <combo_box.item label="中文(简体)(簡體中文)" name="Chinese"/> - <combo_box.item label="日本語(日語)" name="Japanese"/> - <combo_box.item label="한국어(漢語)" name="Korean"/> - </combo_box> + <button label="翻譯…" name="ok_btn"/> + <button label="自動取代…" name="autoreplace_showgui"/> + <button label="拼字檢查…" name="spellcheck_showgui"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_general.xml b/indra/newview/skins/default/xui/zh/panel_preferences_general.xml index 854b1cf828..7e67a0d02d 100644 --- a/indra/newview/skins/default/xui/zh/panel_preferences_general.xml +++ b/indra/newview/skins/default/xui/zh/panel_preferences_general.xml @@ -11,10 +11,12 @@ <combo_box.item label="Español(西班牙語)- 試用版" name="Spanish"/> <combo_box.item label="Français(法語)- 試用版" name="French"/> <combo_box.item label="Italiano(義大利語)- 試用版" name="Italian"/> - <combo_box.item label="Nederlands(荷蘭語)- 試用版" name="Dutch"/> <combo_box.item label="Polski(波蘭語)- 試用版" name="Polish"/> <combo_box.item label="Português(葡萄牙語)- 試用版" name="Portugese"/> + <combo_box.item label="Русский(俄羅斯語)- 測試版" name="Russian"/> + <combo_box.item label="Türkçe(土耳其語)- 試用版" name="Turkish"/> <combo_box.item label="日本語(日語)- 試用版" name="(Japanese)"/> + <combo_box.item label="正體中文 - 測試版" name="Traditional Chinese"/> </combo_box> <text name="language_textbox2"> (須重新啟動) diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_move.xml b/indra/newview/skins/default/xui/zh/panel_preferences_move.xml index ce176b1e3c..3a27477885 100644 --- a/indra/newview/skins/default/xui/zh/panel_preferences_move.xml +++ b/indra/newview/skins/default/xui/zh/panel_preferences_move.xml @@ -7,18 +7,33 @@ </text> <check_box label="建造 / 編輯" name="edit_camera_movement" tool_tip="使用進入或離開編輯模式時自動調整攝影機位置功能"/> <check_box label="編輯外觀" name="appearance_camera_movement" tool_tip="使用編輯模式時自動調整攝影機位置功能"/> - <check_box initial_value="true" label="側邊欄" name="appearance_sidebar_positioning" tool_tip="使用開啟側邊欄時自動調整攝影機位置功能"/> + <text name="keyboard_lbl"> + 鍵盤: + </text> + <check_box label="總是使用方向鍵移動" name="arrow_keys_move_avatar_check"/> + <check_box label="連點按住後跑步" name="tap_tap_hold_to_run"/> + <text name="mouse_lbl"> + 滑鼠: + </text> <check_box label="將我顯示於第一人稱視角中" name="first_person_avatar_visible"/> <text name=" Mouse Sensitivity"> 第一人稱視角滑鼠敏感度: </text> <check_box label="反轉" name="invert_mouse"/> - <check_box label="總是使用方向鍵移動" name="arrow_keys_move_avatar_check"/> - <check_box label="連點按住後跑步" name="tap_tap_hold_to_run"/> - <check_box label="雙擊以:" name="double_click_chkbox"/> - <radio_group name="double_click_action"> - <radio_item label="瞬間傳送" name="radio_teleport"/> - <radio_item label="自動導航駕駛" name="radio_autopilot"/> - </radio_group> + <text name="single_click_action_lbl"> + 在土地上點按一下: + </text> + <combo_box name="single_click_action_combo"> + <combo_box.item label="無動作" name="0"/> + <combo_box.item label="移動至點按的地點" name="1"/> + </combo_box> + <text name="double_click_action_lbl"> + 在土地上點按兩下: + </text> + <combo_box name="double_click_action_combo"> + <combo_box.item label="無動作" name="0"/> + <combo_box.item label="移動至點按的地點" name="1"/> + <combo_box.item label="瞬間傳送至點按的地點" name="2"/> + </combo_box> <button label="其他設備" name="joystick_setup_button"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml b/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml index 85f8154b99..a607a7c33b 100644 --- a/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml +++ b/indra/newview/skins/default/xui/zh/panel_preferences_setup.xml @@ -11,17 +11,6 @@ </text> <check_box label="自訂埠" name="connection_port_enabled"/> <spinner label="埠號:" name="connection_port"/> - <text name="cache_size_label_l"> - 快取尺寸 - </text> - <text name="text_box5"> - MB - </text> - <text name="Cache location"> - 快取位置: - </text> - <button label="瀏覽" label_selected="瀏覽" name="set_cache"/> - <button label="重設" label_selected="重設" name="reset_cache"/> <text name="Web:"> 網頁: </text> @@ -33,12 +22,6 @@ <check_box initial_value="true" label="接受 cookies" name="cookies_enabled"/> <check_box initial_value="true" label="啟用 Javascript" name="browser_javascript_enabled"/> <check_box initial_value="false" label="啟用媒體瀏覽的突顯式視窗" name="media_popup_enabled"/> - <check_box initial_value="false" label="啟用網頁代理伺服器" name="web_proxy_enabled"/> - <text name="Proxy location"> - 代理伺服器位置: - </text> - <line_editor name="web_proxy_editor" tool_tip="你希望使用的代理伺服器的名稱或 IP 位址"/> - <spinner label="埠號:" name="web_proxy_port"/> <text name="Software updates:"> 軟體更新: </text> @@ -46,4 +29,8 @@ <combo_box.item label="自動安裝" name="Install_automatically"/> <combo_box.item label="手動下載及安裝" name="Install_manual"/> </combo_box> + <text name="Proxy Settings:"> + 代理伺服器設定: + </text> + <button label="調整代理伺服器設定" label_selected="瀏覽" name="set_proxy"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_preferences_sound.xml b/indra/newview/skins/default/xui/zh/panel_preferences_sound.xml index 09dd4f6b0b..e57f08fd74 100644 --- a/indra/newview/skins/default/xui/zh/panel_preferences_sound.xml +++ b/indra/newview/skins/default/xui/zh/panel_preferences_sound.xml @@ -19,6 +19,7 @@ <check_box label="已啟用" name="enable_voice_check"/> <check_box label="允許媒體自動播放" name="media_auto_play_btn" tool_tip="若你想要,可以勾選這個允許媒體自動播放" value="true"/> <check_box label="播放附加到其他化身身上的媒體" name="media_show_on_others_btn" tool_tip="若未勾選,將隱藏附著於附近其他化身身上的媒體" value="true"/> + <check_box label="播放來自姿勢的聲音" name="gesture_audio_play_btn" tool_tip="勾選即可聽到來自姿勢的聲音" value="true"/> <text name="voice_chat_settings"> 語音聊天設定 </text> @@ -35,28 +36,5 @@ <button label="設定按鍵" name="set_voice_hotkey_button"/> <button name="set_voice_middlemouse_button" tool_tip="重設滑鼠中鍵按鈕"/> <button label="輸入 / 輸出設備" name="device_settings_btn"/> - <panel label="設備設定" name="device_settings_panel"> - <panel.string name="default_text"> - 預設 - </panel.string> - <panel.string name="default system device"> - 預設系統設備 - </panel.string> - <panel.string name="no device"> - 無設備 - </panel.string> - <text name="Input"> - 輸入 - </text> - <text name="My volume label"> - 我的音量: - </text> - <slider_bar initial_value="1.0" name="mic_volume_slider" tool_tip="用這控制條改變音量"/> - <text name="wait_text"> - 請稍候 - </text> - <text name="Output"> - 輸出 - </text> - </panel> + <panel label="設備設定" name="device_settings_panel"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_region_debug.xml b/indra/newview/skins/default/xui/zh/panel_region_debug.xml index 8639f36410..e5d5e6eaf7 100644 --- a/indra/newview/skins/default/xui/zh/panel_region_debug.xml +++ b/indra/newview/skins/default/xui/zh/panel_region_debug.xml @@ -30,5 +30,5 @@ <button label="取得最常碰撞的物件..." name="top_colliders_btn" tool_tip="條列出目前運作中最常碰撞的物件清單"/> <button label="取得最耗能腳本..." name="top_scripts_btn" tool_tip="條列���目前運作中最耗能的腳本清單"/> <button label="地區重新啟動" name="restart_btn" tool_tip="給予兩分鐘倒數計時並重新啟動"/> - <button label="延遲重新啟動" name="cancel_restart_btn" tool_tip="延遲地區重新啟動一小時"/> + <button label="取消重新啟動" name="cancel_restart_btn" tool_tip="取消地區重新啟動"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_region_environment.xml b/indra/newview/skins/default/xui/zh/panel_region_environment.xml new file mode 100644 index 0000000000..8f466af39e --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_region_environment.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel label="環境" name="panel_env_info"> + <text name="water_settings_title"> + 選擇你希望到你地區的訪客所能看到的水和天空 / 日循環設定。 詳情 + </text> + <radio_group name="region_settings_radio_group"> + <radio_item label="使用第二人生預設值" name="use_sl_default_settings"/> + <radio_item label="使用以下設定" name="use_my_settings"/> + </radio_group> + <panel name="user_environment_settings"> + <text name="water_settings_title"> + 水的設定 + </text> + <combo_box name="water_settings_preset_combo"> + <combo_box.item label="-選擇一個自訂配置-" name="item0"/> + </combo_box> + <text name="sky_dayc_settings_title"> + 天空 / 日循環 + </text> + <radio_group name="sky_dayc_settings_radio_group"> + <radio_item label="固定天空" name="my_sky_settings"/> + <radio_item label="日循環" name="my_dayc_settings"/> + </radio_group> + <combo_box name="sky_settings_preset_combo"> + <combo_box.item label="-選擇一個自訂配置-" name="item0"/> + </combo_box> + <combo_box name="dayc_settings_preset_combo"> + <combo_box.item label="-選擇一個自訂配置-" name="item0"/> + </combo_box> + </panel> + <button label="套用" name="apply_btn"/> + <button label="取消" name="cancel_btn"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_region_estate.xml b/indra/newview/skins/default/xui/zh/panel_region_estate.xml index 2d5dcfa9bb..f3c1c85379 100644 --- a/indra/newview/skins/default/xui/zh/panel_region_estate.xml +++ b/indra/newview/skins/default/xui/zh/panel_region_estate.xml @@ -20,10 +20,10 @@ <slider label="相位" name="sun_hour_slider"/> <check_box label="允許公開出入" name="externally_visible_check"/> <text name="Only Allow"> - 僅允許帳號通過如下驗證的人進出: + 僅允許符合以下條件的居民進入: </text> - <check_box label="預留付款資料" name="limit_payment" tool_tip="禁止身份不明居民"/> - <check_box label="年齡驗證" name="limit_age_verified" tool_tip="禁止尚未驗證年齡的居民。 參閱 [SUPPORT_SITE] 獲取進一步資訊。"/> + <check_box label="已經預留付款資料" name="limit_payment" tool_tip="居民必須提供付款資料才能進入這領地。 參閱 [SUPPORT_SITE] 獲取進一步資訊。"/> + <check_box label="已年滿 18 歲" name="limit_age_verified" tool_tip="居民必須年滿 18 歲才能進入這領地。 參閱 [SUPPORT_SITE] 獲取進一步資訊。"/> <check_box label="允許語音聊天" name="voice_chat_check"/> <check_box label="允許直接瞬間傳送" name="allow_direct_teleport"/> <button label="套用" name="apply_btn"/> diff --git a/indra/newview/skins/default/xui/zh/panel_region_terrain.xml b/indra/newview/skins/default/xui/zh/panel_region_terrain.xml index 30d12dfeb5..85e759e445 100644 --- a/indra/newview/skins/default/xui/zh/panel_region_terrain.xml +++ b/indra/newview/skins/default/xui/zh/panel_region_terrain.xml @@ -9,11 +9,52 @@ <spinner label="水文高度" name="water_height_spin"/> <spinner label="地形提升限制" name="terrain_raise_spin"/> <spinner label="地形降低限制" name="terrain_lower_spin"/> - <check_box label="使用領地的太陽設定" name="use_estate_sun_check"/> - <check_box label="固定太陽" name="fixed_sun_check"/> - <slider label="相位" name="sun_hour_slider"/> - <button label="套用" name="apply_btn"/> + <text name="detail_texture_text"> + 地形材質(須 512x512,24 位元 .tga 檔格式) + </text> + <text name="height_text_lbl"> + 1(低) + </text> + <text name="height_text_lbl2"> + 2 + </text> + <text name="height_text_lbl3"> + 3 + </text> + <text name="height_text_lbl4"> + 4(高) + </text> + <text name="height_text_lbl5"> + 材質海拔範圍 + </text> + <text name="height_text_lbl10"> + 這些值代表以上材質的混合範圍。 + </text> + <text name="height_text_lbl11"> + 以公尺為單位,低值是材質 #1 的最大高度,高值是材質 #4 的最小高度。 + </text> + <text name="height_text_lbl6"> + 西北 + </text> + <text name="height_text_lbl7"> + 東北 + </text> + <spinner label="低" name="height_start_spin_1"/> + <spinner label="低" name="height_start_spin_3"/> + <spinner label="高" name="height_range_spin_1"/> + <spinner label="高" name="height_range_spin_3"/> + <text name="height_text_lbl8"> + 西南 + </text> + <text name="height_text_lbl9"> + 東南 + </text> + <spinner label="低" name="height_start_spin_0"/> + <spinner label="低" name="height_start_spin_2"/> + <spinner label="高" name="height_range_spin_0"/> + <spinner label="高" name="height_range_spin_2"/> <button label="下載 RAW 地形..." name="download_raw_btn" tool_tip="只允許領地所有人而非管理者進行操作"/> <button label="上傳 RAW 地形檔..." name="upload_raw_btn" tool_tip="只允許領地所有人而非管理者進行操作"/> <button label="確定地形" name="bake_terrain_btn" tool_tip="將目前地形設為「升高 / 降低」極限的中心值"/> + <button label="套用" name="apply_btn"/> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_script_ed.xml b/indra/newview/skins/default/xui/zh/panel_script_ed.xml index e59307fcfc..29e9a35869 100644 --- a/indra/newview/skins/default/xui/zh/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/zh/panel_script_ed.xml @@ -22,6 +22,8 @@ <menu label="檔案" name="File"> <menu_item_call label="儲存" name="Save"/> <menu_item_call label="還原全部變更" name="Revert All Changes"/> + <menu_item_call label="從檔案載入…" name="LoadFromFile"/> + <menu_item_call label="存入檔案…" name="SaveToFile"/> </menu> <menu label="編輯" name="Edit"> <menu_item_call label="復原" name="Undo"/> diff --git a/indra/newview/skins/default/xui/zh/panel_script_question_toast.xml b/indra/newview/skins/default/xui/zh/panel_script_question_toast.xml new file mode 100644 index 0000000000..a2d0237da0 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_script_question_toast.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<panel label="script_question_panel" name="panel_script_question_toast"> + <panel label="buttons_panel" name="buttons_panel"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml new file mode 100644 index 0000000000..20fb9b494a --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_snapshot_inventory"> + <text name="title"> + 儲存到我的收納區 + </text> + <text name="hint_lbl"> + 將圖像儲存到收納區的費用為 L$[UPLOAD_COST]。 若要將圖像存為材質,請選擇一個正方格式。 + </text> + <combo_box label="解析度" name="texture_size_combo"> + <combo_box.item label="目前視窗" name="CurrentWindow"/> + <combo_box.item label="小(128x128)" name="Small(128x128)"/> + <combo_box.item label="中(256x256)" name="Medium(256x256)"/> + <combo_box.item label="大(512x512)" name="Large(512x512)"/> + <combo_box.item label="自訂" name="Custom"/> + </combo_box> + <spinner label="寬" name="inventory_snapshot_width"/> + <spinner label="高度" name="inventory_snapshot_height"/> + <check_box label="鎖住比例" name="inventory_keep_aspect_check"/> + <button label="取消" name="cancel_btn"/> + <button label="儲存" name="save_btn"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_local.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_local.xml new file mode 100644 index 0000000000..a929c9a3fb --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_local.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_snapshot_local"> + <text name="title"> + 儲存到電腦上 + </text> + <combo_box label="解析度" name="local_size_combo"> + <combo_box.item label="目前視窗" name="CurrentWindow"/> + <combo_box.item label="320x240" name="320x240"/> + <combo_box.item label="640x480" name="640x480"/> + <combo_box.item label="800x600" name="800x600"/> + <combo_box.item label="1024x768" name="1024x768"/> + <combo_box.item label="1280x1024" name="1280x1024"/> + <combo_box.item label="1600x1200" name="1600x1200"/> + <combo_box.item label="自訂" name="Custom"/> + </combo_box> + <layout_stack name="local_image_params_ls"> + <layout_panel name="local_image_size_lp"> + <spinner label="寬" name="local_snapshot_width"/> + <spinner label="高度" name="local_snapshot_height"/> + <check_box label="鎖住比例" name="local_keep_aspect_check"/> + </layout_panel> + <layout_panel name="local_image_format_quality_lp"> + <combo_box label="格式" name="local_format_combo"> + <combo_box.item label="PNG(零失真)" name="PNG"/> + <combo_box.item label="JPEG" name="JPEG"/> + <combo_box.item label="BMP(零失真)" name="BMP"/> + </combo_box> + <slider label="圖像品質" name="image_quality_slider"/> + <text name="image_quality_level"> + ([QLVL]) + </text> + </layout_panel> + </layout_stack> + <button label="取消" name="cancel_btn"/> + <flyout_button label="儲存" name="save_btn" tool_tip="儲存圖像到檔案"> + <flyout_button.item label="儲存" name="save_item"/> + <flyout_button.item label="另存..." name="saveas_item"/> + </flyout_button> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml new file mode 100644 index 0000000000..82c2b10d8d --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_snapshot_options"> + <button label="送至我的檔案訊息發佈" name="save_to_profile_btn"/> + <button label="電郵" name="save_to_email_btn"/> + <button label="儲存到我的收納區(L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="儲存到電腦上" name="save_to_computer_btn"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_postcard.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_postcard.xml new file mode 100644 index 0000000000..853a856104 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_postcard.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_snapshot_postcard"> + <string name="default_subject"> + 來自 [SECOND_LIFE] 的明信片。 + </string> + <string name="default_message"> + 快來看看這個! + </string> + <string name="upload_message"> + 傳送中... + </string> + <text name="title"> + 電郵 + </text> + <button label="訊息" name="message_btn"/> + <button label="設定" name="settings_btn"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_profile.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_profile.xml new file mode 100644 index 0000000000..6f64a4e83c --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_profile.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="panel_snapshot_profile"> + <text name="title"> + 送至我的檔案訊息發佈 + </text> + <combo_box label="解析度" name="profile_size_combo"> + <combo_box.item label="目前視窗" name="CurrentWindow"/> + <combo_box.item label="640x480" name="640x480"/> + <combo_box.item label="800x600" name="800x600"/> + <combo_box.item label="1024x768" name="1024x768"/> + <combo_box.item label="自訂" name="Custom"/> + </combo_box> + <layout_stack name="profile_image_params_ls"> + <layout_panel name="profile_image_size_lp"> + <spinner label="寬" name="profile_snapshot_width"/> + <spinner label="高度" name="profile_snapshot_height"/> + <check_box label="鎖住比例" name="profile_keep_aspect_check"/> + </layout_panel> + <layout_panel name="profile_image_metadata_lp"> + <text name="caption_label"> + 內容敘述: + </text> + <check_box initial_value="true" label="加入所在位置" name="add_location_cb"/> + </layout_panel> + </layout_stack> + <button label="取消" name="cancel_btn"/> + <button label="發佈" name="post_btn"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/panel_sound_devices.xml b/indra/newview/skins/default/xui/zh/panel_sound_devices.xml index 96b00c3235..fa4e24a605 100644 --- a/indra/newview/skins/default/xui/zh/panel_sound_devices.xml +++ b/indra/newview/skins/default/xui/zh/panel_sound_devices.xml @@ -3,9 +3,18 @@ <panel.string name="default_text"> 預設 </panel.string> + <string name="name_no_device"> + 無設備 + </string> + <string name="name_default_system_device"> + 預設系統設備 + </string> <text name="Input"> 輸入 </text> + <text name="Output"> + 輸出 + </text> <text name="My volume label"> 我的音量: </text> @@ -13,7 +22,4 @@ <text name="wait_text"> 請稍候 </text> - <text name="Output"> - 輸出 - </text> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_status_bar.xml b/indra/newview/skins/default/xui/zh/panel_status_bar.xml index ceebe943de..b4cdff9d6b 100644 --- a/indra/newview/skins/default/xui/zh/panel_status_bar.xml +++ b/indra/newview/skins/default/xui/zh/panel_status_bar.xml @@ -18,6 +18,7 @@ <panel name="balance_bg"> <text name="balance" tool_tip="點按以重新更新你的 L$ 帳戶餘額" value="L$20"/> <button label="購買 L$" name="buyL" tool_tip="點按以購買更多 L$"/> + <button label="購物" name="goShop" tool_tip="打開第二人生購物市集"/> </panel> <text name="TimeText" tool_tip="目前時區(太平洋)"> 24:00 AM PST diff --git a/indra/newview/skins/default/xui/zh/panel_volume_pulldown.xml b/indra/newview/skins/default/xui/zh/panel_volume_pulldown.xml new file mode 100644 index 0000000000..70ec028176 --- /dev/null +++ b/indra/newview/skins/default/xui/zh/panel_volume_pulldown.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<panel name="volumepulldown_floater"> + <slider label="主控音量" name="System Volume"/> + <slider label="按鍵音" name="UI Volume"/> + <slider label="環境" name="Wind Volume"/> + <slider label="聲音" name="SFX Volume"/> + <check_box name="gesture_audio_play_btn" tool_tip="啟用姿勢聲音"/> + <slider label="音樂" name="Music Volume"/> + <check_box name="enable_music" tool_tip="啟用串流音樂"/> + <slider label="媒體" name="Media Volume"/> + <check_box name="enable_media" tool_tip="啟用串流媒體"/> + <slider label="語音" name="Voice Volume"/> + <check_box name="enable_voice_check" tool_tip="啟用語音聊天"/> +</panel> diff --git a/indra/newview/skins/default/xui/zh/sidepanel_inventory.xml b/indra/newview/skins/default/xui/zh/sidepanel_inventory.xml index 08d441a2f2..c8aae15011 100644 --- a/indra/newview/skins/default/xui/zh/sidepanel_inventory.xml +++ b/indra/newview/skins/default/xui/zh/sidepanel_inventory.xml @@ -1,6 +1,27 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel label="事物" name="objects panel"> <panel label="" name="sidepanel__inventory_panel"> + <layout_stack name="inventory_layout_stack"> + <layout_panel name="inbox_layout_panel"> + <panel label="" name="marketplace_inbox"> + <string name="InboxLabelWithArg"> + 收到的物項 ([NUM]) + </string> + <string name="InboxLabelNoArg"> + 收到的物項 + </string> + <button label="收到的物項" name="inbox_btn"/> + <text name="inbox_fresh_new_count"> + [NUM] 項新的 + </text> + <panel name="inbox_inventory_placeholder_panel" tool_tip="將物項拖曳置放到收納區,即可開始使用"> + <text name="inbox_inventory_placeholder"> + 從第二人生購物市集購得物項將送到這裡。 + </text> + </panel> + </panel> + </layout_panel> + </layout_stack> <panel name="button_panel"> <layout_stack name="button_panel_ls"> <layout_panel name="info_btn_lp"> diff --git a/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml b/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml index 949288a015..1b093e0ecd 100644 --- a/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml +++ b/indra/newview/skins/default/xui/zh/sidepanel_item_info.xml @@ -3,6 +3,9 @@ <panel.string name="unknown"> (未知) </panel.string> + <panel.string name="unknown_multiple"> + (未知 / 多項) + </panel.string> <panel.string name="public"> (公開) </panel.string> diff --git a/indra/newview/skins/default/xui/zh/sidepanel_task_info.xml b/indra/newview/skins/default/xui/zh/sidepanel_task_info.xml index 692dd81890..982dde4010 100644 --- a/indra/newview/skins/default/xui/zh/sidepanel_task_info.xml +++ b/indra/newview/skins/default/xui/zh/sidepanel_task_info.xml @@ -18,6 +18,12 @@ <panel.string name="text modify info 4"> 你不能修改這些物件 </panel.string> + <panel.string name="text modify info 5"> + 無法跨地區修改這個物件 + </panel.string> + <panel.string name="text modify info 6"> + 無法跨地區修改這些物件 + </panel.string> <panel.string name="text modify warning"> 這個物件含有聯結的部分 </panel.string> @@ -95,6 +101,9 @@ </combo_box> <spinner label="價格: L$" name="Edit Cost"/> <check_box label="顯示在搜尋中" name="search_check" tool_tip="讓其他人可以在搜尋結果中察看到此物件"/> + <text name="pathfinding_attributes_label"> + 尋徑屬性: + </text> <text name="B:"> B: </text> diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml index 9114cc079d..2ca99e50d4 100644 --- a/indra/newview/skins/default/xui/zh/strings.xml +++ b/indra/newview/skins/default/xui/zh/strings.xml @@ -29,11 +29,14 @@ 快取清除中... </string> <string name="StartupInitializingTextureCache"> - 材質快取初始化中... + 正在初始化材質快取... </string> <string name="StartupInitializingVFS"> VFS 初始化中... </string> + <string name="StartupRequireDriverUpdate"> + 顯像初始化失敗。 請更新你的顯像卡驅動程式! + </string> <string name="ProgressRestoring"> 回存中... </string> @@ -74,10 +77,10 @@ 驗證快取檔案(約需 60-90 秒左右)... </string> <string name="LoginProcessingResponse"> - 回應處理中... + 正在處理回應... </string> <string name="LoginInitializingWorld"> - 世界初始化中... + 正在初始化虛擬世界… </string> <string name="LoginDecodingImages"> 圖像解碼中... @@ -91,6 +94,12 @@ <string name="LoginQuicktimeOK"> QuickTime 已成功初始化。 </string> + <string name="LoginRequestSeedCapGrant"> + 詢問地區負荷力… + </string> + <string name="LoginRetrySeedCapGrant"> + 詢問地區負荷力,第 [NUMBER] 次嘗試… + </string> <string name="LoginWaitingForRegionHandshake"> 地區交握等待中... </string> @@ -119,7 +128,7 @@ 無法檢驗通過網格伺服器傳回的憑證簽名。 請聯絡網格管理員。 </string> <string name="LoginFailedNoNetwork"> - 網路出錯:無法建立連線,請檢查網路連線是否正常。 + 網路錯誤:無法建立連線,請檢查網路連線是否正常。 </string> <string name="LoginFailed"> 登入失敗。 @@ -172,7 +181,7 @@ http://secondlife.com/viewer-access-faq </string> <string name="LoginFailedPremiumOnly"> 第二人生此時暫時限制登入,以確保不影響效能,讓目前虛擬世界裡的用戶享受最佳的體驗。 - + 免費帳戶的用戶此時暫時無法進入第二人生,因為我們必須優先容納付費用戶。 </string> <string name="LoginFailedComputerProhibited"> @@ -325,6 +334,36 @@ http://secondlife.com/viewer-access-faq 只有一個物品可以被拖曳到此處 </string> <string name="TooltipPrice" value="L$[AMOUNT]:"/> + <string name="TooltipOutboxDragToWorld"> + 商家發件匣內的物項無法產生到虛擬世界 + </string> + <string name="TooltipOutboxNoTransfer"> + 至少一個物件無法出售或轉移。 + </string> + <string name="TooltipOutboxNotInInventory"> + 你的商家發件匣只能接受直接來自收納區的物項。 + </string> + <string name="TooltipOutboxWorn"> + 你穿著中的物項無法放入商家發件匣。 + </string> + <string name="TooltipOutboxCallingCard"> + 名片不得放入商家發件匣 + </string> + <string name="TooltipOutboxFolderLevels"> + 巢狀資料夾深度超過 3 + </string> + <string name="TooltipOutboxTooManyFolders"> + 頂層資料夾的子資料夾數目超過 20 + </string> + <string name="TooltipOutboxTooManyObjects"> + 頂層資料夾物項數目超過 200 + </string> + <string name="TooltipDragOntoOwnChild"> + 資料夾不得移到其子資料夾底下 + </string> + <string name="TooltipDragOntoSelf"> + 資料夾移動的目標不得為它本身 + </string> <string name="TooltipHttpUrl"> 點按以察看這個網頁 </string> @@ -788,6 +827,9 @@ http://secondlife.com/viewer-access-faq <string name="anim_yes_head"> 是 </string> + <string name="multiple_textures"> + 多個 + </string> <string name="texture_loading"> 載入中... </string> @@ -836,6 +878,9 @@ http://secondlife.com/viewer-access-faq <string name="ScriptQuestionCautionChatDenied"> 物件「[OBJECTNAME]'」(所有人「[OWNERNAME]」,位於「[REGIONNAME]」,方位「[REGIONPOS]」)已被撤除下列權限:[PERMISSIONS]。 </string> + <string name="AdditionalPermissionsRequestHeader"> + 你如果打開帳戶權限,也將一併允許該物件: + </string> <string name="ScriptTakeMoney"> 由你身上拿走林登幣(L$) </string> @@ -869,6 +914,9 @@ http://secondlife.com/viewer-access-faq <string name="ControlYourCamera"> 控制你的攝影機 </string> + <string name="TeleportYourAgent"> + 瞬間傳送你本人 + </string> <string name="NotConnected"> 未聯接 </string> @@ -947,6 +995,12 @@ http://secondlife.com/viewer-access-faq <string name="choose_the_directory"> 選擇目錄 </string> + <string name="script_files"> + 腳本 + </string> + <string name="dictionary_files"> + 字典 + </string> <string name="AvatarSetNotAway"> 非離開 </string> @@ -1184,6 +1238,65 @@ http://secondlife.com/viewer-access-faq <string name="InventoryNoTexture"> 你的收納區裡沒有這個材質的副本 </string> + <string name="InventoryInboxNoItems"> + 你從第二人生購物市集購買的物品將出現在這裡。 你可以把它們拖曳到你的收納區,開始使用。 + </string> + <string name="MarketplaceURL"> + https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ + </string> + <string name="MarketplaceURL_CreateStore"> + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + </string> + <string name="MarketplaceURL_Dashboard"> + https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard + </string> + <string name="MarketplaceURL_Imports"> + https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/imports + </string> + <string name="MarketplaceURL_LearnMore"> + https://marketplace.[MARKETPLACE_DOMAIN_NAME]/learn_more + </string> + <string name="InventoryOutboxNotMerchantTitle"> + 任何人都可在第二人生購物市集出售物品。 + </string> + <string name="InventoryOutboxNotMerchantTooltip"/> + <string name="InventoryOutboxNotMerchant"> + 如果你想成為商家,你需要在第二人生購物市集[[MARKETPLACE_CREATE_STORE_URL]開設一間商店]。 + </string> + <string name="InventoryOutboxNoItemsTitle"> + 你的發件匣目前是空的。 + </string> + <string name="InventoryOutboxNoItemsTooltip"/> + <string name="InventoryOutboxNoItems"> + 將資料夾拖曳到這個區域,再點按「送往第二人生購物市集」,即可在[[MARKETPLACE_DASHBOARD_URL]第二人生購物市集]登列出售。 + </string> + <string name="Marketplace Error None"> + 零錯誤 + </string> + <string name="Marketplace Error Not Merchant"> + 發生錯誤:將物項送往第二人生購物市集之前,你必須取得商家的身份(免費)。 + </string> + <string name="Marketplace Error Empty Folder"> + 錯誤:此資料夾沒有內容。 + </string> + <string name="Marketplace Error Unassociated Products"> + 錯誤:此物項上傳失敗,因為你的商家帳戶有太多和產品無關聯的物項。 要解決這個問題,請登入第二人生購物市集網站,減低你的無關聯物項數目。 + </string> + <string name="Marketplace Error Object Limit"> + 錯誤:此物項包含太多物件。 要解決這錯誤,請將物件裝箱,使總物件數目不超過 200。 + </string> + <string name="Marketplace Error Folder Depth"> + 錯誤:此物項含有太多層的巢狀資料夾。 請加以重新整理,至多允許 3 層的巢狀資料夾。 + </string> + <string name="Marketplace Error Unsellable Item"> + 錯誤:此物項無法在第二人生購物市集出售。 + </string> + <string name="Marketplace Error Internal Import"> + 錯誤:這個物項有問題。 請稍候再試一次。 + </string> + <string name="Open landmarks"> + 開啟地標 + </string> <string name="no_transfer" value="(禁止轉讓)"/> <string name="no_modify" value="(禁止修改)"/> <string name="no_copy" value="(禁止複製)"/> @@ -1230,9 +1343,6 @@ http://secondlife.com/viewer-access-faq <string name="InvFolder My Inventory"> 我的收納區 </string> - <string name="InvFolder My Favorites"> - 我的最愛 - </string> <string name="InvFolder Library"> 資源庫 </string> @@ -1296,6 +1406,12 @@ http://secondlife.com/viewer-access-faq <string name="InvFolder favorite"> 我的最愛 </string> + <string name="InvFolder Favorites"> + 我的最愛 + </string> + <string name="InvFolder favorites"> + 我的最愛 + </string> <string name="InvFolder Current Outfit"> 目前裝扮 </string> @@ -1311,12 +1427,24 @@ http://secondlife.com/viewer-access-faq <string name="InvFolder Meshes"> 網面 </string> + <string name="InvFolder Received Items"> + 收到的物項 + </string> + <string name="InvFolder Merchant Outbox"> + 商家發件匣 + </string> <string name="InvFolder Friends"> 朋友 </string> <string name="InvFolder All"> 全部 </string> + <string name="no_attachments"> + 未穿著任何附件 + </string> + <string name="Attachments remain"> + 附件(尚可容納 [COUNT] 件) + </string> <string name="Buy"> 購買 </string> @@ -1443,6 +1571,12 @@ http://secondlife.com/viewer-access-faq <string name="Right Pec"> 右胸肌 </string> + <string name="Neck"> + 頸部 + </string> + <string name="Avatar Center"> + 化身中心 + </string> <string name="Invalid Attachment"> 無效的附接點 </string> @@ -1626,6 +1760,9 @@ http://secondlife.com/viewer-access-faq </string> <string name="SummaryForTheWeek" value="本週摘要,起始日:"/> <string name="NextStipendDay" value="下一個發薪日為"/> + <string name="GroupPlanningDate"> + [mthnum,datetime,utc]/[day,datetime,utc]/[year,datetime,utc] + </string> <string name="GroupIndividualShare" value="群組 個人份額"/> <string name="GroupColumn" value="群組"/> <string name="Balance"> @@ -1652,6 +1789,9 @@ http://secondlife.com/viewer-access-faq <string name="IMTeen"> 青少年 </string> + <string name="Anyone"> + 任何人 + </string> <string name="RegionInfoError"> 錯誤 </string> @@ -1874,6 +2014,12 @@ http://secondlife.com/viewer-access-faq <string name="Public"> 公開 </string> + <string name="LocalSettings"> + 本地設定 + </string> + <string name="RegionSettings"> + 地區設定 + </string> <string name="ClassifiedClicksTxt"> 點按:[TELEPORT] 瞬間傳送,[MAP] 地圖,[PROFILE] 檔案 </string> @@ -1946,6 +2092,9 @@ http://secondlife.com/viewer-access-faq <string name="GroupMoneyDebits"> 借記 </string> + <string name="GroupMoneyDate"> + [weekday,datetime,utc] [mth,datetime,utc] [day,datetime,utc], [year,datetime,utc] + </string> <string name="ViewerObjectContents"> 內容 </string> @@ -3719,6 +3868,15 @@ http://secondlife.com/viewer-access-faq <string name="LocationCtrlGeneralIconTooltip"> 一般普級地區 </string> + <string name="LocationCtrlSeeAVsTooltip"> + 可看到本地段外的化身,並與之交談 + </string> + <string name="LocationCtrlPathfindingDirtyTooltip"> + 地區重新產出之前,可移動物件可能無法正常運作。 + </string> + <string name="LocationCtrlPathfindingDisabledTooltip"> + 這地區並未啟用動態尋徑。 + </string> <string name="UpdaterWindowTitle"> [APP_NAME] 更新 </string> @@ -3785,6 +3943,9 @@ http://secondlife.com/viewer-access-faq <string name="Saved_message"> (於 [LONG_TIMESTAMP] 儲存) </string> + <string name="IM_unblock_only_groups_friends"> + 要察看這訊息,你必須到「偏好設定 / 隱私」,取消勾選「只有我的朋友和群組可以 IM 或與我通話」。 + </string> <string name="answered_call"> 你的通話已經接通 </string> @@ -3917,6 +4078,18 @@ http://secondlife.com/viewer-access-faq <string name="you_paid_ldollars_no_name"> 你支付了 L$[AMOUNT]([REASON])。 </string> + <string name="you_paid_failure_ldollars"> + 你支付 L$[AMOUNT] 給 [NAME] 時出錯:[REASON]。 + </string> + <string name="you_paid_failure_ldollars_no_info"> + 你支付 L$[AMOUNT] 時出錯。 + </string> + <string name="you_paid_failure_ldollars_no_reason"> + 你支付 L$[AMOUNT] 給 [NAME] 時出錯。 + </string> + <string name="you_paid_failure_ldollars_no_name"> + 你支付 L$[AMOUNT] 時出錯:[REASON]。 + </string> <string name="for item"> 購買 [ITEM] </string> @@ -3970,7 +4143,7 @@ http://secondlife.com/viewer-access-faq </string> <string name="uploading_abuse_report"> 上傳中... - + 違規舉報 </string> <string name="New Shape"> @@ -4144,6 +4317,87 @@ http://secondlife.com/viewer-access-faq <string name="Female - Wow"> 女性 - 哇塞 </string> + <string name="/bow"> + /彎腰點頭 + </string> + <string name="/clap"> + /拍手 + </string> + <string name="/count"> + /計數 + </string> + <string name="/extinguish"> + /熄菸 + </string> + <string name="/kmb"> + /給我一個吻 + </string> + <string name="/muscle"> + /肌肉 + </string> + <string name="/no"> + /不 + </string> + <string name="/no!"> + /不! + </string> + <string name="/paper"> + /布 + </string> + <string name="/pointme"> + /指自己 + </string> + <string name="/pointyou"> + /指向你 + </string> + <string name="/rock"> + /石頭 + </string> + <string name="/scissor"> + /剪刀 + </string> + <string name="/smoke"> + /抽菸 + </string> + <string name="/stretch"> + /伸展 + </string> + <string name="/whistle"> + /吹口哨 + </string> + <string name="/yes"> + /是 + </string> + <string name="/yes!"> + /是! + </string> + <string name="afk"> + 暫時離開 + </string> + <string name="dance1"> + 跳舞1 + </string> + <string name="dance2"> + 跳舞2 + </string> + <string name="dance3"> + 跳舞3 + </string> + <string name="dance4"> + 跳舞4 + </string> + <string name="dance5"> + 跳舞5 + </string> + <string name="dance6"> + 跳舞6 + </string> + <string name="dance7"> + 跳舞7 + </string> + <string name="dance8"> + 跳舞8 + </string> <string name="AvatarBirthDateFormat"> [mthnum,datetime,slt]/[day,datetime,slt]/[year,datetime,slt] </string> @@ -4157,7 +4411,7 @@ http://secondlife.com/viewer-access-faq <string name="server_is_down"> 儘管我們努力避免,還是發生意料外的錯誤。 - 請察訪 status.secondlifegrid.net 看是否發生了已知狀況。 + 請察訪 status.secondlifegrid.net 看是否發生了已知狀況。 如果文體繼續發生,請檢查你的網路和防火牆設定。 </string> <string name="dateTimeWeekdaysNames"> @@ -4246,6 +4500,12 @@ http://secondlife.com/viewer-access-faq <string name="ExternalEditorFailedToRun"> 執行外部編輯器失敗。 </string> + <string name="TranslationFailed"> + 無法翻譯:[REASON] + </string> + <string name="TranslationResponseParseError"> + 無法剖析平移回應。 + </string> <string name="Esc"> Esc 鍵 </string> @@ -4588,4 +4848,223 @@ http://secondlife.com/viewer-access-faq <string name="ParticleHiding"> 隱藏粒子效果 </string> + <string name="Command_AboutLand_Label"> + 土地資料 + </string> + <string name="Command_Appearance_Label"> + 編輯外觀 + </string> + <string name="Command_Avatar_Label"> + 化身 + </string> + <string name="Command_Build_Label"> + 建造 + </string> + <string name="Command_Chat_Label"> + 聊天 + </string> + <string name="Command_Compass_Label"> + 羅盤 + </string> + <string name="Command_Destinations_Label"> + 目的地 + </string> + <string name="Command_Gestures_Label"> + 姿勢 + </string> + <string name="Command_HowTo_Label"> + 簡易教學 + </string> + <string name="Command_Inventory_Label"> + 收納區 + </string> + <string name="Command_Map_Label"> + 地圖 + </string> + <string name="Command_Marketplace_Label"> + 第二人生購物市集 + </string> + <string name="Command_MiniMap_Label"> + 迷你地圖 + </string> + <string name="Command_Move_Label"> + 行走 / 跑步 / 飛行 + </string> + <string name="Command_Outbox_Label"> + 商家發件匣 + </string> + <string name="Command_People_Label"> + 人群 + </string> + <string name="Command_Picks_Label"> + 精選地點 + </string> + <string name="Command_Places_Label"> + 地點 + </string> + <string name="Command_Preferences_Label"> + 偏好設定 + </string> + <string name="Command_Profile_Label"> + 檔案 + </string> + <string name="Command_Search_Label"> + 搜尋 + </string> + <string name="Command_Snapshot_Label"> + 快照 + </string> + <string name="Command_Speak_Label"> + 說話 + </string> + <string name="Command_View_Label"> + 攝影機控制 + </string> + <string name="Command_Voice_Label"> + 語音設定 + </string> + <string name="Command_AboutLand_Tooltip"> + 有關你所處土地的資訊 + </string> + <string name="Command_Appearance_Tooltip"> + 改變化身 + </string> + <string name="Command_Avatar_Tooltip"> + 選擇一個完整的化身 + </string> + <string name="Command_Build_Tooltip"> + 建製物件和重塑地形 + </string> + <string name="Command_Chat_Tooltip"> + 透過文字和附近人們聊天 + </string> + <string name="Command_Compass_Tooltip"> + 指南針 + </string> + <string name="Command_Destinations_Tooltip"> + 你可能感興趣的目的地 + </string> + <string name="Command_Gestures_Tooltip"> + 你化身可用的姿勢 + </string> + <string name="Command_HowTo_Tooltip"> + 如何完成常用的動作 + </string> + <string name="Command_Inventory_Tooltip"> + 察看並使用你擁有的物件 + </string> + <string name="Command_Map_Tooltip"> + 世界地圖 + </string> + <string name="Command_Marketplace_Tooltip"> + 前往購物 + </string> + <string name="Command_MiniMap_Tooltip"> + 顯示附近的人 + </string> + <string name="Command_Move_Tooltip"> + 移動化身 + </string> + <string name="Command_Outbox_Tooltip"> + 將物項轉移到第二人生購物市集待售 + </string> + <string name="Command_People_Tooltip"> + 朋友、群組和附近的人 + </string> + <string name="Command_Picks_Tooltip"> + 顯示在你的小檔案中的最愛地點 + </string> + <string name="Command_Places_Tooltip"> + 你儲存的地點 + </string> + <string name="Command_Preferences_Tooltip"> + 偏好設定 + </string> + <string name="Command_Profile_Tooltip"> + 編輯或察看你的小檔案 + </string> + <string name="Command_Search_Tooltip"> + 尋找地點、活動、其他人 + </string> + <string name="Command_Snapshot_Tooltip"> + 拍一張照片 + </string> + <string name="Command_Speak_Tooltip"> + 用麥克風和附近人們交談 + </string> + <string name="Command_View_Tooltip"> + 調整攝影機角度 + </string> + <string name="Command_Voice_Tooltip"> + 在虛擬世界裡通話和附近人群的音量控制 + </string> + <string name="Toolbar_Bottom_Tooltip"> + 目前位在你的底部工具列 + </string> + <string name="Toolbar_Left_Tooltip"> + 目前位在你的左工具列 + </string> + <string name="Toolbar_Right_Tooltip"> + 目前位在你的右工具列 + </string> + <string name="Retain%"> + 保留% + </string> + <string name="Detail"> + 細節 + </string> + <string name="Better Detail"> + 更多細節 + </string> + <string name="Surface"> + 表面 + </string> + <string name="Solid"> + 固體 + </string> + <string name="Wrap"> + Wrap + </string> + <string name="Preview"> + 預覽 + </string> + <string name="Normal"> + 正常 + </string> + <string name="Pathfinding_Wiki_URL"> + http://wiki.secondlife.com/wiki/Pathfinding_Tools_in_the_Second_Life_Viewer + </string> + <string name="Pathfinding_Object_Attr_None"> + 無 + </string> + <string name="Pathfinding_Object_Attr_Permanent"> + 影響導航網面 + </string> + <string name="Pathfinding_Object_Attr_Character"> + 角色 + </string> + <string name="Pathfinding_Object_Attr_MultiSelect"> + (多項) + </string> + <string name="snapshot_quality_very_low"> + 很低 + </string> + <string name="snapshot_quality_low"> + 低 + </string> + <string name="snapshot_quality_medium"> + 中 + </string> + <string name="snapshot_quality_high"> + 高 + </string> + <string name="snapshot_quality_very_high"> + 很高 + </string> + <string name="TeleportMaturityExceeded"> + 此居民不得進入此地區。 + </string> + <string name="UserDictionary"> + [User] + </string> </strings> diff --git a/indra/newview/skins/default/xui/zh/teleport_strings.xml b/indra/newview/skins/default/xui/zh/teleport_strings.xml index b43497bbe6..37080a8d0c 100644 --- a/indra/newview/skins/default/xui/zh/teleport_strings.xml +++ b/indra/newview/skins/default/xui/zh/teleport_strings.xml @@ -21,8 +21,8 @@ 請稍後再試。 </message> <message name="NoHelpIslandTP"> - 您不能瞬间转移回“援助岛”。 -去“公共援助岛”重复您的教程。 + 你無法瞬間傳送回「新手導引島」。 +請到「大眾新手導引島」重新參加導引教學。 </message> <message name="noaccess_tport"> 抱歉,你並沒有權限進入要瞬間傳送的目的地。 @@ -45,6 +45,9 @@ <message name="no_inventory_host"> 收納區功能目前無法使用。 </message> + <message name="MustGetAgeRegion"> + 你必須年滿 18 歲才可進入這地區。 + </message> </message_set> <message_set name="progress"> <message name="sending_dest"> @@ -80,5 +83,8 @@ <message name="requesting"> 瞬間傳送要求中... </message> + <message name="pending"> + 等待瞬間傳送… + </message> </message_set> </teleport_messages> diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp index f8923b9868..f8923b9868 100755..100644 --- a/indra/newview/tests/llviewerassetstats_test.cpp +++ b/indra/newview/tests/llviewerassetstats_test.cpp diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 021e529b9c..fdf26519d6 100644 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -28,6 +28,7 @@ $/LicenseInfo$ """ import sys import os.path +import errno import re import tarfile import time @@ -74,20 +75,20 @@ class ViewerManifest(LLManifest): # include the list of Lindens (if any) # see https://wiki.lindenlab.com/wiki/Generated_Linden_Credits linden_names_path = os.getenv("LINDEN_CREDITS") - if linden_names_path : + if not linden_names_path : + print "No 'LINDEN_CREDITS' specified in environment, using built-in list" + else: try: linden_file = open(linden_names_path,'r') + except IOError: + print "No Linden names found at '%s', using built-in list" % linden_names_path + else: # all names should be one line, but the join below also converts to a string linden_names = ', '.join(linden_file.readlines()) self.put_in_file(linden_names, "lindens.txt") linden_file.close() print "Linden names extracted from '%s'" % linden_names_path self.file_list.append([linden_names_path,self.dst_path_of("lindens.txt")]) - except IOError: - print "No Linden names found at '%s', using built-in list" % linden_names_path - pass - else : - print "No 'LINDEN_CREDITS' specified in environment, using built-in list" # ... and the entire windlight directory self.path("windlight") @@ -149,14 +150,9 @@ class ViewerManifest(LLManifest): self.path("gpu_table.txt") # The summary.json file gets left in the base checkout dir by - # build.sh. It's only created for a build.sh build, therefore we - # have to check whether it exists. :-P - summary_json = "summary.json" - summary_json_path = os.path.join(os.pardir, os.pardir, summary_json) - if os.path.exists(os.path.join(self.get_src_prefix(), summary_json_path)): - self.path(summary_json_path, summary_json) - else: - print "No %s" % os.path.join(self.get_src_prefix(), summary_json_path) + # build.sh. It's only created for a build.sh build. + if not self.path2basename(os.path.join(os.pardir, os.pardir), "summary.json"): + print "No summary.json file" def login_channel(self): """Channel reported for login and upgrade purposes ONLY; @@ -327,13 +323,13 @@ class WindowsManifest(ViewerManifest): self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) # Plugin host application - self.path(os.path.join(os.pardir, - 'llplugin', 'slplugin', self.args['configuration'], "slplugin.exe"), - "slplugin.exe") + self.path2basename(os.path.join(os.pardir, + 'llplugin', 'slplugin', self.args['configuration']), + "slplugin.exe") #self.disable_manifest_check() - self.path(src="../viewer_components/updater/scripts/windows/update_install.bat", dst="update_install.bat") + self.path2basename("../viewer_components/updater/scripts/windows", "update_install.bat") # Get shared libs from the shared libs staging directory if self.prefix(src=os.path.join(os.pardir, 'sharedlibs', self.args['configuration']), dst=""): @@ -367,9 +363,7 @@ class WindowsManifest(ViewerManifest): # Get fmod dll, continue if missing - try: - self.path("fmod.dll") - except: + if not self.path("fmod.dll"): print "Skipping fmod.dll" # For textures @@ -711,85 +705,82 @@ class DarwinManifest(ViewerManifest): self.path("uk.lproj") self.path("zh-Hans.lproj") - libdir = "../packages/lib/release" - dylibs = {} + def path_optional(src, dst): + """ + For a number of our self.path() calls, not only do we want + to deal with the absence of src, we also want to remember + which were present. Return either an empty list (absent) + or a list containing dst (present). Concatenate these + return values to get a list of all libs that are present. + """ + if self.path(src, dst): + return [dst] + print "Skipping %s" % dst + return [] - # Need to get the llcommon dll from any of the build directories as well - lib = "llcommon" - libfile = "lib%s.dylib" % lib - try: - self.path(self.find_existing_file(os.path.join(os.pardir, - lib, - self.args['configuration'], - libfile), - os.path.join(libdir, libfile)), - dst=libfile) - except RuntimeError: - print "Skipping %s" % libfile - dylibs[lib] = False - else: - dylibs[lib] = True - - if dylibs["llcommon"]: - for libfile in ("libapr-1.0.dylib", - "libaprutil-1.0.dylib", - "libexpat.1.5.2.dylib", - "libexception_handler.dylib", - "libGLOD.dylib", - "libcollada14dom.dylib" - ): - self.path(os.path.join(libdir, libfile), libfile) - - # SLVoice and vivox lols - for libfile in ('libsndfile.dylib', 'libvivoxoal.dylib', 'libortp.dylib', \ - 'libvivoxsdk.dylib', 'libvivoxplatform.dylib', 'SLVoice') : - self.path(os.path.join(libdir, libfile), libfile) + libdir = "../packages/lib/release" + # dylibs is a list of all the .dylib files we expect to need + # in our bundled sub-apps. For each of these we'll create a + # symlink from sub-app/Contents/Resources to the real .dylib. + # Need to get the llcommon dll from any of the build directories as well. + libfile = "libllcommon.dylib" + dylibs = path_optional(self.find_existing_file(os.path.join(os.pardir, + "llcommon", + self.args['configuration'], + libfile), + os.path.join(libdir, libfile)), + dst=libfile) + + for libfile in ( + "libapr-1.0.dylib", + "libaprutil-1.0.dylib", + "libcollada14dom.dylib", + "libexpat.1.5.2.dylib", + "libexception_handler.dylib", + "libGLOD.dylib", + ): + dylibs += path_optional(os.path.join(libdir, libfile), libfile) + + # SLVoice and vivox lols, no symlinks needed + for libfile in ( + 'libortp.dylib', + 'libsndfile.dylib', + 'libvivoxoal.dylib', + 'libvivoxsdk.dylib', + 'libvivoxplatform.dylib', + 'SLVoice', + ): + self.path2basename(libdir, libfile) - try: - # FMOD for sound - self.path(self.args['configuration'] + "/libfmodwrapper.dylib", "libfmodwrapper.dylib") - except: - print "Skipping FMOD - not found" + # FMOD for sound + libfile = "libfmodwrapper.dylib" + path_optional(os.path.join(self.args['configuration'], libfile), libfile) # our apps - self.path("../mac_crash_logger/" + self.args['configuration'] + "/mac-crash-logger.app", "mac-crash-logger.app") - self.path("../mac_updater/" + self.args['configuration'] + "/mac-updater.app", "mac-updater.app") - - # plugin launcher - self.path("../llplugin/slplugin/" + self.args['configuration'] + "/SLPlugin.app", "SLPlugin.app") - - # our apps dependencies on shared libs - if dylibs["llcommon"]: - mac_crash_logger_res_path = self.dst_path_of("mac-crash-logger.app/Contents/Resources") - mac_updater_res_path = self.dst_path_of("mac-updater.app/Contents/Resources") - slplugin_res_path = self.dst_path_of("SLPlugin.app/Contents/Resources") - for libfile in ("libllcommon.dylib", - "libapr-1.0.dylib", - "libaprutil-1.0.dylib", - "libexpat.1.5.2.dylib", - "libexception_handler.dylib", - "libGLOD.dylib", - "libcollada14dom.dylib" - ): - target_lib = os.path.join('../../..', libfile) - self.run_command("ln -sf %(target)r %(link)r" % - {'target': target_lib, - 'link' : os.path.join(mac_crash_logger_res_path, libfile)} - ) - self.run_command("ln -sf %(target)r %(link)r" % - {'target': target_lib, - 'link' : os.path.join(mac_updater_res_path, libfile)} - ) - self.run_command("ln -sf %(target)r %(link)r" % - {'target': target_lib, - 'link' : os.path.join(slplugin_res_path, libfile)} - ) + for app_bld_dir, app in (("mac_crash_logger", "mac-crash-logger.app"), + ("mac_updater", "mac-updater.app"), + # plugin launcher + (os.path.join("llplugin", "slplugin"), "SLPlugin.app"), + ): + self.path2basename(os.path.join(os.pardir, + app_bld_dir, self.args['configuration']), + app) + + # our apps dependencies on shared libs + # for each app, for each dylib we collected in dylibs, + # create a symlink to the real copy of the dylib. + resource_path = self.dst_path_of(os.path.join(app, "Contents", "Resources")) + for libfile in dylibs: + symlinkf(os.path.join(os.pardir, os.pardir, os.pardir, libfile), + os.path.join(resource_path, libfile)) # plugins if self.prefix(src="", dst="llplugin"): - self.path("../media_plugins/quicktime/" + self.args['configuration'] + "/media_plugin_quicktime.dylib", "media_plugin_quicktime.dylib") - self.path("../media_plugins/webkit/" + self.args['configuration'] + "/media_plugin_webkit.dylib", "media_plugin_webkit.dylib") - self.path("../packages/lib/release/libllqtwebkit.dylib", "libllqtwebkit.dylib") + self.path2basename("../media_plugins/quicktime/" + self.args['configuration'], + "media_plugin_quicktime.dylib") + self.path2basename("../media_plugins/webkit/" + self.args['configuration'], + "media_plugin_webkit.dylib") + self.path2basename("../packages/lib/release", "libllqtwebkit.dylib") self.end_prefix("llplugin") @@ -956,20 +947,25 @@ class LinuxManifest(ViewerManifest): self.path("client-readme-voice.txt","README-linux-voice.txt") self.path("client-readme-joystick.txt","README-linux-joystick.txt") self.path("wrapper.sh","secondlife") - self.path("handle_secondlifeprotocol.sh", "etc/handle_secondlifeprotocol.sh") - self.path("register_secondlifeprotocol.sh", "etc/register_secondlifeprotocol.sh") - self.path("refresh_desktop_app_entry.sh", "etc/refresh_desktop_app_entry.sh") - self.path("launch_url.sh","etc/launch_url.sh") + if self.prefix(src="", dst="etc"): + self.path("handle_secondlifeprotocol.sh") + self.path("register_secondlifeprotocol.sh") + self.path("refresh_desktop_app_entry.sh") + self.path("launch_url.sh") + self.end_prefix("etc") self.path("install.sh") self.end_prefix("linux_tools") # Create an appropriate gridargs.dat for this package, denoting required grid. self.put_in_file(self.flags_list(), 'etc/gridargs.dat') - self.path("secondlife-bin","bin/do-not-directly-run-secondlife-bin") - self.path("../linux_crash_logger/linux-crash-logger","bin/linux-crash-logger.bin") - self.path("../linux_updater/linux-updater", "bin/linux-updater.bin") - self.path("../llplugin/slplugin/SLPlugin", "bin/SLPlugin") + if self.prefix(src="", dst="bin"): + self.path("secondlife-bin","do-not-directly-run-secondlife-bin") + self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin") + self.path("../linux_updater/linux-updater", "linux-updater.bin") + self.path2basename("../llplugin/slplugin", "SLPlugin") + self.path2basename("../viewer_components/updater/scripts/linux", "update_install") + self.end_prefix("bin") if self.prefix("res-sdl"): self.path("*") @@ -985,17 +981,13 @@ class LinuxManifest(ViewerManifest): self.end_prefix("res-sdl") self.end_prefix(icon_path) - self.path("../viewer_components/updater/scripts/linux/update_install", "bin/update_install") - # plugins if self.prefix(src="", dst="bin/llplugin"): - self.path("../media_plugins/webkit/libmedia_plugin_webkit.so", "libmedia_plugin_webkit.so") + self.path2basename("../media_plugins/webkit", "libmedia_plugin_webkit.so") self.path("../media_plugins/gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_gstreamer.so") self.end_prefix("bin/llplugin") - try: - self.path("../llcommon/libllcommon.so", "lib/libllcommon.so") - except: + if not self.path("../llcommon/libllcommon.so", "lib/libllcommon.so"): print "Skipping llcommon.so (assuming llcommon was linked statically)" self.path("featuretable_linux.txt") @@ -1061,9 +1053,21 @@ class Linux_i686Manifest(LinuxManifest): super(Linux_i686Manifest, self).construct() if self.prefix("../packages/lib/release", dst="lib"): - self.path("libapr-1.so*") - self.path("libaprutil-1.so*") - self.path("libbreakpad_client.so*") + self.path("libapr-1.so") + self.path("libapr-1.so.0") + self.path("libapr-1.so.0.4.5") + self.path("libaprutil-1.so") + self.path("libaprutil-1.so.0") + self.path("libaprutil-1.so.0.4.1") + self.path("libboost_program_options-mt.so.1.48.0") + self.path("libboost_regex-mt.so.1.48.0") + self.path("libboost_thread-mt.so.1.48.0") + self.path("libboost_filesystem-mt.so.1.48.0") + self.path("libboost_signals-mt.so.1.48.0") + self.path("libboost_system-mt.so.1.48.0") + self.path("libbreakpad_client.so.0.0.0") + self.path("libbreakpad_client.so.0") + self.path("libbreakpad_client.so") self.path("libcollada14dom.so") self.path("libdb*.so") self.path("libcrypto.so.*") @@ -1079,11 +1083,8 @@ class Linux_i686Manifest(LinuxManifest): self.path("libopenjpeg.so*") self.path("libdirectfb-1.4.so.5") self.path("libfusion-1.4.so.5") - self.path("libdirect-1.4.so.5.0.4") - self.path("libdirect-1.4.so.5") - self.path("libhunspell-1.3.so") - self.path("libhunspell-1.3.so.0") - self.path("libhunspell-1.3.so.0.0.0") + self.path("libdirect-1.4.so.5*") + self.path("libhunspell-1.3.so*") self.path("libalut.so") self.path("libopenal.so", "libopenal.so.1") self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname @@ -1146,5 +1147,25 @@ class Linux_x86_64Manifest(LinuxManifest): ################################################################ +def symlinkf(src, dst): + """ + Like ln -sf, but uses os.symlink() instead of running ln. + """ + try: + os.symlink(src, dst) + except OSError, err: + if err.errno != errno.EEXIST: + raise + # We could just blithely attempt to remove and recreate the target + # file, but that strategy doesn't work so well if we don't have + # permissions to remove it. Check to see if it's already the + # symlink we want, which is the usual reason for EEXIST. + if not (os.path.islink(dst) and os.readlink(dst) == src): + # Here either dst isn't a symlink or it's the wrong symlink. + # Remove and recreate. Caller will just have to deal with any + # exceptions at this stage. + os.remove(dst) + os.symlink(src, dst) + if __name__ == "__main__": main() diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index d480b63094..bdcb068200 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -271,6 +271,16 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD login_para } return; // Done! } + + /* Sometimes we end with "Started" here. Slightly slow server? + * Seems to be ok to just skip it. Otherwise we'd error out and crash in the if below. + */ + if( status == "Started") + { + LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL; + continue; + } + // If we don't recognize status at all, trouble if (! (status == "CURLError" || status == "XMLRPCError" |